diff --git a/.github/workflows/docker-main.yml b/.github/workflows/docker-main.yml index 57d871127a..ffdbe47cdf 100644 --- a/.github/workflows/docker-main.yml +++ b/.github/workflows/docker-main.yml @@ -5,7 +5,8 @@ on: branches: - master paths-ignore: - - 'documentation/**' + - 'docs/**' + - '@l10n/**' - '**.md' jobs: diff --git a/.github/workflows/docker-pr-build.yml b/.github/workflows/docker-pr-build.yml index 65a428e7a7..30c64b570d 100644 --- a/.github/workflows/docker-pr-build.yml +++ b/.github/workflows/docker-pr-build.yml @@ -8,7 +8,8 @@ on: # would otherwise only be evaluated on the next push. types: [opened, synchronize, reopened, labeled, unlabeled] paths-ignore: - - 'documentation/**' + - 'docs/**' + - '@l10n/**' - '**.md' jobs: diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index d5adc617be..3204f104cf 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -4,7 +4,8 @@ on: push: branches: [ windows_build, master ] paths-ignore: - - 'documentation/**' + - 'docs/**' + - '@l10n/**' - '**.md' permissions: diff --git a/.gitignore b/.gitignore index 4ac78ce82e..698b60de5a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,21 +24,9 @@ libraries/wallet/api_documentation.cpp libraries/wallet/doxygen programs/cli_wallet/cli_wallet -programs/js_operation_serializer/js_operation_serializer programs/vizd/vizd -programs/vizd/test -programs/delayed_node programs/build_helpers/cat-parts -programs/size_checker/size_checker programs/util/get_dev_key -programs/util/inflation_model - -tests/app_test -tests/chain_bench -tests/chain_test -tests/plugin_test -tests/intense_test -tests/performance_test wallet.json node_data_dir @@ -58,7 +46,7 @@ build-*/ node_modules/* -.qoder/logs/* +.qoder/ # Local design specs and implementation plans (not part of repo) docs/superpowers/ diff --git a/.qoder/agents/senior-engineer.md b/.qoder/agents/senior-engineer.md deleted file mode 100644 index 01e308ca3c..0000000000 --- a/.qoder/agents/senior-engineer.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -name: senior-engineer -description: Senior engineer focused on implementation speed and correctness. Use for precise, surgical code changes where every modification must trace directly to the task. Ideal for feature implementation, bug fixes, and refactoring with minimal blast radius. -tools: Read, Write, Edit, Bash, Grep, Glob ---- - -# Role Definition - -You are a senior engineer focused on implementation speed and correctness. - -Your job: research bugs, build exactly what is asked, nothing more, nothing less. - -## Language Rules - -- **Chat responses**: respond to the user **only in Russian** -- **Plan, commit messages, code comments**: write in **English** -- **In-code comments**: must be self-contained — no references to external log files, ticket numbers, fix IDs, or task numbers (log files are temporary and will be deleted) - -## Rules - -- Read the full context before writing a single line -- Make surgical changes only, touch nothing adjacent to the task -- If you see a better approach, say so before building -- Validate your changes against existing logic before responding -- Every changed line must trace directly to the task -- For long-horizon tasks, state your plan and verify each step before moving to the next - -## Agent Behavior - -- Report progress every 30 steps -- Flag blockers immediately instead of working around them silently -- If a subtask fails, pause and surface it rather than continuing -- **Never suggest building the project** — this is a public project that auto-builds via GitHub Actions and publishes to Docker Hub. Always assume you are working with the latest build. - -## Debugging Rules - -- **Never guess workflow from logs.** Instead, verify step-by-step execution in the code — trace the actual code path that produced the log output. -- When analyzing a bug, read the relevant source files and follow the execution flow rather than hypothesizing based on log messages alone. - -## Workflow - -1. Read and fully understand the task requirements and surrounding code -2. Identify the minimal set of files and lines that need to change -3. If a better approach exists, state it clearly before proceeding -4. Plan the changes, listing each file and the specific modification -5. Implement changes one at a time, verifying each against existing logic -6. Confirm nothing adjacent was broken -7. After implementation, generate a **medium-length** git commit message in English and output it in chat inside a ``` block **without literal \n characters** so the user can copy-paste it in one click - -## Output Format - -**Plan** -- List of files to modify and what changes are needed -- Write the plan in **English** -- Ask to make changes, implement plan - -**Changes Made** -- File and line references for each modification -- Brief justification tracing back to the task -- Code comments must be **self-contained** — no references to log files, fix numbers, task IDs, or any external artifact - -**Verification** -- How the changes were validated -- Any risks or follow-up items - -**Commit Message** -- After all changes are implemented, output a medium-length English commit message in a single ``` block -- Do NOT use literal `\n` in the message — write actual line breaks -- The message must be ready to copy-paste with one click - -## Constraints - -**MUST DO:** -- Trace every changed line directly to the task -- Verify changes against existing logic before finalizing -- Surface blockers immediately -- Ask to implement plan before making changes to files -- Respond to the user in Russian in chat -- Write plans and commit messages in English -- Make code comments self-contained without references to external artifacts -- Verify code execution paths step by step instead of guessing from logs -- Assume the latest build — never suggest building - -**MUST NOT DO:** -- Touch code unrelated to the task -- Make speculative or "while I'm here" improvements -- Work around blockers silently -- Continue past a failed subtask without surfacing it -- Reference log file paths or names in code comments -- Include fix/task/ticket numbers in code comments -- Guess workflow from logs — verify in code -- Suggest building the project -- Use literal `\n` in commit message blocks - -## Success Criteria - -The change works, nothing else broke, every step is traceable. diff --git a/.qoder/docs/block-log-reader.js b/.qoder/docs/block-log-reader.js deleted file mode 100644 index 510e0d85a1..0000000000 --- a/.qoder/docs/block-log-reader.js +++ /dev/null @@ -1,2107 +0,0 @@ -/** - * VIZ Block Log Reader for JavaScript/Node.js - * - * Reads block_log and dlt_block_log files from VIZ blockchain nodes. - * Based on block-log-spec.json specification. - * - * @module block-log-reader - */ - -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -// ============================================================================ -// Constants -// ============================================================================ - -const CONSTANTS = { - CHAIN_BLOCK_SIZE: 1048576, // Max block size (1 MB) - MIN_VALID_FILE_SIZE: 8, // Minimum valid file size - INDEX_HEADER_SIZE: 8, // DLT index header size - SIGNATURE_SIZE: 65, // Compact signature size - HASH_SIZE_RIPEMD160: 20, // RIPEMD-160 hash size - HASH_SIZE_SHA256: 32, // SHA-256 hash size - NPOS: BigInt('0xFFFFFFFFFFFFFFFF'), // Invalid position marker - - CHAIN_ADDRESS_PREFIX: 'VIZ', // Public key base58 prefix - - // Asset symbols (Steem-style: byte 0 = decimals, bytes 1-6 = ASCII name, byte 7 = 0x00) - // From config.hpp: SHARES_SYMBOL = 6|('S'<<8)|('H'<<16)|('A'<<24)|('R'<<32)|('E'<<40)|('S'<<48) - // TOKEN_SYMBOL = 3|('V'<<8)|('I'<<16)|('Z'<<24) - TOKEN_SYMBOL: BigInt('0x000000005A495603'), - SHARES_SYMBOL: BigInt('0x0053455241485306') -}; - -// Operation type IDs mapping -const OPERATION_TYPES = { - 0: { name: 'vote_operation', isVirtual: false }, - 1: { name: 'content_operation', isVirtual: false }, - 2: { name: 'transfer_operation', isVirtual: false }, - 3: { name: 'transfer_to_vesting_operation', isVirtual: false }, - 4: { name: 'withdraw_vesting_operation', isVirtual: false }, - 5: { name: 'account_update_operation', isVirtual: false }, - 6: { name: 'witness_update_operation', isVirtual: false }, - 7: { name: 'account_witness_vote_operation', isVirtual: false }, - 8: { name: 'account_witness_proxy_operation', isVirtual: false }, - 9: { name: 'delete_content_operation', isVirtual: false }, - 10: { name: 'custom_operation', isVirtual: false }, - 11: { name: 'set_withdraw_vesting_route_operation', isVirtual: false }, - 12: { name: 'request_account_recovery_operation', isVirtual: false }, - 13: { name: 'recover_account_operation', isVirtual: false }, - 14: { name: 'change_recovery_account_operation', isVirtual: false }, - 15: { name: 'escrow_transfer_operation', isVirtual: false }, - 16: { name: 'escrow_dispute_operation', isVirtual: false }, - 17: { name: 'escrow_release_operation', isVirtual: false }, - 18: { name: 'escrow_approve_operation', isVirtual: false }, - 19: { name: 'delegate_vesting_shares_operation', isVirtual: false }, - 20: { name: 'account_create_operation', isVirtual: false }, - 21: { name: 'account_metadata_operation', isVirtual: false }, - 22: { name: 'proposal_create_operation', isVirtual: false }, - 23: { name: 'proposal_update_operation', isVirtual: false }, - 24: { name: 'proposal_delete_operation', isVirtual: false }, - 25: { name: 'chain_properties_update_operation', isVirtual: false }, - 26: { name: 'author_reward_operation', isVirtual: true }, - 27: { name: 'curation_reward_operation', isVirtual: true }, - 28: { name: 'content_reward_operation', isVirtual: true }, - 29: { name: 'fill_vesting_withdraw_operation', isVirtual: true }, - 30: { name: 'shutdown_witness_operation', isVirtual: true }, - 31: { name: 'hardfork_operation', isVirtual: true }, - 32: { name: 'content_payout_update_operation', isVirtual: true }, - 33: { name: 'content_benefactor_reward_operation', isVirtual: true }, - 34: { name: 'return_vesting_delegation_operation', isVirtual: true }, - 35: { name: 'committee_worker_create_request_operation', isVirtual: false }, - 36: { name: 'committee_worker_cancel_request_operation', isVirtual: false }, - 37: { name: 'committee_vote_request_operation', isVirtual: false }, - 38: { name: 'committee_cancel_request_operation', isVirtual: true }, - 39: { name: 'committee_approve_request_operation', isVirtual: true }, - 40: { name: 'committee_payout_request_operation', isVirtual: true }, - 41: { name: 'committee_pay_request_operation', isVirtual: true }, - 42: { name: 'witness_reward_operation', isVirtual: true }, - 43: { name: 'create_invite_operation', isVirtual: false }, - 44: { name: 'claim_invite_balance_operation', isVirtual: false }, - 45: { name: 'invite_registration_operation', isVirtual: false }, - 46: { name: 'versioned_chain_properties_update_operation', isVirtual: false }, - 47: { name: 'award_operation', isVirtual: false }, - 48: { name: 'receive_award_operation', isVirtual: true }, - 49: { name: 'benefactor_award_operation', isVirtual: true }, - 50: { name: 'set_paid_subscription_operation', isVirtual: false }, - 51: { name: 'paid_subscribe_operation', isVirtual: false }, - 52: { name: 'paid_subscription_action_operation', isVirtual: true }, - 53: { name: 'cancel_paid_subscription_operation', isVirtual: true }, - 54: { name: 'set_account_price_operation', isVirtual: false }, - 55: { name: 'set_subaccount_price_operation', isVirtual: false }, - 56: { name: 'buy_account_operation', isVirtual: false }, - 57: { name: 'account_sale_operation', isVirtual: true }, - 58: { name: 'use_invite_balance_operation', isVirtual: false }, - 59: { name: 'expire_escrow_ratification_operation', isVirtual: true }, - 60: { name: 'fixed_award_operation', isVirtual: false }, - 61: { name: 'target_account_sale_operation', isVirtual: false }, - 62: { name: 'bid_operation', isVirtual: true }, - 63: { name: 'outbid_operation', isVirtual: true } -}; - -// ============================================================================ -// Binary Reading Utilities -// ============================================================================ - -class BinaryReader { - constructor(buffer, offset = 0) { - this.buffer = buffer; - this.offset = offset; - } - - /** - * Read unsigned 8-bit integer - */ - readUint8() { - return this.buffer.readUInt8(this.offset++); - } - - /** - * Read unsigned 16-bit integer (little-endian) - */ - readUint16LE() { - const value = this.buffer.readUInt16LE(this.offset); - this.offset += 2; - return value; - } - - /** - * Read unsigned 32-bit integer (little-endian) - */ - readUint32LE() { - const value = this.buffer.readUInt32LE(this.offset); - this.offset += 4; - return value; - } - - /** - * Read unsigned 64-bit integer as BigInt (little-endian) - */ - readUint64LE() { - const value = this.buffer.readBigUInt64LE(this.offset); - this.offset += 8; - return value; - } - - /** - * Read signed 32-bit integer (little-endian) - */ - readInt32LE() { - const value = this.buffer.readInt32LE(this.offset); - this.offset += 4; - return value; - } - - /** - * Read signed 64-bit integer as BigInt (little-endian) - */ - readInt64LE() { - const value = this.buffer.readBigInt64LE(this.offset); - this.offset += 8; - return value; - } - - /** - * Read variable-length unsigned integer (fc::unsigned_int) - * Uses protobuf-style varint encoding - */ - readVarint() { - let value = BigInt(0); - let shift = 0; - - while (true) { - const byte = this.readUint8(); - value |= BigInt(byte & 0x7F) << BigInt(shift); - - if (!(byte & 0x80)) break; - shift += 7; - } - - return value; - } - - /** - * Read variable-length signed integer (fc::signed_int) - * Uses zigzag encoding + varint - */ - readSignedVarint() { - const n = this.readVarint(); - // Un-zigzag: (n >> 1) ^ -(n & 1) - return Number((n >> BigInt(1)) ^ (-(n & BigInt(1)))); - } - - /** - * Read fixed-length bytes - */ - readBytes(length) { - const data = this.buffer.slice(this.offset, this.offset + length); - this.offset += length; - return data; - } - - /** - * Read fc::raw serialized string - */ - readString() { - const length = Number(this.readVarint()); - if (length === 0) return ''; - const data = this.readBytes(length); - return data.toString('utf8'); - } - - /** - * Read fc::raw serialized vector of items using provided reader function - * @param {Function} itemReader - Function to read each item - * @param {number} [maxItems=100000] - Safety cap to prevent OOM from corrupted data - */ - readVector(itemReader, maxItems = 100000) { - const count = Number(this.readVarint()); - if (count > maxItems) { - throw new Error(`readVector: count ${count} exceeds safety cap ${maxItems} (offset ${this.offset})`); - } - const items = []; - - for (let i = 0; i < count; i++) { - items.push(itemReader(this)); - } - - return items; - } - - /** - * Read fc::raw serialized optional value - */ - readOptional(itemReader) { - const flag = this.readUint8(); - if (flag === 0) return null; - return itemReader(this); - } - - /** - * Read flat_set of pairs - */ - readFlatSetOfPairs(keyReader, valueReader) { - const count = Number(this.readVarint()); - const items = []; - for (let i = 0; i < count; i++) { - const key = keyReader(this); - const value = valueReader(this); - items.push([key, value]); - } - return items; - } - - /** - * Get current position - */ - getPosition() { - return this.offset; - } - - /** - * Set position - */ - setPosition(pos) { - this.offset = pos; - } - - /** - * Get remaining bytes - */ - getRemaining() { - return this.buffer.length - this.offset; - } -} - -// ============================================================================ -// Type Deserializers -// ============================================================================ - -/** - * Read RIPEMD-160 hash (20 bytes) - */ -function readRipemd160(reader) { - return reader.readBytes(CONSTANTS.HASH_SIZE_RIPEMD160); -} - -/** - * Read SHA-256 hash (32 bytes) - */ -function readSha256(reader) { - return reader.readBytes(CONSTANTS.HASH_SIZE_SHA256); -} - -/** - * Read compact signature (65 bytes) - */ -function readCompactSignature(reader) { - return reader.readBytes(CONSTANTS.SIGNATURE_SIZE); -} - -/** - * Read time_point_sec (uint32 Unix timestamp) - */ -function readTimePointSec(reader) { - const timestamp = reader.readUint32LE(); - return new Date(timestamp * 1000); -} - -/** - * Read block_header_extension (static_variant) - * - * Type 0: void_t — empty struct, no serialized data - * Type 1: version — uint32_t v_num (major.hardfork.release packed as 8.8.16 bits) - * Type 2: hardfork_version_vote — hardfork_version (uint32_t) + time_point_sec (uint32_t) - */ -function readBlockHeaderExtension(reader) { - const typeIndex = Number(reader.readVarint()); - switch (typeIndex) { - case 0: return { typeIndex, name: 'void_t', data: {} }; - case 1: { - const vNum = reader.readUint32LE(); - const major = (vNum >> 24) & 0xFF; - const hardfork = (vNum >> 16) & 0xFF; - const release = vNum & 0xFFFF; - return { typeIndex, name: 'version', data: { v_num: vNum, version: `${major}.${hardfork}.${release}` } }; - } - case 2: { - const vNum = reader.readUint32LE(); - const major = (vNum >> 24) & 0xFF; - const hardfork = (vNum >> 16) & 0xFF; - const hfTime = readTimePointSec(reader); - return { typeIndex, name: 'hardfork_version_vote', data: { hf_version: `${major}.${hardfork}.0`, hf_time: hfTime } }; - } - default: - // Unknown extension type — can't skip without schema, deserialization will likely fail - return { typeIndex, name: 'unknown_extension', data: { _warning: 'unknown extension type, stream may be corrupted' } }; - } -} - -/** - * Read block_header - * - * Note (Steem/VIZ lineage): block_id_type and checksum_type are fc::ripemd160 (20 bytes), - * NOT fc::sha256 (32 bytes). This is an inherited design from the Steem codebase where - * block IDs and merkle roots use the shorter ripemd160 hash. - */ -function readBlockHeader(reader) { - return { - previous: readRipemd160(reader), - timestamp: readTimePointSec(reader), - witness: reader.readString(), - transaction_merkle_root: readRipemd160(reader), - extensions: reader.readVector(readBlockHeaderExtension) - }; -} - -/** - * Read signed_block_header - */ -function readSignedBlockHeader(reader) { - const header = readBlockHeader(reader); - return { - ...header, - witness_signature: readCompactSignature(reader) - }; -} - -// ============================================================================ -// Common Types for Operations -// ============================================================================ - -/** - * Decode Steem-style asset symbol from uint64. - * Format (from asset.cpp comments): - * byte 0 : decimals (precision) - * bytes 1-6 : ASCII symbol name - * byte 7 : 0x00 (null terminator) - */ -function decodeAssetSymbol(symbol) { - // Read as 8-byte buffer (little-endian uint64) - const buf = Buffer.alloc(8); - buf.writeBigUInt64LE(BigInt(symbol), 0); - - const decimals = buf.readUInt8(0); - let name = ''; - for (let i = 1; i < 7; i++) { - const c = buf.readUInt8(i); - if (c === 0) break; - name += String.fromCharCode(c); - } - return { decimals, name }; -} - -/** - * Read asset (int64 amount + uint64 symbol) - * - * Asset symbol format inherited from Steem codebase: - * byte 0 = decimal precision, bytes 1-6 = ASCII name, byte 7 = null - */ -function readAsset(reader) { - const amount = reader.readInt64LE(); - const symbol = reader.readUint64LE(); - const decoded = decodeAssetSymbol(symbol); - return { amount: Number(amount), symbol: decoded.name, decimals: decoded.decimals }; -} - -/** - * Base58 encode (Bitcoin alphabet). - * Encodes a Buffer to a base58 string. - */ -const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; -function base58Encode(buf) { - let num = BigInt('0x' + buf.toString('hex')); - let result = ''; - while (num > 0n) { - result = BASE58_ALPHABET[Number(num % 58n)] + result; - num = num / 58n; - } - // Leading zero bytes -> leading '1' chars - for (let i = 0; i < buf.length && buf[i] === 0; i++) { - result = '1' + result; - } - return result; -} - -/** - * Convert raw 33-byte compressed public key to "VIZ..." address string. - * Mirrors C++ public_key_type::operator std::string(): - * 1. Compute ripemd160 of the 33 raw bytes - * 2. Take first 4 bytes of the hash as checksum - * 3. Pack: [33 bytes key data][4 bytes checksum LE] = 37 bytes - * 4. Base58 encode and prepend "VIZ" prefix - */ -function publicKeyToString(keyBytes) { - const checksum = crypto.createHash('ripemd160').update(keyBytes).digest(); - const packed = Buffer.alloc(37); - keyBytes.copy(packed, 0); // 33 bytes key data - checksum.copy(packed, 33, 0, 4); // 4 bytes checksum - return CONSTANTS.CHAIN_ADDRESS_PREFIX + base58Encode(packed); -} - -/** - * Read public_key_type (33 bytes compressed) as "VIZ..." string - */ -function readPublicKey(reader) { - const keyBytes = reader.readBytes(33); - return publicKeyToString(keyBytes); -} - -/** - * Read account_name_type (string) - */ -function readAccountName(reader) { - return reader.readString(); -} - -/** - * Read authority - */ -function readAuthority(reader) { - return { - weight_threshold: reader.readUint32LE(), - account_auths: reader.readFlatSetOfPairs(readAccountName, (r) => r.readUint16LE()), - key_auths: reader.readFlatSetOfPairs(readPublicKey, (r) => r.readUint16LE()) - }; -} - -/** - * Read beneficiary_route_type - */ -function readBeneficiaryRoute(reader) { - return { - account: readAccountName(reader), - weight: reader.readUint16LE() - }; -} - -// ============================================================================ -// Operation Deserializers -// ============================================================================ - -/** - * Read transfer_operation (ID: 2) - */ -function readTransferOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - amount: readAsset(reader), - memo: reader.readString() - }; -} - -/** - * Read account_create_operation (ID: 20) - */ -function readAccountCreateOperation(reader) { - return { - fee: readAsset(reader), - delegation: readAsset(reader), - creator: readAccountName(reader), - new_account_name: readAccountName(reader), - master: readAuthority(reader), - active: readAuthority(reader), - regular: readAuthority(reader), - memo_key: readPublicKey(reader), - json_metadata: reader.readString(), - referrer: readAccountName(reader), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read witness_update_operation (ID: 6) - */ -function readWitnessUpdateOperation(reader) { - return { - owner: readAccountName(reader), - url: reader.readString(), - block_signing_key: readPublicKey(reader) - }; -} - -/** - * Read award_operation (ID: 47) - */ -function readAwardOperation(reader) { - return { - initiator: readAccountName(reader), - receiver: readAccountName(reader), - energy: reader.readUint16LE(), - custom_sequence: Number(reader.readUint64LE()), - memo: reader.readString(), - beneficiaries: reader.readVector(readBeneficiaryRoute) - }; -} - -/** - * Read transfer_to_vesting_operation (ID: 3) - */ -function readTransferToVestingOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - amount: readAsset(reader) - }; -} - -/** - * Read delegate_vesting_shares_operation (ID: 19) - */ -function readDelegateVestingSharesOperation(reader) { - return { - delegator: readAccountName(reader), - delegatee: readAccountName(reader), - vesting_shares: readAsset(reader) - }; -} - -/** - * Read create_invite_operation (ID: 43) - */ -function readCreateInviteOperation(reader) { - return { - creator: readAccountName(reader), - balance: readAsset(reader), - invite_key: readPublicKey(reader) - }; -} - -/** - * Read custom_operation (ID: 10) - */ -function readCustomOperation(reader) { - return { - required_active_auths: reader.readVector(readAccountName), - required_regular_auths: reader.readVector(readAccountName), - id: reader.readString(), - json: reader.readString() - }; -} - -/** - * Read author_reward_operation (ID: 26) - virtual - */ -function readAuthorRewardOperation(reader) { - return { - author: readAccountName(reader), - permlink: reader.readString(), - token_payout: readAsset(reader), - vesting_payout: readAsset(reader) - }; -} - -/** - * Read witness_reward_operation (ID: 42) - virtual - */ -function readWitnessRewardOperation(reader) { - return { - witness: readAccountName(reader), - shares: readAsset(reader) - }; -} - -/** - * Read account_update_operation (ID: 5) - */ -function readAccountUpdateOperation(reader) { - return { - account: readAccountName(reader), - master: reader.readOptional(readAuthority), - active: reader.readOptional(readAuthority), - regular: reader.readOptional(readAuthority), - memo_key: readPublicKey(reader), - json_metadata: reader.readString() - }; -} - -/** - * Read withdraw_vesting_operation (ID: 4) - */ -function readWithdrawVestingOperation(reader) { - return { - account: readAccountName(reader), - vesting_shares: readAsset(reader) - }; -} - -// ============================================================================ -// Missing operation readers — must advance reader past all fields correctly -// to prevent stream corruption. Field order from FC_REFLECT in C++ source. -// ============================================================================ - -/** - * Read vote_operation (ID: 0) - deprecated but still in blocks - * Fields: voter, author, permlink, weight - */ -function readVoteOperation(reader) { - return { - voter: readAccountName(reader), - author: readAccountName(reader), - permlink: reader.readString(), - weight: reader.readUint16LE() | 0 - }; -} - -/** - * Read content_operation (ID: 1) - deprecated - * Fields: parent_author, parent_permlink, author, permlink, title, body, curation_percent, json_metadata, extensions - */ -function readContentOperation(reader) { - return { - parent_author: readAccountName(reader), - parent_permlink: reader.readString(), - author: readAccountName(reader), - permlink: reader.readString(), - title: reader.readString(), - body: reader.readString(), - curation_percent: reader.readUint16LE(), - json_metadata: reader.readString(), - extensions: reader.readVector(readContentExtension) - }; -} - -/** - * Read content_extension (static_variant for content_operation extensions) - */ -function readContentExtension(reader) { - const typeIndex = Number(reader.readVarint()); - switch (typeIndex) { - case 0: { - // content_payout_beneficiaries - const beneficiaries = reader.readVector(readBeneficiaryRoute); - return { typeIndex, name: 'content_payout_beneficiaries', data: { beneficiaries } }; - } - default: - return { typeIndex, name: 'unknown_content_extension', data: {} }; - } -} - -/** - * Read account_witness_vote_operation (ID: 7) - * Fields: account, witness, approve - */ -function readAccountWitnessVoteOperation(reader) { - return { - account: readAccountName(reader), - witness: readAccountName(reader), - approve: reader.readUint8() !== 0 - }; -} - -/** - * Read account_witness_proxy_operation (ID: 8) - * Fields: account, proxy - */ -function readAccountWitnessProxyOperation(reader) { - return { - account: readAccountName(reader), - proxy: readAccountName(reader) - }; -} - -/** - * Read delete_content_operation (ID: 9) - deprecated - * Fields: author, permlink - */ -function readDeleteContentOperation(reader) { - return { - author: readAccountName(reader), - permlink: reader.readString() - }; -} - -/** - * Read set_withdraw_vesting_route_operation (ID: 11) - * Fields: from_account, to_account, percent, auto_vest - */ -function readSetWithdrawVestingRouteOperation(reader) { - return { - from_account: readAccountName(reader), - to_account: readAccountName(reader), - percent: reader.readUint16LE(), - auto_vest: reader.readUint8() !== 0 - }; -} - -/** - * Read request_account_recovery_operation (ID: 12) - * Fields: recovery_account, account_to_recover, new_master_authority, extensions - */ -function readRequestAccountRecoveryOperation(reader) { - return { - recovery_account: readAccountName(reader), - account_to_recover: readAccountName(reader), - new_master_authority: readAuthority(reader), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read recover_account_operation (ID: 13) - * Fields: account_to_recover, new_master_authority, recent_master_authority, extensions - */ -function readRecoverAccountOperation(reader) { - return { - account_to_recover: readAccountName(reader), - new_master_authority: readAuthority(reader), - recent_master_authority: readAuthority(reader), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read change_recovery_account_operation (ID: 14) - * Fields: account_to_recover, new_recovery_account, extensions - */ -function readChangeRecoveryAccountOperation(reader) { - return { - account_to_recover: readAccountName(reader), - new_recovery_account: readAccountName(reader), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read escrow_transfer_operation (ID: 15) - * Fields: from, to, token_amount, escrow_id, agent, fee, json_metadata, ratification_deadline, escrow_expiration - */ -function readEscrowTransferOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - token_amount: readAsset(reader), - escrow_id: reader.readUint32LE(), - agent: readAccountName(reader), - fee: readAsset(reader), - json_metadata: reader.readString(), - ratification_deadline: readTimePointSec(reader), - escrow_expiration: readTimePointSec(reader) - }; -} - -/** - * Read escrow_dispute_operation (ID: 16) - * Fields: from, to, agent, who, escrow_id - */ -function readEscrowDisputeOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - agent: readAccountName(reader), - who: readAccountName(reader), - escrow_id: reader.readUint32LE() - }; -} - -/** - * Read escrow_release_operation (ID: 17) - * Fields: from, to, agent, who, receiver, escrow_id, token_amount - */ -function readEscrowReleaseOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - agent: readAccountName(reader), - who: readAccountName(reader), - receiver: readAccountName(reader), - escrow_id: reader.readUint32LE(), - token_amount: readAsset(reader) - }; -} - -/** - * Read escrow_approve_operation (ID: 18) - * Fields: from, to, agent, who, escrow_id, approve - */ -function readEscrowApproveOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - agent: readAccountName(reader), - who: readAccountName(reader), - escrow_id: reader.readUint32LE(), - approve: reader.readUint8() !== 0 - }; -} - -/** - * Read account_metadata_operation (ID: 21) - * Fields: account, json_metadata - */ -function readAccountMetadataOperation(reader) { - return { - account: readAccountName(reader), - json_metadata: reader.readString() - }; -} - -/** - * Read proposal_create_operation (ID: 22) - * Fields: author, title, memo, expiration_time, proposed_operations, review_period_time, extensions - */ -function readProposalCreateOperation(reader) { - return { - author: readAccountName(reader), - title: reader.readString(), - memo: reader.readString(), - expiration_time: readTimePointSec(reader), - proposed_operations: reader.readVector(readOperationWrapper), - review_period_time: reader.readOptional(readTimePointSec), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read operation_wrapper (just wraps an operation) - */ -function readOperationWrapper(reader) { - return { op: readOperation(reader) }; -} - -/** - * Read proposal_update_operation (ID: 23) - * Fields: author, title, active_approvals_to_add, active_approvals_to_remove, - * master_approvals_to_add, master_approvals_to_remove, - * regular_approvals_to_add, regular_approvals_to_remove, - * key_approvals_to_add, key_approvals_to_remove, extensions - */ -function readProposalUpdateOperation(reader) { - return { - author: readAccountName(reader), - title: reader.readString(), - active_approvals_to_add: reader.readVector(readAccountName), - active_approvals_to_remove: reader.readVector(readAccountName), - master_approvals_to_add: reader.readVector(readAccountName), - master_approvals_to_remove: reader.readVector(readAccountName), - regular_approvals_to_add: reader.readVector(readAccountName), - regular_approvals_to_remove: reader.readVector(readAccountName), - key_approvals_to_add: reader.readVector(readPublicKey), - key_approvals_to_remove: reader.readVector(readPublicKey), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read proposal_delete_operation (ID: 24) - * FC_REFLECT: (author)(title)(requester)(extensions) - */ -function readProposalDeleteOperation(reader) { - return { - author: readAccountName(reader), - title: reader.readString(), - requester: readAccountName(reader), - extensions: reader.readVector(() => reader.readVarint()) - }; -} - -/** - * Read chain_properties_update_operation (ID: 25) - * Fields: owner, props - */ -function readChainPropertiesUpdateOperation(reader) { - return { - owner: readAccountName(reader), - props: readChainPropertiesInit(reader) - }; -} - -/** - * Read chain_properties_init (used by chain_properties_update_operation) - * FC_REFLECT: (account_creation_fee)(maximum_block_size)(create_account_delegation_ratio) - * (create_account_delegation_time)(min_delegation)(min_curation_percent)(max_curation_percent) - * (bandwidth_reserve_percent)(bandwidth_reserve_below)(flag_energy_additional_cost) - * (vote_accounting_min_rshares)(committee_request_approve_min_percent) - */ -function readChainPropertiesInit(reader) { - return { - account_creation_fee: readAsset(reader), - maximum_block_size: reader.readUint32LE(), - create_account_delegation_ratio: reader.readUint32LE(), - create_account_delegation_time: reader.readUint32LE(), - min_delegation: readAsset(reader), - min_curation_percent: reader.readUint16LE() | 0, - max_curation_percent: reader.readUint16LE() | 0, - bandwidth_reserve_percent: reader.readUint16LE() | 0, - bandwidth_reserve_below: readAsset(reader), - flag_energy_additional_cost: reader.readUint16LE() | 0, - vote_accounting_min_rshares: reader.readUint32LE(), - committee_request_approve_min_percent: reader.readUint16LE() | 0 - }; -} - -/** - * Read versioned_chain_properties (static_variant of chain_properties_init/hf4/hf6/hf9) - * Used by versioned_chain_properties_update_operation. - * FC_REFLECT_DERIVED means each variant includes all base fields + its own. - */ -function readVersionedChainProperties(reader) { - const typeIndex = Number(reader.readVarint()); - const init = readChainPropertiesInit(reader); - - if (typeIndex === 0) { - // chain_properties_init — no additional fields - return { _type: 'chain_properties_init', ...init }; - } - - // chain_properties_hf4 = init + 3 fields - init.inflation_witness_percent = reader.readUint16LE() | 0; - init.inflation_ratio_committee_vs_reward_fund = reader.readUint16LE() | 0; - init.inflation_recalc_period = reader.readUint32LE(); - - if (typeIndex === 1) { - return { _type: 'chain_properties_hf4', ...init }; - } - - // chain_properties_hf6 = hf4 + 3 fields - init.data_operations_cost_additional_bandwidth = reader.readUint32LE(); - init.witness_miss_penalty_percent = reader.readUint16LE() | 0; - init.witness_miss_penalty_duration = reader.readUint32LE(); - - if (typeIndex === 2) { - return { _type: 'chain_properties_hf6', ...init }; - } - - // chain_properties_hf9 = hf6 + 7 fields - init.create_invite_min_balance = readAsset(reader); - init.committee_create_request_fee = readAsset(reader); - init.create_paid_subscription_fee = readAsset(reader); - init.account_on_sale_fee = readAsset(reader); - init.subaccount_on_sale_fee = readAsset(reader); - init.witness_declaration_fee = readAsset(reader); - init.withdraw_intervals = reader.readUint16LE(); - - return { _type: 'chain_properties_hf9', ...init }; -} - -// ---- Virtual operations (ID: 27-34, 38-41, 48-49, 52-53, 57, 59, 62-63) ---- - -/** - * Read curation_reward_operation (ID: 27) - virtual - * Fields: curator, reward, content_author, content_permlink - */ -function readCurationRewardOperation(reader) { - return { - curator: readAccountName(reader), - reward: readAsset(reader), - content_author: readAccountName(reader), - content_permlink: reader.readString() - }; -} - -/** - * Read content_reward_operation (ID: 28) - virtual - * Fields: author, permlink, payout - */ -function readContentRewardOperation(reader) { - return { - author: readAccountName(reader), - permlink: reader.readString(), - payout: readAsset(reader) - }; -} - -/** - * Read fill_vesting_withdraw_operation (ID: 29) - virtual - * Fields: from_account, to_account, withdrawn, deposited - */ -function readFillVestingWithdrawOperation(reader) { - return { - from_account: readAccountName(reader), - to_account: readAccountName(reader), - withdrawn: readAsset(reader), - deposited: readAsset(reader) - }; -} - -/** - * Read shutdown_witness_operation (ID: 30) - virtual - * Fields: owner - */ -function readShutdownWitnessOperation(reader) { - return { - owner: readAccountName(reader) - }; -} - -/** - * Read hardfork_operation (ID: 31) - virtual - * Fields: hardfork_id - */ -function readHardforkOperation(reader) { - return { - hardfork_id: reader.readUint32LE() - }; -} - -/** - * Read content_payout_update_operation (ID: 32) - virtual - * Fields: author, permlink - */ -function readContentPayoutUpdateOperation(reader) { - return { - author: readAccountName(reader), - permlink: reader.readString() - }; -} - -/** - * Read content_benefactor_reward_operation (ID: 33) - virtual - * Fields: benefactor, author, permlink, reward - */ -function readContentBenefactorRewardOperation(reader) { - return { - benefactor: readAccountName(reader), - author: readAccountName(reader), - permlink: reader.readString(), - reward: readAsset(reader) - }; -} - -/** - * Read return_vesting_delegation_operation (ID: 34) - virtual - * Fields: account, vesting_shares - */ -function readReturnVestingDelegationOperation(reader) { - return { - account: readAccountName(reader), - vesting_shares: readAsset(reader) - }; -} - -/** - * Read committee_worker_create_request_operation (ID: 35) - * Fields: creator, url, worker, required_amount_min, required_amount_max, duration - */ -function readCommitteeWorkerCreateRequestOperation(reader) { - return { - creator: readAccountName(reader), - url: reader.readString(), - worker: readAccountName(reader), - required_amount_min: readAsset(reader), - required_amount_max: readAsset(reader), - duration: reader.readUint32LE() - }; -} - -/** - * Read committee_worker_cancel_request_operation (ID: 36) - * Fields: creator, request_id - */ -function readCommitteeWorkerCancelRequestOperation(reader) { - return { - creator: readAccountName(reader), - request_id: reader.readUint32LE() - }; -} - -/** - * Read committee_vote_request_operation (ID: 37) - * Fields: voter, request_id, vote_percent - */ -function readCommitteeVoteRequestOperation(reader) { - return { - voter: readAccountName(reader), - request_id: reader.readUint32LE(), - vote_percent: reader.readUint16LE() | 0 - }; -} - -/** - * Read committee_cancel_request_operation (ID: 38) - virtual - * Fields: request_id - */ -function readCommitteeCancelRequestOperation(reader) { - return { - request_id: reader.readUint32LE() - }; -} - -/** - * Read committee_approve_request_operation (ID: 39) - virtual - * Fields: request_id - */ -function readCommitteeApproveRequestOperation(reader) { - return { - request_id: reader.readUint32LE() - }; -} - -/** - * Read committee_payout_request_operation (ID: 40) - virtual - * Fields: request_id - */ -function readCommitteePayoutRequestOperation(reader) { - return { - request_id: reader.readUint32LE() - }; -} - -/** - * Read committee_pay_request_operation (ID: 41) - virtual - * Fields: worker, request_id, tokens - */ -function readCommitteePayRequestOperation(reader) { - return { - worker: readAccountName(reader), - request_id: reader.readUint32LE(), - tokens: readAsset(reader) - }; -} - -/** - * Read claim_invite_balance_operation (ID: 44) - * Fields: initiator, receiver, invite_secret - */ -function readClaimInviteBalanceOperation(reader) { - return { - initiator: readAccountName(reader), - receiver: readAccountName(reader), - invite_secret: reader.readString() // WIF-encoded private key string - }; -} - -/** - * Read invite_registration_operation (ID: 45) - * Fields: initiator, new_account_name, invite_secret, new_account_key - */ -function readInviteRegistrationOperation(reader) { - return { - initiator: readAccountName(reader), - new_account_name: readAccountName(reader), - invite_secret: reader.readString(), // WIF-encoded private key string - new_account_key: readPublicKey(reader) - }; -} - -/** - * Read versioned_chain_properties_update_operation (ID: 46) - * Fields: owner, props - */ -function readVersionedChainPropertiesUpdateOperation(reader) { - return { - owner: readAccountName(reader), - props: readVersionedChainProperties(reader) - }; -} - -/** - * Read receive_award_operation (ID: 48) - virtual - * Fields: initiator, receiver, custom_sequence, memo, shares - */ -function readReceiveAwardOperation(reader) { - return { - initiator: readAccountName(reader), - receiver: readAccountName(reader), - custom_sequence: Number(reader.readUint64LE()), - memo: reader.readString(), - shares: readAsset(reader) - }; -} - -/** - * Read benefactor_award_operation (ID: 49) - virtual - * Fields: same as award but for benefactor - */ -function readBenefactorAwardOperation(reader) { - return { - initiator: readAccountName(reader), - benefactor: readAccountName(reader), - receiver: readAccountName(reader), - custom_sequence: Number(reader.readUint64LE()), - memo: reader.readString(), - shares: readAsset(reader) - }; -} - -/** - * Read set_paid_subscription_operation (ID: 50) - * Fields: account, url, levels, amount, period - */ -function readSetPaidSubscriptionOperation(reader) { - return { - account: readAccountName(reader), - url: reader.readString(), - levels: reader.readUint16LE(), - amount: readAsset(reader), - period: reader.readUint16LE() - }; -} - -/** - * Read paid_subscribe_operation (ID: 51) - * Fields: subscriber, account, level, amount, period, auto_renewal - */ -function readPaidSubscribeOperation(reader) { - return { - subscriber: readAccountName(reader), - account: readAccountName(reader), - level: reader.readUint16LE(), - amount: readAsset(reader), - period: reader.readUint16LE(), - auto_renewal: reader.readUint8() !== 0 - }; -} - -/** - * Read paid_subscription_action_operation (ID: 52) - virtual - * FC_REFLECT: (subscriber)(account)(level)(amount)(period)(summary_duration_sec)(summary_amount) - */ -function readPaidSubscriptionActionOperation(reader) { - return { - subscriber: readAccountName(reader), - account: readAccountName(reader), - level: reader.readUint16LE(), - amount: readAsset(reader), - period: reader.readUint16LE(), - summary_duration_sec: Number(reader.readUint64LE()), - summary_amount: readAsset(reader) - }; -} - -/** - * Read cancel_paid_subscription_operation (ID: 53) - virtual - * FC_REFLECT: (subscriber)(account) - */ -function readCancelPaidSubscriptionOperation(reader) { - return { - subscriber: readAccountName(reader), - account: readAccountName(reader) - }; -} - -/** - * Read set_account_price_operation (ID: 54) - * Fields: account, account_seller, account_offer_price, account_on_sale - */ -function readSetAccountPriceOperation(reader) { - return { - account: readAccountName(reader), - account_seller: readAccountName(reader), - account_offer_price: readAsset(reader), - account_on_sale: reader.readUint8() !== 0 - }; -} - -/** - * Read set_subaccount_price_operation (ID: 55) - * Fields: account, subaccount_seller, subaccount_offer_price, subaccount_on_sale - */ -function readSetSubaccountPriceOperation(reader) { - return { - account: readAccountName(reader), - subaccount_seller: readAccountName(reader), - subaccount_offer_price: readAsset(reader), - subaccount_on_sale: reader.readUint8() !== 0 - }; -} - -/** - * Read buy_account_operation (ID: 56) - * Fields: buyer, account, account_offer_price, account_authorities_key, tokens_to_shares - */ -function readBuyAccountOperation(reader) { - return { - buyer: readAccountName(reader), - account: readAccountName(reader), - account_offer_price: readAsset(reader), - account_authorities_key: readPublicKey(reader), - tokens_to_shares: readAsset(reader) - }; -} - -/** - * Read account_sale_operation (ID: 57) - virtual - */ -function readAccountSaleOperation(reader) { - return { - account: readAccountName(reader), - price: readAsset(reader), - buyer: readAccountName(reader), - seller: readAccountName(reader) - }; -} - -/** - * Read use_invite_balance_operation (ID: 58) - * Fields: initiator, receiver, invite_secret - */ -function readUseInviteBalanceOperation(reader) { - return { - initiator: readAccountName(reader), - receiver: readAccountName(reader), - invite_secret: reader.readString() // WIF-encoded private key string - }; -} - -/** - * Read expire_escrow_ratification_operation (ID: 59) - virtual - */ -function readExpireEscrowRatificationOperation(reader) { - return { - from: readAccountName(reader), - to: readAccountName(reader), - agent: readAccountName(reader), - escrow_id: reader.readUint32LE(), - token_amount: readAsset(reader), - fee: readAsset(reader), - ratification_deadline: readTimePointSec(reader) - }; -} - -/** - * Read fixed_award_operation (ID: 60) - * Fields: initiator, receiver, reward_amount, max_energy, custom_sequence, memo, beneficiaries - */ -function readFixedAwardOperation(reader) { - return { - initiator: readAccountName(reader), - receiver: readAccountName(reader), - reward_amount: readAsset(reader), - max_energy: reader.readUint16LE(), - custom_sequence: Number(reader.readUint64LE()), - memo: reader.readString(), - beneficiaries: reader.readVector(readBeneficiaryRoute) - }; -} - -/** - * Read target_account_sale_operation (ID: 61) - * Fields: account, account_seller, target_buyer, account_offer_price, account_on_sale - */ -function readTargetAccountSaleOperation(reader) { - return { - account: readAccountName(reader), - account_seller: readAccountName(reader), - target_buyer: readAccountName(reader), - account_offer_price: readAsset(reader), - account_on_sale: reader.readUint8() !== 0 - }; -} - -/** - * Read bid_operation (ID: 62) - virtual - */ -function readBidOperation(reader) { - return { - account: readAccountName(reader), - bidder: readAccountName(reader), - bid: readAsset(reader) - }; -} - -/** - * Read outbid_operation (ID: 63) - virtual - */ -function readOutbidOperation(reader) { - return { - account: readAccountName(reader), - bidder: readAccountName(reader), - bid: readAsset(reader) - }; -} - -// Operation deserializer registry -const OPERATION_READERS = { - 0: readVoteOperation, - 1: readContentOperation, - 2: readTransferOperation, - 3: readTransferToVestingOperation, - 4: readWithdrawVestingOperation, - 5: readAccountUpdateOperation, - 6: readWitnessUpdateOperation, - 7: readAccountWitnessVoteOperation, - 8: readAccountWitnessProxyOperation, - 9: readDeleteContentOperation, - 10: readCustomOperation, - 11: readSetWithdrawVestingRouteOperation, - 12: readRequestAccountRecoveryOperation, - 13: readRecoverAccountOperation, - 14: readChangeRecoveryAccountOperation, - 15: readEscrowTransferOperation, - 16: readEscrowDisputeOperation, - 17: readEscrowReleaseOperation, - 18: readEscrowApproveOperation, - 19: readDelegateVestingSharesOperation, - 20: readAccountCreateOperation, - 21: readAccountMetadataOperation, - 22: readProposalCreateOperation, - 23: readProposalUpdateOperation, - 24: readProposalDeleteOperation, - 25: readChainPropertiesUpdateOperation, - 26: readAuthorRewardOperation, - 27: readCurationRewardOperation, - 28: readContentRewardOperation, - 29: readFillVestingWithdrawOperation, - 30: readShutdownWitnessOperation, - 31: readHardforkOperation, - 32: readContentPayoutUpdateOperation, - 33: readContentBenefactorRewardOperation, - 34: readReturnVestingDelegationOperation, - 35: readCommitteeWorkerCreateRequestOperation, - 36: readCommitteeWorkerCancelRequestOperation, - 37: readCommitteeVoteRequestOperation, - 38: readCommitteeCancelRequestOperation, - 39: readCommitteeApproveRequestOperation, - 40: readCommitteePayoutRequestOperation, - 41: readCommitteePayRequestOperation, - 42: readWitnessRewardOperation, - 43: readCreateInviteOperation, - 44: readClaimInviteBalanceOperation, - 45: readInviteRegistrationOperation, - 46: readVersionedChainPropertiesUpdateOperation, - 47: readAwardOperation, - 48: readReceiveAwardOperation, - 49: readBenefactorAwardOperation, - 50: readSetPaidSubscriptionOperation, - 51: readPaidSubscribeOperation, - 52: readPaidSubscriptionActionOperation, - 53: readCancelPaidSubscriptionOperation, - 54: readSetAccountPriceOperation, - 55: readSetSubaccountPriceOperation, - 56: readBuyAccountOperation, - 57: readAccountSaleOperation, - 58: readUseInviteBalanceOperation, - 59: readExpireEscrowRatificationOperation, - 60: readFixedAwardOperation, - 61: readTargetAccountSaleOperation, - 62: readBidOperation, - 63: readOutbidOperation -}; - -/** - * Read operation (static_variant) - * Fully deserializes known operations, returns raw data for unknown ones - */ -function readOperation(reader) { - const typeIndex = Number(reader.readVarint()); - if (typeIndex > 1000) { - throw new Error(`readOperation: typeIndex ${typeIndex} exceeds safety cap (offset ${reader.offset})`); - } - const typeInfo = OPERATION_TYPES[typeIndex] || { name: 'unknown_operation', isVirtual: false }; - const opReader = OPERATION_READERS[typeIndex]; - - let data; - const opStartOffset = reader.offset; - if (opReader) { - try { - data = opReader(reader); - } catch (e) { - const bytesRead = reader.offset - opStartOffset; - console.error(` readOperation ERROR: type=${typeIndex}(${typeInfo.name}) offset=${opStartOffset} bytesRead=${bytesRead} err=${e.message}`); - throw e; - } - } else { - // Unknown operation - can't skip without schema, deserialization will likely fail - console.error(` readOperation: unknown type ${typeIndex} at offset ${opStartOffset}, stream will be corrupted`); - data = { _raw: `unknown operation type ${typeIndex}` }; - } - - // Log per-operation byte consumption when verbose debug is enabled - if (global.__BLR_VERBOSE_OPS__) { - const bytesRead = reader.offset - opStartOffset; - process.stderr.write(` op[${typeIndex}]=${typeInfo.name} @${opStartOffset} +${bytesRead}B\n`); - } - - return { - typeId: typeIndex, - typeName: typeInfo.name, - isVirtual: typeInfo.isVirtual, - data - }; -} - -/** - * Read transaction - */ -function readTransaction(reader) { - return { - ref_block_num: reader.readUint16LE(), - ref_block_prefix: reader.readUint32LE(), - expiration: readTimePointSec(reader), - operations: reader.readVector(readOperation), - extensions: reader.readVector(() => reader.readVarint()) // extensions_type is empty vector - }; -} - -/** - * Read signed_transaction - */ -function readSignedTransaction(reader) { - const trx = readTransaction(reader); - return { - ...trx, - signatures: reader.readVector(readCompactSignature) - }; -} - -/** - * Read signed_block - */ -function readSignedBlock(reader) { - const startOffset = reader.offset; - const header = readSignedBlockHeader(reader); - const headerEndOffset = reader.offset; - let transactions; - try { - const txCount = Number(reader.readVarint()); - if (txCount > 100000) { - throw new Error(`readSignedBlock: tx count ${txCount} exceeds safety cap (offset ${reader.offset})`); - } - const items = []; - for (let i = 0; i < txCount; i++) { - const txStartOffset = reader.offset; - try { - items.push(readSignedTransaction(reader)); - } catch (e) { - console.error(` readSignedBlock: tx[${i}] FAILED at offset ${txStartOffset} (headerEnd=${headerEndOffset}) err=${e.message}`); - throw e; - } - } - transactions = items; - } catch (e) { - console.error(` readSignedBlock ERROR at offset ${startOffset}: headerEnd=${headerEndOffset} err=${e.message}`); - throw e; - } - return { - ...header, - transactions - }; -} - -// ============================================================================ -// Block Number Utilities -// ============================================================================ - -/** - * Extract block number from block_id_type (ripemd160 hash) - * The block number is stored in the first 4 bytes. - * C++ uses fc::endian_reverse_u32 which reads as big-endian uint32. - */ -function numFromId(blockId) { - if (!blockId || blockId.length < 4) return 0; - return blockId.readUInt32BE(0); -} - -/** - * Get block number from signed_block - */ -function getBlockNum(block) { - return numFromId(block.previous) + 1; -} - -/** - * Convert block ID to hex string - */ -function blockIdToHex(blockId) { - return blockId.toString('hex'); -} - -// ============================================================================ -// Block Log Reader -// ============================================================================ - -/** - * Block Log Reader for standard block_log files - */ -class BlockLogReader { - constructor() { - this.dataFd = null; - this.indexFd = null; - this.dataPath = null; - this.indexPath = null; - this.dataSize = 0n; - this.indexSize = 0n; - this._headBlock = null; - this._headBlockNum = 0; - } - - /** - * Open block log files - * @param {string} dataPath - Path to block_log file - * @param {string} [indexPath] - Path to block_log.index file (auto-derived if not provided) - */ - open(dataPath, indexPath) { - this.dataPath = dataPath; - this.indexPath = indexPath || (dataPath + '.index'); - - // Open data file - this.dataFd = fs.openSync(this.dataPath, 'r'); - const dataStats = fs.fstatSync(this.dataFd); - this.dataSize = BigInt(dataStats.size); - - // Open index file - this.indexFd = fs.openSync(this.indexPath, 'r'); - const indexStats = fs.fstatSync(this.indexFd); - this.indexSize = BigInt(indexStats.size); - - // Determine head block number from the index file size. - // Index has 8 bytes per block, starting from block 1 at offset 0. - // head_block_num = index_size / 8 - // This is reliable — we don't need to deserialize the head block for this. - if (this.indexSize >= 8n) { - this._headBlockNum = Number(this.indexSize / 8n); - } - - // Optionally cache the head block (may fail for blocks with unknown ops) - if (this.dataSize > BigInt(CONSTANTS.MIN_VALID_FILE_SIZE)) { - try { - this._headBlock = this.readHead(); - } catch (e) { - this._headBlock = null; - } - } - } - - /** - * Close block log files - */ - close() { - if (this.dataFd !== null) { - fs.closeSync(this.dataFd); - this.dataFd = null; - } - if (this.indexFd !== null) { - fs.closeSync(this.indexFd); - this.indexFd = null; - } - this._headBlock = null; - this._headBlockNum = 0; - } - - /** - * Check if log is open - */ - isOpen() { - return this.dataFd !== null; - } - - /** - * Read bytes from data file - */ - _readData(position, length) { - const buffer = Buffer.allocUnsafe(Number(length)); - fs.readSync(this.dataFd, buffer, 0, Number(length), Number(position)); - return buffer; - } - - /** - * Read bytes from index file - */ - _readIndex(position, length) { - const buffer = Buffer.allocUnsafe(Number(length)); - fs.readSync(this.indexFd, buffer, 0, Number(length), Number(position)); - return buffer; - } - - /** - * Read uint64 from data file - */ - _readDataUint64(position) { - const buffer = this._readData(position, 8); - return buffer.readBigUInt64LE(0); - } - - /** - * Read uint64 from index file - */ - _readIndexUint64(position) { - const buffer = this._readIndex(position, 8); - return buffer.readBigUInt64LE(0); - } - - /** - * Get head block position (last 8 bytes of data file) - */ - _getHeadPosition() { - return this._readDataUint64(this.dataSize - 8n); - } - - /** - * Read block at specified position - * @param {bigint} position - File offset - * @returns {{ block: object, nextPosition: bigint }} - */ - readBlock(position) { - const maxBlockSize = CONSTANTS.CHAIN_BLOCK_SIZE; - const availableSize = Number(this.dataSize - position); - const readSize = Math.min(availableSize, maxBlockSize + 8); - - const buffer = this._readData(position, readSize); - const reader = new BinaryReader(buffer); - - const block = readSignedBlock(reader); - const blockEndPos = reader.getPosition(); - - // Read position marker - const posMarker = buffer.readBigUInt64LE(blockEndPos); - - // Verify position marker matches our read position - if (posMarker !== position) { - throw new Error(`Position mismatch: expected ${position}, got ${posMarker}`); - } - - const nextPosition = blockEndPos + 8; - - return { block, nextPosition }; - } - - /** - * Read head block - */ - readHead() { - if (this._headBlock) return this._headBlock; - - const pos = this._getHeadPosition(); - const result = this.readBlock(pos); - return result.block; - } - - /** - * Get block position from index by block number - * Matches C++ block_log: offset = 8 * (block_num - 1) - * @param {number} blockNum - Block number (1-indexed, matching C++) - * @returns {bigint} Position in data file, or NPOS if out of range - */ - getBlockPos(blockNum) { - if (blockNum < 1 || blockNum > this._headBlockNum) { - return CONSTANTS.NPOS; - } - - const indexOffset = BigInt(blockNum - 1) * 8n; - // Bounds check: index may be incomplete (fewer entries than head block) - if (indexOffset + 8n > this.indexSize) { - return CONSTANTS.NPOS; - } - return this._readIndexUint64(indexOffset); - } - - /** - * Read block by number - * @param {number} blockNum - Block number - * @returns {object|null} Signed block, or null if block_num out of range - * @throws Error if block found but deserialization failed - */ - readBlockByNum(blockNum) { - const pos = this.getBlockPos(blockNum); - if (pos === CONSTANTS.NPOS) return null; - - const result = this.readBlock(pos); - return result.block; - } - - /** - * Read only the block header by number (no transactions/operations). - * Useful when full block deserialization fails due to unknown operation types. - * @param {number} blockNum - Block number - * @returns {object|null} Block header object, or null if out of range - */ - readBlockHeaderByNum(blockNum) { - const pos = this.getBlockPos(blockNum); - if (pos === CONSTANTS.NPOS) return null; - - try { - const maxBlockSize = CONSTANTS.CHAIN_BLOCK_SIZE; - const availableSize = Number(this.dataSize - pos); - const readSize = Math.min(availableSize, maxBlockSize + 8); - - const buffer = this._readData(pos, readSize); - const br = new BinaryReader(buffer); - - // Only read the signed_block_header part - const header = readSignedBlockHeader(br); - return { - ...header, - _blockNum: getBlockNum(header), - _position: Number(pos), - _headerOnly: true - }; - } catch (e) { - return null; - } - } - - /** - * Read raw block bytes by number. - * Uses the index to find block start offset and the next block's offset to determine size. - * Returns the raw data including the trailing 8-byte position marker. - * @param {number} blockNum - Block number - * @returns {Buffer|null} Raw block bytes, or null if out of range - */ - readBlockRawData(blockNum) { - const startPos = this.getBlockPos(blockNum); - if (startPos === CONSTANTS.NPOS) return null; - - let endPos; - // If there's a next block in the index, its offset is our end - const nextPos = this.getBlockPos(blockNum + 1); - if (nextPos !== CONSTANTS.NPOS) { - endPos = Number(nextPos); - } else { - // Last block: read until end of data file minus the final 8-byte head position pointer - endPos = Number(this.dataSize) - 8; - } - - const size = endPos - Number(startPos); - if (size <= 0 || size > CONSTANTS.CHAIN_BLOCK_SIZE + 16) return null; - - return this._readData(startPos, size); - } - - /** - * Read only the transaction count for a block by number. - * Uses exact block size from index (no 1MB over-read) and skips operation deserialization. - * Virtual operations are NOT in block_log transactions, so tx_count > 0 means the block - * has non-free operations. - * @param {number} blockNum - Block number - * @returns {number} Transaction count, or -1 if out of range / error - */ - readBlockTxCountByNum(blockNum) { - const startPos = this.getBlockPos(blockNum); - if (startPos === CONSTANTS.NPOS) return -1; - - // Calculate exact block size from index - let endPos; - const nextPos = this.getBlockPos(blockNum + 1); - if (nextPos !== CONSTANTS.NPOS) { - endPos = Number(nextPos); - } else { - endPos = Number(this.dataSize) - 8; - } - const size = endPos - Number(startPos); - if (size <= 0 || size > CONSTANTS.CHAIN_BLOCK_SIZE + 16) return -1; - - try { - const buffer = this._readData(startPos, size); - const br = new BinaryReader(buffer); - - // Skip past the signed_block_header (we don't need its data) - readSignedBlockHeader(br); - - // The next varint is the transactions vector length - return Number(br.readVarint()); - } catch (e) { - return -1; - } - } - - /** - * Read a batch of block positions from the index file in one I/O operation. - * Much faster than calling getBlockPos() one at a time for sequential scanning. - * @param {number} startBlockNum - First block number - * @param {number} count - Number of positions to read - * @returns {bigint[]} Array of positions, may be shorter than count if near end - */ - readBlockPosBatch(startBlockNum, count) { - if (startBlockNum < 1 || startBlockNum > this._headBlockNum) return []; - - const actualCount = Math.min(count, this._headBlockNum - startBlockNum + 2); // +1 for next-block boundary - const indexOffset = BigInt(startBlockNum - 1) * 8n; - const readBytes = Number(BigInt(actualCount) * 8n); - - if (indexOffset + BigInt(readBytes) > this.indexSize) { - // Trim to available index - const avail = Number((this.indexSize - indexOffset) / 8n); - if (avail <= 0) return []; - return this.readBlockPosBatch(startBlockNum, avail); - } - - const buf = Buffer.alloc(readBytes); - fs.readSync(this.indexFd, buf, 0, readBytes, Number(indexOffset)); - - const positions = []; - for (let i = 0; i < actualCount; i++) { - positions.push(buf.readBigUInt64LE(i * 8)); - } - return positions; - } - - /** - * Get head block number - */ - getHeadBlockNum() { - return this._headBlockNum; - } - - /** - * Get start block number (always 1 for standard block_log, matching C++) - */ - getStartBlockNum() { - return 1; - } - - /** - * Get total number of blocks (based on index entries) - */ - getNumBlocks() { - return Number(this.indexSize / 8n); - } - - /** - * Iterate all blocks sequentially - * @yields {object} Signed block - */ - *iterateBlocks() { - let pos = 0n; - - while (pos < this.dataSize - 8n) { - const result = this.readBlock(pos); - yield result.block; - pos = BigInt(result.nextPosition); - } - } - - /** - * Validate index file against data file - */ - validateIndex() { - const dataHeadPos = this._getHeadPosition(); - const indexHeadPos = this._readIndexUint64(this.indexSize - 8n); - - return dataHeadPos === indexHeadPos; - } -} - -// ============================================================================ -// DLT Block Log Reader -// ============================================================================ - -/** - * Block Log Reader for DLT (rolling) block log files - */ -class DltBlockLogReader extends BlockLogReader { - constructor() { - super(); - this._startBlockNum = 0; - } - - /** - * Open DLT block log files - */ - open(dataPath, indexPath) { - super.open(dataPath, indexPath); - - // Read start_block_num from index header - if (this.indexSize >= BigInt(CONSTANTS.INDEX_HEADER_SIZE)) { - this._startBlockNum = Number(this._readIndexUint64(0n)); - } - } - - /** - * Get block position from index by block number - * @param {number} blockNum - Block number - * @returns {bigint} Position in data file - */ - getBlockPos(blockNum) { - if (this._startBlockNum === 0) return CONSTANTS.NPOS; - - if (blockNum < this._startBlockNum || blockNum > this._headBlockNum) { - return CONSTANTS.NPOS; - } - - const indexOffset = BigInt(CONSTANTS.INDEX_HEADER_SIZE) + BigInt(blockNum - this._startBlockNum) * 8n; - return this._readIndexUint64(indexOffset); - } - - /** - * Get start block number - */ - getStartBlockNum() { - return this._startBlockNum; - } - - /** - * Get total number of blocks - */ - getNumBlocks() { - if (!this._headBlock || this._startBlockNum === 0) return 0; - return this._headBlockNum - this._startBlockNum + 1; - } - - /** - * Validate index file - */ - validateIndex() { - const dataHeadPos = this._getHeadPosition(); - const indexHeadPos = this._readIndexUint64(this.indexSize - 8n); - - return dataHeadPos === indexHeadPos; - } -} - -// ============================================================================ -// Factory Function -// ============================================================================ - -/** - * Create appropriate block log reader based on file type - * @param {string} dataPath - Path to block log data file - * @param {string} [indexPath] - Path to index file (auto-derived if not provided) - * @param {boolean} [isDlt=false] - True for DLT block log - * @returns {BlockLogReader|DltBlockLogReader} - */ -function createBlockLogReader(dataPath, indexPath, isDlt = false) { - const reader = isDlt ? new DltBlockLogReader() : new BlockLogReader(); - reader.open(dataPath, indexPath); - return reader; -} - -// ============================================================================ -// Exports -// ============================================================================ - -module.exports = { - // Constants - CONSTANTS, - OPERATION_TYPES, - - // Classes - BinaryReader, - BlockLogReader, - DltBlockLogReader, - - // Factory - createBlockLogReader, - - // Utilities - numFromId, - getBlockNum, - blockIdToHex, - - // Deserializers - readRipemd160, - readSha256, - readCompactSignature, - readTimePointSec, - readBlockHeader, - readSignedBlockHeader, - readTransaction, - readSignedTransaction, - readSignedBlock, - - // Operation deserializers - readAsset, - readPublicKey, - publicKeyToString, - base58Encode, - readAccountName, - readAuthority, - readBeneficiaryRoute, - readOperation, - readTransferOperation, - readAccountCreateOperation, - readWitnessUpdateOperation, - readAwardOperation, - readTransferToVestingOperation, - readDelegateVestingSharesOperation, - readCreateInviteOperation, - readCustomOperation, - readAuthorRewardOperation, - readWitnessRewardOperation, - readAccountUpdateOperation, - readWithdrawVestingOperation, - OPERATION_READERS -}; diff --git a/.qoder/docs/block-log-spec.json b/.qoder/docs/block-log-spec.json deleted file mode 100644 index d9c0a87252..0000000000 --- a/.qoder/docs/block-log-spec.json +++ /dev/null @@ -1,1182 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "block-log-spec.json", - "title": "VIZ Block Log File Format Specification", - "description": "Machine-readable specification for VIZ blockchain block log files", - "version": "1.1.0", - - "definitions": { - "varint": { - "type": "object", - "description": "Variable-length integer encoding (fc::unsigned_int)", - "properties": { - "type": { "const": "varint" }, - "encoding": { "const": "protobuf-style" }, - "maxBytes": { "const": 5 }, - "description": { - "const": "7 bits per byte, MSB=1 means more bytes follow. Each byte: [7 data bits][1 continuation bit]" - } - } - }, - - "zigzag_varint": { - "type": "object", - "description": "Signed variable-length integer (fc::signed_int)", - "properties": { - "type": { "const": "zigzag_varint" }, - "encoding": { "const": "zigzag + varint" }, - "description": { - "const": "Zigzag encode: (n << 1) ^ (n >> 31), then varint" - } - } - }, - - "uint8": { - "type": "object", - "properties": { - "type": { "const": "uint8" }, - "size": { "const": 1 }, - "endian": { "const": "none" } - } - }, - - "uint16_le": { - "type": "object", - "properties": { - "type": { "const": "uint16" }, - "size": { "const": 2 }, - "endian": { "const": "little" } - } - }, - - "uint32_le": { - "type": "object", - "properties": { - "type": { "const": "uint32" }, - "size": { "const": 4 }, - "endian": { "const": "little" } - } - }, - - "uint64_le": { - "type": "object", - "properties": { - "type": { "const": "uint64" }, - "size": { "const": 8 }, - "endian": { "const": "little" }, - "jsType": { "const": "BigInt" } - } - }, - - "ripemd160": { - "type": "object", - "properties": { - "type": { "const": "ripemd160" }, - "size": { "const": 20 }, - "description": { "const": "20-byte RIPEMD-160 hash" } - } - }, - - "sha256": { - "type": "object", - "properties": { - "type": { "const": "sha256" }, - "size": { "const": 32 }, - "description": { "const": "32-byte SHA-256 hash" } - } - }, - - "compact_signature": { - "type": "object", - "properties": { - "type": { "const": "compact_signature" }, - "size": { "const": 65 }, - "description": { "const": "secp256k1 compact signature: [1 byte recovery id][32 bytes r][32 bytes s]" } - } - }, - - "public_key_type": { - "type": "object", - "description": "secp256k1 compressed public key with base58 string representation", - "properties": { - "type": { "const": "public_key_type" }, - "wire_size": { "const": 33 }, - "wire_format": { "const": "Raw compressed secp256k1 point: [0x02/0x03 prefix][32 bytes x-coordinate]" }, - "string_encoding": { - "type": "object", - "description": "How public_key_type is displayed as a string (not stored on wire)", - "steps": [ - "Compute ripemd160 hash of the 33 raw key bytes", - "Take first 4 bytes of hash as checksum", - "Concatenate: [33 bytes key][4 bytes checksum] = 37 bytes", - "Base58-encode the 37-byte buffer", - "Prepend CHAIN_ADDRESS_PREFIX ('VIZ')" - ], - "example": "VIZ7wMEutJdCfdSKNgVAp17v9uoTqwwkUqn2kwVsJ6zG5XYJcvj81" - } - } - }, - - "flat_set": { - "type": "object", - "description": "fc::raw serialized flat_set (same wire format as vector)", - "properties": { - "type": { "const": "flat_set" }, - "structure": { - "type": "array", - "items": [ - { "$ref": "#/definitions/varint", "description": "Element count" }, - { "type": "object", "description": "Repeated element type (sorted, unique)" } - ] - } - } - }, - - "flat_map": { - "type": "object", - "description": "fc::raw serialized flat_map (same wire format as vector>)", - "properties": { - "type": { "const": "flat_map" }, - "structure": { - "type": "array", - "items": [ - { "$ref": "#/definitions/varint", "description": "Pair count" }, - { "type": "object", "description": "Repeated pair (K then V for each)" } - ] - } - } - }, - - "optional": { - "type": "object", - "description": "fc::raw serialized optional", - "properties": { - "type": { "const": "optional" }, - "structure": { - "type": "array", - "items": [ - { "type": "object", "properties": { "type": { "const": "uint8" }, "description": { "const": "Flag: 0=absent, 1=present" } } }, - { "type": "object", "description": "Value (only if flag=1)" } - ] - } - } - }, - - "extensions_type": { - "type": "object", - "description": "future_extensions type used in operation extensions fields", - "properties": { - "type": { "const": "extensions_type" }, - "definition": { "const": "flat_set" }, - "future_extensions": { "const": "static_variant" }, - "wire_format": { "const": "varint count + [varint type_index=0] for each item" }, - "usual_state": { "const": "Empty (count=0, serialized as single byte 0x00)" } - } - }, - - "time_point_sec": { - "type": "object", - "properties": { - "type": { "const": "time_point_sec" }, - "size": { "const": 4 }, - "description": { "const": "Unix timestamp in seconds (uint32_t)" } - } - }, - - "fc_string": { - "type": "object", - "description": "fc::raw serialized string", - "properties": { - "type": { "const": "fc_string" }, - "structure": { - "type": "array", - "items": [ - { "$ref": "#/definitions/varint", "description": "Length in bytes" }, - { "type": "object", "properties": { "type": { "const": "bytes" }, "encoding": { "const": "utf-8" } } } - ] - } - } - }, - - "fc_vector": { - "type": "object", - "description": "fc::raw serialized vector", - "properties": { - "type": { "const": "fc_vector" }, - "elementType": { "type": "object" }, - "structure": { - "type": "array", - "items": [ - { "$ref": "#/definitions/varint", "description": "Element count" }, - { "type": "object", "description": "Repeated element type" } - ] - } - } - }, - - "block_header": { - "type": "object", - "description": "block_header structure", - "properties": { - "name": { "const": "block_header" }, - "fields": { - "type": "array", - "items": [ - { - "name": "previous", - "type": { "$ref": "#/definitions/ripemd160" }, - "description": "Hash of previous block (block_id_type)" - }, - { - "name": "timestamp", - "type": { "$ref": "#/definitions/time_point_sec" }, - "description": "Block creation time" - }, - { - "name": "witness", - "type": { "$ref": "#/definitions/fc_string" }, - "description": "Witness account name" - }, - { - "name": "transaction_merkle_root", - "type": { "$ref": "#/definitions/ripemd160" }, - "description": "Merkle root of transactions (checksum_type)" - }, - { - "name": "extensions", - "type": { "$ref": "#/definitions/fc_vector" }, - "description": "Block header extensions (usually empty)" - } - ] - } - } - }, - - "signed_block_header": { - "type": "object", - "description": "signed_block_header extends block_header", - "properties": { - "name": { "const": "signed_block_header" }, - "extends": { "$ref": "#/definitions/block_header" }, - "additionalFields": { - "type": "array", - "items": [ - { - "name": "witness_signature", - "type": { "$ref": "#/definitions/compact_signature" }, - "description": "Witness signature" - } - ] - } - } - }, - - "transaction": { - "type": "object", - "description": "transaction structure", - "properties": { - "name": { "const": "transaction" }, - "fields": { - "type": "array", - "items": [ - { - "name": "ref_block_num", - "type": { "$ref": "#/definitions/uint16_le" }, - "description": "Reference block number" - }, - { - "name": "ref_block_prefix", - "type": { "$ref": "#/definitions/uint32_le" }, - "description": "Reference block prefix" - }, - { - "name": "expiration", - "type": { "$ref": "#/definitions/time_point_sec" }, - "description": "Transaction expiration" - }, - { - "name": "operations", - "type": { "$ref": "#/definitions/fc_vector" }, - "description": "List of operations (static_variant)" - }, - { - "name": "extensions", - "type": { "$ref": "#/definitions/fc_vector" }, - "description": "Transaction extensions (usually empty)" - } - ] - } - } - }, - - "signed_transaction": { - "type": "object", - "description": "signed_transaction extends transaction", - "properties": { - "name": { "const": "signed_transaction" }, - "extends": { "$ref": "#/definitions/transaction" }, - "additionalFields": { - "type": "array", - "items": [ - { - "name": "signatures", - "type": { - "type": "object", - "properties": { - "type": { "const": "fc_vector" }, - "elementType": { "$ref": "#/definitions/compact_signature" } - } - }, - "description": "Transaction signatures" - } - ] - } - } - }, - - "signed_block": { - "type": "object", - "description": "signed_block extends signed_block_header", - "properties": { - "name": { "const": "signed_block" }, - "extends": { "$ref": "#/definitions/signed_block_header" }, - "additionalFields": { - "type": "array", - "items": [ - { - "name": "transactions", - "type": { - "type": "object", - "properties": { - "type": { "const": "fc_vector" }, - "elementType": { "$ref": "#/definitions/signed_transaction" } - } - }, - "description": "List of transactions in block" - } - ] - } - } - }, - - "block_log_entry": { - "type": "object", - "description": "Single block entry in block_log", - "properties": { - "name": { "const": "block_log_entry" }, - "structure": { - "type": "array", - "items": [ - { - "name": "block_data", - "type": { "$ref": "#/definitions/signed_block" }, - "description": "Serialized signed_block" - }, - { - "name": "position_marker", - "type": { "$ref": "#/definitions/uint64_le" }, - "description": "Offset of this block's start position in the file" - } - ] - } - } - }, - - "block_log_index_entry": { - "type": "object", - "description": "Entry in block_log.index", - "properties": { - "name": { "const": "block_log_index_entry" }, - "structure": { - "type": "array", - "items": [ - { - "name": "position", - "type": { "$ref": "#/definitions/uint64_le" }, - "description": "File offset of block in block_log" - } - ] - } - } - } - }, - - "file_formats": { - "block_log": { - "description": "Main block data file", - "filename": "block_log", - "structure": { - "type": "sequence", - "description": "Sequence of block_log_entry structures", - "entry": { "$ref": "#/definitions/block_log_entry" } - }, - "features": { - "head_block_position": { - "description": "Last 8 bytes contain position of head block", - "offset": "file_size - 8", - "type": { "$ref": "#/definitions/uint64_le" } - }, - "backward_scan": { - "description": "Can scan backwards by reading position markers", - "method": "Read last 8 bytes for position, seek to position, read block, repeat" - } - }, - "index_file": "block_log.index" - }, - - "block_log_index": { - "description": "Index file for block_log", - "filename": "block_log.index", - "structure": { - "type": "array", - "entry": { "$ref": "#/definitions/block_log_index_entry" }, - "entry_size": 8 - }, - "lookup": { - "description": "Find position of block N", - "formula": "offset = 8 * (N - 1)", - "example": { - "block_1": "offset 0", - "block_2": "offset 8", - "block_1000000": "offset 7999992" - } - }, - "validation": { - "description": "Last entry should match last position in block_log", - "check": "read_last_uint64(block_log) == read_last_uint64(block_log.index)" - } - }, - - "dlt_block_log": { - "description": "DLT rolling block data file (same format as block_log)", - "filename": "dlt_block_log", - "structure": { - "type": "sequence", - "description": "Sequence of block_log_entry structures (starts at arbitrary block number)", - "entry": { "$ref": "#/definitions/block_log_entry" } - }, - "index_file": "dlt_block_log.index", - "notes": [ - "Same binary format as block_log", - "Can start at any block number", - "Supports truncation of old blocks" - ] - }, - - "dlt_block_log_index": { - "description": "Index file for dlt_block_log (offset-aware)", - "filename": "dlt_block_log.index", - "structure": { - "type": "object", - "fields": [ - { - "name": "header", - "offset": 0, - "size": 8, - "type": { "$ref": "#/definitions/uint64_le" }, - "description": "start_block_num - the first block number in this log" - }, - { - "name": "entries", - "offset": 8, - "type": "array", - "entry": { "$ref": "#/definitions/block_log_index_entry" }, - "entry_size": 8, - "description": "Array of block positions" - } - ] - }, - "lookup": { - "description": "Find position of block N", - "formula": "offset = 8 + 8 * (N - start_block_num)", - "prerequisite": "Read header first to get start_block_num", - "example": { - "start_block_num": 10000000, - "block_10000000": "offset 8", - "block_10000001": "offset 16", - "block_10050000": "offset 400008" - } - } - } - }, - - "algorithms": { - "read_head_block": { - "description": "Read the most recent (head) block from block_log or dlt_block_log", - "steps": [ - { "step": 1, "action": "Get file size" }, - { "step": 2, "action": "Seek to position: file_size - 8" }, - { "step": 3, "action": "Read uint64_t position (little-endian)" }, - { "step": 4, "action": "Seek to that position" }, - { "step": 5, "action": "Deserialize signed_block" } - ] - }, - - "read_block_by_number": { - "description": "Read a specific block by number using the index", - "steps": [ - { "step": 1, "action": "Calculate index offset: 8 * (N - 1) for block_log, or 8 + 8 * (N - start_block_num) for dlt_block_log" }, - { "step": 2, "action": "Read uint64_t position from index file" }, - { "step": 3, "action": "Seek to position in data file" }, - { "step": 4, "action": "Deserialize signed_block" } - ] - }, - - "scan_all_blocks": { - "description": "Sequential scan of all blocks", - "steps": [ - { "step": 1, "action": "Start at position 0" }, - { "step": 2, "action": "Deserialize signed_block" }, - { "step": 3, "action": "Read uint64_t position marker (should match current position)" }, - { "step": 4, "action": "Next block at: current_position + block_size + 8" }, - { "step": 5, "action": "Repeat until end of file" } - ] - }, - - "reconstruct_index": { - "description": "Reconstruct index file from data file", - "steps": [ - { "step": 1, "action": "Scan all blocks sequentially" }, - { "step": 2, "action": "Record each block's start position" }, - { "step": 3, "action": "For block_log: write positions array starting at offset 0" }, - { "step": 4, "action": "For dlt_block_log: write start_block_num header (8 bytes), then positions array" } - ] - }, - - "get_block_number": { - "description": "Extract block number from a signed_block", - "method": "block_num = num_from_id(block.previous) + 1", - "num_from_id": { - "description": "Extract block number from block_id_type", - "input": "20-byte ripemd160 hash", - "output": "uint32_t", - "method": "Read first 4 bytes as little-endian uint32_t" - }, - "genesis_block": { - "description": "Genesis block has previous = all zeros", - "block_num": 1 - } - } - }, - - "javascript_types": { - "block_log_reader": { - "description": "TypeScript/JavaScript interface for block log reader", - "interface": { - "name": "BlockLogReader", - "methods": [ - { - "name": "open", - "params": [{ "name": "dataPath", "type": "string" }, { "name": "indexPath", "type": "string" }], - "returns": "void" - }, - { - "name": "close", - "params": [], - "returns": "void" - }, - { - "name": "readBlock", - "params": [{ "name": "position", "type": "bigint" }], - "returns": "{ block: SignedBlock, nextPosition: bigint }" - }, - { - "name": "readBlockByNum", - "params": [{ "name": "blockNum", "type": "number" }], - "returns": "SignedBlock | null" - }, - { - "name": "readHead", - "params": [], - "returns": "SignedBlock" - }, - { - "name": "getHeadBlockNum", - "params": [], - "returns": "number" - }, - { - "name": "getStartBlockNum", - "params": [], - "returns": "number", - "description": "Returns 1 for block_log, or start_block_num for dlt_block_log" - } - ] - } - }, - - "signed_block": { - "description": "TypeScript interface for signed_block", - "interface": { - "name": "SignedBlock", - "extends": "SignedBlockHeader", - "properties": [ - { "name": "transactions", "type": "SignedTransaction[]" } - ] - } - }, - - "signed_block_header": { - "description": "TypeScript interface for signed_block_header", - "interface": { - "name": "SignedBlockHeader", - "extends": "BlockHeader", - "properties": [ - { "name": "witness_signature", "type": "Buffer" } - ] - } - }, - - "block_header": { - "description": "TypeScript interface for block_header", - "interface": { - "name": "BlockHeader", - "properties": [ - { "name": "previous", "type": "Buffer", "size": 20 }, - { "name": "timestamp", "type": "Date" }, - { "name": "witness", "type": "string" }, - { "name": "transaction_merkle_root", "type": "Buffer", "size": 20 }, - { "name": "extensions", "type": "any[]" } - ] - } - }, - - "signed_transaction": { - "description": "TypeScript interface for signed_transaction", - "interface": { - "name": "SignedTransaction", - "extends": "Transaction", - "properties": [ - { "name": "signatures", "type": "Buffer[]" } - ] - } - }, - - "transaction": { - "description": "TypeScript interface for transaction", - "interface": { - "name": "Transaction", - "properties": [ - { "name": "ref_block_num", "type": "number" }, - { "name": "ref_block_prefix", "type": "number" }, - { "name": "expiration", "type": "Date" }, - { "name": "operations", "type": "Operation[]" }, - { "name": "extensions", "type": "any[]" } - ] - } - }, - - "operation": { - "description": "TypeScript interface for operation (static_variant)", - "interface": { - "name": "Operation", - "properties": [ - { "name": "typeId", "type": "number", "description": "Operation type index (0-63)" }, - { "name": "typeName", "type": "string", "description": "Operation name" }, - { "name": "isVirtual", "type": "boolean" }, - { "name": "data", "type": "object", "description": "Operation-specific data" } - ] - } - } - }, - - "operations": { - "description": "VIZ protocol operation definitions", - "serialization": { - "format": "static_variant", - "structure": [ - { "name": "type_index", "type": "varint", "description": "Operation type ID (0-63)" }, - { "name": "operation_data", "type": "operation_specific", "description": "Fields depend on operation type" } - ] - }, - "type_ids": [ - { "id": 0, "name": "vote_operation", "isVirtual": false, "status": "deprecated" }, - { "id": 1, "name": "content_operation", "isVirtual": false, "status": "deprecated" }, - { "id": 2, "name": "transfer_operation", "isVirtual": false, "status": "active" }, - { "id": 3, "name": "transfer_to_vesting_operation", "isVirtual": false, "status": "active" }, - { "id": 4, "name": "withdraw_vesting_operation", "isVirtual": false, "status": "active" }, - { "id": 5, "name": "account_update_operation", "isVirtual": false, "status": "active" }, - { "id": 6, "name": "witness_update_operation", "isVirtual": false, "status": "active" }, - { "id": 7, "name": "account_witness_vote_operation", "isVirtual": false, "status": "active" }, - { "id": 8, "name": "account_witness_proxy_operation", "isVirtual": false, "status": "active" }, - { "id": 9, "name": "delete_content_operation", "isVirtual": false, "status": "deprecated" }, - { "id": 10, "name": "custom_operation", "isVirtual": false, "status": "active" }, - { "id": 11, "name": "set_withdraw_vesting_route_operation", "isVirtual": false, "status": "active" }, - { "id": 12, "name": "request_account_recovery_operation", "isVirtual": false, "status": "active" }, - { "id": 13, "name": "recover_account_operation", "isVirtual": false, "status": "active" }, - { "id": 14, "name": "change_recovery_account_operation", "isVirtual": false, "status": "active" }, - { "id": 15, "name": "escrow_transfer_operation", "isVirtual": false, "status": "active" }, - { "id": 16, "name": "escrow_dispute_operation", "isVirtual": false, "status": "active" }, - { "id": 17, "name": "escrow_release_operation", "isVirtual": false, "status": "active" }, - { "id": 18, "name": "escrow_approve_operation", "isVirtual": false, "status": "active" }, - { "id": 19, "name": "delegate_vesting_shares_operation", "isVirtual": false, "status": "active" }, - { "id": 20, "name": "account_create_operation", "isVirtual": false, "status": "active" }, - { "id": 21, "name": "account_metadata_operation", "isVirtual": false, "status": "active" }, - { "id": 22, "name": "proposal_create_operation", "isVirtual": false, "status": "active" }, - { "id": 23, "name": "proposal_update_operation", "isVirtual": false, "status": "active" }, - { "id": 24, "name": "proposal_delete_operation", "isVirtual": false, "status": "active" }, - { "id": 25, "name": "chain_properties_update_operation", "isVirtual": false, "status": "active" }, - { "id": 26, "name": "author_reward_operation", "isVirtual": true, "status": "active" }, - { "id": 27, "name": "curation_reward_operation", "isVirtual": true, "status": "active" }, - { "id": 28, "name": "content_reward_operation", "isVirtual": true, "status": "active" }, - { "id": 29, "name": "fill_vesting_withdraw_operation", "isVirtual": true, "status": "active" }, - { "id": 30, "name": "shutdown_witness_operation", "isVirtual": true, "status": "active" }, - { "id": 31, "name": "hardfork_operation", "isVirtual": true, "status": "active" }, - { "id": 32, "name": "content_payout_update_operation", "isVirtual": true, "status": "active" }, - { "id": 33, "name": "content_benefactor_reward_operation", "isVirtual": true, "status": "active" }, - { "id": 34, "name": "return_vesting_delegation_operation", "isVirtual": true, "status": "active" }, - { "id": 35, "name": "committee_worker_create_request_operation", "isVirtual": false, "status": "active" }, - { "id": 36, "name": "committee_worker_cancel_request_operation", "isVirtual": false, "status": "active" }, - { "id": 37, "name": "committee_vote_request_operation", "isVirtual": false, "status": "active" }, - { "id": 38, "name": "committee_cancel_request_operation", "isVirtual": true, "status": "active" }, - { "id": 39, "name": "committee_approve_request_operation", "isVirtual": true, "status": "active" }, - { "id": 40, "name": "committee_payout_request_operation", "isVirtual": true, "status": "active" }, - { "id": 41, "name": "committee_pay_request_operation", "isVirtual": true, "status": "active" }, - { "id": 42, "name": "witness_reward_operation", "isVirtual": true, "status": "active" }, - { "id": 43, "name": "create_invite_operation", "isVirtual": false, "status": "active" }, - { "id": 44, "name": "claim_invite_balance_operation", "isVirtual": false, "status": "active" }, - { "id": 45, "name": "invite_registration_operation", "isVirtual": false, "status": "active" }, - { "id": 46, "name": "versioned_chain_properties_update_operation", "isVirtual": false, "status": "active" }, - { "id": 47, "name": "award_operation", "isVirtual": false, "status": "active" }, - { "id": 48, "name": "receive_award_operation", "isVirtual": true, "status": "active" }, - { "id": 49, "name": "benefactor_award_operation", "isVirtual": true, "status": "active" }, - { "id": 50, "name": "set_paid_subscription_operation", "isVirtual": false, "status": "active" }, - { "id": 51, "name": "paid_subscribe_operation", "isVirtual": false, "status": "active" }, - { "id": 52, "name": "paid_subscription_action_operation", "isVirtual": true, "status": "active" }, - { "id": 53, "name": "cancel_paid_subscription_operation", "isVirtual": true, "status": "active" }, - { "id": 54, "name": "set_account_price_operation", "isVirtual": false, "status": "active" }, - { "id": 55, "name": "set_subaccount_price_operation", "isVirtual": false, "status": "active" }, - { "id": 56, "name": "buy_account_operation", "isVirtual": false, "status": "active" }, - { "id": 57, "name": "account_sale_operation", "isVirtual": true, "status": "active" }, - { "id": 58, "name": "use_invite_balance_operation", "isVirtual": false, "status": "active" }, - { "id": 59, "name": "expire_escrow_ratification_operation", "isVirtual": true, "status": "active" }, - { "id": 60, "name": "fixed_award_operation", "isVirtual": false, "status": "active" }, - { "id": 61, "name": "target_account_sale_operation", "isVirtual": false, "status": "active" }, - { "id": 62, "name": "bid_operation", "isVirtual": true, "status": "active" }, - { "id": 63, "name": "outbid_operation", "isVirtual": true, "status": "active" } - ], - "common_types": { - "asset": { - "description": "Token amount with symbol", - "wire_size": "16 bytes (fixed)", - "fields": [ - { "name": "amount", "type": "int64", "size": 8, "description": "Amount in tolikah (signed)" }, - { "name": "symbol", "type": "uint64", "size": 8, "description": "Asset symbol identifier (packed: byte0=decimals, bytes1-6=ASCII name, byte7=0x00)" } - ], - "js_output": "{ amount: number, symbol: string, decimals: number }", - "symbols": { - "VIZ": "0x000000005A495603", - "SHARES": "0x0053455241485306" - } - }, - "authority": { - "description": "Multi-signature authority", - "fields": [ - { "name": "weight_threshold", "type": "uint32" }, - { "name": "account_auths", "type": "flat_map" }, - { "name": "key_auths", "type": "flat_map" } - ] - }, - "public_key_type": { - "description": "secp256k1 compressed public key", - "wire_size": 33, - "wire_format": "Raw bytes: [0x02/0x03 prefix][32 bytes x-coordinate]", - "string_format": "VIZ + base58([33 key bytes][4 ripemd160 checksum bytes])", - "example": "VIZ7wMEutJdCfdSKNgVAp17v9uoTqwwkUqn2kwVsJ6zG5XYJcvj81" - }, - "beneficiary_route_type": { - "description": "Beneficiary route for awards", - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "weight", "type": "uint16" } - ] - }, - "chain_properties_init": { - "description": "Chain properties (12 fields, used by chain_properties_update_operation)", - "fields": [ - { "name": "account_creation_fee", "type": "asset" }, - { "name": "maximum_block_size", "type": "uint32" }, - { "name": "create_account_delegation_ratio", "type": "uint32" }, - { "name": "create_account_delegation_time", "type": "uint32" }, - { "name": "min_delegation", "type": "asset" }, - { "name": "min_curation_percent", "type": "uint16" }, - { "name": "max_curation_percent", "type": "uint16" }, - { "name": "bandwidth_reserve_percent", "type": "uint16" }, - { "name": "bandwidth_reserve_below", "type": "asset" }, - { "name": "flag_energy_additional_cost", "type": "uint16" }, - { "name": "vote_accounting_min_rshares", "type": "uint32" }, - { "name": "committee_request_approve_min_percent", "type": "uint16" } - ] - }, - "versioned_chain_properties": { - "description": "static_variant of chain_properties variants", - "format": "[varint: type_index][chain_properties_init base][additional fields based on type]", - "variants": [ - { "type_index": 0, "name": "chain_properties_init", "fields": 12 }, - { "type_index": 1, "name": "chain_properties_hf4", "base": "init", "additional_fields": ["inflation_witness_percent (uint16)", "inflation_ratio_committee_vs_reward_fund (uint16)", "inflation_recalc_period (uint32)"] }, - { "type_index": 2, "name": "chain_properties_hf6", "base": "hf4", "additional_fields": ["data_operations_cost_additional_bandwidth (uint32)", "witness_miss_penalty_percent (uint16)", "witness_miss_penalty_duration (uint32)"] }, - { "type_index": 3, "name": "chain_properties_hf9", "base": "hf6", "additional_fields": ["create_invite_min_balance (asset)", "committee_create_request_fee (asset)", "create_paid_subscription_fee (asset)", "account_on_sale_fee (asset)", "subaccount_on_sale_fee (asset)", "witness_declaration_fee (asset)", "withdraw_intervals (uint16)"] } - ] - } - }, - "operation_schemas": { - "transfer_operation": { - "id": 2, - "fields": [ - { "name": "from", "type": "account_name_type" }, - { "name": "to", "type": "account_name_type" }, - { "name": "amount", "type": "asset" }, - { "name": "memo", "type": "string" } - ] - }, - "account_create_operation": { - "id": 20, - "fields": [ - { "name": "fee", "type": "asset" }, - { "name": "delegation", "type": "asset" }, - { "name": "creator", "type": "account_name_type" }, - { "name": "new_account_name", "type": "account_name_type" }, - { "name": "master", "type": "authority" }, - { "name": "active", "type": "authority" }, - { "name": "regular", "type": "authority" }, - { "name": "memo_key", "type": "public_key_type" }, - { "name": "json_metadata", "type": "string" }, - { "name": "referrer", "type": "account_name_type" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "witness_update_operation": { - "id": 6, - "fields": [ - { "name": "owner", "type": "account_name_type" }, - { "name": "url", "type": "string" }, - { "name": "block_signing_key", "type": "public_key_type" } - ] - }, - "award_operation": { - "id": 47, - "fields": [ - { "name": "initiator", "type": "account_name_type" }, - { "name": "receiver", "type": "account_name_type" }, - { "name": "energy", "type": "uint16" }, - { "name": "custom_sequence", "type": "uint64" }, - { "name": "memo", "type": "string" }, - { "name": "beneficiaries", "type": "vector" } - ] - }, - "transfer_to_vesting_operation": { - "id": 3, - "fields": [ - { "name": "from", "type": "account_name_type" }, - { "name": "to", "type": "account_name_type" }, - { "name": "amount", "type": "asset" } - ] - }, - "delegate_vesting_shares_operation": { - "id": 19, - "fields": [ - { "name": "delegator", "type": "account_name_type" }, - { "name": "delegatee", "type": "account_name_type" }, - { "name": "vesting_shares", "type": "asset" } - ] - }, - "create_invite_operation": { - "id": 43, - "fields": [ - { "name": "creator", "type": "account_name_type" }, - { "name": "balance", "type": "asset" }, - { "name": "invite_key", "type": "public_key_type" } - ] - }, - "custom_operation": { - "id": 10, - "fields": [ - { "name": "required_active_auths", "type": "flat_set" }, - { "name": "required_regular_auths", "type": "flat_set" }, - { "name": "id", "type": "string" }, - { "name": "json", "type": "string" } - ] - }, - "author_reward_operation": { - "id": 26, - "isVirtual": true, - "fields": [ - { "name": "author", "type": "account_name_type" }, - { "name": "permlink", "type": "string" }, - { "name": "token_payout", "type": "asset" }, - { "name": "vesting_payout", "type": "asset" } - ] - }, - "witness_reward_operation": { - "id": 42, - "isVirtual": true, - "fields": [ - { "name": "witness", "type": "account_name_type" }, - { "name": "shares", "type": "asset" } - ] - }, - "invite_registration_operation": { - "id": 45, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "new_account_key", "type": "public_key_type" }, - { "name": "invite_secret", "type": "string", "description": "WIF-encoded private key (e.g. '5Kd...'), NOT raw bytes" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "claim_invite_balance_operation": { - "id": 44, - "fields": [ - { "name": "initiator", "type": "account_name_type" }, - { "name": "receiver", "type": "account_name_type" }, - { "name": "invite_secret", "type": "string", "description": "WIF-encoded private key string" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "use_invite_balance_operation": { - "id": 58, - "fields": [ - { "name": "initiator", "type": "account_name_type" }, - { "name": "receiver", "type": "account_name_type" }, - { "name": "invite_secret", "type": "string", "description": "WIF-encoded private key string" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "chain_properties_update_operation": { - "id": 25, - "fields": [ - { "name": "owner", "type": "account_name_type" }, - { "name": "props", "type": "chain_properties_init", "description": "12-field struct, NOT chain_properties_hf9" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "versioned_chain_properties_update_operation": { - "id": 46, - "fields": [ - { "name": "owner", "type": "account_name_type" }, - { "name": "props", "type": "versioned_chain_properties", "description": "static_variant with progressive fields" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "benefactor_award_operation": { - "id": 49, - "isVirtual": true, - "fields": [ - { "name": "initiator", "type": "account_name_type" }, - { "name": "benefactor", "type": "account_name_type", "description": "NOTE: FC_REFLECT order is initiator, benefactor (not benefactor, initiator)" }, - { "name": "receiver", "type": "account_name_type" }, - { "name": "custom_sequence", "type": "uint64" }, - { "name": "memo", "type": "string" }, - { "name": "shares", "type": "asset" } - ] - }, - "set_paid_subscription_operation": { - "id": 50, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "url", "type": "string" }, - { "name": "levels", "type": "uint16", "description": "NOT uint8" }, - { "name": "amount", "type": "asset" }, - { "name": "period", "type": "uint16", "description": "NOT uint32" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "paid_subscribe_operation": { - "id": 51, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "subscriber", "type": "account_name_type" }, - { "name": "level", "type": "uint16", "description": "NOT uint8" }, - { "name": "amount", "type": "asset" }, - { "name": "period", "type": "uint16", "description": "NOT uint32" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "paid_subscription_action_operation": { - "id": 52, - "isVirtual": true, - "fields": [ - { "name": "subscriber", "type": "account_name_type" }, - { "name": "account", "type": "account_name_type" }, - { "name": "level", "type": "uint16" }, - { "name": "amount", "type": "asset" }, - { "name": "period", "type": "uint16" }, - { "name": "summary_duration_sec", "type": "uint64" }, - { "name": "summary_amount", "type": "asset" } - ] - }, - "cancel_paid_subscription_operation": { - "id": 53, - "isVirtual": true, - "fields": [ - { "name": "subscriber", "type": "account_name_type" }, - { "name": "account", "type": "account_name_type" } - ] - }, - "buy_account_operation": { - "id": 56, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "buyer", "type": "account_name_type" }, - { "name": "tokens_to_shares", "type": "asset", "description": "NOT bool/uint8" }, - { "name": "extensions", "type": "extensions_type" } - ] - }, - "account_sale_operation": { - "id": 57, - "isVirtual": true, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "price", "type": "asset" }, - { "name": "buyer", "type": "account_name_type" }, - { "name": "seller", "type": "account_name_type" } - ] - }, - "expire_escrow_ratification_operation": { - "id": 59, - "isVirtual": true, - "fields": [ - { "name": "from", "type": "account_name_type" }, - { "name": "to", "type": "account_name_type" }, - { "name": "agent", "type": "account_name_type" }, - { "name": "escrow_id", "type": "uint32" }, - { "name": "token_amount", "type": "asset" }, - { "name": "fee", "type": "asset" }, - { "name": "ratification_deadline", "type": "time_point_sec" } - ] - }, - "bid_operation": { - "id": 62, - "isVirtual": true, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "bidder", "type": "account_name_type", "description": "NOTE: FC_REFLECT order is account, bidder (not bidder, account)" }, - { "name": "bid", "type": "asset" } - ] - }, - "outbid_operation": { - "id": 63, - "isVirtual": true, - "fields": [ - { "name": "account", "type": "account_name_type" }, - { "name": "bidder", "type": "account_name_type", "description": "NOTE: FC_REFLECT order is account, bidder (not bidder, account)" }, - { "name": "bid", "type": "asset" } - ] - }, - "proposal_delete_operation": { - "id": 24, - "fields": [ - { "name": "author", "type": "account_name_type" }, - { "name": "title", "type": "string" }, - { "name": "requester", "type": "account_name_type" }, - { "name": "extensions", "type": "extensions_type" } - ] - } - } - }, - - "file_formats_additional": { - "block_log_bitmask": { - "description": "Bitmask file marking which blocks have non-free operations", - "filename": "block_log.bitmask", - "structure": { - "type": "object", - "fields": [ - { - "name": "header", - "offset": 0, - "size": 16, - "fields": [ - { "name": "start_block_num", "offset": 0, "size": 8, "type": "uint64_le", "description": "First block number in range" }, - { "name": "end_block_num", "offset": 8, "size": 8, "type": "uint64_le", "description": "Last block number in range" } - ] - }, - { - "name": "bit_array", - "offset": 16, - "type": "bit_array", - "description": "1 bit per block, bit=1 means block has non-free operations", - "bit_order": "bit 0 = start_block_num, bit 1 = start_block_num+1, etc.", - "size": "ceil((end - start + 1) / 8) bytes" - } - ] - }, - "example": "10,000,000 blocks = ~1.25 MB bitmask file", - "auto_load": "Loaded on startup if file exists and range matches current block_log" - }, - - "search_export": { - "description": "Export file from 'e' command in block-log-viewer", - "filename_pattern": "search_export_.json", - "format": "JSON array of operation record objects", - "record_fields": [ - { "name": "block", "type": "number", "description": "Block number" }, - { "name": "timestamp", "type": "string", "description": "Formatted UTC timestamp" }, - { "name": "witness", "type": "string", "description": "Witness account name" }, - { "name": "typeId", "type": "number", "description": "Operation type ID (0-63)" }, - { "name": "typeName", "type": "string", "description": "Operation type name" }, - { "name": "isVirtual", "type": "boolean", "description": "Whether this is a virtual operation" }, - { "name": "data", "type": "object", "description": "Operation-specific data (Buffers as hex, BigInts as decimal strings)" } - ] - } - }, - - "viewer_commands": { - "description": "block-log-viewer.js interactive commands", - "navigation": [ - { "cmd": "f", "description": "First block" }, - { "cmd": "l", "description": "Last block" }, - { "cmd": "n", "description": "Next block" }, - { "cmd": "p", "description": "Previous block" }, - { "cmd": "N", "description": "Next block with non-free operations (bitmask-accelerated)" }, - { "cmd": "P", "description": "Prev block with non-free operations (bitmask-accelerated)" }, - { "cmd": "g ", "description": "Go to block #num" }, - { "cmd": "", "description": "Jump to block number directly" } - ], - "operations": [ - { "cmd": "o", "description": "Show all operations in current block" }, - { "cmd": "o ", "description": "Show operations matching type name (e.g. o transfer)" }, - { "cmd": "s ", "description": "Search forward for operation by type name" }, - { "cmd": "S ", "description": "Search forward for substring in any operation's data (incl. virtual)" }, - { "cmd": "S =", "description": "Search forward for exact string match (= prefix for exact, not substring)" }, - { "cmd": "R ", "description": "Fast raw ASCII byte search in block data (no UTF-8/emoji)" }, - { "cmd": "e ", "description": "Export all ops containing string to search_export_.json" }, - { "cmd": "e =", "description": "Export all ops exactly matching string (= prefix for exact match)" }, - { "cmd": "c", "description": "Continue last search (s/S/R/e)" } - ], - "other": [ - { "cmd": "scan", "description": "Scan all blocks, build & save bitmask for fast navigation" }, - { "cmd": "i", "description": "Show block header info" }, - { "cmd": "hex", "description": "Show raw block data in hex" }, - { "cmd": "h", "description": "Help" }, - { "cmd": "q", "description": "Quit" } - ] - }, - - "constants": { - "CHAIN_BLOCK_SIZE": { - "value": 1048576, - "description": "Maximum block size in bytes (1 MB)" - }, - "MIN_VALID_FILE_SIZE": { - "value": 8, - "description": "Minimum valid file size (at least one position marker)" - }, - "INDEX_HEADER_SIZE": { - "value": 8, - "description": "DLT index header size (start_block_num)" - }, - "SIGNATURE_SIZE": { - "value": 65, - "description": "Compact signature size in bytes" - }, - "HASH_SIZE_RIPEMD160": { - "value": 20, - "description": "RIPEMD-160 hash size in bytes" - }, - "HASH_SIZE_SHA256": { - "value": 32, - "description": "SHA-256 hash size in bytes" - } - } -} diff --git a/.qoder/docs/block-log-spec.md b/.qoder/docs/block-log-spec.md deleted file mode 100644 index 98e9780fd4..0000000000 --- a/.qoder/docs/block-log-spec.md +++ /dev/null @@ -1,978 +0,0 @@ -# Block Log File Format Specification - -This document specifies the binary file formats for VIZ blockchain block logs. - -## Overview - -VIZ uses two types of block logs: -- **block_log**: Full blockchain history (used by archive nodes) -- **dlt_block_log**: Rolling window of recent blocks (used by DLT/snapshot nodes) - -Each log consists of two files: -- **Data file**: Contains serialized block data -- **Index file**: Contains offsets for random access by block number - ---- - -## fc::raw Binary Serialization - -All data in block logs is serialized using `fc::raw` format. - -### Primitive Types - -| Type | Size | Format | -|------|------|--------| -| `uint8_t` | 1 byte | Little-endian | -| `uint16_t` | 2 bytes | Little-endian | -| `uint32_t` | 4 bytes | Little-endian | -| `uint64_t` | 8 bytes | Little-endian | -| `int8_t` | 1 byte | Two's complement | -| `int16_t` | 2 bytes | Little-endian, two's complement | -| `int32_t` | 4 bytes | Little-endian, two's complement | -| `int64_t` | 8 bytes | Little-endian, two's complement | -| `bool` | 1 byte | `0x00` = false, `0x01` = true | - -### Variable-Length Integer (varint) - -`fc::unsigned_int` uses a variable-length encoding similar to protobuf: - -``` -Each byte: [7 data bits][1 continuation bit] -- Continuation bit = 1: more bytes follow -- Continuation bit = 0: last byte - -Value is reconstructed by concatenating 7-bit chunks in order. -``` - -**Examples:** -- `0x00` → 0 -- `0x01` → 1 -- `0x7F` → 127 -- `0x80 0x01` → 128 -- `0xFF 0x01` → 255 -- `0x80 0x02` → 256 - -`fc::signed_int` uses zigzag encoding before varint: -- Zigzag: `(n << 1) ^ (n >> 31)` for encoding -- Un-zigzag: `(n >> 1) ^ -(n & 1)` for decoding - -### String - -``` -[varint: length][bytes: UTF-8 string data] -``` - -- Length is serialized as `fc::unsigned_int` (varint) -- Empty string: `[0x00]` - -### Vector - -``` -[varint: element_count][element_1][element_2]...[element_n] -``` - -### Optional - -``` -[uint8: flag][value if flag=1] -``` - -- `flag = 0`: no value (empty optional) -- `flag = 1`: value follows - -### flat_set - -``` -[varint: element_count][element_1][element_2]...[element_n] -``` - -Same wire format as `vector`. Elements are sorted and unique in the data structure, but serialized in sorted order. - -### flat_map - -``` -[varint: pair_count][key_1][value_1][key_2][value_2]...[key_n][value_n] -``` - -Same wire format as `vector>`. Each pair is serialized as key then value. - -### Optional - -``` -[uint8: flag][value if flag=1] -``` - -- `flag = 0`: no value (empty optional) -- `flag = 1`: value follows - -### Static Variant - -``` -[varint: type_index][serialized_value] -``` - -The type index identifies which type in the variant is stored. - -### extensions_type - -``` -[varint: count][varint: type_index_0]...[varint: type_index_n] -``` - -Defined as `flat_set` where `future_extensions = static_variant`. -Each extension item is just a varint type index (always 0 for `void_t`). -Usually empty (serialized as single byte `0x00`). - -### Reflected Structures - -Structures with `FC_REFLECT` macro are serialized field-by-field in the order defined by the macro. **Field order matters** — the binary layout must match the `FC_REFLECT` declaration exactly. - ---- - -## block_log (Data File) - -**Filename:** `block_log` - -### File Layout - -``` -+------------------+--------------------+------------------+--------------------+-----+ -| Block 1 (binary) | Position (8 bytes) | Block 2 (binary) | Position (8 bytes) | ... | -+------------------+--------------------+------------------+--------------------+-----+ -``` - -Each block entry consists of: -1. **Block data**: fc::raw serialized `signed_block` -2. **Position**: `uint64_t` little-endian offset of this block's start position - -### Reading the Head Block - -1. Seek to the last 8 bytes of the file -2. Read `uint64_t` position value -3. Seek to that position -4. Deserialize `signed_block` - -### Sequential Scan - -Starting from position 0: -1. Deserialize `signed_block` -2. Read next 8 bytes as `uint64_t` position (should match current position) -3. Next block starts at `current_position + block_size + 8` -4. Repeat until end of file - -### Block Number Extraction - -Block number is NOT stored directly. It is derived from: -- `block.previous` field (20-byte `block_id_type` / `ripemd160` hash) -- `block_num = num_from_id(previous) + 1` -- For genesis block: `previous` is all zeros, `block_num = 1` - -The `num_from_id` function extracts bytes 0-3 (first 4 bytes) of the hash as `uint32_t` little-endian. - ---- - -## block_log.index (Index File) - -**Filename:** `block_log.index` - -### File Layout - -``` -+------------------+------------------+-----+------------------------+ -| Position of #1 | Position of #2 | ... | Position of Head Block | -+------------------+------------------+-----+------------------------+ - 8 bytes 8 bytes 8 bytes -``` - -### Index Entry Location - -For block number `N`: -``` -offset = 8 * (N - 1) -``` - -**Example:** -- Block 1: offset 0 -- Block 2: offset 8 -- Block 1000000: offset 7999992 - -### Index Validation - -The last 8 bytes of both files should contain the same position value: -- `block_log`: position of head block -- `block_log.index`: position of head block - -If they differ, the index should be reconstructed from the data file. - ---- - -## dlt_block_log (Data File) - -**Filename:** `dlt_block_log` - -### File Layout - -Identical to regular `block_log`: -``` -+------------------+--------------------+------------------+--------------------+-----+ -| Block N (binary) | Position (8 bytes) | Block N+1 (bin) | Position (8 bytes) | ... | -+------------------+--------------------+------------------+--------------------+-----+ -``` - -The key difference: **can start at any block number**, not necessarily block 1. - ---- - -## dlt_block_log.index (Index File) - -**Filename:** `dlt_block_log.index` - -### File Layout - -``` -+-------------------+------------------+------------------+-----+------------------------+ -| start_block_num | Position of #S | Position of #S+1 | ... | Position of Head Block | -| (8 bytes header) | (8 bytes) | (8 bytes) | | (8 bytes) | -+-------------------+------------------+------------------+-----+------------------------+ -``` - -### Header - -- **Bytes 0-7**: `uint64_t` little-endian `start_block_num` -- The first block number stored in this rolling log - -### Index Entry Location - -For block number `N`: -``` -offset = 8 + 8 * (N - start_block_num) -``` - -**Example** (start_block_num = 10000000): -- Block 10000000: offset 8 -- Block 10000001: offset 16 -- Block 10050000: offset 400008 - -### Reading a Block by Number - -1. Read header (first 8 bytes) → `start_block_num` -2. Verify: `N >= start_block_num` and `N <= head_block_num` -3. Calculate offset: `8 + 8 * (N - start_block_num)` -4. Read `uint64_t` position at that offset -5. Seek to position in `dlt_block_log` -6. Deserialize `signed_block` - ---- - -## signed_block Structure - -``` -signed_block extends signed_block_header - └─ signed_block_header extends block_header - └─ block_header -``` - -### block_header Fields - -| Field | Type | Description | -|-------|------|-------------| -| `previous` | `block_id_type` (20 bytes) | Hash of previous block | -| `timestamp` | `time_point_sec` (4 bytes) | Block creation time (Unix timestamp) | -| `witness` | `string` | Witness account name | -| `transaction_merkle_root` | `checksum_type` (20 bytes) | Merkle root of transactions | -| `extensions` | `vector` | Future extensions (usually empty) | - -### block_header_extension (static_variant) - -Defined as `static_variant` in -`libraries/protocol/include/graphene/protocol/base.hpp`. - -| Type Index | Name | Serialized Data | Description | -|------------|------|----------------|-------------| -| 0 | `void_t` | (none) | Empty placeholder | -| 1 | `version` | `uint32_t v_num` (4 bytes) | Witness version reporting (8.8.16 bit packing) | -| 2 | `hardfork_version_vote` | `uint32_t` hf_version + `uint32_t` hf_time (8 bytes) | Hardfork vote | - -Version `v_num` packing: `(major << 24) | (hardfork << 16) | release`. Example: `0x00000001` = version 0.0.1. - -### signed_block_header Additional Fields - -| Field | Type | Description | -|-------|------|-------------| -| `witness_signature` | `signature_type` (65 bytes) | Witness signature (compact) | - -### signed_block Additional Fields - -| Field | Type | Description | -|-------|------|-------------| -| `transactions` | `vector` | List of transactions | - -### Serialization Order - -``` -block_header: - [20 bytes: previous] - [4 bytes: timestamp] - [varint + string: witness] - [20 bytes: transaction_merkle_root] - [varint + extensions: extensions] - -signed_block_header (after block_header): - [65 bytes: witness_signature] - -signed_block (after signed_block_header): - [varint + transactions: transactions] -``` - ---- - -## signed_transaction Structure - -### transaction Fields - -| Field | Type | Description | -|-------|------|-------------| -| `ref_block_num` | `uint16_t` (2 bytes) | Reference block number | -| `ref_block_prefix` | `uint32_t` (4 bytes) | Reference block prefix | -| `expiration` | `time_point_sec` (4 bytes) | Transaction expiration | -| `operations` | `vector` | List of operations | -| `extensions` | `extensions_type` | Extensions (usually empty) | - -### signed_transaction Additional Fields - -| Field | Type | Description | -|-------|------|-------------| -| `signatures` | `vector` | Transaction signatures | - -### Serialization Order - -``` -transaction: - [2 bytes: ref_block_num] - [4 bytes: ref_block_prefix] - [4 bytes: expiration] - [varint + operations: operations] - [varint + extensions: extensions] - -signed_transaction (after transaction): - [varint + signatures: signatures] -``` - ---- - -## Special Types - -### block_id_type / checksum_type / transaction_id_type - -All are `fc::ripemd160` hashes (20 bytes). - -> **Steem lineage note:** VIZ inherits this design from the Steem codebase. Rather than using -> the full 32-byte `fc::sha256` for block IDs and merkle roots, VIZ uses the shorter 20-byte -> `fc::ripemd160`. The `block_id_type` is defined as `typedef fc::ripemd160 block_id_type` and -> `checksum_type` as `typedef fc::ripemd160 checksum_type` in -> `libraries/protocol/include/graphene/protocol/types.hpp`. This is sometimes called a "feature" -> by the original developers — the shorter hash saves space and the collision risk is considered -> acceptable for block identification within a running chain. - -### signature_type - -`fc::ecc::compact_signature` - 65 bytes: -- 1 byte: recovery id -- 32 bytes: r coordinate -- 32 bytes: s coordinate - -### time_point_sec - -`uint32_t` Unix timestamp (seconds since 1970-01-01 00:00:00 UTC). - -### account_name_type - -`fc::fixed_string_32` - serialized as regular string (varint length + UTF-8 bytes). - ---- - -## JavaScript Implementation Notes - -### Endianness - -All multi-byte integers are **little-endian**. - -### BigInt Handling - -- Use `BigInt` for `uint64_t` positions (JavaScript numbers lose precision above 2^53) -- Convert to `Number` only when safe (< 2^53) - -### Buffer Reading - -```javascript -const fs = require('fs'); -const { Buffer } = require('buffer'); - -// Read uint64_t as BigInt -function readUint64LE(buffer, offset) { - return buffer.readBigUInt64LE(offset); -} - -// Read uint32_t -function readUint32LE(buffer, offset) { - return buffer.readUInt32LE(offset); -} - -// Read varint (fc::unsigned_int) -function readVarint(buffer, offset) { - let value = 0n; - let shift = 0; - let pos = offset; - - while (true) { - const byte = buffer.readUInt8(pos++); - value |= BigInt(byte & 0x7F) << BigInt(shift); - if (!(byte & 0x80)) break; - shift += 7; - } - - return { value: Number(value), bytesRead: pos - offset }; -} -``` - -### File Reading Strategy - -1. Use `fs.open()` + `fs.read()` for random access -2. Or use `mmap`-like approach with `Buffer` for frequent access -3. Cache the index file in memory for fast lookups - ---- - -## Error Handling - -### Index Mismatch - -When `block_log` and `block_log.index` positions don't match: -1. Delete the index file -2. Reconstruct by scanning the data file -3. Write new index entries - -### Corrupted Block - -If a block fails to deserialize: -1. Check if position marker matches actual position -2. If mismatch, scan backward from end of file -3. The file may be truncated from a crash - -### Empty Files - -- New/empty `block_log`: 1 null byte (implementation artifact) -- Valid minimum size: > 8 bytes (at least one position marker) - ---- - -## Operation Types - -Operations are serialized as `fc::static_variant` with a type index followed by the operation data. - -### Operation Type IDs - -| ID | Operation Name | Type | -|----|----------------|------| -| 0 | `vote_operation` | deprecated | -| 1 | `content_operation` | deprecated | -| 2 | `transfer_operation` | regular | -| 3 | `transfer_to_vesting_operation` | regular | -| 4 | `withdraw_vesting_operation` | regular | -| 5 | `account_update_operation` | regular | -| 6 | `witness_update_operation` | regular | -| 7 | `account_witness_vote_operation` | regular | -| 8 | `account_witness_proxy_operation` | regular | -| 9 | `delete_content_operation` | deprecated | -| 10 | `custom_operation` | regular | -| 11 | `set_withdraw_vesting_route_operation` | regular | -| 12 | `request_account_recovery_operation` | regular | -| 13 | `recover_account_operation` | regular | -| 14 | `change_recovery_account_operation` | regular | -| 15 | `escrow_transfer_operation` | regular | -| 16 | `escrow_dispute_operation` | regular | -| 17 | `escrow_release_operation` | regular | -| 18 | `escrow_approve_operation` | regular | -| 19 | `delegate_vesting_shares_operation` | regular | -| 20 | `account_create_operation` | regular | -| 21 | `account_metadata_operation` | regular | -| 22 | `proposal_create_operation` | regular | -| 23 | `proposal_update_operation` | regular | -| 24 | `proposal_delete_operation` | regular | -| 25 | `chain_properties_update_operation` | regular | -| 26 | `author_reward_operation` | virtual | -| 27 | `curation_reward_operation` | virtual | -| 28 | `content_reward_operation` | virtual | -| 29 | `fill_vesting_withdraw_operation` | virtual | -| 30 | `shutdown_witness_operation` | virtual | -| 31 | `hardfork_operation` | virtual | -| 32 | `content_payout_update_operation` | virtual | -| 33 | `content_benefactor_reward_operation` | virtual | -| 34 | `return_vesting_delegation_operation` | virtual | -| 35 | `committee_worker_create_request_operation` | regular | -| 36 | `committee_worker_cancel_request_operation` | regular | -| 37 | `committee_vote_request_operation` | regular | -| 38 | `committee_cancel_request_operation` | virtual | -| 39 | `committee_approve_request_operation` | virtual | -| 40 | `committee_payout_request_operation` | virtual | -| 41 | `committee_pay_request_operation` | virtual | -| 42 | `witness_reward_operation` | virtual | -| 43 | `create_invite_operation` | regular | -| 44 | `claim_invite_balance_operation` | regular | -| 45 | `invite_registration_operation` | regular | -| 46 | `versioned_chain_properties_update_operation` | regular | -| 47 | `award_operation` | regular | -| 48 | `receive_award_operation` | virtual | -| 49 | `benefactor_award_operation` | virtual | -| 50 | `set_paid_subscription_operation` | regular | -| 51 | `paid_subscribe_operation` | regular | -| 52 | `paid_subscription_action_operation` | virtual | -| 53 | `cancel_paid_subscription_operation` | virtual | -| 54 | `set_account_price_operation` | regular | -| 55 | `set_subaccount_price_operation` | regular | -| 56 | `buy_account_operation` | regular | -| 57 | `account_sale_operation` | virtual | -| 58 | `use_invite_balance_operation` | regular | -| 59 | `expire_escrow_ratification_operation` | virtual | -| 60 | `fixed_award_operation` | regular | -| 61 | `target_account_sale_operation` | regular | -| 62 | `bid_operation` | virtual | -| 63 | `outbid_operation` | virtual | - -### Operation Serialization Format - -``` -[varint: type_index][operation_specific_fields...] -``` - -### Common Types - -#### asset - -``` -[int64: amount][uint64: symbol] -``` - -**Asset symbol format** (inherited from Steem codebase, defined in `asset.cpp`): - -The `uint64 symbol` is a packed structure with the following byte layout (little-endian): - -| Byte | Field | Description | -|------|-------|-------------| -| 0 | decimals | Number of decimal places (0-14) | -| 1-6 | name | ASCII symbol name (up to 6 chars) | -| 7 | null | Always 0x00 (null terminator) | - -Known symbols (from `config.hpp`): - -| Name | Decimals | uint64 (LE bytes) | uint64 (hex) | -|------|----------|--------------------|--------------| -| `VIZ` | 3 | `03 56 49 5A 00 00 00 00` | `0x000000005A495603` | -| `SHARES` | 6 | `06 53 48 41 52 45 53 00` | `0x0053455241485306` | - -> **Steem lineage note:** This symbol encoding format is inherited from the Steem codebase. -> Rather than using an enum or string, the symbol is packed as a uint64 with precision in byte 0 -> and the ASCII name in bytes 1-6. This design allows the symbol to carry its own decimal -> precision information without requiring a separate lookup. - -#### authority -``` -[uint32: weight_threshold] -[flat_map: account_auths] -[flat_map: key_auths] -``` - -> **Note:** `account_auths` and `key_auths` are `flat_map` (not `flat_set>`). -> Wire format is identical to `vector>`: varint count + (key, value) pairs. - -#### public_key_type - -**Wire format:** 33 raw bytes of compressed secp256k1 public key: -``` -[1 byte: 0x02 or 0x03 prefix][32 bytes: x-coordinate] -``` - -**String representation** (in JSON output, not on wire): -1. Compute `ripemd160(33_key_bytes)` -2. First 4 bytes of hash = checksum -3. Concatenate: `[33 key bytes][4 checksum bytes]` = 37 bytes -4. Base58-encode the 37-byte buffer -5. Prepend `"VIZ"` (CHAIN_ADDRESS_PREFIX) - -Example: `VIZ7wMEutJdCfdSKNgVAp17v9uoTqwwkUqn2kwVsJ6zG5XYJcvj81` - -Matches C++ `public_key_type::operator std::string()` in `libraries/protocol/types.cpp`. - -#### chain_properties_init - -Used by `chain_properties_update_operation` (ID 25). **12 fields only** — NOT the same as `chain_properties_hf9`. -``` -[asset: account_creation_fee] -[uint32: maximum_block_size] -[uint32: create_account_delegation_ratio] -[uint32: create_account_delegation_time] -[asset: min_delegation] -[uint16: min_curation_percent] -[uint16: max_curation_percent] -[uint16: bandwidth_reserve_percent] -[asset: bandwidth_reserve_below] -[uint16: flag_energy_additional_cost] -[uint32: vote_accounting_min_rshares] -[uint16: committee_request_approve_min_percent] -``` - -#### versioned_chain_properties - -Used by `versioned_chain_properties_update_operation` (ID 46). A `static_variant` of chain property variants: - -``` -[varint: type_index][chain_properties_init base fields][variant-specific additional fields] -``` - -| Type Index | Name | Base | Additional Fields | -|------------|------|------|-------------------| -| 0 | `chain_properties_init` | — | (12 base fields only) | -| 1 | `chain_properties_hf4` | init | +3: `inflation_witness_percent`(uint16), `inflation_ratio_committee_vs_reward_fund`(uint16), `inflation_recalc_period`(uint32) | -| 2 | `chain_properties_hf6` | hf4 | +3: `data_operations_cost_additional_bandwidth`(uint32), `witness_miss_penalty_percent`(uint16), `witness_miss_penalty_duration`(uint32) | -| 3 | `chain_properties_hf9` | hf6 | +7: `create_invite_min_balance`(asset), `committee_create_request_fee`(asset), `create_paid_subscription_fee`(asset), `account_on_sale_fee`(asset), `subaccount_on_sale_fee`(asset), `witness_declaration_fee`(asset), `withdraw_intervals`(uint16) | - -### Operation Structures - -#### transfer_operation (ID: 2) -``` -[account_name_type: from] -[account_name_type: to] -[asset: amount] -[string: memo] -``` - -#### account_create_operation (ID: 20) -``` -[asset: fee] -[asset: delegation] -[account_name_type: creator] -[account_name_type: new_account_name] -[authority: master] -[authority: active] -[authority: regular] -[public_key_type: memo_key] -[string: json_metadata] -[account_name_type: referrer] -[extensions_type: extensions] -``` - -#### witness_update_operation (ID: 6) -``` -[account_name_type: owner] -[string: url] -[public_key_type: block_signing_key] -``` - -#### award_operation (ID: 47) -``` -[account_name_type: initiator] -[account_name_type: receiver] -[uint16: energy] -[uint64: custom_sequence] -[string: memo] -[vector: beneficiaries] -``` - -#### beneficiary_route_type -``` -[account_name_type: account] -[uint16: weight] -``` - -#### invite_registration_operation (ID: 45) -``` -[account_name_type: account] -[public_key_type: new_account_key] -[string: invite_secret] ← WIF-encoded private key (e.g. '5Kd...'), NOT raw bytes -[extensions_type: extensions] -``` - -> **Important:** `invite_secret` is a `string` (WIF-encoded private key), NOT raw 32 bytes. -> The same applies to `claim_invite_balance_operation` (ID 44) and `use_invite_balance_operation` (ID 58). - -#### claim_invite_balance_operation (ID: 44) -``` -[account_name_type: initiator] -[account_name_type: receiver] -[string: invite_secret] ← WIF-encoded private key string -[extensions_type: extensions] -``` - -#### use_invite_balance_operation (ID: 58) -``` -[account_name_type: initiator] -[account_name_type: receiver] -[string: invite_secret] ← WIF-encoded private key string -[extensions_type: extensions] -``` - -#### chain_properties_update_operation (ID: 25) -``` -[account_name_type: owner] -[chain_properties_init: props] ← 12-field struct, NOT chain_properties_hf9 -[extensions_type: extensions] -``` - -#### versioned_chain_properties_update_operation (ID: 46) -``` -[account_name_type: owner] -[versioned_chain_properties: props] ← static_variant -[extensions_type: extensions] -``` - -#### benefactor_award_operation (ID: 49, virtual) -``` -[account_name_type: initiator] ← FC_REFLECT order: initiator BEFORE benefactor -[account_name_type: benefactor] -[account_name_type: receiver] -[uint64: custom_sequence] -[string: memo] -[asset: shares] -``` - -#### set_paid_subscription_operation (ID: 50) -``` -[account_name_type: account] -[string: url] -[uint16: levels] ← uint16, NOT uint8 -[asset: amount] -[uint16: period] ← uint16, NOT uint32 -[extensions_type: extensions] -``` - -#### paid_subscribe_operation (ID: 51) -``` -[account_name_type: account] -[account_name_type: subscriber] -[uint16: level] ← uint16, NOT uint8 -[asset: amount] -[uint16: period] ← uint16, NOT uint32 -[extensions_type: extensions] -``` - -#### paid_subscription_action_operation (ID: 52, virtual) -``` -[account_name_type: subscriber] -[account_name_type: account] -[uint16: level] -[asset: amount] -[uint16: period] -[uint64: summary_duration_sec] -[asset: summary_amount] -``` - -#### cancel_paid_subscription_operation (ID: 53, virtual) -``` -[account_name_type: subscriber] -[account_name_type: account] -``` - -> Only 2 fields — no `level` field. - -#### buy_account_operation (ID: 56) -``` -[account_name_type: account] -[account_name_type: buyer] -[asset: tokens_to_shares] ← asset type, NOT bool/uint8 -[extensions_type: extensions] -``` - -#### account_sale_operation (ID: 57, virtual) -``` -[account_name_type: account] -[asset: price] -[account_name_type: buyer] -[account_name_type: seller] -``` - -#### expire_escrow_ratification_operation (ID: 59, virtual) -``` -[account_name_type: from] -[account_name_type: to] -[account_name_type: agent] -[uint32: escrow_id] -[asset: token_amount] -[asset: fee] -[time_point_sec: ratification_deadline] -``` - -#### bid_operation (ID: 62, virtual) -``` -[account_name_type: account] ← FC_REFLECT order: account BEFORE bidder -[account_name_type: bidder] -[asset: bid] -``` - -#### outbid_operation (ID: 63, virtual) -``` -[account_name_type: account] ← FC_REFLECT order: account BEFORE bidder -[account_name_type: bidder] -[asset: bid] -``` - -#### proposal_delete_operation (ID: 24) -``` -[account_name_type: author] -[string: title] -[account_name_type: requester] ← was previously missing -[extensions_type: extensions] -``` - -## Tools - -### block-log-reader.js - -JavaScript module for reading block_log and dlt_block_log files. Provides programmatic access to block data. - -**Usage:** -```javascript -const { createBlockLogReader, getBlockNum, publicKeyToString } = require('./block-log-reader'); - -const reader = createBlockLogReader('/path/to/block_log'); -// Or for DLT: createBlockLogReader('/path/to/dlt_block_log', undefined, true); - -const block = reader.readBlockByNum(1000); -console.log(getBlockNum(block), block.witness); -reader.close(); -``` - -### block-log-viewer.js - -Interactive terminal UI for browsing block logs. No external dependencies. - -**Usage:** -``` -node block-log-viewer.js [--dlt] [--reader=] -``` - -`` can be either: -- Path to a `block_log` or `dlt_block_log` file directly -- Path to a directory containing `block_log` / `dlt_block_log` (auto-detected) - -**Options:** - -| Option | Description | -|--------|-------------| -| `--dlt` | Use DLT (rolling) block log reader | -| `--reader=` | Path to `block-log-reader.js` module | - -**Directory auto-detection** (when `` is a directory): - -| Files present | `--dlt` | Result | -|--------------|---------|--------| -| `block_log` only | no | Standard mode | -| `dlt_block_log` only | no | Auto-switches to DLT mode | -| Both | no | Standard mode (`block_log`) | -| Both | yes | DLT mode (`dlt_block_log`) | -| Neither | — | Error | - -**Module resolution** (if `block-log-reader.js` is not in the same directory): -- `--reader=/path/to/block-log-reader.js` — explicit CLI option -- `BLOCK_LOG_READER=/path/to/block-log-reader.js` — environment variable - -#### Navigation Commands - -| Command | Description | -|---------|-------------| -| `f` | First block | -| `l` | Last block | -| `n` | Next block | -| `p` | Previous block | -| `N` | Next block with non-free operations (bitmask-accelerated) | -| `P` | Prev block with non-free operations (bitmask-accelerated) | -| `g ` | Go to block #num | -| `` | Jump to block number directly | - -#### Operation Commands - -| Command | Description | -|---------|-------------| -| `o` | Show all operations in current block (JSON) | -| `o ` | Show operations matching type name (e.g. `o transfer`) | -| `s ` | Search forward for block containing operation by type name | -| `S ` | Search forward for substring in any operation's data (incl. virtual) | -| `S =` | Search forward for **exact** string match (`=` prefix disables substring matching) | -| `R ` | Fast raw ASCII byte search in block data (no UTF-8/emoji) | -| `e ` | Export all ops containing string to `search_export_.json` | -| `e =` | Export all ops **exactly matching** string (`=` prefix for exact match) | -| `c` | Continue last search (s/S/R/e) | - -> **Search modes:** Without `=` prefix, string search uses substring matching (e.g. `S VIZ` matches -> any occurrence of "VIZ" in data). With `=` prefix, uses exact match (e.g. `e ="V"` finds only -> the value `V`, not `VIZ`). Surrounding quotes are automatically stripped. - -#### Other - -| Command | Description | -|---------|-------------| -| `scan` | Scan all blocks, build & save bitmask for fast navigation | -| `i` | Show block header info | -| `hex` | Show raw block data in hex | -| `h` | Help | -| `q` | Quit | - -#### Bitmask File (block_log.bitmask) - -The `scan` command builds a compact bitmask file that marks which blocks contain non-free (non-virtual) operations. Once built, `N` and `P` commands jump instantly between non-empty blocks without deserializing skipped blocks. - -**File format:** - -``` -+-------------------+-------------------+-------------------------------+ -| start_block_num | end_block_num | bit array | -| (8 bytes, uint64) | (8 bytes, uint64) | 1 bit per block | -+-------------------+-------------------+-------------------------------+ -``` - -- **Bytes 0–7**: `uint64_t` LE — `start_block_num` -- **Bytes 8–15**: `uint64_t` LE — `end_block_num` -- **Bytes 16+**: bit array, 1 bit per block (bit 0 = `start_block_num`) - - `1` = block has non-free operations - - `0` = block is empty or has only virtual operations -- Total size: `16 + ceil((end - start + 1) / 8)` bytes - -**Example:** 10,000,000 blocks → ~1.25 MB bitmask file. - -The bitmask is auto-loaded on startup if it exists and matches the current block range. If the range differs, a rescan is suggested. - -#### String Search (`S`), Raw Search (`R`), and Export (`e`) - -The `S` command searches the **full operation data** (including virtual) for a substring match. The `=` prefix enables exact match mode — `S ="V"` finds only the exact value `V`, not `VIZ` or other strings containing `V`. - -The `R` command performs a fast raw ASCII byte search directly in the block's binary data, without deserialization. No UTF-8/emoji support. Useful for quickly locating blocks containing specific ASCII patterns. - -The `e` command performs the same search as `S` across **all** blocks and writes results to a JSON file in the block_log directory: - -``` -search_export_1745312345.json -``` - -Export format: -```json -[ - { - "block": 12345, - "timestamp": "2024-01-15 10:30:00 UTC", - "witness": "on1x", - "typeId": 47, - "typeName": "award_operation", - "isVirtual": false, - "data": { "initiator": "alice", "receiver": "on1x", ... } - } -] -``` - -Buffers are serialized as hex strings; BigInts as decimal strings. - ---- - -## See Also - -- [data-types.md](data-types.md) - VIZ data type definitions -- [snapshot-plugin.md](snapshot-plugin.md) - DLT mode documentation -- [block-log-spec.json](block-log-spec.json) - Machine-readable specification diff --git a/.qoder/docs/block-log-viewer.js b/.qoder/docs/block-log-viewer.js deleted file mode 100644 index 8e35fcf511..0000000000 --- a/.qoder/docs/block-log-viewer.js +++ /dev/null @@ -1,1309 +0,0 @@ -#!/usr/bin/env node -/** - * VIZ Block Log Viewer - Interactive terminal UI - * No external dependencies. Uses block-log-reader.js for parsing. - * - * Usage: node block-log-viewer.js [--dlt] [--reader=] - * - * The viewer will look for block-log-reader.js in this order: - * 1. --reader= CLI option - * 2. Same directory as this script - * 3. BLOCK_LOG_READER environment variable - */ - -const fs = require('fs'); -const path = require('path'); -const readline = require('readline'); - -// Resolve block-log-reader module from multiple locations -function resolveReaderModule() { - const cliReader = process.argv.find(a => a.startsWith('--reader=')); - if (cliReader) { - const p = cliReader.split('=')[1]; - if (fs.existsSync(p)) return p; - console.error(`--reader path not found: ${p}`); - process.exit(1); - } - - const candidates = [ - path.join(__dirname, 'block-log-reader'), // same dir as this script - process.env.BLOCK_LOG_READER, // env var - ].filter(Boolean); - - for (const candidate of candidates) { - try { - require.resolve(candidate); - return candidate; - } catch (e) { /* not found, try next */ } - } - - console.error('Cannot find block-log-reader.js'); - console.error('Place it in the same directory as this script, or use:'); - console.error(' node block-log-viewer.js --reader=/path/to/block-log-reader.js'); - console.error(' set BLOCK_LOG_READER=/path/to/block-log-reader.js'); - process.exit(1); -} - -const readerModule = resolveReaderModule(); -const { - createBlockLogReader, - getBlockNum, - blockIdToHex, - OPERATION_TYPES, - CONSTANTS, - BinaryReader, - readSignedBlockHeader, - readSignedBlock -} = require(readerModule); - -// ============================================================================ -// State -// ============================================================================ - -let reader = null; -let currentBlockNum = 0; -let startBlock = 1; -let endBlock = 0; -let scanning = false; -let lastSearch = null; // { type: 's'|'S'|'e', term: string } - -// Bitmask: 1 bit per block, 1 = has non-free ops, 0 = empty/virtual-only -let bitmask = null; // Buffer or null -let bitmaskStart = 0; // start_block_num stored in bitmask -let bitmaskEnd = 0; // end_block_num stored in bitmask - -// ============================================================================ -// Helpers -// ============================================================================ - -function isNonFreeOp(op) { - return !op.isVirtual; -} - -/** - * Check if a Buffer contains an ASCII string (case-insensitive). - * Searches raw bytes without any string conversion or object allocation. - */ -function bufIncludesAscii(buf, searchLower) { - const len = searchLower.length; - const bufLen = buf.length; - outer: - for (let i = 0; i <= bufLen - len; i++) { - for (let j = 0; j < len; j++) { - let c = buf[i + j]; - // Convert to lowercase: A-Z (0x41-0x5A) -> a-z (0x61-0x7A) - if (c >= 0x41 && c <= 0x5A) c += 0x20; - if (c !== searchLower.charCodeAt(j)) continue outer; - } - return true; - } - return false; -} - -function hasNonFreeOps(block) { - for (const tx of block.transactions) { - for (const op of tx.operations) { - if (isNonFreeOp(op)) return true; - } - } - return false; -} - -function collectOps(block) { - const ops = []; - for (const tx of block.transactions) { - for (const op of tx.operations) { - ops.push(op); - } - } - return ops; -} - -function formatTimestamp(d) { - return d.toISOString().replace('T', ' ').replace('.000Z', ' UTC'); -} - -function hashHex(h) { - return h ? Buffer.from(h).toString('hex') : '(null)'; -} - -function shortenHex(hex, len = 16) { - if (!hex || hex.length <= len * 2) return hex; - return hex.slice(0, len) + '...' + hex.slice(-len); -} - -// ============================================================================ -// Bitmask (block_log.bitmask) -// ============================================================================ -// -// File format: -// [8 bytes] start_block_num (uint64 LE) -// [8 bytes] end_block_num (uint64 LE) -// [N bytes] bit array, 1 bit per block (bit i = block startBlock+i) -// bit=1 means block has non-free (non-virtual) operations -// Total bytes = ceil((end - start + 1) / 8) -// -// Saved alongside block_log as block_log.bitmask -// ============================================================================ - -function bitmaskPath() { - return reader.dataPath + '.bitmask'; -} - -/** - * Set bit for blockNum (1 = has non-free ops) - */ -function bitmaskSet(buf, start, blockNum) { - const idx = blockNum - start; - const byteIdx = idx >> 3; - const bitIdx = idx & 7; - buf[byteIdx] |= (1 << bitIdx); -} - -/** - * Get bit for blockNum. Returns 0 or 1. If no bitmask loaded, returns -1. - */ -function bitmaskGet(blockNum) { - if (!bitmask) return -1; - if (blockNum < bitmaskStart || blockNum > bitmaskEnd) return -1; - const idx = blockNum - bitmaskStart; - const byteIdx = idx >> 3; - const bitIdx = idx & 7; - return (bitmask[byteIdx] >> bitIdx) & 1; -} - -/** - * Check if bitmask is loaded and valid for current range - */ -function bitmaskValid() { - return bitmask && bitmaskStart === startBlock && bitmaskEnd === endBlock; -} - -/** - * Load bitmask from file. Returns true if loaded and valid. - */ -function bitmaskLoad() { - const p = bitmaskPath(); - if (!fs.existsSync(p)) return false; - try { - const fd = fs.openSync(p, 'r'); - const stat = fs.fstatSync(fd); - if (stat.size < 16) { fs.closeSync(fd); return false; } - - const hdr = Buffer.allocUnsafe(16); - fs.readSync(fd, hdr, 0, 16, 0); - const s = Number(hdr.readBigUInt64LE(0)); - const e = Number(hdr.readBigUInt64LE(8)); - - const expectedBits = e - s + 1; - const expectedBytes = Math.ceil(expectedBits / 8); - if (stat.size < 16 + expectedBytes) { fs.closeSync(fd); return false; } - - const bits = Buffer.allocUnsafe(expectedBytes); - fs.readSync(fd, bits, 0, expectedBytes, 16); - fs.closeSync(fd); - - bitmask = bits; - bitmaskStart = s; - bitmaskEnd = e; - - if (s === startBlock && e === endBlock) { - return true; // fully valid - } - console.log(` Bitmask range #${s}-#${e} differs from log #${startBlock}-#${endBlock}, will rescan.`); - return false; - } catch (e) { - return false; - } -} - -/** - * Scan all blocks, build bitmask, save to file. - * Uses lightweight tx-count check (no full block deserialization) with - * batch I/O for the index file and progressive bitmask writes. - * Virtual operations are NOT stored in block_log, so tx_count > 0 = has ops. - */ -function bitmaskScan() { - if (scanning) { console.log(' Already scanning.'); return; } - scanning = true; - const total = endBlock - startBlock + 1; - const byteLen = Math.ceil(total / 8); - const BATCH_SIZE = 50000; - - // Prepare file - const p = bitmaskPath(); - const hdr = Buffer.allocUnsafe(16); - hdr.writeBigUInt64LE(BigInt(startBlock), 0); - hdr.writeBigUInt64LE(BigInt(endBlock), 8); - - // Write header + empty bitmask placeholder - const fd = fs.openSync(p, 'w'); - fs.writeSync(fd, hdr, 0, 16, 0); - const zeroChunk = Buffer.alloc(Math.min(byteLen, 65536)); - for (let off = 0; off < byteLen; off += zeroChunk.length) { - const writeLen = Math.min(zeroChunk.length, byteLen - off); - fs.writeSync(fd, zeroChunk, 0, writeLen, 16 + off); - } - - console.log(` Scanning ${total} blocks for non-free operations...`); - console.log(` Bitmask file: ${path.basename(p)} (${((16 + byteLen) / 1024).toFixed(1)} KB)`); - console.log(` Batch size: ${BATCH_SIZE} blocks (lightweight: header + tx count only)`); - - // Process in batches - let currentNum = startBlock; - let nonFreeCount = 0; - let lastPct = -1; - - function processBatch() { - const batchEnd = Math.min(currentNum + BATCH_SIZE - 1, endBlock); - const batchCount = batchEnd - currentNum + 1; - const batchBits = Buffer.alloc(Math.ceil(batchCount / 8)); - - // Read index positions in bulk (one I/O for the whole batch + 1 extra for size boundary) - const positions = reader.readBlockPosBatch(currentNum, batchCount + 1); - if (positions.length === 0) { - currentNum = batchEnd + 1; - setImmediate(processBatch); - return; - } - - for (let i = 0; i < batchCount; i++) { - const num = currentNum + i; - const startPos = Number(positions[i]); - const endPos = (i + 1 < positions.length) ? Number(positions[i + 1]) : (Number(reader.dataSize) - 8); - const blockSize = endPos - startPos; - - if (blockSize <= 0 || blockSize > CONSTANTS.CHAIN_BLOCK_SIZE + 16) continue; - - try { - // Read exact block bytes and parse only header + tx count - const buffer = reader._readData(BigInt(startPos), blockSize); - const br = new BinaryReader(buffer); - readSignedBlockHeader(br); // skip header - const txCount = Number(br.readVarint()); - - if (txCount > 0) { - const byteIdx = Math.floor(i / 8); - const bitOffset = i % 8; - batchBits[byteIdx] |= (1 << bitOffset); - nonFreeCount++; - } - } catch (e) { - // Skip blocks that fail to parse - } - } - - // Write this batch's bits to the file - const batchFileOffset = Math.floor((currentNum - startBlock) / 8); - fs.writeSync(fd, batchBits, 0, batchBits.length, 16 + batchFileOffset); - - // Progress - const processed = batchEnd - startBlock + 1; - const pct = Math.floor((processed / total) * 100); - if (pct !== lastPct) { - lastPct = pct; - const memMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024); - process.stdout.write(`\r Scanning... ${pct}% (#${batchEnd}, ${nonFreeCount} with ops, ${memMB} MB heap) `); - } - - currentNum = batchEnd + 1; - if (currentNum <= endBlock) { - setImmediate(processBatch); - } else { - // Done — load the completed bitmask - fs.closeSync(fd); - - bitmaskLoad(); - - const empty = total - nonFreeCount; - const sizeKB = ((16 + byteLen) / 1024).toFixed(1); - console.log(`\r Done. ${nonFreeCount} with ops, ${empty} empty. Saved ${sizeKB} KB to ${path.basename(p)} `); - scanning = false; - showPrompt(); - } - } - - processBatch(); -} - -/** - * Find next block with non-free ops using bitmask (fast, no deserialization) - * Returns block number or 0 if none found - */ -function bitmaskFindNext(fromBlock) { - if (!bitmaskValid()) return 0; - for (let num = fromBlock; num <= bitmaskEnd; num++) { - if (bitmaskGet(num) === 1) return num; - } - return 0; -} - -/** - * Find prev block with non-free ops using bitmask - */ -function bitmaskFindPrev(fromBlock) { - if (!bitmaskValid()) return 0; - for (let num = fromBlock; num >= bitmaskStart; num--) { - if (bitmaskGet(num) === 1) return num; - } - return 0; -} - -// ============================================================================ -// Display -// ============================================================================ - -function showBlock(block) { - const num = getBlockNum(block); - const ops = collectOps(block); - const nonFree = ops.filter(isNonFreeOp); - const virtual = ops.filter(o => o.isVirtual); - - console.log(''); - console.log('='.repeat(72)); - console.log(` Block #${num}`); - console.log('='.repeat(72)); - console.log(` Timestamp : ${formatTimestamp(block.timestamp)}`); - console.log(` Witness : ${block.witness}`); - console.log(` Previous : ${shortenHex(hashHex(block.previous))}`); - console.log(` Tx Merkle : ${shortenHex(hashHex(block.transaction_merkle_root))}`); - console.log(` Signature : ${shortenHex(hashHex(block.witness_signature))}`); - console.log(` Tx count : ${block.transactions.length}`); - if (block.extensions && block.extensions.length > 0) { - for (const ext of block.extensions) { - if (ext.name === 'version') { - console.log(` Extension : version = ${ext.data.version}`); - } else if (ext.name === 'hardfork_version_vote') { - console.log(` Extension : hardfork_version_vote = ${ext.data.hf_version} at ${ext.data.hf_time}`); - } else { - console.log(` Extension : ${ext.name} ${JSON.stringify(ext.data)}`); - } - } - } - console.log(` Ops total : ${ops.length} (non-free: ${nonFree.length}, virtual: ${virtual.length})`); - console.log('-'.repeat(72)); -} - -function showOps(block, filter) { - const ops = collectOps(block); - const filtered = filter ? ops.filter(o => o.typeName.includes(filter)) : ops; - - if (filtered.length === 0) { - if (filter) { - console.log(` No operations matching "${filter}" in this block.`); - } else { - console.log(' (no operations)'); - } - return; - } - - for (let i = 0; i < filtered.length; i++) { - const op = filtered[i]; - const tag = op.isVirtual ? '[V]' : ' '; - console.log(` ${tag} [${i}] ${op.typeName}`); - try { - const json = JSON.stringify(op.data, (key, val) => { - if (val && val.type === 'Buffer') return ``; - if (Buffer.isBuffer(val)) return ``; - if (typeof val === 'bigint') return val.toString(); - return val; - }, 2); - for (const line of json.split('\n')) { - console.log(' ' + line); - } - } catch (e) { - console.log(' (serialization error)'); - } - console.log(''); - } -} - -function showHelp() { - console.log(''); - console.log('Commands:'); - console.log(' f - First block'); - console.log(' l - Last block'); - console.log(' n - Next block'); - console.log(' p - Previous block'); - console.log(' N - Next block with non-free operations (uses bitmask)'); - console.log(' P - Prev block with non-free operations (uses bitmask)'); - console.log(' g - Go to block #num'); - console.log(' o - Show operations in current block'); - console.log(' o - Show operations matching name'); - console.log(' s - Search forward for block containing operation name'); - console.log(' S - Search forward for string in op JSON (incl. virtual)'); - console.log(' Prefix with = for exact match: S =\"V\" finds V but not VIZ'); - console.log(' R - Fast raw ASCII byte search (no UTF-8/emoji)'); - console.log(' e - Export all ops containing string to search_export_.json'); - console.log(' Prefix with = for exact match: e =\"V\" exports only exact V matches'); - console.log(' c - Continue last search (s/S/e)'); - console.log(' scan - Scan all blocks, build & save bitmask for fast nav'); - console.log(' i - Show block info (header only)'); - console.log(' hex - Show raw block data in hex'); - console.log(' h - This help'); - console.log(' q - Quit'); - const bm = bitmaskValid() ? `LOADED (${endBlock - startBlock + 1} blocks)` : 'not loaded'; - console.log(` Bitmask : ${bm}`); - console.log(''); -} - -function showPrompt() { - const pct = endBlock > startBlock ? Math.round(((currentBlockNum - startBlock) / (endBlock - startBlock)) * 100) : 0; - process.stdout.write(`[${currentBlockNum}/${endBlock}] ${pct}% > `); -} - -// ============================================================================ -// Navigation -// ============================================================================ - -/** - * Safely read a block by number. Returns the block or null. - * Logs errors but does NOT throw — for use in scan loops. - */ -function safeReadBlock(num) { - try { - return reader.readBlockByNum(num); - } catch (e) { - return null; // skip blocks that fail to deserialize - } -} - -function showBlockHeaderOnly(header, blockNum, errorMsg) { - console.log(''); - console.log('='.repeat(72)); - console.log(` Block #${blockNum} (HEADER ONLY - deserialization failed)`); - console.log('='.repeat(72)); - if (header) { - console.log(` Timestamp : ${formatTimestamp(header.timestamp)}`); - console.log(` Witness : ${header.witness}`); - console.log(` Previous : ${shortenHex(hashHex(header.previous))}`); - console.log(` Tx Merkle : ${shortenHex(hashHex(header.transaction_merkle_root))}`); - console.log(` Signature : ${shortenHex(hashHex(header.witness_signature))}`); - console.log(` Block Num : ${header._blockNum} (from previous)`); - console.log(` File Pos : ${header._position}`); - } else { - console.log(' (header also could not be read)'); - } - console.log('-'.repeat(72)); - console.log(` Error: ${errorMsg}`); - console.log('-'.repeat(72)); -} - -function goTo(num) { - num = Math.max(startBlock, Math.min(endBlock, num)); - let block; - try { - block = reader.readBlockByNum(num); - } catch (e) { - // Full deserialization failed — try header-only - const header = reader.readBlockHeaderByNum(num); - showBlockHeaderOnly(header, num, e.message); - currentBlockNum = num; - return; - } - if (!block) { - // Block number out of index range - console.log(`Block #${num} is not in the index (out of range).`); - return; - } - currentBlockNum = num; - showBlock(block); -} - -function goNext() { - if (currentBlockNum >= endBlock) { console.log('Already at last block.'); return; } - goTo(currentBlockNum + 1); -} - -function goPrev() { - if (currentBlockNum <= startBlock) { console.log('Already at first block.'); return; } - goTo(currentBlockNum - 1); -} - -function goFirst() { - goTo(startBlock); -} - -function goLast() { - goTo(endBlock); -} - -function goNextWithOps() { - // Fast path: use bitmask to skip empty blocks without deserialization - if (bitmaskValid()) { - const next = bitmaskFindNext(currentBlockNum + 1); - if (next === 0) { - console.log(' No more blocks with non-free operations forward.'); - return; - } - // Only read & deserialize the one block we found - currentBlockNum = next; - try { - const block = reader.readBlockByNum(next); - if (block) showBlock(block); - else showBlockHeaderOnly(reader.readBlockHeaderByNum(next), next, 'Block returned null'); - } catch (e) { - showBlockHeaderOnly(reader.readBlockHeaderByNum(next), next, e.message); - } - return; - } - - // Slow fallback: scan by reading each block - for (let num = currentBlockNum + 1; num <= endBlock; num++) { - const block = safeReadBlock(num); - if (block && hasNonFreeOps(block)) { - currentBlockNum = num; - showBlock(block); - return; - } - if ((num - currentBlockNum) % 1000 === 0) { - process.stdout.write(`\r Scanning... #${num} `); - } - } - console.log('\r No more blocks with non-free operations forward. '); -} - -function goPrevWithOps() { - // Fast path: use bitmask - if (bitmaskValid()) { - const prev = bitmaskFindPrev(currentBlockNum - 1); - if (prev === 0) { - console.log(' No more blocks with non-free operations backward.'); - return; - } - currentBlockNum = prev; - try { - const block = reader.readBlockByNum(prev); - if (block) showBlock(block); - else showBlockHeaderOnly(reader.readBlockHeaderByNum(prev), prev, 'Block returned null'); - } catch (e) { - showBlockHeaderOnly(reader.readBlockHeaderByNum(prev), prev, e.message); - } - return; - } - - // Slow fallback - for (let num = currentBlockNum - 1; num >= startBlock; num--) { - const block = safeReadBlock(num); - if (block && hasNonFreeOps(block)) { - currentBlockNum = num; - showBlock(block); - return; - } - if ((currentBlockNum - num) % 1000 === 0) { - process.stdout.write(`\r Scanning... #${num} `); - } - } - console.log('\r No more blocks with non-free operations backward. '); -} - -/** - * Run a search function in batches to avoid OOM. - * The checkFn receives (blockNum) and should return false to skip this block, - * or true to proceed with full deserialization via matchFn. - * matchFn receives (blockNum, block) and should return truthy to stop (the match). - * Calls onProgress(currentNum) for status updates. - */ -function batchSearch(checkFn, matchFn, onProgress, onDone) { - if (scanning) { console.log(' Already scanning.'); return; } - scanning = true; - const BATCH_SIZE = 10000; - let currentNum = currentBlockNum + 1; - - function processBatch() { - const batchEnd = Math.min(currentNum + BATCH_SIZE - 1, endBlock); - - for (let num = currentNum; num <= batchEnd; num++) { - // Lightweight pre-check: skip empty blocks - if (!checkFn(num)) continue; - - // Full deserialize only blocks that pass the check - const block = safeReadBlock(num); - if (block) { - const result = matchFn(num, block); - if (result) { - currentBlockNum = num; - scanning = false; - onDone(null); - return; - } - } - if ((num - currentNum) % 2000 === 0 && num > currentNum) { - onProgress(num); - } - } - - onProgress(batchEnd); - currentNum = batchEnd + 1; - if (currentNum <= endBlock) { - setImmediate(processBatch); - } else { - scanning = false; - onDone('not found'); - } - } - - processBatch(); -} - -function searchOpForward(name) { - lastSearch = { type: 's', term: name }; - console.log(` Searching for operation "${name}" forward from #${currentBlockNum + 1}...`); - - batchSearch( - (num) => { - // Skip blocks with no transactions (bitmask only — no per-block I/O) - if (bitmaskValid()) return bitmaskGet(num) === 1; - return true; // without bitmask, can't skip - }, - (num, block) => { - const ops = collectOps(block); - if (ops.some(o => o.typeName.includes(name))) { - showBlock(block); - showOps(block, name); - return true; - } - return false; - }, - (num) => process.stdout.write(`\r Scanning... #${num} `), - (err) => { - if (err === 'not found') console.log('\r No matching operations found. '); - else console.log('\r No more matching operations. '); - showPrompt(); - } - ); -} - -// ============================================================================ -// String Search in Operation JSON (including virtual ops) -// ============================================================================ - -/** - * Parse search term: strip surrounding quotes, detect = prefix for exact match. - * Returns { term: string, exact: boolean } - * Examples: - * '="V"' → { term: 'V', exact: true } - * '=V' → { term: 'V', exact: true } - * '"V"' → { term: 'V', exact: false } - * 'VIZ' → { term: 'VIZ', exact: false } - */ -function parseSearch(str) { - if (!str) return { term: str, exact: false }; - let exact = false; - if (str.startsWith('=')) { - exact = true; - str = str.slice(1); - } - if ((str.startsWith('"') && str.endsWith('"')) || - (str.startsWith("'") && str.endsWith("'"))) { - str = str.slice(1, -1); - } - return { term: str, exact }; -} - -/** - * Recursively walk an object looking for a string value matching searchStr. - * Zero-allocation: no JSON.stringify, just walks existing objects. - * If exact=true, uses === instead of .includes() for string values. - */ -function deepIncludes(obj, searchStr, exact) { - if (obj === null || obj === undefined) return false; - if (typeof obj === 'string') return exact ? (obj === searchStr) : obj.includes(searchStr); - if (typeof obj === 'number' || typeof obj === 'boolean') return false; - if (typeof obj === 'bigint') return exact ? (obj.toString() === searchStr) : obj.toString().includes(searchStr); - if (Buffer.isBuffer(obj)) return false; // skip binary - if (Array.isArray(obj)) { - for (let i = 0; i < obj.length; i++) { - if (deepIncludes(obj[i], searchStr, exact)) return true; - } - return false; - } - if (typeof obj === 'object') { - for (const key of Object.keys(obj)) { - if (exact ? (key === searchStr) : key.includes(searchStr)) return true; - if (deepIncludes(obj[key], searchStr, exact)) return true; - } - } - return false; -} - -/** - * Check if any operation in a block contains the search string. - * Uses recursive walk instead of JSON.stringify to avoid large temp strings. - * Returns matching operations array (empty if none). - */ -function findOpsByString(block, searchStr, exact) { - const ops = collectOps(block); - const matched = []; - for (const op of ops) { - if (op.typeName && (exact ? op.typeName === searchStr : op.typeName.includes(searchStr))) { - matched.push(op); - continue; - } - if (deepIncludes(op.data, searchStr, exact)) { - matched.push(op); - } - } - return matched; -} - -/** - * Deserialize a block from an existing raw buffer (no re-read, no 1MB over-allocate). - * Returns the block object or null on error. - */ -function deserializeFromRaw(rawBuf) { - try { - const br = new BinaryReader(rawBuf); - const block = readSignedBlock(br); - return block; - } catch (e) { - console.error(` deserializeFromRaw ERROR: ${e.message} (bufLen=${rawBuf.length})`); - return null; - } -} - -/** - * Fast raw ASCII byte search forward — no deserialization, just scans raw block bytes. - * Only finds ASCII strings (English, digits). Use S for UTF-8/emoji search. - */ -function searchRawForward(str) { - lastSearch = { type: 'R', term: str }; - if (scanning) { console.log(' Already scanning.'); return; } - scanning = true; - const YIELD_EVERY = 200; // raw scan is lighter, can do more per yield - let currentNum = currentBlockNum + 1; - - console.log(` Raw searching for ASCII "${str}" forward from #${currentNum}...`); - - function processChunk() { - const chunkEnd = Math.min(currentNum + YIELD_EVERY - 1, endBlock); - - const positions = reader.readBlockPosBatch(currentNum, chunkEnd - currentNum + 2); - if (positions.length === 0) { - currentNum = chunkEnd + 1; - setImmediate(processChunk); - return; - } - - for (let num = currentNum; num <= chunkEnd; num++) { - const i = num - currentNum; - const startPos = Number(positions[i]); - const endPos = (i + 1 < positions.length) ? Number(positions[i + 1]) : (Number(reader.dataSize) - 8); - const blockSize = endPos - startPos; - if (blockSize <= 0 || blockSize > CONSTANTS.CHAIN_BLOCK_SIZE + 16) continue; - - let rawBuf = null; - try { - rawBuf = reader._readData(BigInt(startPos), blockSize); - if (bufIncludesAscii(rawBuf, str)) { - // Found — deserialize to show context - const block = deserializeFromRaw(rawBuf); - rawBuf = null; - if (block) { - currentBlockNum = num; - showBlock(block); - showOps(block); - } else { - currentBlockNum = num; - console.log(` Block #${num} contains "${str}" in raw bytes (could not deserialize).`); - } - scanning = false; - showPrompt(); - return; - } - } catch (e) { /* skip */ } - rawBuf = null; // free buffer - } - - process.stdout.write(`\r Scanning... #${chunkEnd} `); - currentNum = chunkEnd + 1; - if (currentNum <= endBlock) { - setImmediate(processChunk); - } else { - console.log('\r No blocks containing ASCII "' + str + '" found. '); - scanning = false; - showPrompt(); - } - } - - processChunk(); -} - -/** - * Search forward for next block containing string in any operation's JSON. - * Only deserializes blocks that have operations (bitmask or tx-count filter). - * Yields to event loop every YIELD_EVERY blocks so GC can reclaim memory. - * Supports UTF-8, emoji, and any string in operation data. - */ -function searchStringForward(str, exact) { - lastSearch = { type: 'S', term: str, exact: !!exact }; - if (scanning) { console.log(' Already scanning.'); return; } - scanning = true; - const YIELD_EVERY = 100; - let currentNum = currentBlockNum + 1; - - console.log(` Searching for ${exact ? 'exact' : 'substring'} "${str}" in op JSON forward from #${currentNum}...`); - - function processChunk() { - const chunkEnd = Math.min(currentNum + YIELD_EVERY - 1, endBlock); - - // Read index positions in bulk for this chunk - const positions = reader.readBlockPosBatch(currentNum, chunkEnd - currentNum + 2); - if (positions.length === 0) { - currentNum = chunkEnd + 1; - setImmediate(processChunk); - return; - } - - for (let num = currentNum; num <= chunkEnd; num++) { - const i = num - currentNum; - - // Skip blocks with no operations (bitmask only — no per-block I/O) - if (bitmaskValid() && bitmaskGet(num) === 0) continue; - - // Read exact-size bytes and deserialize - const startPos = Number(positions[i]); - const endPos = (i + 1 < positions.length) ? Number(positions[i + 1]) : (Number(reader.dataSize) - 8); - const blockSize = endPos - startPos; - if (blockSize <= 0 || blockSize > CONSTANTS.CHAIN_BLOCK_SIZE + 16) continue; - - // Debug: log block number and size every 1000 blocks or if block is large - if (num % 1000 === 0 || blockSize > 100000) { - const memMB = process.memoryUsage().heapUsed / 1024 / 1024; - process.stdout.write(`\r #${num} size=${blockSize} heap=${memMB.toFixed(0)}MB \n`); - } - - let rawBuf = null; - let block = null; - try { - rawBuf = reader._readData(BigInt(startPos), blockSize); - block = deserializeFromRaw(rawBuf); - } catch (e) { /* skip */ } - rawBuf = null; - - if (block) { - const matched = findOpsByString(block, str, exact); - if (matched.length > 0) { - currentBlockNum = num; - showBlock(block); - for (const op of matched) { - const tag = op.isVirtual ? '[V]' : ' '; - console.log(` ${tag} ${op.typeName}`); - try { - const json = JSON.stringify(op.data, (key, val) => { - if (val && val.type === 'Buffer') return ``; - if (Buffer.isBuffer(val)) return ``; - if (typeof val === 'bigint') return val.toString(); - return val; - }, 2); - for (const line of json.split('\n')) { - console.log(' ' + line); - } - } catch (e) { - console.log(' (serialization error)'); - } - console.log(''); - } - scanning = false; - showPrompt(); - return; - } - } - block = null; - } - - process.stdout.write(`\r Scanning... #${chunkEnd} `); - currentNum = chunkEnd + 1; - if (currentNum <= endBlock) { - setImmediate(processChunk); - } else { - console.log('\r No matching operations found. '); - scanning = false; - showPrompt(); - } - } - - processChunk(); -} - -/** - * JSON replacer for Buffer/bigint serialization - */ -function jsonReplacer(key, val) { - if (val && val.type === 'Buffer' && Array.isArray(val.data)) return Buffer.from(val.data).toString('hex'); - if (Buffer.isBuffer(val)) return val.toString('hex'); - if (typeof val === 'bigint') return val.toString(); - return val; -} - -/** - * Search all blocks and export matching operations to JSON file. - * Streams results to disk: writes each chunk's matches as JSON objects, - * then clears them from memory. Yields every YIELD_EVERY blocks for GC. - * Final file = "[" + obj1 + "," + obj2 + ... + "]". - */ -function searchExport(str, exact) { - lastSearch = { type: 'e', term: str, exact: !!exact }; - if (scanning) { console.log(' Already scanning.'); return; } - scanning = true; - - let matchCount = 0; - let blockCount = 0; - let writtenBytes = 0; - const total = endBlock - startBlock + 1; - let lastPct = -1; - const YIELD_EVERY = 100; - let currentNum = startBlock; - let needComma = false; // whether to prepend "," before next object - - // Open export file, write opening "[" - const unixTime = Math.floor(Date.now() / 1000); - const outPath = path.join(path.dirname(reader.dataPath), `search_export_${unixTime}.json`); - const fd = fs.openSync(outPath, 'w'); - fs.writeSync(fd, '[\n', null, 'utf8'); - writtenBytes += 2; - - console.log(` Exporting all operations ${exact ? 'exactly matching' : 'containing'} "${str}" from #${startBlock} to #${endBlock}...`); - console.log(` Output: ${path.basename(outPath)}`); - - function processChunk() { - const chunkEnd = Math.min(currentNum + YIELD_EVERY - 1, endBlock); - - // Read index positions in bulk - const positions = reader.readBlockPosBatch(currentNum, chunkEnd - currentNum + 2); - - for (let num = currentNum; num <= chunkEnd; num++) { - const i = num - currentNum; - - // Skip blocks with no operations (bitmask only — no per-block I/O) - if (bitmaskValid() && bitmaskGet(num) === 0) continue; - - const startPos = (i < positions.length) ? Number(positions[i]) : 0; - const endPos = (i + 1 < positions.length) ? Number(positions[i + 1]) : 0; - const blockSize = endPos - startPos; - if (blockSize <= 0 || blockSize > CONSTANTS.CHAIN_BLOCK_SIZE + 16) continue; - - // Debug: log block number and size every 1000 blocks or if block is large - if (num % 1000 === 0 || blockSize > 100000) { - const memMB = process.memoryUsage().heapUsed / 1024 / 1024; - process.stdout.write(`\r #${num} size=${blockSize} heap=${memMB.toFixed(0)}MB \n`); - } - - // Read exact-size bytes and deserialize - let rawBuf = null; - let block = null; - try { - rawBuf = reader._readData(BigInt(startPos), blockSize); - block = deserializeFromRaw(rawBuf); - } catch (e) { /* skip */ } - rawBuf = null; - - if (block) { - const matched = findOpsByString(block, str, exact); - if (matched.length > 0) { - blockCount++; - for (const op of matched) { - matchCount++; - // Flush each op immediately — don't accumulate references - const prefix = needComma ? ',\n' : ''; - needComma = true; - const record = { - block: num, - timestamp: formatTimestamp(block.timestamp), - witness: block.witness, - typeId: op.typeId, - typeName: op.typeName, - isVirtual: op.isVirtual, - data: op.data - }; - const json = JSON.stringify(record, jsonReplacer, 2); - const chunk = prefix + json; - fs.writeSync(fd, chunk, null, 'utf8'); - writtenBytes += Buffer.byteLength(chunk, 'utf8'); - } - } - } - block = null; - } - - const pct = Math.floor(((chunkEnd - startBlock) / total) * 100); - if (pct !== lastPct && pct % 5 === 0) { - lastPct = pct; - const sizeMB = (writtenBytes / 1024 / 1024).toFixed(1); - process.stdout.write(`\r Scanning... ${pct}% (#${chunkEnd}, ${matchCount} ops in ${blockCount} blocks, ${sizeMB} MB written) `); - } else { - process.stdout.write(`\r Scanning... #${chunkEnd} (${matchCount} ops) `); - } - - currentNum = chunkEnd + 1; - if (currentNum <= endBlock) { - setImmediate(processChunk); - } else { - // Close the JSON array - fs.writeSync(fd, '\n]', null, 'utf8'); - writtenBytes += 2; - fs.closeSync(fd); - - if (matchCount === 0) { - // Remove empty file - fs.unlinkSync(outPath); - console.log('\r No matching operations found. '); - } else { - const sizeMB = (writtenBytes / 1024 / 1024).toFixed(1); - console.log(`\r Done. ${matchCount} ops in ${blockCount} blocks. Saved ${sizeMB} MB to ${path.basename(outPath)} `); - } - scanning = false; - showPrompt(); - } - } - - processChunk(); -} - -function showCurrentOps(filter) { - try { - const block = reader.readBlockByNum(currentBlockNum); - if (!block) { console.log('No current block (deserialization failed).'); return; } - showOps(block, filter || null); - } catch (e) { - console.log(`Cannot show ops: ${e.message}`); - } -} - -function showCurrentInfo() { - try { - const block = reader.readBlockByNum(currentBlockNum); - if (!block) { - const header = reader.readBlockHeaderByNum(currentBlockNum); - showBlockHeaderOnly(header, currentBlockNum, 'Full deserialization failed'); - return; - } - showBlock(block); - } catch (e) { - const header = reader.readBlockHeaderByNum(currentBlockNum); - showBlockHeaderOnly(header, currentBlockNum, e.message); - } -} - -function showBlockHex() { - const raw = reader.readBlockRawData(currentBlockNum); - if (!raw) { - console.log(`Block #${currentBlockNum} raw data not available (out of range).`); - return; - } - - const pos = reader.getBlockPos(currentBlockNum); - console.log(''); - console.log(`Raw block #${currentBlockNum}: offset ${pos}, ${raw.length} bytes`); - console.log('='.repeat(73)); - - const BYTES_PER_LINE = 16; - for (let offset = 0; offset < raw.length; offset += BYTES_PER_LINE) { - const slice = raw.slice(offset, Math.min(offset + BYTES_PER_LINE, raw.length)); - const hexParts = []; - const asciiParts = []; - - for (let i = 0; i < BYTES_PER_LINE; i++) { - if (i < slice.length) { - hexParts.push(slice.readUInt8(i).toString(16).padStart(2, '0')); - const c = slice.readUInt8(i); - asciiParts.push(c >= 0x20 && c < 0x7f ? String.fromCharCode(c) : '.'); - } else { - hexParts.push(' '); - asciiParts.push(' '); - } - if (i === 7) hexParts.push(''); // extra space in the middle - } - - const addr = offset.toString(16).padStart(8, '0'); - console.log(` ${addr} ${hexParts.join(' ')} |${asciiParts.join('')}|`); - } - - console.log('='.repeat(73)); - console.log(` ${raw.length} bytes total`); - - // Also show as continuous hex on one line for easy copy - const hexLine = raw.toString('hex'); - console.log(` Hex (first 128 bytes): ${hexLine.slice(0, 256)}`); - if (hexLine.length > 256) { - console.log(` ... (${hexLine.length / 2 - 128} more bytes)`); - } -} - -// ============================================================================ -// Main Loop -// ============================================================================ - -function main() { - const args = process.argv.slice(2); - if (args.length < 1) { - console.log('Usage: node block-log-viewer.js [--dlt] [--reader=]'); - console.log(''); - console.log(' can be:'); - console.log(' - Path to block_log or dlt_block_log file directly'); - console.log(' - Path to directory containing block_log / dlt_block_log'); - console.log(''); - console.log('Options:'); - console.log(' --dlt Use DLT (rolling) block log reader'); - console.log(' --reader= Path to block-log-reader.js module'); - process.exit(1); - } - - let dataPath = args.find(a => !a.startsWith('--')); - let isDlt = args.includes('--dlt'); - - // If path is a directory, auto-detect block_log or dlt_block_log inside it - if (fs.existsSync(dataPath) && fs.statSync(dataPath).isDirectory()) { - const dltPath = path.join(dataPath, 'dlt_block_log'); - const stdPath = path.join(dataPath, 'block_log'); - - if (!isDlt && fs.existsSync(dltPath) && !fs.existsSync(stdPath)) { - // Only dlt_block_log exists — auto-switch to DLT mode - dataPath = dltPath; - isDlt = true; - } else if (isDlt) { - if (!fs.existsSync(dltPath)) { - console.log(`dlt_block_log not found in: ${dataPath}`); - process.exit(1); - } - dataPath = dltPath; - } else { - if (!fs.existsSync(stdPath)) { - console.log(`block_log not found in: ${dataPath}`); - process.exit(1); - } - dataPath = stdPath; - } - } - - if (!fs.existsSync(dataPath)) { - console.log(`File not found: ${dataPath}`); - process.exit(1); - } - - try { - reader = createBlockLogReader(dataPath, undefined, isDlt); - } catch (e) { - console.log(`Failed to open block log: ${e.message}`); - process.exit(1); - } - - startBlock = reader.getStartBlockNum(); - endBlock = reader.getHeadBlockNum(); - - if (endBlock === 0) { - console.log('Block log appears empty (index has no entries).'); - reader.close(); - process.exit(1); - } - - console.log(''); - console.log(`VIZ Block Log Viewer`); - console.log(` File : ${dataPath}`); - console.log(` Type : ${isDlt ? 'DLT (rolling)' : 'Standard'}`); - console.log(` Blocks : #${startBlock} - #${endBlock} (${endBlock - startBlock + 1} total)`); - - // Try to load bitmask - const bmLoaded = bitmaskLoad(); - if (bmLoaded) { - console.log(` Bitmask : LOADED (${path.basename(bitmaskPath())})`); - } else if (bitmask) { - console.log(` Bitmask : outdated, run 'scan' to rebuild`); - } else { - console.log(` Bitmask : not found, run 'scan' to build`); - } - console.log(''); - - // Show first block - goTo(startBlock); - showHelp(); - - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - prompt: '' - }); - - const onLine = (line) => { - if (scanning) return; // ignore input during scan - const trimmed = line.trim(); - if (!trimmed) { showPrompt(); return; } - - const parts = trimmed.split(/\s+/); - const cmd = parts[0]; - - switch (cmd) { - case 'f': goFirst(); break; - case 'l': goLast(); break; - case 'n': goNext(); break; - case 'p': goPrev(); break; - case 'N': goNextWithOps(); break; - case 'P': goPrevWithOps(); break; - case 'scan': bitmaskScan(); break; - case 'g': { - const num = parseInt(parts[1], 10); - if (isNaN(num)) { console.log('Usage: g '); break; } - goTo(num); - break; - } - case 'o': { - showCurrentOps(parts.slice(1).join(' ') || null); - break; - } - case 's': { - const name = parts.slice(1).join(' '); - if (!name) { console.log('Usage: s '); break; } - searchOpForward(name); - break; - } - case 'S': { - const { term, exact } = parseSearch(parts.slice(1).join(' ')); - if (!term) { console.log('Usage: S (prefix with = for exact match, e.g. S ="V")'); break; } - searchStringForward(term, exact); - break; - } - case 'R': { - const { term } = parseSearch(parts.slice(1).join(' ')); - if (!term) { console.log('Usage: R (ASCII only)'); break; } - searchRawForward(term); - break; - } - case 'e': { - const { term, exact } = parseSearch(parts.slice(1).join(' ')); - if (!term) { console.log('Usage: e (prefix with = for exact match, e.g. e ="V")'); break; } - searchExport(term, exact); - break; - } - case 'c': { - if (!lastSearch) { console.log('No previous search. Use s, S, R, or e first.'); break; } - if (lastSearch.type === 's') searchOpForward(lastSearch.term); - else if (lastSearch.type === 'S') searchStringForward(lastSearch.term, lastSearch.exact); - else if (lastSearch.type === 'R') searchRawForward(lastSearch.term); - else if (lastSearch.type === 'e') searchExport(lastSearch.term, lastSearch.exact); - break; - } - case 'i': showCurrentInfo(); break; - case 'hex': showBlockHex(); break; - case 'h': showHelp(); break; - case 'q': - console.log('Bye.'); - reader.close(); - process.exit(0); - break; - default: - // Allow raw number input to jump to block - const maybeNum = parseInt(cmd, 10); - if (!isNaN(maybeNum) && parts.length === 1) { - goTo(maybeNum); - } else { - console.log(`Unknown command: ${cmd}. Type h for help.`); - } - } - showPrompt(); - }; - - rl.on('line', onLine); - showPrompt(); -} - -main(); diff --git a/.qoder/docs/block-processing.md b/.qoder/docs/block-processing.md deleted file mode 100644 index 82ed5e64c0..0000000000 --- a/.qoder/docs/block-processing.md +++ /dev/null @@ -1,413 +0,0 @@ -# VIZ Blockchain — Block Processing & Pending Transactions - -Internal mechanics of block application, transaction queuing, and the pending transaction lifecycle. - ---- - -## Overview - -When a node receives a new block via P2P, it must: -1. Temporarily remove its pending (mempool) transactions from the database -2. Apply the incoming block -3. Re-apply the pending transactions that were not included in the block - -This process is managed by the `without_pending_transactions` helper in `db_with.hpp`. - ---- - -## Key Data Structures - -| Structure | Type | Location | Purpose | -|---|---|---|---| -| `_pending_tx` | `vector` | `database.hpp:473` | Transactions received from the network, waiting to be included in a block | -| `_popped_tx` | `deque` | `database.hpp:472` | Transactions from a popped block (during fork switch), to be re-applied | -| `_pending_tx_session` | `optional` | `database.hpp:517` | Undo session covering all pending transaction state changes | - ---- - -## Block Application Flow - -### `push_block()` → `without_pending_transactions()` - -``` -push_block(new_block) - └─ without_pending_transactions(db, skip, _pending_tx, callback) - ├─ pending_transactions_restorer constructor: clear_pending() - ├─ callback: _push_block(new_block) ← apply the incoming block - └─ ~pending_transactions_restorer() ← restore pending transactions -``` - -Source: [database.cpp:897-920](../../libraries/chain/database.cpp#L897) - -The destructor of `pending_transactions_restorer` is where the "Postponed" log messages appear. - ---- - -## Pending Transaction Restoration (Destructor Logic) - -Source: [db_with.hpp](../../libraries/chain/include/graphene/chain/db_with.hpp) - -The destructor processes two lists in order: - -### Step 1: Re-apply `_popped_tx` (from fork switches) - -``` -for each tx in _popped_tx: - if time limit exceeded → push to _pending_tx (postpone) - else if is_known_transaction → skip (already in chain) - else → _push_transaction(tx) → applied_txs++ -``` - -### Step 2: Re-apply `_pending_transactions` (original mempool) - -``` -for each tx in _pending_transactions: - if time limit exceeded → push to _pending_tx (postpone) - else if is_known_transaction → skip (already in the new block) - else → _push_transaction(tx) → applied_txs++ - on transaction_exception → dlog (invalid, discard) - on fc::exception → silently discard -``` - -### Step 3: Log summary - -If any transactions were postponed, a single warning is logged: -``` -Postponed N pending transactions. M were applied. -``` - ---- - -## Time Limit: CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT - -**Value:** `fc::milliseconds(200)` ([config.hpp:141](../../libraries/protocol/include/graphene/protocol/config.hpp#L141)) - -The destructor tracks elapsed time since the start of restoration. Once 200ms is exceeded, all remaining transactions are postponed (pushed back to `_pending_tx`) without attempting to apply them. This prevents the node from blocking for too long when re-applying a large number of pending transactions. - -### When the time limit triggers - -- High transaction throughput blocks (many pending txs to restore) -- Slow individual transaction evaluation (complex operations) -- System under load (CPU contention) - ---- - -## Block Size Limit: CHAIN_BLOCK_GENERATION_POSTPONED_TX_LIMIT - -**Value:** `5` ([config.hpp:140](../../libraries/protocol/include/graphene/protocol/config.hpp#L140)) - -During block **generation** (`_generate_block`), transactions that would exceed `maximum_block_size` are skipped. After `CHAIN_BLOCK_GENERATION_POSTPONED_TX_LIMIT` consecutive oversized transactions, the loop breaks entirely. These transactions remain in `_pending_tx` for the next block. - -This is a different code path from the `pending_transactions_restorer` and produces a separate log: -``` -Postponed N transactions due to block size limit -``` - -Source: [database.cpp:1125-1160](../../libraries/chain/database.cpp#L1125) - ---- - -## Fork DB Head-Seeding - -Source: [database.cpp `_push_block`](../../libraries/chain/database.cpp) - -Before pushing a block to `fork_db`, `_push_block()` ensures the current database head block is present in `fork_db`. After snapshot import, stale sync recovery, or fork_db trimming, the head block may be absent from `fork_db`'s `_index`. - -Without this seed, any block whose `previous == head_block_id()` would throw `unlinkable_block_exception` inside `fork_database::_push_block()` ("block does not link to known chain"), silently rejecting valid next-blocks and preventing head advancement. - -``` -if new_block.previous == head_block_id() - AND head_block_id() NOT in fork_db: - fetch head block from block log - fork_db.start_block(head_block) ← seeds fork_db with the head -``` - -This also fixes **validator nodes that generate their own blocks**: `generate_block()` sets `pending_block.previous = head_block_id()`, and without the seed the self-generated block would fail to push into `fork_db`. - ---- - -## Direct-Extension Bypass - -Source: [database.cpp `_push_block`](../../libraries/chain/database.cpp) - -After pushing a block to `fork_db`, `_push_block()` checks whether the block directly extends the database head (`new_block.previous == head_block_id()`). If so, the fork switch logic is bypassed entirely and the block falls through to `apply_block()`. - -This handles the case where `fork_db._head` points to a stale higher block accumulated from previous failed sync cycles (stale sync recovery does not reset `fork_db`). Without this bypass: - -1. `fork_db.push_block()` returns the stale `_head` (e.g., block #79609893) -2. `new_head->data.previous != head_block_id()` evaluates to TRUE -3. The fork switch logic either rejects the block (head not in fork_db) or can't compare branches -4. The valid next-block is silently dropped, head never advances - -``` -if new_block.previous == head_block_id(): - → skip fork switch, fall through to apply_block -else if new_head->data.previous != head_block_id(): - → existing fork switch logic (unchanged) -``` - -Together with fork_db head-seeding, this ensures blocks that correctly link to the database head are always applied, regardless of `fork_db`'s internal `_head` state. - ---- - -## Fork Switch Flow - -When a node switches to a different fork: - -1. `pop_block()` removes the current head block - - Transactions from the popped block are saved to `_popped_tx` - - Source: [database.cpp](../../libraries/chain/database.cpp) - -2. The new block is applied via `push_block()` - -3. In `~pending_transactions_restorer()`: - - `_popped_tx` transactions are processed first (from the old fork) - - Then original `_pending_transactions` are processed - - Duplicate transactions (already in the new chain) are silently skipped - -### Linear Extension vs. Actual Fork - -When `fork_db._push_next()` auto-links orphan blocks from the unlinked index, the fork_db head can jump multiple blocks ahead of the database head in a single `push_block()` call. This triggers the fork switch code path (`new_head->data.previous != head_block_id()`), but there is no actual fork — the new chain extends directly from the current head. - -`fetch_branch_from(new_head, head_block_id)` always appends the common ancestor to **both** branches. For a linear extension, the common ancestor IS the current head: -- `branches.first` = `[new_tip, ..., HEAD]` (blocks to apply + common ancestor) -- `branches.second` = `[HEAD]` (just the common ancestor) - -**Detection:** `is_linear_extension = branches.second.size() == 1 && branches.second.back()->id == head_block_id()`. - -**Behavior when linear:** -- Skip the pop loop entirely (the common ancestor is already applied, no blocks to undo) -- Skip the common ancestor when applying `branches.first` (avoid re-applying HEAD) -- On error: pop any newly applied blocks back to the common ancestor, set fork_db head to it - -**Why this matters in DLT mode:** In DLT mode, LIB = head, so undo sessions are committed (not just pushed). `pop_block()` → `undo()` has no effect — `head_block_id()` never changes. The pop loop becomes infinite, eventually emptying the fork_db and crashing with "popping head block would leave fork DB empty". - -For **actual forks** (`branches.second.size() > 1` or common ancestor != head), the original behavior is preserved: pop old-fork blocks including the common ancestor, then re-apply the common ancestor and new-fork blocks from `branches.first`. - -### Debug Logging - -Diagnostic logs at every `pop_block()` call site: - -| Log prefix | Location | Meaning | -|---|---|---| -| `Fork switch: new_head=#X, db_head=#Y, branches.first=N, branches.second=M` | Before fork switch | Shows branch sizes; `branches.second=0` = linear extension | -| `FORK-SWITCH-POP: popping head #H` | Main pop loop | Normal fork switch pop | -| `FORK-RECOVER-POP: popping head #H` | Error recovery pop loop | Reverting failed fork switch | -| `POP_BLOCK: db_head=#X, fork_db_head=#Y, fork_db_head_prev=Z` | Inside `database::pop_block()` | Fork_db state before every pop; `prev=0` = root block (will crash) | - ---- - -## Orphan Block Handling (Unlinked Index) - -Source: [database.cpp `_push_block`](../../libraries/chain/database.cpp), [fork_database.cpp](../../libraries/chain/fork_database.cpp) - -When a block arrives whose parent is unknown (missed broadcast), the node can either reject it or defer it for later linking. - -### Pre-check in `_push_block()` - -``` -if block.num > head_num - AND block.previous != head_block_id - AND block.previous not in fork_db: - if gap > 100 → reject (too far ahead, avoid memory bloat) - if gap <= 100 → allow through to fork_db -``` - -Blocks within 100 of head pass to `fork_db.push_block()`, which throws `unlinkable_block_exception` but stores the block in `_unlinked_index` first. - -### Auto-linking via `_push_next()` - -When the missing parent block finally arrives and is pushed to fork_db: -1. `_push_block(parent)` links the parent to the chain -2. `_push_next(parent)` searches `_unlinked_index` for children of `parent` -3. Found children are moved from `_unlinked_index` to `_index` and recursively linked -4. fork_db head may jump multiple blocks ahead in one call - -This triggers the linear extension fork switch (see above). - -### P2P Recovery - -When `unlinkable_block_exception` propagates to the P2P layer (`process_block_during_normal_operation`): -- Block **at or below head** → strike counter incremented (soft-ban after 20 strikes) -- Block **ahead of head** → `start_synchronizing_with_peer()` restarts sync to fetch the missing block - ---- - -## Peer Strike-Based Soft-Ban - -Source: [node.cpp](../../libraries/network/node.cpp), [peer_connection.hpp](../../libraries/network/include/graphene/network/peer_connection.hpp) - -Peers are not immediately soft-banned for sending unlinkable or rejected blocks. Instead, a strike counter accumulates: - -| Path | Threshold | Counter field | -|---|---|---| -| Normal operation: unlinkable block at/below head | 20 strikes | `unlinkable_block_strikes` | -| Sync path: generic block rejection | 20 strikes | `unlinkable_block_strikes` | -| Dead fork / block too old | Immediate | N/A | - -**Reset on valid block:** When a peer sends a block that is successfully accepted (normal or sync), their `unlinkable_block_strikes` counter resets to 0. This allows honest peers to recover from transient errors (snapshot reload, timing races, brief micro-forks). - ---- - -## Bug Fix: False "Postponed" Log Messages - -### Original Bug (fixed) - -In `db_with.hpp`, the `~pending_transactions_restorer()` destructor had three bugs on the logging line: - -```cpp -// BUGGY CODE (before fix) -if( postponed_txs++ ) { - wlog( "Postponed ${p} pending transactions. ${a} were applied.", ("p", postponed_txs)("a", applied_txs) ); -} -``` - -| Bug | Impact | -|---|---| -| `postponed_txs++` inside `if` condition | Double increment: once in `else` branch, once in `if` condition — inflated counter | -| Log inside the `for` loop | Message printed on every iteration after first increment, instead of once at the end | -| Counter incremented for skipped known transactions | Transactions already in the block (`is_known_transaction` = true) still triggered the `if(postponed_txs++)` check | - -### Example of False Output - -With 3 pending transactions that are all already in the incoming block: -``` -Postponed 2 pending transactions. 0 were applied. ← iteration 2 (postponed_txs was 1, now 2) -Postponed 3 pending transactions. 0 were applied. ← iteration 3 (postponed_txs was 2, now 3) -``` - -None of the transactions were actually postponed — they were already known, just skipped. - -### Fix - -Move the log outside the loop and remove the double increment: - -```cpp -// FIXED CODE -} // end of for loop -if( postponed_txs > 0 ) { - wlog( "Postponed ${p} pending transactions. ${a} were applied.", ("p", postponed_txs)("a", applied_txs) ); -} -``` - -Now the log only fires once, after both loops complete, with an accurate count of truly postponed transactions. - ---- - -## Bug Fix: Validator Plugin Option Parsing - -Source: [validator.cpp](../../plugins/validator/validator.cpp) - -### Bug 1: `enable-stale-production` with `implicit_value(false)` - -```cpp -// BUGGY CODE (before fix) -("enable-stale-production", bpo::value()->implicit_value(false), ...) -``` - -Using `--enable-stale-production` on the command line (without `=true`) would set the value to `false` — the same as the default. The flag was effectively a no-op unless you explicitly wrote `--enable-stale-production=true`. - -**Fix:** Changed to `implicit_value(true)` so the bare flag enables stale production as expected. - -### Bug 2: `required-participation` double-scaling - -```cpp -// BUGGY CODE (before fix) -("required-participation", bpo::value()->implicit_value(33), ...) -// ... -int e = options["required-participation"].as(); -_required_witness_participation = uint32_t(e * CHAIN_1_PERCENT); -``` - -The value passed by the user (e.g. `33` meaning 33%) was multiplied by `CHAIN_1_PERCENT` (100), producing 3300 basis points. This was correct for percentage input but: -- It was inconsistent with internal representation (basis points) -- Users putting basis points in config files would get 100× scaling -- `implicit_value(33)` made the bare flag `--required-participation` set 33%, but the behavior was unclear - -**Fix:** Changed to `default_value(33 * CHAIN_1_PERCENT)` and removed the multiplication in parsing. The value is now always in basis points (0–10000 = 0%–100%): - -```cpp -// FIXED CODE -("required-participation", bpo::value()->default_value(33 * CHAIN_1_PERCENT), ...) -// ... -_required_witness_participation = options["required-participation"].as(); -``` - ---- - -## Legitimate Reasons for Pending Transaction Postponement - -| Reason | Mechanism | Log | -|---|---|---| -| **200ms timeout exceeded** | `CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT` — remaining txs pushed to `_pending_tx` | `Postponed N pending transactions. M were applied.` | -| **Block size limit** (during generation) | `maximum_block_size` — oversized txs skipped, >5 consecutive → break | `Postponed N transactions due to block size limit` | -| **Transaction became invalid** | State changed by new block (e.g., account balance insufficient) | Caught by `fc::exception`, silently discarded | -| **Transaction already in block** | `is_known_transaction()` returns true | Silently skipped (no postpone, no log) | - ---- - -## validator Block Production Timing - -Source: [validator.cpp](../../plugins/validator/validator.cpp) - -### Production Loop Mechanism - -The Validator Plugin uses a timer-based production loop with a look-ahead to detect when it's time to produce a block: - -1. **Timer** fires every **250ms** (aligned to 250ms boundaries, minimum sleep 50ms) -2. On each tick, `maybe_produce_block()` computes `now = NTP_time + 250ms` (look-ahead) -3. `get_slot_at_time(now)` finds which slot corresponds to `now` -4. If the slot belongs to one of our validators and `|scheduled_time - now| <= 500ms`, the block is produced with `scheduled_time` as the timestamp - -The block timestamp is always the **deterministic slot time** (computed from `head_block_time` rounded to `CHAIN_BLOCK_INTERVAL` boundary + `slot_num × 3s`), never the current clock time. - -### Why 250ms tick + 250ms look-ahead? - -With these matching values, the tick at `T_slot - 250ms` aligns `now` exactly to the slot boundary: - -``` -Slot at T=6.000, tick at T=5.750: - now = 5.750 + 0.250 = 6.000 → slot matched → lag = 0ms → PRODUCE -``` - -This gives a **500ms safety margin** against the LAG threshold, compared to 0ms margin with the previous 1000ms tick + 500ms look-ahead. - -### Missed Block Behavior - -When a validator misses their slot, the production loop does NOT wait or retry. The next tick simply finds a later slot: - -``` -Slot T=3 missed (validator A absent): - Tick at T=3.000 → now=3.250 → slot=1 → validator A → not our validator → not_my_turn - (A's slot passes unclaimed) - -Slot T=6 (validator B - our validator): - Tick at T=5.750 → now=6.000 → slot=2 → validator B → PRODUCE with timestamp T=6.000 -``` - -When block at T=6 is pushed, `update_global_dynamic_data()` counts `missed_blocks = get_slot_at_time(6.000) - 1 = 1` and increments `current_aslot` accordingly. - -### Production Conditions (in order) - -| Check | Condition | Result if failed | -|---|---|---| -| Sync status | Chain is not stale (or `enable-stale-production`) | `not_synced` | -| Slot time | `get_slot_at_time(now) > 0` | `not_time_yet` | -| validator ownership | Scheduled validator is in our `_validators` set | `not_my_turn` | -| Signing key | validator has non-zero `signing_key` on chain | `not_my_turn` | -| Private key | We have the private key for the signing key | `no_private_key` | -| Participation | Network participation ≥ required (pre-HF12 only) | `low_participation` | -| Lag | `|scheduled_time - now| <= 500ms` | `lag` | -| Fork collision | No competing block at same height in fork_db | `fork_collision` | -| Minority fork | Last 21 blocks NOT all from our own validators (or `enable-stale-production` or emergency mode) | `minority_fork` | - ---- - -## Configuration Constants - -| Constant | Value | Purpose | -|---|---|---| -| `CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT` | 200ms | Max time to spend re-applying pending txs after block push | -| `CHAIN_BLOCK_GENERATION_POSTPONED_TX_LIMIT` | 5 | Max consecutive oversized txs to skip during block generation | -| `CHAIN_BLOCK_SIZE` | 65536 bytes | Hard limit on block size | -| `maximum_block_size` | Dynamic (validator median) | Soft limit on block size | diff --git a/.qoder/docs/chain-properties-governance.md b/.qoder/docs/chain-properties-governance.md deleted file mode 100644 index f147a95648..0000000000 --- a/.qoder/docs/chain-properties-governance.md +++ /dev/null @@ -1,235 +0,0 @@ -# Chain Properties: How validators Govern Network Parameters - -## Overview - -In VIZ, there is no central authority that sets network fees, block sizes, inflation rates, or other critical parameters. Instead, **every active validator publishes their preferred values**, and the blockchain automatically calculates the **median** — the middle value that represents the consensus of all elected validators. - -Since validators are elected by stake-weighted voting from all SHARES holders, chain properties are ultimately governed by the community: users vote for validators whose parameter choices align with their vision of the public good. - ---- - -## How It Works - -### Step 1: validators Publish Their Preferences - -Each validator publishes their preferred chain properties using the `versioned_chain_properties_update_operation`: - -```json -["versioned_chain_properties_update", { - "owner": "witness1", - "props": [3, { - "account_creation_fee": "1.000 VIZ", - "maximum_block_size": 131072, - "create_account_delegation_ratio": 10, - "create_account_delegation_time": 2592000, - "min_delegation": "1.000 VIZ", - "min_curation_percent": 0, - "max_curation_percent": 10000, - "bandwidth_reserve_percent": 1000, - "bandwidth_reserve_below": "500.000000 SHARES", - "flag_energy_additional_cost": 0, - "vote_accounting_min_rshares": 5000000, - "committee_request_approve_min_percent": 1000, - "inflation_witness_percent": 2000, - "inflation_ratio_committee_vs_reward_fund": 5000, - "inflation_recalc_period": 806400, - "data_operations_cost_additional_bandwidth": 0, - "witness_miss_penalty_percent": 100, - "witness_miss_penalty_duration": 86400, - "create_invite_min_balance": "10.000 VIZ", - "committee_create_request_fee": "100.000 VIZ", - "create_paid_subscription_fee": "100.000 VIZ", - "account_on_sale_fee": "10.000 VIZ", - "subaccount_on_sale_fee": "100.000 VIZ", - "witness_declaration_fee": "10.000 VIZ", - "withdraw_intervals": 28 - }] -} -``` - -The `[3, {...}]` format indicates the version — `3` means `chain_properties_hf9` (the latest). Older versions (`0` = init, `1` = hf4, `2` = hf6) are accepted for backward compatibility. - -### Step 2: Median Calculation - -Every time the validator schedule is updated, the blockchain runs `update_median_witness_props()`. For **each property independently**: - -1. Collect the property value from every active validator -2. Sort the values -3. Pick the **median** (the middle value) - -``` -Example: 5 validators set account_creation_fee to: - 0.5 VIZ, 1.0 VIZ, 1.0 VIZ, 2.0 VIZ, 5.0 VIZ - ↑ - median = 1.0 VIZ -``` - -The algorithm uses `std::nth_element` with position `active.size() / 2`, which selects the value at the middle index after partial sorting. - -**Why median?** The median is resistant to extremes. A single validator cannot push a parameter to an absurdly high or low value — they can only shift the median by one position. To change a parameter significantly, a **majority of active validators** must agree. - -### Step 3: Application - -The calculated `median_props` is stored in the `witness_schedule_object` and used across the entire blockchain to enforce rules. - ---- - -## All Governable Properties - -### Account & Delegation Rules - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `account_creation_fee` | asset (VIZ) | 1.000 VIZ | Minimum fee to create a new account | -| `create_account_delegation_ratio` | uint32 | 10 | Multiplier: delegation = ratio × fee | -| `create_account_delegation_time` | uint32 (sec) | 30 days | How long creation delegation is locked | -| `min_delegation` | asset (VIZ) | 1.000 VIZ | Minimum amount for any delegation | - -**How it's used**: When someone creates a new account, they must pay at least `account_creation_fee` and provide delegation of at least `ratio × fee` in SHARES equivalent. The delegation is locked for `create_account_delegation_time`. This prevents cheap mass account creation (Sybil attacks) while keeping the network accessible. - -### Block Size & Bandwidth - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `maximum_block_size` | uint32 (bytes) | 131072 | Maximum block size — controls network throughput | -| `bandwidth_reserve_percent` | int16 (bp) | 1000 (10%) | Extra bandwidth for small accounts | -| `bandwidth_reserve_below` | asset (SHARES) | 500.000000 | Threshold for bandwidth reserve | -| `data_operations_cost_additional_bandwidth` | uint32 (%) | 0 | Extra bandwidth cost for data-heavy operations | - -**How it's used**: Transaction bandwidth is allocated proportionally to SHARES. Accounts below `bandwidth_reserve_below` get an additional `bandwidth_reserve_percent` reserve so they can still transact. `maximum_block_size` directly controls how many transactions the network can process per block. - -### Inflation & Economics - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `inflation_witness_percent` | int16 (bp) | 2000 (20%) | validator share of block inflation | -| `inflation_ratio_committee_vs_reward_fund` | int16 (bp) | 5000 (50%) | How remaining inflation is split between committee fund and reward fund | -| `inflation_recalc_period` | uint32 (blocks) | 806400 (28 days) | How often inflation parameters are recalculated | - -**How it's used**: Each block creates new tokens (inflation). First, `inflation_witness_percent` goes to the block-producing validator. The remainder is split: `inflation_ratio_committee_vs_reward_fund` percent goes to the committee DAO fund, the rest to the reward fund (used for awards). validators directly control how the economy works. - -### Reward System - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `min_curation_percent` | int16 (bp) | 500 (5%) | Minimum curation reward share | -| `max_curation_percent` | int16 (bp) | 500 (5%) | Maximum curation reward share | -| `vote_accounting_min_rshares` | uint32 | 5000000 | Minimum rshares for an award to have effect | -| `flag_energy_additional_cost` | int16 (bp) | 0 | Extra energy cost for downvoting | - -**How it's used**: When content receives awards, curation rewards are bounded by `[min_curation_percent, max_curation_percent]`. Awards with fewer than `vote_accounting_min_rshares` rshares produce zero reward (dust filter). `flag_energy_additional_cost` can make downvotes more expensive than upvotes. - -### validator Accountability - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `witness_miss_penalty_percent` | int16 (bp) | 100 (1%) | Vote reduction for missing a block | -| `witness_miss_penalty_duration` | uint32 (sec) | 86400 (1 day) | How long the penalty lasts | - -**How it's used**: When a validator misses their scheduled block, their effective votes are reduced by `witness_miss_penalty_percent` for `witness_miss_penalty_duration` seconds. This is self-governing accountability: validators vote on how harshly missed blocks are punished. - -### Fee Structure - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `committee_create_request_fee` | asset (VIZ) | 100.000 VIZ | Fee to create a DAO proposal | -| `create_paid_subscription_fee` | asset (VIZ) | 100.000 VIZ | Fee to create a paid subscription | -| `account_on_sale_fee` | asset (VIZ) | 10.000 VIZ | Fee to list an account for sale | -| `subaccount_on_sale_fee` | asset (VIZ) | 100.000 VIZ | Fee to list subaccounts for sale | -| `witness_declaration_fee` | asset (VIZ) | 10.000 VIZ | One-time fee for new validator registration | -| `create_invite_min_balance` | asset (VIZ) | 10.000 VIZ | Minimum balance to create an invite | - -**How it's used**: All fees go to the **committee fund** (DAO treasury). validators control how expensive various network operations are. Higher fees discourage spam; lower fees improve accessibility. The community decides the balance through validator elections. - -### Vesting Withdrawal - -| Property | Type | Default | What It Controls | -|---|---|---|---| -| `withdraw_intervals` | uint16 | 28 | Number of daily installments for unstaking | - -**How it's used**: When a user unstakes SHARES, the withdrawal happens over `withdraw_intervals` days (one installment per day). validators can make unstaking faster or slower, affecting how liquid the network's governance token is. - ---- - -## The Governance Loop: Users → validators → Parameters - -### Users Shape the Network Through validator Selection - -Users cannot directly set chain properties. Instead, they **vote for validators** whose published properties match their preferences. This creates a representative governance system: - -``` -Users (SHARES holders) - │ - ├── Vote for validators who want LOW fees - │ → More validators with low fee props get elected - │ → Median fees decrease - │ - ├── Vote for validators who want HIGH inflation to reward fund - │ → More reward-focused validators get elected - │ → Inflation shifts toward reward fund - │ - └── Vote for validators who want STRICT miss penalties - → More accountability-focused validators get elected - → Miss penalties increase -``` - -### Why This Is a Public Good Mechanism - -Traditional blockchains set parameters through hard-coded values or foundation decisions. VIZ makes **every parameter a public good decision**: - -1. **Transparency**: every validator's preferred properties are on-chain and publicly visible -2. **Accountability**: if a validator sets harmful parameters, users can unvote them -3. **Gradual change**: the median shifts slowly — no single validator can cause sudden parameter swings -4. **No single point of failure**: even if some validators are compromised, the median protects the network -5. **Aligned incentives**: validators earn block rewards, so they're incentivized to keep the network healthy - -### Example: How a Fee Change Happens - -Suppose the community wants to lower the `committee_create_request_fee` from 100 VIZ to 50 VIZ: - -1. Users discuss in community channels that the fee is too high -2. Some validators update their properties: `committee_create_request_fee: "50.000 VIZ"` -3. Users shift votes to validators who support lower fees -4. As more low-fee validators enter the active set, the **median shifts down** -5. Once more than half of active validators publish 50 VIZ or less, the median becomes 50 VIZ -6. The new fee takes effect automatically — no hardfork, no governance proposal, no vote counting - -### Comparing Governance Models - -| Approach | VIZ Median Properties | Token Voting (e.g., Snapshot) | Foundation Governance | -|---|---|---|---| -| **Who decides** | Elected validators (indirectly: all SHARES holders) | Token holders directly | Core team / foundation | -| **Resistance to extremes** | Strong (median) | Weak (whale dominance) | N/A (centralized) | -| **Speed of change** | Gradual (median shifts slowly) | Fast (single vote) | Fast or slow (depends on team) | -| **Parameter granularity** | Every parameter independently | Usually binary proposals | Any | -| **Sybil resistance** | Built-in (stake-weighted Fair-DPOS) | Depends on implementation | N/A | -| **Transparency** | Full (all validator props on-chain) | Partial (off-chain voting) | Low | - ---- - -## Versioning and Hardfork Compatibility - -Properties were introduced in stages: - -| Version | Hardfork | Properties Added | -|---|---|---| -| `chain_properties_init` | Genesis | account_creation_fee, maximum_block_size, delegation params, curation, bandwidth, flag cost, vote min rshares, committee threshold | -| `chain_properties_hf4` | HF4 | inflation_witness_percent, inflation_ratio_committee_vs_reward_fund, inflation_recalc_period | -| `chain_properties_hf6` | HF6 | data_operations_cost_additional_bandwidth, witness_miss_penalty_percent, witness_miss_penalty_duration | -| `chain_properties_hf9` | HF9 | create_invite_min_balance, committee_create_request_fee, create_paid_subscription_fee, account_on_sale_fee, subaccount_on_sale_fee, witness_declaration_fee, withdraw_intervals | - -validators publish properties using `versioned_chain_properties` — a variant that accepts any version. The evaluator validates the version against the current hardfork (you can't publish HF9 properties before HF9 activates). Properties from older versions use default values for newer fields. - ---- - -## Summary - -Chain properties governance in VIZ is a **continuous, median-based, representative system** where: - -- **validators** are the direct governors who publish their preferred parameters -- **Users** are the ultimate governors who elect validators based on their published properties -- **The median** ensures no single actor can impose extreme values -- **Every parameter** — from fees to inflation to bandwidth — is a public good decision made collectively -- **Changes happen organically**: as community preferences shift, validator elections shift, and the median follows - -This creates a self-regulating network where the "rules of the game" are constantly optimized by the people who have the most at stake. diff --git a/.qoder/docs/cli-wallet.md b/.qoder/docs/cli-wallet.md deleted file mode 100644 index 3d1be9cf00..0000000000 --- a/.qoder/docs/cli-wallet.md +++ /dev/null @@ -1,1126 +0,0 @@ -# VIZ CLI Wallet — Complete Command Reference - -Complete reference for all `cli_wallet` commands with syntax and examples. - ---- - -## Table of Contents - -1. [Wallet Management](#wallet-management) -2. [Key Management](#key-management) -3. [Query Operations](#query-operations) -4. [Account Operations](#account-operations) -5. [Transfer & Vesting](#transfer--vesting) -6. [validator Operations](#validator-operations) -7. [Content Operations](#content-operations) -8. [Escrow Operations](#escrow-operations) -9. [Recovery Operations](#recovery-operations) -10. [Committee Operations](#committee-operations) -11. [Invite System](#invite-system) -12. [Award Operations](#award-operations) -13. [Subscription Operations](#subscription-operations) -14. [Account Market](#account-market) -15. [Proposal Operations](#proposal-operations) -16. [Transaction Builder](#transaction-builder) -17. [NS DNS Helpers](#ns-dns-helpers) -18. [Private Messaging](#private-messaging) - ---- - -## Wallet Management - -### help -Returns a list of all commands supported by the wallet API. - -```bash -help -``` - -### gethelp -Returns detailed help on a single API command. - -```bash -gethelp "transfer" -``` - -### about -Returns info such as client version, git version, version of boost, openssl. - -```bash -about -``` - -### is_new -Checks whether the wallet has just been created and has not yet had a password set. - -```bash -is_new -# Returns: true or false -``` - -### is_locked -Checks whether the wallet is locked (is unable to use its private keys). - -```bash -is_locked -# Returns: true or false -``` - -### lock -Locks the wallet immediately. - -```bash -lock -``` - -### unlock -Unlocks the wallet. - -```bash -unlock "your_password" -``` - -### set_password -Sets a new password on the wallet. The wallet must be either 'new' or 'unlocked'. - -```bash -set_password "your_new_password" -``` - -### load_wallet_file -Loads a specified wallet file. - -```bash -load_wallet_file "wallet.json" -# Or reload current file: -load_wallet_file "" -``` - -### save_wallet_file -Saves the current wallet to the given filename. - -```bash -save_wallet_file "backup_wallet.json" -# Or save to current filename: -save_wallet_file "" -``` - -### quit -Quits the wallet application. - -```bash -quit -``` - -### set_transaction_expiration -Sets the amount of time in the future until a transaction expires. - -```bash -set_transaction_expiration 60 # 60 seconds -``` - ---- - -## Key Management - -### import_key -Imports a WIF Private Key into the wallet to be used to sign transactions. - -```bash -import_key "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" -``` - -### suggest_brain_key -Suggests a safe brain key to use for creating your account. - -```bash -suggest_brain_key -# Returns: { "brain_priv_key": "...", "pub_key": "VIZ...", "wif_priv_key": "5K..." } -``` - -### list_keys -Dumps all private keys owned by the wallet in WIF format. - -```bash -list_keys -# Returns: { "VIZpubkey...": "5Kprivkey..." } -``` - -### get_private_key -Get the WIF private key corresponding to a public key. The private key must already be in the wallet. - -```bash -get_private_key "VIZ6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" -``` - -### get_private_key_from_password -Generates a private key from account name, role, and password. - -```bash -get_private_key_from_password "myaccount" "active" "mypassword123" -# Returns: ["VIZpubkey...", "5Kprivkey..."] -``` - -### normalize_brain_key -Transforms a brain key to reduce the chance of errors when re-entering. - -```bash -normalize_brain_key "my brain key words here" -``` - ---- - -## Query Operations - -### info -Returns info about the current state of the blockchain. - -```bash -info -``` - -### database_info -Returns info about database objects. - -```bash -database_info -``` - -### get_block -Returns the information about a block. - -```bash -get_block 1000000 -``` - -### get_ops_in_block -Returns sequence of operations in a specified block. - -```bash -get_ops_in_block 1000000 false # all operations -get_ops_in_block 1000000 true # only virtual operations -``` - -### get_active_validators -Returns the list of validators producing blocks in the current round (21 blocks). - -```bash -get_active_validators -``` - -### get_account -Returns information about the given account. - -```bash -get_account "alice" -``` - -### list_accounts -Lists all accounts registered in the blockchain. - -```bash -list_accounts "" 100 # First 100 accounts -list_accounts "bob" 100 # 100 accounts starting from "bob" -``` - -### list_my_accounts -Gets the account information for all accounts for which this wallet has a private key. - -```bash -list_my_accounts -``` - -### get_account_history -Returns account operations history in the range [from-limit, from]. - -```bash -get_account_history "alice" -1 100 # Last 100 operations -get_account_history "alice" 500 100 # Operations 400-500 -``` - -### get_transaction -Returns transaction by ID. - -```bash -get_transaction "0123456789abcdef0123456789abcdef01234567" -``` - -### get_master_history -Returns master authority history for an account. - -```bash -get_master_history "alice" -``` - -### get_withdraw_routes -Returns vesting withdraw routes for an account. - -```bash -get_withdraw_routes "alice" "all" # all routes -get_withdraw_routes "alice" "incoming" # incoming routes -get_withdraw_routes "alice" "outgoing" # outgoing routes -``` - ---- - -## Account Operations - -### create_account -Creates a new account with auto-generated keys (controlled by this wallet). - -```bash -create_account "creator" "1.000 VIZ" "10.000000 SHARES" "newaccount" "{}" true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| creator | string | Account creating the new account | -| tokens_fee | asset | Amount of VIZ to pay as fee | -| delegated_vests | asset | Amount of SHARES to delegate | -| new_account_name | string | Name of the new account | -| json_metadata | string | JSON metadata for the account | -| broadcast | bool | Whether to broadcast the transaction | - -### create_account_with_keys -Creates a new account with specified keys (for faucets). - -```bash -create_account_with_keys "creator" "1.000 VIZ" "10.000000 SHARES" "newaccount" "{}" \ - "VIZmaster..." "VIZactive..." "VIZregular..." "VIZmemo..." true -``` - -### update_account -Updates the keys of an existing account. - -```bash -update_account "myaccount" "{\"profile\":\"test\"}" \ - "VIZmaster..." "VIZactive..." "VIZregular..." "VIZmemo..." true -``` - -### update_account_auth_key -Updates a key of an authority for an existing account. - -```bash -update_account_auth_key "myaccount" "active" "VIZ6newkey..." 1 true -# Set weight to 0 to remove key: -update_account_auth_key "myaccount" "active" "VIZ6oldkey..." 0 true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| account_name | string | Account to update | -| type | enum | Authority type: `master`, `active`, or `regular` | -| key | public_key | Public key to add/remove | -| weight | uint16 | Weight (0 = remove) | -| broadcast | bool | Whether to broadcast | - -### update_account_auth_account -Updates an account authority for an existing account. - -```bash -update_account_auth_account "myaccount" "active" "guardian" 1 true -# Remove account from authority: -update_account_auth_account "myaccount" "active" "guardian" 0 true -``` - -### update_account_auth_threshold -Updates the weight threshold of an authority. - -```bash -update_account_auth_threshold "myaccount" "active" 2 true -``` - -### update_account_meta -Updates the account JSON metadata. - -```bash -update_account_meta "myaccount" "{\"profile\":{\"name\":\"Alice\"}}" true -``` - -### update_account_memo_key -Updates the memo key of an account. - -```bash -update_account_memo_key "myaccount" "VIZnewmemokey..." true -``` - -### delegate_vesting_shares -Delegates SHARES from one account to another. - -```bash -delegate_vesting_shares "alice" "bob" "100.000000 SHARES" true -# Remove delegation (delegate 0): -delegate_vesting_shares "alice" "bob" "0.000000 SHARES" true -``` - ---- - -## Transfer & Vesting - -### transfer -Transfer funds from one account to another. - -```bash -transfer "alice" "bob" "10.000 VIZ" "payment memo" true -# Encrypted memo (prefix with #): -transfer "alice" "bob" "10.000 VIZ" "#secret message" true -``` - -### transfer_to_vesting -Transfer VIZ into vesting fund (SHARES). - -```bash -transfer_to_vesting "alice" "bob" "100.000 VIZ" true -# Self-vest: -transfer_to_vesting "alice" "alice" "100.000 VIZ" true -``` - -### withdraw_vesting -Set up a vesting withdraw request (power down). - -```bash -withdraw_vesting "alice" "100.000000 SHARES" true -# Cancel withdrawal (withdraw 0): -withdraw_vesting "alice" "0.000000 SHARES" true -``` - -### set_withdraw_vesting_route -Set up a vesting withdraw route. - -```bash -# Route 50% of withdrawals to "bob" as VIZ: -set_withdraw_vesting_route "alice" "bob" 5000 false true -# Route 25% to "charlie" as SHARES: -set_withdraw_vesting_route "alice" "charlie" 2500 true true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| from | string | Account withdrawing | -| to | string | Destination account | -| percent | uint16 | Percent (100 = 1%, 10000 = 100%) | -| auto_vest | bool | true = receive as SHARES, false = receive as VIZ | -| broadcast | bool | Whether to broadcast | - ---- - -## validator Operations - -### list_validators -Lists all validators registered in the blockchain. - -```bash -list_validators "" 100 # First 100 validators -list_validators "bob" 100 # 100 validators starting from "bob" -``` - -### get_validator -Returns information about the given validator. - -```bash -get_validator "validatorname" -``` - -### update_validator -Update a validator object. - -```bash -update_validator "myvalidator" "https://myvalidator.com" "VIZsigningkey..." true -# Disable block production (empty key): -update_validator "myvalidator" "" "" true -``` - -### update_chain_properties -Vote for the chain properties. - -```bash -update_chain_properties "myvalidator" \ - {"account_creation_fee":"1.000 VIZ","maximum_block_size":65536,"create_account_delegation_ratio":10,...} \ - true -``` - -### versioned_update_chain_properties -Vote for the versioned chain properties. - -```bash -versioned_update_chain_properties "myvalidator" \ - {"account_creation_fee":"1.000 VIZ","maximum_block_size":65536,...} \ - true -``` - -### set_voting_proxy -Set the voting proxy for an account. - -```bash -set_voting_proxy "alice" "trustedvoter" true -# Remove proxy: -set_voting_proxy "alice" "" true -``` - -### vote_for_validator -Vote for a validator to become a block producer. - -```bash -vote_for_validator "alice" "myvalidator" true true # Vote for -vote_for_validator "alice" "myvalidator" false true # Vote against -``` - ---- - -## Content Operations - -> Note: Content operations are deprecated in VIZ. - -### post_content -Post or update a content. - -```bash -post_content "author" "my-permlink" "" "" "Title" "Body content" 5000 "{}" true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| author | string | Account authoring the content | -| permlink | string | Unique permlink for the content | -| parent_author | string | Parent author (empty for top-level) | -| parent_permlink | string | Parent permlink (empty for top-level) | -| title | string | Title of the content | -| body | string | Body of the content | -| curation_percent | int16 | Curation reward percent (0-10000) | -| json | string | JSON metadata | -| broadcast | bool | Whether to broadcast | - -### vote -Vote on a content. - -```bash -vote "voter" "author" "permlink" 100 true # 100% upvote -vote "voter" "author" "permlink" -100 true # 100% downvote -vote "voter" "author" "permlink" 0 true # Remove vote -``` - -### delete_content -Delete a content. - -```bash -delete_content "author" "permlink" true -``` - -### custom -Broadcast a custom operation. - -```bash -custom ["alice"] [] "follow" "{\"follower\":\"alice\",\"following\":\"bob\"}" true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| required_active_auths | array | Accounts requiring active authority | -| required_regular_auths | array | Accounts requiring regular authority | -| id | string | Custom operation type identifier | -| json | string | JSON data for the custom operation | -| broadcast | bool | Whether to broadcast | - ---- - -## Escrow Operations - -### escrow_transfer -Transfer funds using escrow. - -```bash -escrow_transfer "alice" "bob" "agent" 1 "100.000 VIZ" "1.000 VIZ" \ - "2024-01-15T12:00:00" "2024-02-15T12:00:00" "{}" true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| from | string | Funding account | -| to | string | Destination account | -| agent | string | Escrow agent account | -| escrow_id | uint32 | Unique escrow ID | -| token_amount | asset | Amount to escrow | -| fee | asset | Agent fee | -| ratification_deadline | time | Deadline for approval | -| escrow_expiration | time | Expiration time | -| json_metadata | string | JSON metadata | -| broadcast | bool | Whether to broadcast | - -### escrow_approve -Approve a proposed escrow transfer. - -```bash -escrow_approve "alice" "bob" "agent" "bob" 1 true true # Approve -escrow_approve "alice" "bob" "agent" "agent" 1 true true # Agent approves -escrow_approve "alice" "bob" "agent" "bob" 1 false true # Reject (refund) -``` - -### escrow_dispute -Raise a dispute on the escrow transfer. - -```bash -escrow_dispute "alice" "bob" "agent" "alice" 1 true # from disputes -escrow_dispute "alice" "bob" "agent" "bob" 1 true # to disputes -``` - -### escrow_release -Release funds held in escrow. - -```bash -# Agent releases to bob: -escrow_release "alice" "bob" "agent" "agent" "bob" 1 "100.000 VIZ" true -# After expiration, alice can release to herself: -escrow_release "alice" "bob" "agent" "alice" "alice" 1 "100.000 VIZ" true -``` - ---- - -## Recovery Operations - -### request_account_recovery -Create an account recovery request as a recovery account. - -```bash -request_account_recovery "recovery_account" "account_to_recover" \ - {"weight_threshold":1,"account_auths":[],"key_auths":[["VIZnewkey...",1]]} true -``` - -### recover_account -Recover your account using a recovery request. - -```bash -recover_account "myaccount" \ - {"weight_threshold":1,"account_auths":[],"key_auths":[["VIZoldkey...",1]]} \ - {"weight_threshold":1,"account_auths":[],"key_auths":[["VIZnewkey...",1]]} true -``` - -### change_recovery_account -Change your recovery account (30 day delay). - -```bash -change_recovery_account "myaccount" "new_recovery_account" true -``` - ---- - -## Committee Operations - -### committee_worker_create_request -Create a committee worker request. - -```bash -committee_worker_create_request "creator" "https://proposal.com/info" "worker" \ - "100.000 VIZ" "500.000 VIZ" 2592000 true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| creator | string | Account creating the request | -| url | string | URL with request information | -| worker | string | Worker account to receive payment | -| required_amount_min | asset | Minimum amount requested | -| required_amount_max | asset | Maximum amount requested | -| duration | uint32 | Duration in seconds | -| broadcast | bool | Whether to broadcast | - -### committee_worker_cancel_request -Cancel a committee worker request. - -```bash -committee_worker_cancel_request "creator" 123 true -``` - -### committee_vote_request -Vote on a committee worker request. - -```bash -committee_vote_request "voter" 123 10000 true # 100% support -committee_vote_request "voter" 123 -10000 true # 100% against -committee_vote_request "voter" 123 0 true # Remove vote -``` - ---- - -## Invite System - -### create_invite -Create an invite with a balance. - -```bash -create_invite "creator" "10.000 VIZ" "VIZinvitekey..." true -``` - -### claim_invite_balance -Claim an invite balance to an existing account. - -```bash -claim_invite_balance "initiator" "receiver" "5Kinvitesecret..." true -``` - -### invite_registration -Register a new account using an invite. - -```bash -invite_registration "initiator" "newaccount" "5Kinvitesecret..." "VIZnewaccountkey..." true -``` - -### use_invite_balance -Use invite balance to transfer to vesting. - -```bash -use_invite_balance "initiator" "receiver" "5Kinvitesecret..." true -``` - ---- - -## Award Operations - -### award -Award an account (energy-based reward). - -```bash -# Simple award: -award "initiator" "receiver" 1000 0 "Great work!" [] true - -# Award with beneficiaries: -award "initiator" "receiver" 1000 0 "memo" \ - [{"account":"beneficiary1","weight":5000},{"account":"beneficiary2","weight":5000}] true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| initiator | string | Account giving the award | -| receiver | string | Account receiving the award | -| energy | uint16 | Energy to use (0-10000 = 0-100%) | -| custom_sequence | uint64 | Custom sequence number | -| memo | string | Memo for the award | -| beneficiaries | array | List of beneficiaries with weights | -| broadcast | bool | Whether to broadcast | - -### fixed_award -Fixed award an account (fixed amount reward). - -```bash -fixed_award "initiator" "receiver" "10.000 VIZ" 10000 0 "Fixed award" [] true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| initiator | string | Account giving the award | -| receiver | string | Account receiving the award | -| reward_amount | asset | Fixed reward amount | -| max_energy | uint16 | Maximum energy to use | -| custom_sequence | uint64 | Custom sequence number | -| memo | string | Memo for the award | -| beneficiaries | array | List of beneficiaries | -| broadcast | bool | Whether to broadcast | - ---- - -## Subscription Operations - -### set_paid_subscription -Set up a paid subscription. - -```bash -set_paid_subscription "creator" "https://sub.com/info" 5 "10.000 VIZ" 2592000 true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| account | string | Account setting up subscription | -| url | string | URL with subscription info | -| levels | uint16 | Number of subscription levels | -| amount | asset | Cost per level | -| period | uint16 | Subscription period in seconds | -| broadcast | bool | Whether to broadcast | - -### paid_subscribe -Subscribe to a paid subscription. - -```bash -paid_subscribe "subscriber" "creator" 1 "10.000 VIZ" 2592000 true true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| subscriber | string | Subscribing account | -| account | string | Account with the subscription | -| level | uint16 | Subscription level | -| amount | asset | Payment amount | -| period | uint16 | Subscription period | -| auto_renewal | bool | Enable auto renewal | -| broadcast | bool | Whether to broadcast | - ---- - -## Account Market - -### set_account_price -Set an account up for sale. - -```bash -# Put account on sale: -set_account_price "myaccount" "myaccount" "100.000 VIZ" true true -# Remove from sale: -set_account_price "myaccount" "myaccount" "0.000 VIZ" false true -``` - -### set_subaccount_price -Set subaccount creation for sale. - -```bash -# Allow subaccount creation for a fee: -set_subaccount_price "myaccount" "myaccount" "50.000 VIZ" true true -# Disable: -set_subaccount_price "myaccount" "myaccount" "0.000 VIZ" false true -``` - -### buy_account -Buy an account. - -```bash -buy_account "buyer" "accountforsale" "100.000 VIZ" "VIZnewkey..." "0.000 VIZ" true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| buyer | string | Buying account | -| account | string | Account being bought | -| account_offer_price | asset | Purchase price | -| account_authorities_key | public_key | New public key for the account | -| tokens_to_shares | asset | Amount to convert to shares | -| broadcast | bool | Whether to broadcast | - -### target_account_sale -Set an account for sale to a specific target buyer. - -```bash -target_account_sale "myaccount" "myaccount" "targetbuyer" "100.000 VIZ" true true -``` - ---- - -## Proposal Operations - -### approve_proposal -Approve or disapprove a proposal. - -```bash -# Add active approval: -approve_proposal "author" "proposal_title" \ - {"active_approvals_to_add":["myaccount"],"active_approvals_to_remove":[]} true - -# Remove master approval: -approve_proposal "author" "proposal_title" \ - {"master_approvals_to_remove":["myaccount"]} true -``` - -### delete_proposal -Delete a proposal. - -```bash -delete_proposal "author" "proposal_title" "requester" true -``` - -### get_proposed_transactions -Returns proposals for the account. - -```bash -get_proposed_transactions "myaccount" 0 100 -``` - ---- - -## Transaction Builder - -### begin_builder_transaction -Begins building a new transaction. - -```bash -begin_builder_transaction -# Returns: transaction handle (e.g., 0) -``` - -### add_operation_to_builder_transaction -Adds an operation to a builder transaction. - -```bash -add_operation_to_builder_transaction 0 [2,{"from":"alice","to":"bob","amount":"10.000 VIZ","memo":""}] -``` - -### add_operation_copy_to_builder_transaction -Copies an operation from one builder transaction to another. - -```bash -add_operation_copy_to_builder_transaction 0 1 0 -``` - -### replace_operation_in_builder_transaction -Replaces an operation in a builder transaction. - -```bash -replace_operation_in_builder_transaction 0 0 [2,{"from":"alice","to":"charlie","amount":"5.000 VIZ","memo":""}] -``` - -### preview_builder_transaction -Previews a builder transaction. - -```bash -preview_builder_transaction 0 -``` - -### sign_builder_transaction -Signs and optionally broadcasts a builder transaction. - -```bash -sign_builder_transaction 0 true # Sign and broadcast -sign_builder_transaction 0 false # Sign only -``` - -### propose_builder_transaction -Creates a proposal from a builder transaction. - -```bash -propose_builder_transaction 0 "author" "proposal_title" "memo" \ - "2024-02-01T00:00:00" "2024-01-15T00:00:00" true -``` - -### remove_builder_transaction -Removes a builder transaction. - -```bash -remove_builder_transaction 0 -``` - -### get_prototype_operation -Returns an uninitialized object representing a given blockchain operation. - -```bash -get_prototype_operation "transfer_operation" -get_prototype_operation "award_operation" -``` - -### serialize_transaction -Converts a signed transaction in JSON form to its binary representation. - -```bash -serialize_transaction {"ref_block_num":...,"operations":[...]} -``` - -### sign_transaction -Signs a transaction with the necessary keys. - -```bash -sign_transaction {"ref_block_num":...,"operations":[...]} true -``` - ---- - -## NS DNS Helpers - -VIZ DNS Nameserver helpers for storing DNS records in account metadata. - -### ns_validate_ipv4 -Validates an IPv4 address string. - -```bash -ns_validate_ipv4 "188.120.231.153" -# Returns: true - -ns_validate_ipv4 "256.0.0.1" -# Returns: false -``` - -### ns_validate_sha256_hash -Validates a SHA256 hash string (64 hex characters). - -```bash -ns_validate_sha256_hash "4a4613daef37cbc5c4a5156cd7b24ea2e6ee2e5f1e7461262a2df2b63cbf17e2" -# Returns: true -``` - -### ns_validate_ttl -Validates a TTL value (must be positive integer). - -```bash -ns_validate_ttl 28800 -# Returns: true -``` - -### ns_validate_ssl_txt_record -Validates an SSL TXT record format (ssl=). - -```bash -ns_validate_ssl_txt_record "ssl=4a4613daef37cbc5c4a5156cd7b24ea2e6ee2e5f1e7461262a2df2b63cbf17e2" -# Returns: true -``` - -### ns_validate_metadata -Performs complete validation of NS metadata options. - -```bash -ns_validate_metadata {"a_records":["188.120.231.153"],"ssl_hash":"4a4613...","ttl":28800} -# Returns: {"is_valid":true,"errors":[]} -``` - -### ns_create_metadata -Creates NS metadata JSON object from options. - -```bash -ns_create_metadata {"a_records":["188.120.231.153"],"ssl_hash":"4a4613daef37cbc5c4a5156cd7b24ea2e6ee2e5f1e7461262a2df2b63cbf17e2","ttl":28800} -# Returns: {"ns":[["A","188.120.231.153"],["TXT","ssl=4a4613..."]],"ttl":28800} -``` - -### ns_get_summary -Gets complete NS summary from an account's metadata. - -```bash -ns_get_summary "myaccount" -# Returns: {"a_records":["188.120.231.153"],"ssl_hash":"4a4613...","ttl":28800,"has_ns_data":true} -``` - -### ns_extract_a_records -Extracts A records (IPv4 addresses) from an account's metadata. - -```bash -ns_extract_a_records "myaccount" -# Returns: ["188.120.231.153", "192.168.1.100"] -``` - -### ns_extract_ssl_hash -Extracts SSL hash from an account's metadata TXT records. - -```bash -ns_extract_ssl_hash "myaccount" -# Returns: "4a4613daef37cbc5c4a5156cd7b24ea2e6ee2e5f1e7461262a2df2b63cbf17e2" -``` - -### ns_extract_ttl -Extracts TTL value from an account's metadata. - -```bash -ns_extract_ttl "myaccount" -# Returns: 28800 -``` - -### ns_set_records -Sets NS records for an account (merges with existing metadata). - -```bash -# Set A record only: -ns_set_records "myaccount" {"a_records":["188.120.231.153"],"ttl":28800} true - -# Set A records with SSL hash: -ns_set_records "myaccount" \ - {"a_records":["188.120.231.153","192.168.1.100"],"ssl_hash":"4a4613daef37cbc5c4a5156cd7b24ea2e6ee2e5f1e7461262a2df2b63cbf17e2","ttl":28800} \ - true - -# Round-robin DNS with multiple A records: -ns_set_records "myaccount" \ - {"a_records":["188.120.231.153","192.168.1.100","10.0.0.50"],"ttl":3600} \ - true -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| account_name | string | Account to update | -| options | object | NS metadata options | -| options.a_records | array | List of IPv4 addresses | -| options.ssl_hash | string | SHA256 hash for SSL (optional) | -| options.ttl | uint32 | TTL in seconds (default: 28800) | -| broadcast | bool | Whether to broadcast | - -### ns_remove_records -Removes NS records from an account's metadata (preserves other fields). - -```bash -ns_remove_records "myaccount" true -``` - ---- - -## Private Messaging - -### get_encrypted_memo -Returns the encrypted memo if memo starts with '#', otherwise returns memo. - -```bash -get_encrypted_memo "alice" "bob" "#secret message" -``` - -### decrypt_memo -Returns the decrypted memo if possible given wallet's known private keys. - -```bash -decrypt_memo "#encrypteddata..." -``` - -### get_inbox -Gets private messages received by an account. - -```bash -get_inbox "myaccount" "2024-01-15T00:00:00" 100 0 -``` - -### get_outbox -Gets private messages sent by an account. - -```bash -get_outbox "myaccount" "2024-01-15T00:00:00" 100 0 -``` - ---- - -## Data Types Reference - -### Asset Format -- **VIZ**: 3 decimal places, e.g., `"10.000 VIZ"` -- **SHARES**: 6 decimal places, e.g., `"10.000000 SHARES"` - -### Authority Object -```json -{ - "weight_threshold": 1, - "account_auths": [["guardian", 1]], - "key_auths": [["VIZ6...", 1]] -} -``` - -### Beneficiary Route -```json -{"account": "beneficiary", "weight": 5000} -``` -Weight is in basis points (5000 = 50%). - -### NS Metadata Options -```json -{ - "a_records": ["188.120.231.153"], - "ssl_hash": "4a4613daef37cbc5c4a5156cd7b24ea2e6ee2e5f1e7461262a2df2b63cbf17e2", - "ttl": 28800 -} -``` - ---- - -## Authority Requirements Summary - -| Operation | Required Authority | -|-----------|-------------------| -| Account creation | active | -| Account update | master (for keys), active (for other) | -| Account metadata | regular | -| Transfer VIZ | active | -| Transfer SHARES | master | -| Vesting operations | active | -| validator operations | active | -| Content operations | regular | -| Custom operation | active or regular (specified) | -| Recovery operations | varies | -| Committee operations | regular | -| Invite operations | active | -| Award operations | regular | -| Subscription operations | active | -| Account sale | master | -| NS operations | regular (via account_metadata) | diff --git a/.qoder/docs/committee-dao-and-prediction-markets.md b/.qoder/docs/committee-dao-and-prediction-markets.md deleted file mode 100644 index 6d44d12b2c..0000000000 --- a/.qoder/docs/committee-dao-and-prediction-markets.md +++ /dev/null @@ -1,304 +0,0 @@ -# Committee DAO System and Prediction Market Dispute Resolution - -## Part 1: How the VIZ Committee System Works - -### Overview - -The VIZ committee is a **decentralized autonomous governance** (DAO) mechanism built directly into the blockchain protocol. It enables community-funded worker proposals: any account can create a funding request, and all VIZ SHARES holders vote to approve or reject it using **stake-weighted bipolar voting** (positive and negative). - -The committee fund accumulates from request creation fees. Approved requests are paid out proportionally from this fund. - -### Core Operations - -| Operation | Description | -|---|---| -| `committee_worker_create_request` | Create a funding proposal with URL, min/max amounts, duration (5–30 days) | -| `committee_worker_cancel_request` | Creator cancels their own active proposal | -| `committee_vote_request` | Any account votes on an active proposal (−10000 to +10000) | - -### Voting Mechanics - -Every VIZ account can vote on any active committee request. The vote is expressed as `vote_percent` in range **−10000 to +10000** (basis points, i.e. −100% to +100%). - -- **Positive vote** (+1 to +10000): supports the request -- **Negative vote** (−10000 to −1): opposes the request -- **Vote weight** is proportional to the voter's `effective_vesting_shares` (staked VIZ) - -Each voter can change their vote at any time while the request is active. Only one vote per account per request is stored; updating replaces the previous vote. - -### Stake-Weighted Calculation - -When the request's `end_time` is reached, the blockchain evaluates votes: - -``` -max_rshares = SUM( voter.effective_vesting_shares ) // for all voters -actual_rshares = SUM( voter.effective_vesting_shares * vote_percent / 10000 ) // signed sum -``` - -**Three thresholds must be met for approval:** - -1. **Participation threshold**: `max_rshares >= total_vesting_shares * committee_request_approve_min_percent / 10000` - - If insufficient participation → status = 2 (rejected, not enough voters) - -2. **Consensus threshold**: `actual_rshares > 0` - - If net negative → status = 3 (rejected, community says no) - -3. **Minimum payout**: `calculated_payment >= required_amount_min` - - Payout formula: `calculated_payment = required_amount_max * (actual_rshares / max_rshares)` - - If payout too low → status = 3 (rejected, below minimum) - - If all pass → status = 4 (approved) - -### Payout Processing - -Approved requests (status=4) receive payouts from `committee_fund` every 10 minutes (200 blocks). The fund is split equally across all approved requests. Each cycle: - -``` -max_payment_per_request = committee_fund / count_of_approved_requests -current_payment = min(max_payment_per_request, remain_payout_amount) -``` - -When `remain_payout_amount` reaches 0, status becomes 5 (completed). - -### Request Lifecycle - -``` -[Created, status=0] → voting period (5–30 days) - → Insufficient participation → [Rejected, status=2] - → Net negative votes → [Expired, status=3] - → Payout below minimum → [Expired, status=3] - → Creator cancels → [Canceled, status=1] - → Approved → [Approved, status=4] → payouts → [Completed, status=5] -``` - -### Key Properties - -1. **Bipolar**: every voter can express support OR opposition with fine-grained intensity -2. **Stake-weighted**: votes are weighted by locked tokens, making Sybil attacks expensive -3. **Proportional payout**: stronger consensus → higher payout (up to `required_amount_max`) -4. **Self-funding**: creation fees flow into the committee fund -5. **Non-custodial**: no trusted intermediary; the protocol enforces all rules - ---- - -## Part 2: Applying the Committee Model to Prediction Markets - -### Why Committee Voting is Useful for Dispute Resolution - -The committee system solves a fundamental governance problem: **how to make a collective, weighted decision where the outcome is proportional to conviction**. This is exactly what prediction markets need when: - -- An oracle reports a result that participants dispute -- A market creator sets up an unfair or fraudulent market -- The correct outcome is ambiguous and requires human judgment - -The committee model is useful for dispute resolution because: - -1. **It's already battle-tested** on the VIZ blockchain for DAO funding -2. **Negative votes matter** — they aren't just "abstain", they actively oppose, creating a true signal -3. **Weighted by stake** — actors with more skin in the game have proportionally more say -4. **Proportional outcomes** — the result isn't binary "yes/no" but a gradient, which maps well to penalties/rewards -5. **Participation threshold** — prevents tiny minorities from dictating outcomes - -### Prediction Market Dispute Resolution: Implementation Proposal - -#### Market Structure - -A prediction market has: -- **Market creator**: account that defines the event and outcomes -- **Oracle**: account responsible for reporting the correct outcome -- **Outcomes**: N possible results (e.g., "Team A wins", "Team B wins", "Draw") -- **Oracle insurance fund**: a deposit the oracle stakes to guarantee honest reporting -- **Participants**: accounts that buy outcome shares - -#### Dispute Flow - -``` -[Oracle reports outcome] → [Challenge period starts] - → No disputes filed → [Outcome finalized] - → Dispute filed (fee required) → [Committee vote opens] - → Vote concludes → [Resolution applied] -``` - -#### New Operations (Conceptual) - -##### `prediction_market_create_operation` - -``` -creator: account_name_type // who creates the market -url: string // market description -outcomes: vector // e.g. ["Team A", "Team B", "Draw"] -oracle: account_name_type // designated result reporter -oracle_insurance: asset // oracle's deposit (staked as guarantee) -resolution_duration: uint32_t // voting period if disputed (seconds) -penalty_percent: int16_t // −10000 to +10000 (see below) -oracle_ban_type: uint8_t // 0=no ban, 1=temporary, 2=permanent -creator_ban_type: uint8_t // 0=no ban, 1=temporary, 2=permanent -ban_duration: uint32_t // seconds (if temporary) -``` - -**About `penalty_percent`:** - -| Value | Meaning | -|---|---| -| +5000 (+50%) | If dispute succeeds, oracle loses 50% of insurance fund | -| +10000 (+100%) | Oracle loses entire insurance fund | -| 0 | No penalty (dispute just corrects the outcome) | -| −5000 (−50%) | Oracle is actually **rewarded** 50% bonus from dispute fee pool — use this for markets where oracles face genuine ambiguity and shouldn't be punished for honest mistakes | - -A negative `penalty_percent` means: "we understand this market is hard to judge, so we don't penalize the oracle even if the community overrides the result." This is critical for markets involving subjective outcomes (e.g., "Was the product delivered satisfactorily?"). - -##### `prediction_dispute_operation` - -``` -disputer: account_name_type -market_id: uint32_t -proposed_outcome: uint16_t // which outcome the disputer believes is correct (index into outcomes[]) -``` - -Filing a dispute requires a fee (similar to committee request creation fee) to prevent spam. This opens a **committee-style voting period**. - -##### `prediction_dispute_vote_operation` - -``` -voter: account_name_type -market_id: uint32_t -vote_outcome: uint16_t // which outcome this voter believes is correct (1 of N) -vote_percent: int16_t // −10000 to +10000 (conviction strength) -``` - -**Key difference from committee voting**: here the voter selects **one correct outcome** from multiple options AND expresses conviction strength. - -- `vote_outcome` = the outcome index the voter believes is correct -- `vote_percent` = how confident they are (positive = "I'm sure this is right", negative = "I'm sure the oracle was actually correct, reject the dispute") - -#### Resolution Algorithm (Weighted Decision) - -When the dispute voting period ends: - -```cpp -// Step 1: Calculate stake-weighted votes per outcome -for each vote in dispute_votes: - voter_weight = voter.effective_vesting_shares - if vote_percent > 0: - // Voter supports changing the outcome - outcome_rshares[vote_outcome] += voter_weight * vote_percent / 10000 - total_change_rshares += voter_weight * vote_percent / 10000 - else: - // Voter opposes the dispute (supports oracle's original result) - oracle_defense_rshares += voter_weight * abs(vote_percent) / 10000 - max_rshares += voter_weight - -// Step 2: Participation check -approve_min_shares = total_vesting_shares * dispute_approve_min_percent / 10000 -if max_rshares < approve_min_shares: - // Not enough participation — oracle's result stands - finalize_with_oracle_result() - return - -// Step 3: Compare oracle defense vs total change votes -if oracle_defense_rshares >= total_change_rshares: - // Community supports oracle — dispute rejected - finalize_with_oracle_result() - // Dispute fee goes to oracle as compensation - return - -// Step 4: Find winning outcome among change votes -winning_outcome = argmax(outcome_rshares) -winning_rshares = outcome_rshares[winning_outcome] - -// Step 5: Calculate penalty proportional to conviction -// consensus_strength = winning_rshares / max_rshares (0.0 to 1.0) -// This makes the penalty proportional to how strongly the community disagrees -consensus_strength = winning_rshares * CHAIN_100_PERCENT / max_rshares - -if penalty_percent > 0: - actual_penalty = oracle_insurance * penalty_percent / 10000 - // Scale by consensus strength — weak consensus = smaller penalty - actual_penalty = actual_penalty * consensus_strength / CHAIN_100_PERCENT - // Deduct from oracle insurance, distribute to dispute participants - oracle_insurance -= actual_penalty -elif penalty_percent < 0: - // Negative penalty = oracle gets rewarded even when overridden - oracle_bonus = dispute_fee_pool * abs(penalty_percent) / 10000 - oracle_balance += oracle_bonus - -// Step 6: Apply bans based on consensus strength -if oracle_ban_type == 1 and consensus_strength > BAN_THRESHOLD: - // Temporary ban — duration scaled by consensus strength - oracle.banned_until = now + ban_duration * consensus_strength / CHAIN_100_PERCENT -elif oracle_ban_type == 2 and consensus_strength > PERMANENT_BAN_THRESHOLD: - oracle.permanently_banned = true - -// Same logic for creator bans (for fraudulent market setup) -if creator_ban_type == 1 and consensus_strength > BAN_THRESHOLD: - creator.banned_until = now + ban_duration * consensus_strength / CHAIN_100_PERCENT -elif creator_ban_type == 2 and consensus_strength > PERMANENT_BAN_THRESHOLD: - creator.permanently_banned = true - -// Step 7: Override result -finalize_with_outcome(winning_outcome) -``` - -#### Making the Decision Weighted - -The key insight from the committee system is that **all decisions should be proportional, not binary**. Here's how each aspect is weighted: - -##### 1. Outcome Selection is Weighted - -Unlike a simple majority vote, the winning outcome must accumulate more stake-weighted support than ALL other options combined (including oracle defense). This prevents a small but passionate minority from overriding the oracle. - -##### 2. Penalty is Weighted by Consensus Strength - -If `penalty_percent = +10000` (100%) but the community only barely overrides the oracle (51% vs 49%), the actual penalty applied is: - -``` -actual_penalty = insurance * 10000/10000 * 5100/10000 = insurance * 51% -``` - -Strong consensus (90% agreement) → nearly full penalty. Weak consensus → mild penalty. This is fair because a close call suggests the oracle's mistake was understandable. - -##### 3. Ban Duration is Weighted - -A temporary ban isn't fixed-length — it scales with `consensus_strength`: - -``` -effective_ban = ban_duration * consensus_strength / 10000 -``` - -If `ban_duration = 30 days` and consensus is 70%, the ban is 21 days. - -##### 4. Negative Penalty Protects Good-Faith Oracles - -Setting `penalty_percent = -5000` means: "even if the community overrides this oracle, give the oracle a bonus from the dispute fees." This is useful for: -- Subjective markets ("Best movie of 2025") -- Markets where the ground truth is genuinely ambiguous -- Encouraging oracles to participate in difficult markets - -##### 5. Creator Accountability - -The `creator_ban_type` field allows the community to also penalize market creators who set up misleading or fraudulent markets. The same weighted logic applies — a temporary ban scaled by consensus strength, or a permanent ban only with overwhelming agreement. - -### Comparison: Committee DAO vs Prediction Market Disputes - -| Aspect | Committee DAO | Prediction Market Dispute | -|---|---|---| -| **What is voted on** | Whether to fund a worker | Which outcome is correct | -| **Vote type** | Single scalar (−100% to +100%) | Outcome choice + conviction (−100% to +100%) | -| **Positive vote** | "Fund this worker" | "This outcome is correct" | -| **Negative vote** | "Don't fund" | "Oracle was right, reject dispute" | -| **Payout calculation** | `max * (actual_rshares / max_rshares)` | Penalty: `insurance * penalty% * consensus_strength` | -| **Participation threshold** | `approve_min_percent` of total vesting | Same mechanism | -| **Result** | Proportional payout from committee fund | Outcome correction + proportional penalty | -| **Self-funding** | Creation fees → committee fund | Dispute fees → resolution pool | - -### Summary - -The VIZ committee system provides a proven, battle-tested model for **stake-weighted collective decision-making** with bipolar voting. Applying the same principles to prediction market dispute resolution gives us: - -1. **Fair outcome selection**: 1 correct result chosen from N options, weighted by stake -2. **Proportional penalties**: oracle insurance fund penalty scales with community conviction -3. **Flexible punishment**: negative penalty percent to protect good-faith oracles in ambiguous markets -4. **Graduated bans**: temporary or permanent, with duration proportional to consensus strength -5. **Creator accountability**: same weighted mechanism applies to market creators -6. **Sybil resistance**: all votes weighted by staked VIZ, making manipulation expensive - -The fundamental advantage of this approach over binary "guilty/not guilty" arbitration systems is that **every parameter of the resolution is a gradient**, not a switch. This produces fairer outcomes in a decentralized setting where absolute truth is often elusive. diff --git a/.qoder/docs/consensus-emergency-params.md b/.qoder/docs/consensus-emergency-params.md deleted file mode 100644 index 6f0a2bf334..0000000000 --- a/.qoder/docs/consensus-emergency-params.md +++ /dev/null @@ -1,475 +0,0 @@ -# VIZ Blockchain — Consensus Emergency Parameters - -Parameters used to restart stuck consensus, their operational mechanics, and the micro-fork risks when operators forget to revert them after the emergency is resolved. - ---- - -## Overview - -When the VIZ network stalls — no blocks are produced because too few validators are online — operators can activate emergency parameters to unblock production: - -| Parameter | Normal Value | Emergency Value | Location | -|---|---|---|---| -| `enable-stale-production` | `false` | `true` | Validator Plugin | -| `required-participation` | `3300` (33%) | `0` | Validator Plugin | -| `fork_db` `_max_size` | `1024` (dynamic) | `1024` (initial) | chain library | - -Starting with Hardfork 12, the on-chain **emergency consensus activation** is fully automatic and deterministic. When the network stalls for >1 hour (`b.timestamp - lib_block.timestamp >= 3600`), emergency consensus mode activates on every node — no config flags needed. The activation uses only signed block timestamps from the chain state, ensuring identical results during replay. **All real validators are disabled** on activation (signing_key zeroed); operators must re-register via `witness_update_operation`. LIB advances every block (capped at HEAD−1), so the undo limit is never reached. Emergency exits automatically when 75% (16/21) of schedule slots are real validators. The older `enable-stale-production` and `required-participation` overrides are still available for pre-HF12 scenarios or manual recovery. - -These parameters are essential for chain recovery, but if left in emergency mode after the network stabilizes, they become the **root cause of micro-forks**: isolated delegates continue producing blocks on their own divergent chain during any subsequent network partition. - ---- - -## Parameter 1: `enable-stale-production` - -### Definition - -Controls whether the validator node produces blocks when the chain is "stale" — i.e., the node has not received recent blocks and its head block is behind the network. - -| Property | Value | -|---|---| -| Type | `bool` | -| Default | `false` | -| Command line | `--enable-stale-production` | -| Config file | `enable-stale-production = true` | -| Source | [validator.cpp:126](../../plugins/validator/validator.cpp#L126) | - -### How It Works - -When `enable-stale-production = false` (default), the Validator Plugin starts with `_production_enabled = false`. Before each production attempt, the check at [validator.cpp:333-339](../../plugins/validator/validator.cpp#L333) runs: - -```cpp -if (!_production_enabled) { - if (db.get_slot_time(1) >= now) { - _production_enabled = true; // auto-enable once caught up - } else { - return block_production_condition::not_synced; - } -} -``` - -The node will **not** produce blocks until it receives a block whose timestamp places the next slot in the present or future — confirming it is synchronized with the network. Once caught up, production auto-enables permanently. - -When `enable-stale-production = true`, `_production_enabled` is set to `true` immediately at initialization ([validator.cpp:149-151](../../plugins/validator/validator.cpp#L149)), bypassing the sync check entirely. - -### Side Effect: `skip_undo_history_check` - -Setting `enable-stale-production = true` also activates the `skip_undo_history_check` production flag ([validator.cpp:184](../../plugins/validator/validator.cpp#L184)): - -```cpp -if (pimpl->_production_enabled) { - pimpl->_production_skip_flags |= graphene::chain::database::skip_undo_history_check; -} -``` - -This bypasses a critical safety assertion in `_apply_block` ([database.cpp:4114-4123](../../libraries/chain/database.cpp#L4114)): - -```cpp -if (!(skip & skip_undo_history_check)) { - CHAIN_ASSERT( - _dgp.head_block_number - _dgp.last_irreversible_block_num < CHAIN_MAX_UNDO_HISTORY, - undo_database_exception, - "The database does not have enough undo history..."); -} -``` - -`CHAIN_MAX_UNDO_HISTORY` = 10000 blocks ([config.hpp:108](../../libraries/protocol/include/graphene/protocol/config.hpp#L108)). Without this check, a node producing blocks alone can accumulate an unlimited gap between head and last irreversible block (LIB), since LIB only advances when enough validators sign off via block post-validation. - -### Emergency Use Case - -When the network has completely stalled (no validators producing), setting `enable-stale-production = true` on at least one validator node allows it to start producing blocks from its current head, even if it considers the chain stale. This breaks the deadlock and restarts block production. - -### Micro-Fork Risk - -**If the operator forgets to revert this to `false` after the network recovers**, any subsequent network partition causes the node to continue producing blocks in isolation: - -1. The node loses P2P connectivity to other validators -2. No new blocks are received, so `get_slot_time(1) < now` — but since `enable-stale-production = true`, this check is skipped -3. The node keeps producing blocks on its own fork -4. `skip_undo_history_check` means there is **no limit** on how far the head-LIB gap grows -5. When connectivity is restored, the node has a long divergent fork that must be reconciled - ---- - -## Parameter 2: `required-participation` - -### Definition - -The minimum validator participation rate (in basis points) required for block production. The participation rate measures what fraction of the last 128 block slots were actually filled by validators. - -| Property | Value | -|---|---| -| Type | `uint32_t` | -| Default | `33 * CHAIN_1_PERCENT` = `3300` (33%) | -| Range | 0–9900 (0%–99%) | -| Command line | `--required-participation ` | -| Config file | `required-participation = ` | -| Source | [validator.cpp:127](../../plugins/validator/validator.cpp#L127) | - -### Internal Representation - -The value is stored in **basis points** where `CHAIN_100_PERCENT = 10000` and `CHAIN_1_PERCENT = 100` ([config.hpp:57-58](../../libraries/protocol/include/graphene/protocol/config.hpp#L57)): - -- Config value `3300` = 33% participation threshold -- Config value `0` = no participation required (emergency) -- Config value `9900` = 99% participation required (very strict) - -### Participation Rate Calculation - -The rate is computed in [database.cpp:870-873](../../libraries/chain/database.cpp#L870): - -```cpp -uint32_t database::witness_participation_rate() const { - const dynamic_global_property_object &dpo = get_dynamic_global_properties(); - return uint64_t(CHAIN_100_PERCENT) * - dpo.recent_slots_filled.popcount() / 128; -} -``` - -`recent_slots_filled` is a 128-bit bitmask ([global_property_object.hpp:94](../../libraries/chain/include/graphene/chain/global_property_object.hpp#L94)) where each bit represents one of the last 128 block slots. A `1` means the slot was filled by a validator, `0` means it was missed. The rate is the percentage of filled slots. - -The bitmask is updated on each block application at [database.cpp:4060-4063](../../libraries/chain/database.cpp#L4060): - -```cpp -for (uint32_t i = 0; i < missed_blocks + 1; i++) { - dgp.participation_count -= dgp.recent_slots_filled.hi & 0x8000000000000000ULL ? 1 : 0; - dgp.recent_slots_filled = (dgp.recent_slots_filled << 1) + (i == 0 ? 1 : 0); - dgp.participation_count += (i == 0 ? 1 : 0); -} -``` - -For each missed block, a `0` is shifted in. For the current block, a `1` is shifted in. This gives a rolling 128-slot window of participation history. - -### Production Check - -Before producing a block, the Validator Plugin checks ([validator.cpp:436-439](../../plugins/validator/validator.cpp#L436)): - -```cpp -uint32_t prate = db.witness_participation_rate(); -if (prate < _required_witness_participation) { - capture("pct", uint32_t(prate / CHAIN_1_PERCENT)); - return block_production_condition::low_participation; -} -``` - -If the participation rate is below the threshold, block production is suppressed and the error message logs: -``` -Not producing block because node appears to be on a minority fork with only X% validator participation -``` - -### Emergency Use Case - -When the network has stalled, `recent_slots_filled` will be mostly zeros (many missed slots), so the participation rate will be very low — potentially below the 33% default threshold. Even if `enable-stale-production = true`, blocks won't be produced because the participation check fails. Setting `required-participation = 0` bypasses this safety check entirely. - -### Micro-Fork Risk - -**If the operator forgets to revert this to `3300` (33%) after the network recovers**, the participation check becomes ineffective: - -1. A network partition occurs -2. On the minority fork, `recent_slots_filled` decays (more missed slots) -3. With the default 33% threshold, production would stop within ~85 missed slots (when participation drops below 33%) -4. With `required-participation = 0`, the node **never stops producing** regardless of how isolated it is -5. A single validator on a completely isolated node continues producing blocks alone - -This is the most dangerous of the three parameters because it removes the **last automatic safeguard** against solo block production on a minority fork. - ---- - -## Parameter 3: `fork_db` `_max_size = 1024` - -### Definition - -The maximum number of blocks the fork database retains for fork detection and resolution. This is not a user-configurable parameter — it is hardcoded in the chain library. - -| Property | Value | -|---|---| -| Type | `uint32_t` | -| Default | `1024` blocks | -| Source | [fork_database.hpp:117](../../libraries/chain/include/graphene/chain/fork_database.hpp#L117) | -| Configurable | No (hardcoded) | - -### How It Works - -The fork database (`fork_database`) maintains a tree of recently received blocks, allowing the node to detect and switch between competing chain tips. When a new block arrives that extends a different parent than the current head, the node has a fork. - -The `_max_size` determines how many blocks are retained in the fork database. Blocks with `block_num < head_block_num - _max_size` are pruned ([fork_database.cpp:105-137](../../libraries/chain/fork_database.cpp#L105)). - -At 3-second block intervals, 1024 blocks equals approximately **51 minutes** of chain history. - -### Dynamic Resizing - -During normal operation, `_max_size` is dynamically resized to match the gap between head and LIB: - -```cpp -_fork_db.set_max_size(dpo.head_block_number - dpo.last_irreversible_block_num + 1); -``` - -This happens in three places: -- [database.cpp:4244-4245](../../libraries/chain/database.cpp#L4244) — after block post-validation chain check -- [database.cpp:4389-4390](../../libraries/chain/database.cpp#L4389) — after block post-validation application -- [database.cpp:4629-4630](../../libraries/chain/database.cpp#L4629) — after `update_last_irreversible_block` - -In normal operation with healthy participation, LIB advances within a few blocks of head, so `_max_size` is small (e.g., 10-30 blocks). The initial 1024 is only used until the first LIB advancement after startup. - -### Emergency Relevance - -When consensus is stuck, the fork database is mostly irrelevant (it contains few blocks). The 1024-block default is sufficient for initial chain restart scenarios. - -However, the fork_db becomes critical **after** the emergency, when forgotten parameters cause a delegate to produce blocks alone: - -1. **Isolation < 51 minutes**: The fork database can hold both the main chain and the divergent chain blocks. When connectivity is restored, fork resolution works correctly — the longer chain wins. - -2. **Isolation > 51 minutes**: The fork database has already pruned older blocks from the divergent chain. When the node reconnects, it cannot fully compare branches. The node must re-sync from the main chain, which involves popping blocks and replaying — a costly operation. - -3. **Isolation > CHAIN_MAX_UNDO_HISTORY blocks (~8.3 hours)**: Even with `skip_undo_history_check` bypassed during solo production, the state divergence is enormous. After reconnection, the node may need a full replay from a snapshot or block log. - -### Why 1024 Specifically - -The value 1024 matches the `MAX_BLOCK_REORDERING` constant ([fork_database.hpp:57](../../libraries/chain/include/graphene/chain/fork_database.hpp#L57)): - -```cpp -const static int MAX_BLOCK_REORDERING = 1024; -``` - -This limits how far back a block can be inserted out-of-order. Blocks more than 1024 positions behind the head are rejected with `unlinkable_block_exception`. The fork_db size and reordering limit are aligned to ensure consistent behavior. - ---- - -## The Combined Micro-Fork Scenario - -The three parameters interact to create a specific failure pattern: - -``` -Timeline: -──────────────────────────────────────────────────────────────────► - -1. Network stalls 2. Emergency activated 3. Network recovers - (no blocks) (delegate sets: (delegate forgets - enable-stale=true, to revert settings) - required-participation=0) - -4. Normal operation 5. Network partition 6. Micro-fork detected - (with emergency (delegate loses (delegate built blocks - settings still active) P2P connectivity) on its own fork) -``` - -### Step-by-step breakdown - -1. **Network stalls**: Insufficient validators are online to meet the 33% participation threshold. No blocks are produced. - -2. **Emergency activated**: A delegate sets `enable-stale-production = true` and `required-participation = 0` in their config. The node starts producing blocks, restarting the chain. - -3. **Network recovers**: Other validators come online, see the new blocks, and start participating. The network is healthy again — but the emergency settings are still active in the delegate's config. - -4. **Normal operation with emergency settings**: Everything appears fine. The delegate is producing blocks normally. The participation rate is high, so `required-participation = 0` makes no difference. The node is synced, so `enable-stale-production = true` makes no difference. - -5. **Network partition occurs**: The delegate's server loses P2P connectivity (ISP issue, DDoS, routing problem, etc.). - -6. **Micro-fork**: With the emergency settings still active: - - `enable-stale-production = true` → The node does not stop producing when it stops receiving blocks - - `required-participation = 0` → The participation check doesn't stop production even as `recent_slots_filled` decays - - `skip_undo_history_check` → No limit on head-LIB gap growth - - The delegate produces blocks on their own fork - -7. **Reconnection**: When connectivity is restored: - - If the isolation lasted < 51 minutes (< 1024 blocks): Fork resolution occurs via fork_db. The longer main chain wins, and the delegate's fork blocks are discarded. Short disruption. - - If the isolation lasted > 51 minutes but < ~8.3 hours: The fork_db has pruned old blocks. Re-sync required. Moderate disruption. - - If the isolation lasted > ~8.3 hours: Massive state divergence. Full replay may be needed. Severe disruption. - -### Why Normal Settings Prevent This - -With normal settings (`enable-stale-production = false`, `required-participation = 3300`): - -1. **Network partition occurs**: The node stops receiving blocks from other validators. - -2. **Participation decays**: `recent_slots_filled` shifts in zeros for each missed slot. After ~85 missed slots (about 4 minutes), participation drops below 33%. - -3. **Production stops**: The `low_participation` check suppresses block production. The node logs: - ``` - Not producing block because node appears to be on a minority fork with only X% validator participation - ``` - -4. **Even if participation hasn't decayed yet**: When the node's chain becomes stale (no recent blocks), the `_production_enabled` check would block production if `enable-stale-production = false` had been set and the node had somehow lost its synced state. - -The dual safeguard (stale check + participation check) ensures that an isolated delegate stops producing within minutes, keeping any potential fork to at most ~85 blocks (~4 minutes) — well within the fork_db's 1024-block resolution window. - ---- - -## Operational Guidelines - -### Activating Emergency Mode - -When the network has stalled and block production must be restarted: - -1. **Edit config.ini** (or pass command-line flags): - ```ini - # Needed for block production on a stale chain: - enable-stale-production = true - required-participation = 0 - ``` - Note: Emergency consensus activation (HF12+) is automatic and deterministic — no config flag is needed. When `b.timestamp - lib_block.timestamp >= 3600`, emergency mode activates on all nodes. - -2. **Restart the node**: - ```bash - vizd --enable-stale-production --required-participation=0 - ``` - -3. **Monitor production** — confirm blocks are being generated: - ``` - Generated block #N with timestamp T at time C by W - ``` - -4. **Wait for network recovery** — once other validators are back online and the participation rate stabilizes above 33%, **immediately revert the settings**. - -### Reverting Emergency Mode (Critical Step) - -```ini -enable-stale-production = false -required-participation = 3300 -``` - -Then restart the node. **Do not skip this step.** Leaving emergency settings active is the primary cause of micro-forks in practice. - -### Checklist for Emergency Activation - -| Step | Action | Verification | -|------|--------|-------------| -| 1 | Set `enable-stale-production = true` | Node produces blocks on stale chain | -| 2 | Set `required-participation = 0` | Production continues despite low participation | -| 3 | Monitor participation rate via API | `witness_participation_rate` rises as others rejoin | -| 4 | **When participation > 50%** | Revert both settings to normal values | -| 5 | Restart node with normal config | Confirm production continues with normal checks active | -| 6 | Verify `low_participation` safeguard works | Node would stop if isolated (test by briefly disconnecting) | - -### Red Flags: Emergency Settings Still Active - -If you observe any of these log patterns, a validator is likely running with emergency settings still active: - -- **Blocks produced during a network partition**: A validator that should have stopped continues generating blocks -- **Participation rate logs showing < 33% but blocks still produced**: `required-participation = 0` is active -- **Head-LIB gap growing beyond 10000 blocks**: `skip_undo_history_check` is active (via `enable-stale-production = true`) -- **Fork collision warnings with rapid reoccurrence**: Isolated validator creates competing blocks at the same heights - ---- - -## Configuration File Examples - -### Production validator (Normal Operation) - -```ini -# Normal validator configuration — SAFE -enable-stale-production = false -required-participation = 3300 -``` - -Source: [config_witness.ini:76-80](../../share/vizd/config/config_witness.ini#L76) - -### Debug / Testnet (Emergency Mode Acceptable) - -```ini -# Testnet/debug configuration — emergency settings acceptable -enable-stale-production = true -required-participation = 0 -``` - -Source: [config_debug.ini:95-99](../../share/vizd/config/config_debug.ini#L95), [config_testnet.ini:99-101](../../share/vizd/config/config_testnet.ini#L99) - -### Emergency Recovery (Temporary) - -```ini -# EMERGENCY ONLY — revert immediately after network recovers! -enable-stale-production = true -required-participation = 0 -``` - ---- - -## Technical Reference - -### Key Constants - -| Constant | Value | Meaning | Source | -|---|---|---|---| -| `CHAIN_100_PERCENT` | 10000 | 100% in basis points | [config.hpp:57](../../libraries/protocol/include/graphene/protocol/config.hpp#L57) | -| `CHAIN_1_PERCENT` | 100 | 1% in basis points | [config.hpp:58](../../libraries/protocol/include/graphene/protocol/config.hpp#L58) | -| `CHAIN_MAX_UNDO_HISTORY` | 10000 | Max head-LIB gap before undo history exception | [config.hpp:108](../../libraries/protocol/include/graphene/protocol/config.hpp#L108) | -| `CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC` | 3600 | Seconds since LIB before emergency activates | [config.hpp:112](../../libraries/protocol/include/graphene/protocol/config.hpp#L112) | -| `CHAIN_IRREVERSIBLE_THRESHOLD` | 7500 (75%) | validator validation threshold for LIB advancement | [config.hpp:110](../../libraries/protocol/include/graphene/protocol/config.hpp#L110) | -| `fork_db._max_size` | 1024 | Default fork database depth | [fork_database.hpp:117](../../libraries/chain/include/graphene/chain/fork_database.hpp#L117) | -| `MAX_BLOCK_REORDERING` | 1024 | Max out-of-order block insertion distance | [fork_database.hpp:57](../../libraries/chain/include/graphene/chain/fork_database.hpp#L57) | - -### Participation Rate Timeline - -With 3-second block intervals and 128-slot window: - -| Missed Slots | Participation Rate | Status | -|---|---|---| -| 0 | 100% | All validators active | -| 32 (1.6 min) | 75% | Healthy | -| 64 (3.2 min) | 50% | Degraded | -| 85 (4.3 min) | 33.6% | Just above default threshold | -| 86 (4.3 min) | 32.8% | **Below 33% — production stops** (with normal settings) | -| 96 (4.8 min) | 25% | Significant participation loss | -| 128 (6.4 min) | 0% | Complete stall | - -### Fork Database Depth Timeline - -| Solo Production Duration | Approx. Blocks | Fork DB Coverage | -|---|---|---| -| < 51 minutes | < 1024 | Full coverage — clean fork resolution | -| 51 min – 1 hour | 1020–1200 | Partial pruning begins | -| 1–8 hours | 1200–9600 | Significant pruning, re-sync likely needed | -| > 8.3 hours | > 10000 | Exceeds CHAIN_MAX_UNDO_HISTORY, replay may be needed | - -### Code Flow: Block Production Decision - -``` -maybe_produce_block() - │ - ├─ [HF12+] Is emergency_consensus_active? - │ └─ Yes: Three-state safety handles production/sync checks: - │ ├─ IS emergency master? (emergency key in _validators) - │ │ └─ Yes: _production_enabled = true (bypass sync/stale/participation) - │ └─ NOT emergency master (slave): - │ ├─ _production_enabled already? → continue - │ └─ Else: check get_slot_time(1) >= now - │ ├─ Yes: _production_enabled = true - │ └─ No: return not_synced - │ - ├─ Is _production_enabled? - │ ├─ No: Is chain synced (get_slot_time(1) >= now)? - │ │ ├─ Yes: _production_enabled = true (auto-enable) - │ │ └─ No: return not_synced ← BYPASSED when enable-stale-production=true - │ └─ Yes: continue - │ - ├─ Is it my turn? (scheduled validator check) - │ └─ No: return not_my_turn - │ - ├─ Do I have the private key? - │ └─ No: return no_private_key - │ - ├─ Is participation rate >= required-participation? - │ └─ No: return low_participation ← BYPASSED when required-participation=0 - │ - ├─ Am I within 500ms of scheduled time? - │ └─ No: return lag - │ - ├─ Fork collision in fork_db? - │ └─ Yes: return fork_collision - │ - ├─ Minority fork detection? (last 21 blocks all ours) - │ └─ Yes: return minority_fork ← BYPASSED when enable-stale-production=true - │ - └─ Generate and broadcast block - └─ return produced - -update_global_dynamic_data() — Emergency activation: - │ - ├─ HF12 not active or emergency already active? → skip - ├─ LIB block not available (snapshot restore)? → skip - ├─ seconds_since_lib = b.timestamp - lib_block.timestamp - ├─ seconds_since_lib < 3600? → skip - └─ Activate emergency consensus mode -``` - -The two emergency parameters bypass the two most important safeguards (steps 1 and 4), removing all automatic protection against solo production during network partitions. diff --git a/.qoder/docs/data-types.md b/.qoder/docs/data-types.md deleted file mode 100644 index f454271cb5..0000000000 --- a/.qoder/docs/data-types.md +++ /dev/null @@ -1,322 +0,0 @@ -# VIZ Blockchain — Common Data Types - -This document describes all shared data types used across VIZ protocol operations and virtual operations. These types appear as field types throughout operation structures. - ---- - -## Primitive Types - -| C++ type | JSON representation | Description | -|---|---|---| -| `string` | `string` | UTF-8 string | -| `bool` | `boolean` | true / false | -| `uint8_t` | `integer` | Unsigned 8-bit integer | -| `uint16_t` | `integer` | Unsigned 16-bit integer (0–65535) | -| `int16_t` | `integer` | Signed 16-bit integer (-32768–32767) | -| `uint32_t` | `integer` | Unsigned 32-bit integer | -| `int32_t` | `integer` | Signed 32-bit integer | -| `uint64_t` | `string` or `integer` | Unsigned 64-bit integer (use string in JS to avoid overflow) | -| `int64_t` | `string` or `integer` | Signed 64-bit integer | -| `share_type` | `integer` | Alias for `safe` — token satoshi amount | -| `time_point_sec` | `string` | ISO 8601 UTC datetime: `"2024-01-15T12:00:00"` | - ---- - -## `account_name_type` - -Fixed-length string (max 32 bytes). Must comply with domain-name rules: -- Dot-separated labels, each label 3+ characters -- Begins with a letter, ends with letter or digit -- Only lowercase letters, digits, hyphens -- Min length: `CHAIN_MIN_ACCOUNT_NAME_LENGTH` (2) -- Max length: `CHAIN_MAX_ACCOUNT_NAME_LENGTH` (16) - -**JSON:** plain string, e.g. `"alice"`, `"alice.bob"` - ---- - -## `public_key_type` - -A secp256k1 compressed public key encoded in base58check with `VIZ` prefix. - -**JSON:** string, e.g. `"VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9"` - -### Checklist -- [ ] Prefix must be `VIZ` (not `STM`, `GLS`, etc.) -- [ ] 33-byte compressed public key + 4-byte checksum = 37 bytes base58-encoded -- [ ] Validate checksum on deserialization - ---- - -## `asset` - -Represents a token amount with its symbol. - -``` -{ - "amount": integer, // satoshi value (int64) - "symbol": integer // asset_symbol_type (uint64) -} -``` - -**However**, in JSON API the asset is typically serialized as a string: -``` -"10.000 VIZ" -"5.000000 SHARES" -``` - -### Asset Symbols - -| Symbol name | String | Decimals | Description | -|---|---|---|---| -| `TOKEN_SYMBOL` | `VIZ` | 3 | Main liquid token | -| `SHARES_SYMBOL` | `SHARES` | 6 | Vesting shares (staked VIZ) | - -### Checklist -- [ ] Parse/format amount with correct decimal places (VIZ=3, SHARES=6) -- [ ] When constructing `asset` for operations, use the string format: `"10.000 VIZ"` -- [ ] Validate symbol matches expected token type per operation field - ---- - -## `authority` - -Multi-signature authority structure controlling an account's permission level. - -```json -{ - "weight_threshold": 1, - "account_auths": [ - ["alice", 1] - ], - "key_auths": [ - ["VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9", 1] - ] -} -``` - -### Fields - -| Field | Type | Description | -|---|---|---| -| `weight_threshold` | `uint32_t` | Minimum total weight required to satisfy authority | -| `account_auths` | `[[account_name, weight], ...]` | Account-based signers | -| `key_auths` | `[[public_key, weight], ...]` | Key-based signers | - -### Authority Levels - -| Level | Used for | -|---|---| -| `master` | Highest security — changing keys, account recovery | -| `active` | Token operations — transfer, vesting, validator voting | -| `regular` | Social operations — content, awards, committee voting | - -### Checklist -- [ ] Sum of weights for satisfied keys/accounts must be >= `weight_threshold` -- [ ] `account_auths` entries are `[string, uint16]` pairs -- [ ] `key_auths` entries are `[string, uint16]` pairs (public key as VIZ-prefixed base58) -- [ ] Empty authority = `{ "weight_threshold": 0, "account_auths": [], "key_auths": [] }` - ---- - -## `beneficiary_route_type` - -Specifies a beneficiary account and their share weight for content rewards. - -```json -{ - "account": "alice", - "weight": 2500 -} -``` - -| Field | Type | Description | -|---|---|---| -| `account` | `account_name_type` | Beneficiary account name | -| `weight` | `uint16_t` | Weight in basis points (100% = 10000) | - -### Checklist -- [ ] Sum of all beneficiary weights must not exceed 10000 (100%) -- [ ] Beneficiaries array must be sorted by account name (ascending) -- [ ] Each beneficiary account must exist on-chain - ---- - -## `extensions_type` - -Currently unused — always an empty array `[]`. - -```json -"extensions": [] -``` - ---- - -## `versioned_chain_properties` - -A static variant holding one of the chain property versions. The variant is serialized as a 2-element array `[type_index, object]`. - -| Index | Type | -|---|---| -| 0 | `chain_properties_init` | -| 1 | `chain_properties_hf4` | -| 2 | `chain_properties_hf6` | -| 3 | `chain_properties_hf9` | - -Example (hf9 = index 3): -```json -[3, { - "account_creation_fee": "1.000 VIZ", - "maximum_block_size": 65536, - "create_account_delegation_ratio": 10, - "create_account_delegation_time": 2592000, - "min_delegation": "1.000 VIZ", - "min_curation_percent": 0, - "max_curation_percent": 10000, - "bandwidth_reserve_percent": 1000, - "bandwidth_reserve_below": "1.000000 SHARES", - "flag_energy_additional_cost": 1000, - "vote_accounting_min_rshares": 0, - "committee_request_approve_min_percent": 1000, - "inflation_witness_percent": 2000, - "inflation_ratio_committee_vs_reward_fund": 1000, - "inflation_recalc_period": 28800, - "data_operations_cost_additional_bandwidth": 0, - "witness_miss_penalty_percent": 100, - "witness_miss_penalty_duration": 86400, - "create_invite_min_balance": "1.000 VIZ", - "committee_create_request_fee": "1.000 VIZ", - "create_paid_subscription_fee": "1.000 VIZ", - "account_on_sale_fee": "10.000 VIZ", - "subaccount_on_sale_fee": "1.000 VIZ", - "witness_declaration_fee": "1.000 VIZ", - "withdraw_intervals": 28 -}] -``` - ---- - -## `chain_properties_init` Fields - -| Field | Type | Default | Description | -|---|---|---|---| -| `account_creation_fee` | `asset` (VIZ) | `1.000 VIZ` | Fee to create a new account | -| `maximum_block_size` | `uint32_t` | 131072 | Max block size in bytes | -| `create_account_delegation_ratio` | `uint32_t` | 10 | Ratio of delegated SHARES on account creation | -| `create_account_delegation_time` | `uint32_t` | 2592000 | Minimum delegation time (seconds) | -| `min_delegation` | `asset` (VIZ) | `1.000 VIZ` | Minimum delegation amount | -| `min_curation_percent` | `int16_t` | 0 | Min curation reward percent (basis points) | -| `max_curation_percent` | `int16_t` | 10000 | Max curation reward percent (basis points) | -| `bandwidth_reserve_percent` | `int16_t` | 1000 | % of bandwidth reserved for low-stake accounts | -| `bandwidth_reserve_below` | `asset` (SHARES) | `1.000000 SHARES` | Threshold for bandwidth reserve | -| `flag_energy_additional_cost` | `int16_t` | 1000 | Extra energy cost for flag/downvote | -| `vote_accounting_min_rshares` | `uint32_t` | 0 | Min rshares for payout accounting | -| `committee_request_approve_min_percent` | `int16_t` | 1000 | Min approval % for committee requests | - -## Additional fields in `chain_properties_hf4` - -| Field | Type | Description | -|---|---|---| -| `inflation_witness_percent` | `int16_t` | validator reward % from block inflation | -| `inflation_ratio_committee_vs_reward_fund` | `int16_t` | Ratio committee/reward fund | -| `inflation_recalc_period` | `uint32_t` | Blocks per inflation recalc | - -## Additional fields in `chain_properties_hf6` - -| Field | Type | Description | -|---|---|---| -| `data_operations_cost_additional_bandwidth` | `uint32_t` | Extra bandwidth % for data operations | -| `witness_miss_penalty_percent` | `int16_t` | Vote penalty % for missed block | -| `witness_miss_penalty_duration` | `uint32_t` | Duration of miss penalty (seconds) | - -## Additional fields in `chain_properties_hf9` - -| Field | Type | Description | -|---|---|---| -| `create_invite_min_balance` | `asset` (VIZ) | Min balance to create invite | -| `committee_create_request_fee` | `asset` (VIZ) | Fee to create committee request | -| `create_paid_subscription_fee` | `asset` (VIZ) | Fee to create paid subscription | -| `account_on_sale_fee` | `asset` (VIZ) | Fee to list account for sale | -| `subaccount_on_sale_fee` | `asset` (VIZ) | Fee to list subaccounts for sale | -| `witness_declaration_fee` | `asset` (VIZ) | Fee to declare as validator | -| `withdraw_intervals` | `uint16_t` | Number of withdraw intervals | - ---- - -## Operation Type Indices - -The `operation` is a `static_variant`. When serialized, it is a 2-element array: `[type_id, operation_object]`. - -### Regular Operations - -| ID | Operation Name | -|---|---| -| 0 | `vote_operation` *(deprecated)* | -| 1 | `content_operation` *(deprecated)* | -| 2 | `transfer_operation` | -| 3 | `transfer_to_vesting_operation` | -| 4 | `withdraw_vesting_operation` | -| 5 | `account_update_operation` | -| 6 | `witness_update_operation` | -| 7 | `account_witness_vote_operation` | -| 8 | `account_witness_proxy_operation` | -| 9 | `delete_content_operation` *(deprecated)* | -| 10 | `custom_operation` | -| 11 | `set_withdraw_vesting_route_operation` | -| 12 | `request_account_recovery_operation` | -| 13 | `recover_account_operation` | -| 14 | `change_recovery_account_operation` | -| 15 | `escrow_transfer_operation` | -| 16 | `escrow_dispute_operation` | -| 17 | `escrow_release_operation` | -| 18 | `escrow_approve_operation` | -| 19 | `delegate_vesting_shares_operation` | -| 20 | `account_create_operation` | -| 21 | `account_metadata_operation` | -| 22 | `proposal_create_operation` | -| 23 | `proposal_update_operation` | -| 24 | `proposal_delete_operation` | -| 25 | `chain_properties_update_operation` | - -### Virtual Operations - -| ID | Operation Name | -|---|---| -| 26 | `author_reward_operation` | -| 27 | `curation_reward_operation` | -| 28 | `content_reward_operation` | -| 29 | `fill_vesting_withdraw_operation` | -| 30 | `shutdown_witness_operation` | -| 31 | `hardfork_operation` | -| 32 | `content_payout_update_operation` | -| 33 | `content_benefactor_reward_operation` | -| 34 | `return_vesting_delegation_operation` | -| 35 | `committee_worker_create_request_operation` | -| 36 | `committee_worker_cancel_request_operation` | -| 37 | `committee_vote_request_operation` | -| 38 | `committee_cancel_request_operation` *(virtual)* | -| 39 | `committee_approve_request_operation` *(virtual)* | -| 40 | `committee_payout_request_operation` *(virtual)* | -| 41 | `committee_pay_request_operation` *(virtual)* | -| 42 | `witness_reward_operation` *(virtual)* | -| 43 | `create_invite_operation` | -| 44 | `claim_invite_balance_operation` | -| 45 | `invite_registration_operation` | -| 46 | `versioned_chain_properties_update_operation` | -| 47 | `award_operation` | -| 48 | `receive_award_operation` *(virtual)* | -| 49 | `benefactor_award_operation` *(virtual)* | -| 50 | `set_paid_subscription_operation` | -| 51 | `paid_subscribe_operation` | -| 52 | `paid_subscription_action_operation` *(virtual)* | -| 53 | `cancel_paid_subscription_operation` *(virtual)* | -| 54 | `set_account_price_operation` | -| 55 | `set_subaccount_price_operation` | -| 56 | `buy_account_operation` | -| 57 | `account_sale_operation` *(virtual)* | -| 58 | `use_invite_balance_operation` | -| 59 | `expire_escrow_ratification_operation` *(virtual)* | -| 60 | `fixed_award_operation` | -| 61 | `target_account_sale_operation` | -| 62 | `bid_operation` *(virtual)* | -| 63 | `outbid_operation` *(virtual)* | diff --git a/.qoder/docs/dlt-4-node-code-audit-2026-05-08.md b/.qoder/docs/dlt-4-node-code-audit-2026-05-08.md deleted file mode 100644 index 0b174a6b9f..0000000000 --- a/.qoder/docs/dlt-4-node-code-audit-2026-05-08.md +++ /dev/null @@ -1,321 +0,0 @@ -# DLT P2P Node — Code Audit vs. Documentation (2026-05-08) - -Audit of the `fix-validator` branch against the problem/fix log in -`dlt-4-node-sync-scenarios.md`. Checks which fixes are actually in the code, -identifies logic errors not yet documented, and lists undocumented problems -found via code comments. - ---- - -## Status of Documented Fixes - -### Fixes (2026-05-05) — alignment & handshake - -| # | Description | Status | -|---|-------------|--------| -| P1/P2/P6/P7/P8/P13/P15 | `check_fork_alignment` extended with boundary link + range overlap | **DONE** — `dlt_p2p_node.cpp:788-828` | -| P9 | Empty peer (`head_block_num==0`) returns `true` immediately | **DONE** — line 796-798 | -| P3/P14 | `on_dlt_hello` lifecycle: `hello.node_status == DLT_NODE_STATUS_SYNC` added | **DONE** — line ~867 | -| P4 | Block range start clamped to `peer_dlt_earliest` | **DONE** — line 1049 | - -### Fixes (2026-05-06) — stability & crash protection - -| # | Description | Status | -|---|-------------|--------| -| P20/P21 | `DEAD_FORK` enum + `accept_block` returns DEAD_FORK for unlinkable at/below head | **DONE** — `p2p_plugin.cpp:240-283` | -| P20/P21 | `on_dlt_block_range_reply` soft-bans on DEAD_FORK | **DONE** — line 1263-1271 | -| P20/P21 | `on_dlt_block_reply` soft-bans on DEAD_FORK | **DONE** — line 1456-1462 | -| P20/P21 | `transition_to_forward()` in range reply guarded by `any_block_applied` | **DONE** — line 1297 | -| P17 | `dlt_block_log::is_consistent_with()` + corruption auto-reset in `database::open()` | **DONE** | -| P22 | Grace period: near-head blocks → FORK_DB_ONLY not DEAD_FORK for first 60s | **DONE** — `p2p_plugin.cpp:248-277` | -| P24 | `_block_processing_paused` early-return in periodic task | **DONE** — line 598 | -| P24 | Per-block pause check inside range processing loop | **DONE** — line 1195 | -| P27 | Write lock diagnostic logging in `notify_applied_block` | **DONE** | -| P19 | Gap detection: log warning when our head < peer_earliest | **DONE** — line 1012-1014 | -| P25 | `exchange_enabled` re-evaluated when peer block accepted | **DONE** — line 1247-1249 | -| P26 | `check_sync_catchup()` called on single-block accept | **DONE** — line 1495 | -| P31 | `constexpr uint32_t dlt_peer_state::MAX_RECONNECT_BACKOFF_SEC;` defined | **DONE** — line 25 | -| P28/P29/P30 | Build errors in p2p_plugin.cpp / multimap::erase / validator header | **DONE** | - ---- - -## Logic Errors Found in Current Code - -### BUG-A: `range_fallback_mode` transition to FORWARD without `any_block_applied` guard - -**File:** [dlt_p2p_node.cpp:1513-1527](libraries/network/dlt_p2p_node.cpp#L1513-L1527) - -```cpp -// on_dlt_block_reply — fallback path -if (state.range_fallback_mode && _node_status == DLT_NODE_STATUS_SYNC) { - if (reply.next_available > 0) { - // request next block - } else if (reply.is_last) { - state.range_fallback_mode = false; - transition_to_forward(); // ← UNGUARDED - } -} -``` - -**Problem:** When range deserialization fails, the node falls back to requesting blocks -one-at-a-time. If the peer's replies are all `ALREADY_KNOWN` or `FORK_DB_ONLY` (no -block actually applied to the chain) and the peer sends `is_last=true`, the node calls -`transition_to_forward()` without having made any real progress. The node can enter -FORWARD mode while still behind the network. - -**Contrast:** The range reply handler at line 1297 has the correct guard: -```cpp -if (any_block_applied) { - if (reply.is_last) { transition_to_forward(); } -} -``` - -**Fix:** Track `any_block_applied` in `on_dlt_block_reply` and gate the fallback -`transition_to_forward()` on it, OR replace the call with `check_sync_catchup()` which -already verifies all active peers' heads before transitioning. - -**Severity:** HIGH — node can enter FORWARD without catching up, misses blocks, never -re-enters SYNC because it thinks it's caught up. - ---- - -### BUG-B: `check_fork_alignment` returns `true` for empty peer but `exchange_enabled` becomes `true` - -**File:** [dlt_p2p_node.cpp:792-798](libraries/network/dlt_p2p_node.cpp#L792-L798) - -```cpp -if (hello.head_block_num == 0) { - return true; // recognized_head_out stays zero_id -} -``` - -For empty peers `fork_alignment=true` → `exchange_enabled=true`. This is intentional -(they stay connected). However, in `send_to_all_our_fork_peers`, block broadcasts are -sent to all `exchange_enabled` peers. An empty peer (slaveC) will receive forward blocks -it cannot process (its fork_db is empty). This wastes bandwidth and could fill its -fork_db with unlinked blocks if it has no snapshot yet. - -**Current mitigation:** `request_blocks_from_peer` checks `peer_latest == 0` and -doesn't send range requests. But broadcast blocks still arrive. - -**Severity:** LOW — bandwidth waste; not a correctness issue since blocks that can't -link go to `_unlinked_index` and are eventually pruned. - ---- - -### BUG-C: `check_fork_alignment` range-overlap branch can silently fail for in-range peers - -**File:** [dlt_p2p_node.cpp:803-808](libraries/network/dlt_p2p_node.cpp#L803-L808) - -```cpp -if (hello.head_block_num >= our_earliest && hello.head_block_num <= our_latest) { - if (_delegate->is_block_known(hello.head_block_id)) { - recognized_head_out = hello.head_block_id; - } - // ← no else: if is_block_known returns false, we silently skip -} -``` - -If a peer's head is numerically within our DLT range but `is_block_known` returns -`false` (e.g., it's a competing fork tip within our range), `recognized_head_out` stays -zero. The code then falls to the LIB check. If LIB also fails, `fork_alignment=false`. -This is the **correct** behavior for a hostile fork peer. - -However: in DLT mode, blocks at the boundary of the rolling window can be partially -pruned from the chain index. A legitimate peer at e.g. `our_earliest + 2` may have its -block ID not in `is_block_known` if the index was trimmed. This is a narrow window but -can cause a false `fork_alignment=false` for a valid peer. - -**Severity:** LOW — rare; only during the first few blocks of the DLT window. The -boundary link check and LIB fallback provide redundancy. - ---- - -### BUG-D: `transition_to_forward()` called from `check_sync_catchup()` without checking peers with `peer_head_num == 0` - -**File:** [dlt_p2p_node.cpp:2406-2445](libraries/network/dlt_p2p_node.cpp#L2406-L2445) - -```cpp -// check_sync_catchup: -for (const auto& _peer_item : _peer_states) { - ... - if (state.peer_head_num == 0) continue; // skip peers with no head info - if (our_head < state.peer_head_num) { - has_peer_ahead = true; - break; - } -} -// If no peer is ahead → transition to FORWARD -``` - -Peers are skipped if `peer_head_num == 0`. Empty peers (slaveC) and peers whose hello -didn't include a head num will be skipped. If slaveC is the ONLY connected peer and our -head is 1500, `has_peer_ahead = false` → `transition_to_forward()`. The node enters -FORWARD mode "connected" to an empty peer, with no real connectivity to the network. - -This can happen during initial startup or after all block-bearing peers disconnect while -only an empty peer remains. - -**Severity:** MEDIUM — leads to an oscillation: FORWARD on an empty peer → no blocks -arrive → `check_forward_stagnation()` falls back to SYNC → requests from empty peer → -no reply → stagnation again. - ---- - -## Undocumented Problems Fixed in Code (P32+) - -The code comments reference problems not yet in `dlt-4-node-sync-scenarios.md`. - -| Code tag | Description inferred from comments | -|----------|------------------------------------| -| **P36** | Out-of-order single block received in FORWARD mode triggers unnecessary SYNC→FORWARD oscillation. Fixed: gap fill requested instead of mode switch. (`dlt_p2p_node.cpp:1378-1383`) | -| **P37** | `peer_head_num` goes stale after hello — not updated when the peer sends us blocks. Fixed: `peer_head_num` updated from received block number in both range and single-block handlers. (`lines 1183-1185, 1339-1341`) | -| **P39** | `fork_db._head` jumps ahead of database head via `_push_next` cascade when a parent block arrives. The block being pushed can then appear "too old" (block_too_old_exception). Fixed in `_push_block`. (`p2p_plugin.cpp:185-192`) | -| **P40** | Iterator invalidation in `periodic_lifecycle_timeout_check()` when `handle_disconnect` erases entries from `_peer_states` during iteration. Fixed: collect timed-out peers into a vector first. (`dlt_p2p_node.cpp:417-418`) | -| **P42** | `request_blocks_from_peer` uses stale `peer_dlt_latest` as the peer's chain tip. Fixed: `peer_latest = max(peer_dlt_latest, peer_head_num)` to use whichever is fresher. (`dlt_p2p_node.cpp:956-960`) | -| **P49** | Range request starts at `our_head + 1`, skipping our own head block. If two validators signed competing blocks at the same height, we'd never detect the divergence. Fixed: start at `our_head`. (`dlt_p2p_node.cpp:982-986`) | - ---- - -## Documentation Gaps - -The `dlt-4-node-sync-scenarios.md` file ends at P31 but the codebase implements fixes -through at least P49. The following sections are absent from the documentation: - -1. **P32-P35** — not referenced in any code comment; may be internal tracking numbers - or obsolete entries. -2. **P36-P40** — out-of-order oscillation, stale peer head, fork_db cascade, iterator - invalidation — all fixed in code but not in docs. -3. **P42, P49** — stale peer_dlt_latest, sync start offset — fixed in code, not in docs. - -**Recommendation:** Update `dlt-4-node-sync-scenarios.md` with a "Post-P31 Fixes" -section covering P36-P49. - ---- - ---- - -### BUG-E: Spam strike on fork_db-only range response causes false soft-ban of master peer - -**File:** [dlt_p2p_node.cpp:1288](libraries/network/dlt_p2p_node.cpp#L1288) - -```cpp -record_packet_result(peer, any_block_applied); // BUG: penalises legit fork_db batches -``` - -During a large-gap sync (`head=79740486`, `peer_head=79746356`, gap=5854), the node syncs -from LIB using `request_blocks_from_peer`. The peer is on the majority fork; the node's -head diverged. Every range response has `any_block_applied=false` (all blocks go to -`fork_db` as competing-fork candidates). After 10 such responses `spam_strikes` reaches -`SPAM_STRIKE_THRESHOLD` → soft-ban for 3600s. - -The same `on_dlt_block_range_r` handler explicitly continues fetching for `fork_db`-only -batches (line 1304-1310, "competing fork? — continue fetch") while simultaneously -punishing the peer for sending them. Self-contradictory. - -**Observed log:** -``` -Soft-banning peer 185.146.232.170:2001 for 3600s (reason: spam strike threshold exceeded) -``` - -**Severity:** HIGH — the node's only sync peer gets banned mid-sync; gap never fills. - ---- - -### BUG-F: DLT snapshot node cannot accept competing fork starting at snapshot LIB block - -**Files:** [database.cpp:355-376](libraries/chain/database.cpp#L355), [database.cpp:1518-1522](libraries/chain/database.cpp#L1518), [fork_database.cpp](libraries/chain/fork_database.cpp) - -After importing a DLT snapshot at block N (e.g. 79740482): -1. DLT block log starts at N+1; block N is **not stored** anywhere `fetch_block_by_id` - can reach (no main block_log in DLT mode; DLT log starts at N+1; fork_db seeded - with head-only via `start_block`). -2. Fork_db seeded top-down: only the head block (N+4) is inserted via `start_block` - with `prev=null`; blocks N+1…N+3 are absent from fork_db entirely. -3. Master peer is on the majority fork diverging at block N+1. -4. Master sends sync range starting at N: block N arrives → `ALREADY_KNOWN` - (same ID) → silently discarded. Block N+1_master (parent=N.id) arrives: - - `is_known_block(N.id)` → **false** (N not in fork_db) - - `fetch_block_by_id(N.id)` → **null** (not in any log) - - → DEAD_FORK. Rejected forever. -5. Fork switch never triggers; node stays stuck at N+4 while master advances. - -**Observed log:** -``` -Rejecting block 79740483 from a different fork: parent not in fork_db and not on main chain (head=79740486) -Range stored in fork_db only (competing fork?), continuing fetch from #79740682 -``` -*(continues indefinitely, head never advances)* - -**Root causes (two independent failures):** -- DLT seeding builds no `prev` chain; `fetch_branch_from` would crash walking the slave - branch past the null `prev` on the `start_block` root. -- Snapshot block N is unreachable via `fetch_block_by_id`, so `_push_block` cannot seed - fork_db with it when N+1_master arrives. - -**Severity:** CRITICAL — node with a diverged head after snapshot is permanently stuck; -gap never fills regardless of how many peers are connected. - ---- - -## Summary - -``` -BUG-A [HIGH] range_fallback_mode transition_to_forward() not guarded → FIXED (2026-05-08) -BUG-B [LOW] Empty peer gets exchange_enabled=true → broadcast waste (accepted as-is) -BUG-C [LOW] check_fork_alignment range-overlap silent fail at window boundary (accepted as-is) -BUG-D [MEDIUM] check_sync_catchup ignores peer_head_num==0 peers → FIXED (2026-05-08) -BUG-E [HIGH] fork_db-only range response counted as spam → soft-ban → FIXED (2026-05-08) -BUG-F [CRITICAL] DLT snapshot: competing fork starting at LIB permanently rejected → FIXED (2026-05-08) - -DOCS [INFO] P32-P49 implemented in code but not documented in dlt-4-node-sync-scenarios.md -``` - -### Fixes Applied (2026-05-08) - -**BUG-A** — [dlt_p2p_node.cpp:1523-1527](libraries/network/dlt_p2p_node.cpp#L1523-L1527) - -Replaced `transition_to_forward()` with `check_sync_catchup()` in the `range_fallback_mode` path. `check_sync_catchup()` already verifies that our head ≥ all known-head peers before transitioning, so it correctly handles the case where all single-block replies were ALREADY_KNOWN/FORK_DB_ONLY with no real progress. - -**BUG-D** — [dlt_p2p_node.cpp:2420-2462](libraries/network/dlt_p2p_node.cpp#L2420-L2462) - -Added `known_head_peers` counter alongside `active_peer_count`. Empty peers (`peer_head_num==0`) still count toward `active_peer_count` (so isolation detection works) but do not increment `known_head_peers`. The final transition guard now requires `known_head_peers > 0 && !has_peer_ahead`: a node surrounded only by empty peers will never claim "caught up" and stays in SYNC until the stagnation / snapshot-plugin recovery path fires. - -**BUG-E** — [dlt_p2p_node.cpp:1290](libraries/network/dlt_p2p_node.cpp#L1290) - -Changed `record_packet_result(peer, any_block_applied)` to -`record_packet_result(peer, any_block_applied || any_fork_db_only)`. - -A peer that sends valid blocks landing in fork_db (normal during competing-fork or -LIB-based sync) provides useful data and must not be penalised. The existing continuation -path at line 1304 already recognises this ("competing fork — keep fetching"), making the -spam penalty a direct contradiction. With the fix, spam strikes only accumulate when the -peer sends batches that produce neither applied blocks nor fork_db entries (true spam or -dead-fork responses). - -**BUG-F** — [database.cpp](libraries/chain/database.cpp), [fork_database.cpp](libraries/chain/fork_database.cpp), [fork_database.hpp](libraries/chain/include/graphene/chain/fork_database.hpp) - -Three coordinated changes: - -1. **DLT mode startup seeding** ([database.cpp:~355](libraries/chain/database.cpp#L355)): - Replaced single-block `start_block(head)` with bottom-up seeding. Scans the DLT - block log for the oldest block within a 100-block window, uses `start_block` on it, - then pushes each successive block in order up to the head. Result: the slave's recent - chain (e.g. N+1…N+4) is in fork_db with correct `prev` pointers so - `fetch_branch_from` can walk the slave branch during fork switch. - -2. **ALREADY_KNOWN path** ([database.cpp:1518](libraries/chain/database.cpp#L1518)): - When block N arrives as ALREADY_KNOWN and is not yet in fork_db, call - `_fork_db.insert_as_base(new_block)`. The peer sent us the full block data — this - is the only opportunity to seed fork_db with the snapshot LIB block whose data is - absent from every log file. - -3. **`fork_database::insert_as_base` + `_repair_child_prev_links`** - ([fork_database.cpp](libraries/chain/fork_database.cpp)): - `insert_as_base(b)` inserts block `b` into `_index` without requiring its parent to - be present (it is a known-chain anchor). Calls `_push_next` to link any blocks in - `_unlinked_index` waiting for this parent, then calls `_repair_child_prev_links` which - finds child blocks already in `_index` (inserted via `start_block` with null `prev`) - and sets their `prev` pointer. This reconnects the slave chain so - `fetch_branch_from` can traverse it: both the slave branch and the master's - competing branch now walk back to block N as their common ancestor. diff --git a/.qoder/docs/dlt-4-node-sync-scenarios.md b/.qoder/docs/dlt-4-node-sync-scenarios.md deleted file mode 100644 index 46e49a26c1..0000000000 --- a/.qoder/docs/dlt-4-node-sync-scenarios.md +++ /dev/null @@ -1,992 +0,0 @@ -# DLT 4-Node Sync Scenarios — Problems & Analysis - -Analysis of a 4-node DLT network under emergency consensus: 1 master + 3 slaves, based on the current `dlt_p2p_node.cpp` implementation. - ---- - -## Network Setup - -``` -Master: dlt_block_log [1000-2000], snapshot at block 1500, FORWARD mode -slaveA: head at block 800, no snapshot, SYNC mode -slaveB: head at block 999, no snapshot, SYNC mode -slaveC: no blocks at all, SYNC mode -``` - -All nodes have each other as seeds. Emergency consensus is active. - ---- - -## Scenario 1: slaveA (head=800) - -### Step-by-step - -**1. slaveA connects to master → sends `dlt_hello_message`:** -``` -head_block_num=800, head_block_id= -lib_block_num=790, lib_block_id= -dlt_earliest_block=0 (or whatever), dlt_latest_block=800 -node_status=SYNC -``` - -**2. Master receives slaveA's hello → `on_dlt_hello()`:** - -- Stores slaveA's chain state in `dlt_peer_state` -- Calls `build_hello_reply(peer, hello)`: - - `check_fork_alignment(H800, H790)`: - - `is_block_known(H800)` → **FALSE** — master's dlt block log is [1000-2000], block 800 has been pruned. If `is_block_known` also checks the chain index and block 800 was pruned from there too, it returns false. - - `is_block_known(H790)` → **FALSE** — same reason. - - **Result: `fork_alignment = false`** - - `exchange_enabled = fork_alignment = false` - -- **Lifecycle transition check (line 500-504):** - ```cpp - if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC) - ``` - - `exchange_enabled = false` - - `_node_status` = DLT_NODE_STATUS_FORWARD (master) - - `false || false` → **false** → slaveA stays in **HANDSHAKING** on master - -- Records packet as good: `record_packet_result(peer, true)` — no spam strike - -**3. Master sends `dlt_hello_reply_message`:** -``` -exchange_enabled=false, fork_alignment=false -``` - -**4. Master's periodic check → `periodic_lifecycle_timeout_check()`:** -- slaveA has been in HANDSHAKING for >10s → **TIMEOUT** → `handle_disconnect()` -- slaveA disconnected with backoff - -**5. slaveA's perspective (receiving master's hello):** - -When master sent its hello (head=2000, LIB=1990), slaveA received it: -- `check_fork_alignment(H2000, H1990)`: - - `is_block_known(H2000)` → **FALSE** (slaveA only has up to 800) - - `is_block_known(H1990)` → **FALSE** - - **Result: `fork_alignment = false`** - -- Lifecycle transition: `exchange_enabled(false) || _node_status(SYNC)` → **true** (slaveA is SYNC) -- Master transitions to ACTIVE on slaveA's side -- `_node_status == SYNC && exchange_enabled` → `true && false` → **does NOT request blocks** - -**6. slaveA sync stagnation:** -- `sync_stagnation_check()` runs every ~5s via periodic task -- After 30s with no block received → retry 1/3 → re-requests from all active peers (master: `request_blocks_from_peer`) - - `our_head(800) >= peer_latest(2000)`? → **No**, 800 < 2000 - - Requests blocks 801-1000 from master - - Master receives `dlt_get_block_range_message(start=801, end=1000)` - - `on_dlt_get_block_range()`: loops 801..1000, calls `read_block_by_num(n)` — blocks 801-999 return empty (not in dlt block log), blocks 1000 returns the block - - Reply contains only block 1000 (blocks 801-999 are missing from master's dlt log) - - slaveA receives block 1000, `prev=H999` ≠ `slaveA's head H800` → "does NOT link to our head — possible fork" - - Block goes to fork_db as unlinkable - -- After 3 stagnation retries (90s total) → `transition_to_forward()` - -**7. Snapshot plugin kicks in:** -- `stalled-sync-timeout-minutes` (default 2 min) after last block -- Snapshot plugin: detects stall → tries P2P recovery (trigger_resync) → waits 1 min → downloads snapshot from trusted peers (master) -- After snapshot import at block 1500, slaveA resyncs from 1500 → catches up to 2000 - -### Problems Identified - -| # | Problem | Severity | -|---|---------|----------| -| P1 | **`check_fork_alignment` is too narrow**: Only checks if peer's head/LIB ID is known. In DLT mode, old blocks are pruned. A peer at block 800 IS on the same chain but can't prove it because blocks 800-999 are gone. | **CRITICAL** | -| P2 | **No range-overlap check**: The code doesn't check if `peer_head_num` is within `[our_dlt_earliest, our_dlt_latest]` or adjacent to it. This is the obvious fix — if ranges are adjacent and the boundary block links, the peer IS fork-aligned. | **CRITICAL** | -| P3 | **HANDSHAKING timeout loop**: When exchange_enabled=false and master is FORWARD, the peer stays in HANDSHAKING forever → 10s timeout → disconnect → reconnect → repeat. The backoff grows to 3600s. This wastes connections and delays recovery. | **HIGH** | -| P4 | **Wasted block requests**: slaveA requests blocks 801-1000 from master. Master only has 1000. Blocks 801-999 are served as empty. This wastes bandwidth and the partial reply (just block 1000) goes to fork_db. | **MEDIUM** | -| P5 | **Recovery is entirely snapshot-plugin driven**: The P2P layer has no "you need a snapshot" signal. It relies on the snapshot plugin's stalled-sync detection, which takes 2+ minutes. | **HIGH** | - ---- - -## Scenario 2: slaveB (head=999) - -### Step-by-step - -**1. slaveB connects to master → sends `dlt_hello_message`:** -``` -head_block_num=999, head_block_id= -lib_block_num=989, lib_block_id= -dlt_earliest_block=0, dlt_latest_block=999 -node_status=SYNC -``` - -**2. Master receives slaveB's hello → `on_dlt_hello()`:** - -- `check_fork_alignment(H999, H989)`: - - `is_block_known(H999)` → **FALSE** — block 999 is NOT in master's dlt block log [1000-2000]. It was pruned. - - `is_block_known(H989)` → **FALSE** — same. - - **Result: `fork_alignment = false`** - -**This is the key bug.** slaveB's head is at block 999, and master's earliest dlt block is 1000. Block 1000's `previous` field is exactly block 999's ID. This means: - -``` -slaveB.head_block_id == master.dlt_block_log.block_at(1000).previous -``` - -But `check_fork_alignment` **never checks this boundary condition**. It only checks `is_block_known()` on the head/LIB IDs directly, which fails because block 999 has been pruned from the master's dlt block log. - -**3. Everything else proceeds identically to slaveA:** -- `exchange_enabled = false` -- Master (FORWARD): slaveB stays HANDSHAKING → 10s timeout → disconnect -- slaveB (SYNC): master transitions to ACTIVE but no blocks are requested -- slaveB stagnation: requests blocks, gets partial reply, blocks go to fork_db -- Eventually: snapshot plugin downloads snapshot at 1500 - -### The Expected Behavior vs. Reality - -| Expected | Reality | -|----------|---------| -| slaveB's head (999) links to master's earliest block (1000) via `previous` | Code doesn't check `previous` linkage | -| slaveB should get `fork_alignment=true` and sync blocks 1000-2000 | `fork_alignment=false`, gets disconnected | -| slaveB should transition to FORWARD after catching up | slaveB has to go through snapshot download | - -### Problems Identified - -| # | Problem | Severity | -|---|---------|----------| -| P6 | **Missing "boundary link" check**: When `peer_head_num + 1 == our_dlt_earliest`, the code should check if `our_dlt_earliest_block.previous == peer_head_id`. This proves the peer is on the same chain without needing the pruned block. | **CRITICAL** | -| P7 | **`check_fork_alignment` doesn't consider DLT range at all**: The function receives only two block IDs. It has no access to `peer_dlt_earliest`, `peer_dlt_latest`, `our_dlt_earliest`, `our_dlt_latest`, or the block log to verify range adjacency. | **CRITICAL** | -| P8 | **No block number check in alignment**: Even a simple check like "peer_head_num >= our_dlt_earliest - 1" would catch this case. The function only processes IDs, not numbers. | **HIGH** | - ---- - -## Scenario 3: slaveC (no blocks at all) - -### Step-by-step - -**1. slaveC connects to master → sends `dlt_hello_message`:** -``` -head_block_num=0, head_block_id= (all zeros) -lib_block_num=0, lib_block_id= -dlt_earliest_block=0, dlt_latest_block=0 -node_status=SYNC -``` - -**2. Master receives slaveC's hello → `on_dlt_hello()`:** - -- `check_fork_alignment(zero_id, zero_id)`: - - `is_block_known(zero_id)` → **FALSE** (all-zeros block ID is never a real block) - - **Result: `fork_alignment = false`** - -- Lifecycle: `exchange_enabled(false) || FORWARD` → false → slaveC stays HANDSHAKING - -- `record_packet_result(peer, true)` → **GOOD packet** — no spam strike - -**3. HANDSHAKING timeout → disconnect → reconnect loop** (same as slaveA/slaveB) - -**4. Does master soft-ban slaveC?** **NO.** - -- Soft-ban only triggers when `spam_strikes >= SPAM_STRIKE_THRESHOLD (10)` via `record_packet_result(peer, false)` -- The hello handler always records `true` (good packet) for hello messages (line 507) -- Even the disconnect isn't triggered by spam — it's a lifecycle timeout -- **No soft-ban, no "not my guy" flag, no counting of useless peers** - -**5. Does master mark slaveC as "don't send forward blocks"?** - -The `exchange_enabled` flag IS set to `false` for slaveC. When master broadcasts blocks via `send_to_all_our_fork_peers()`, it only sends to peers where `exchange_enabled == true`. So slaveC won't receive forward blocks. - -But this is a side effect, not an intentional "this peer needs a snapshot" design. - -**6. slaveC's recovery path:** - -- Stagnation (90s) → FORWARD with 0 blocks → snapshot plugin detects stall → downloads snapshot from trusted peers → imports at block 1500 → resyncs - -**7. What about peer exchange? Does slaveC get shared?** - -No. `on_dlt_peer_exchange_request()` only shares peers where `exchange_enabled=true` (line 868): -```cpp -if (!s.exchange_enabled) continue; -``` - -### Does Master Tell slaveC "You Need a Snapshot"? - -**No.** There is no message type for this. The protocol has no way to say: -- "Your blocks are too old, get a snapshot" -- "I have a snapshot at block 1500 you can download" -- "I don't recognize your chain at all" - -The snapshot serving happens on a **separate TCP port** (e.g., 8092) via a completely different protocol. The DLT P2P layer has zero awareness of snapshots. - -### Problems Identified - -| # | Problem | Severity | -|---|---------|----------| -| P9 | **No "empty peer" recognition**: An all-zero hello should be recognized as "new node with no state" and treated differently. Currently it's indistinguishable from a far-behind peer. | **MEDIUM** | -| P10 | **No "needs snapshot" signal**: The protocol can't tell a peer "you're too far behind, download a snapshot from me/trusted peers." The snapshot mechanism is entirely separate. | **HIGH** | -| P11 | **Peer exchange works correctly** (doesn't share useless peers) but for the wrong reason — it's by accident via `exchange_enabled=false`, not by design. | **LOW** | -| P12 | **No anti-spam for "useless" peers**: A peer that repeatedly connects with 0 blocks doesn't accumulate spam strikes. It just loops: connect → handshake timeout → disconnect → reconnect. | **LOW** | - ---- - -## Cross-Cutting Problems - -### P13: `check_fork_alignment` Signature is Insufficient - -```cpp -// Current signature (dlt_p2p_node.hpp line 116-117): -bool check_fork_alignment(const block_id_type& head_id, const block_id_type& lib_id, - block_id_type& recognized_head_out, block_id_type& recognized_lib_out) const; -``` - -This function receives **only two block IDs**. It has **no access** to: -- The peer's block numbers (head_num, lib_num) -- The peer's DLT range (dlt_earliest, dlt_latest) -- Our DLT range -- The actual block data to check `previous` linkage - -It delegates to `_delegate->is_block_known()` which is a **binary yes/no** on whether a specific block ID exists in the chain or fork_db. In DLT mode, blocks outside the rolling window are pruned, so `is_block_known()` returns false for them. - -**The function needs to be redesigned to consider DLT range adjacency.** - -### P14: `on_dlt_hello` Lifecycle Logic is Broken for FORWARD Mode - -```cpp -// Lines 500-504: -if (state.lifecycle_state == DLT_PEER_LIFECYCLE_HANDSHAKING) { - if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC) { - state.lifecycle_state = DLT_PEER_LIFECYCLE_ACTIVE; - } -} -``` - -When the master is FORWARD and a peer has `exchange_enabled=false`: -- Peer stays HANDSHAKING → 10s timeout → disconnect -- This is correct behavior for a **hostile fork** peer -- But it also applies to **same-chain peers whose blocks were pruned** — they can never transition to ACTIVE - -**The condition should be:** -```cpp -if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC || - hello.node_status == DLT_NODE_STATUS_SYNC) // Also let SYNC peers through -``` - -Or better, separate the concepts: -- `fork_alignment` = "are we on the same chain?" (structure check including range adjacency) -- `exchange_enabled` = "do we want to do full block exchange with this peer?" (policy) - -### P15: No DLT Range Negotiation in Hello - -The hello message carries DLT range info but it's only used for display and for `request_blocks_from_peer` (to know `peer_dlt_latest`). No logic uses the range to determine fork alignment. - -What should happen in `build_hello_reply`: -``` -1. If peer_head_num >= our_dlt_earliest: - → Check is_block_known(peer_head_id) as now -2. If peer_head_num == our_dlt_earliest - 1: - → Check our_dlt_earliest_block.previous == peer_head_id - → If match: fork_aligned = true (boundary link) -3. If peer_head_num < our_dlt_earliest - 1: - → Not aligned via DLT range — fall back to is_block_known on LIB -4. If peer_head_num == 0 (empty peer): - → Special case: "needs snapshot" or accept as aligned with caveat -``` - -### P16: Master Doesn't Differentiate "Why" exchange_enabled is False - -Currently `exchange_enabled=false` means one of: -- "You're on a different fork" (hostile) -- "Your blocks are too old and I pruned them" (needs snapshot) -- "You have no blocks" (new node) -- "Protocol version mismatch" - -All four cases get the same treatment: HANDSHAKING timeout → disconnect. The protocol should distinguish these and respond differently: -- Hostile fork → disconnect immediately, maybe soft-ban -- Needs snapshot → keep connection, mark as "snapshot candidate", don't send blocks -- New node → redirect to snapshot server -- Version mismatch → disconnect with clear reason - ---- - -## Summary of All Problems - -``` -P1 [CRIT] check_fork_alignment only checks is_block_known — fails for pruned blocks -P2 [CRIT] No range-overlap check in fork alignment -P3 [HIGH] HANDSHAKING timeout loop for same-chain peers -P4 [MED] Wasted block range requests that return mostly empty -P5 [HIGH] Recovery entirely depends on snapshot plugin (2+ min delay) -P6 [CRIT] Missing boundary link check (peer_head+1 == our_earliest) -P7 [CRIT] check_fork_alignment doesn't have access to DLT ranges -P8 [HIGH] No block number check in alignment logic -P9 [MED] No "empty peer" recognition -P10 [HIGH] No "needs snapshot" signal in protocol -P11 [LOW] Peer exchange excludes empty peers by accident, not design -P12 [LOW] No anti-spam for empty peer reconnect loops -P13 [CRIT] check_fork_alignment signature is insufficient (needs range data) -P14 [HIGH] on_dlt_hello lifecycle logic broken for FORWARD mode -P15 [CRIT] No DLT range negotiation in hello handshake -P16 [HIGH] No differentiation between reasons for exchange_enabled=false -``` - ---- - -## Recommended Fix Priority - -### Immediate (would fix all 3 slave scenarios) - -1. **Extend `check_fork_alignment`** to accept the peer's `dlt_hello_message` and our DLT range: - - Add boundary link check: `if (peer.head_num + 1 == our_dlt_earliest)` → verify `our_block_at(earliest).previous == peer.head_id` - - Add range overlap check: `if (peer.head_num >= our_dlt_earliest)` → use existing `is_block_known` logic - - Add empty peer check: `if (peer.head_num == 0)` → treat as "needs snapshot" (aligned but with zero blocks) - -2. **Fix `on_dlt_hello` lifecycle transition** so SYNC peers always reach ACTIVE state: - ```cpp - if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC - || hello.node_status == DLT_NODE_STATUS_SYNC) - ``` - -3. **Add a `peer_needs_snapshot` flag** to `dlt_peer_state` and expose it so the snapshot plugin can act on it without waiting for the 2-minute stall timeout. - -### Short-term - -4. **Add a `dlt_need_snapshot` message type** so master can actively tell far-behind peers to get a snapshot instead of disconnecting them. - -5. **Skip empty range requests**: In `request_blocks_from_peer`, if `our_head + 1 < peer_dlt_earliest`, don't send a request — we know the peer doesn't have those blocks. - -### Long-term - -6. **Integrate P2P with snapshot serving**: Allow the master to advertise its snapshot endpoint in the hello message so peers know where to download without separate config. - -7. **Add a dedicated lifecycle state** for "snapshot-needed" peers so they don't cycle through HANDSHAKING→DISCONNECTED repeatedly. - ---- - -## Implemented Fixes (2026-05-05) - -### Changes Made - -**Files modified:** -- `libraries/network/include/graphene/network/dlt_p2p_node.hpp` — `check_fork_alignment` signature -- `libraries/network/dlt_p2p_node.cpp` — implementation + lifecycle transitions - -**1. `check_fork_alignment` extended** (fixes P1, P2, P6, P7, P8, P9, P13, P15): - -``` -OLD signature: - bool check_fork_alignment(const block_id_type& head_id, const block_id_type& lib_id, - block_id_type& recognized_head_out, block_id_type& recognized_lib_out) const; - -NEW signature — accepts the full hello for DLT-range-aware alignment: - bool check_fork_alignment(const dlt_hello_message& hello, - block_id_type& recognized_head_out, block_id_type& recognized_lib_out) const; -``` - -New alignment checks (see `dlt_p2p_node.cpp` lines 448-489): - -| Check | Condition | Result | -|-------|-----------|--------| -| Empty peer | `head_block_num == 0` | Returns `true` — no fork to be on | -| Range overlap | `head_num >= our_earliest && head_num <= our_latest` | Uses `is_block_known(head_id)` | -| Boundary link | `head_num + 1 == our_earliest` | Reads `our_earliest_block`, checks `previous == head_id` | -| LIB fallback | Always | `is_block_known(lib_id)` as before | - -**2. `on_dlt_hello` lifecycle transition fixed** (fixes P3, P14): - -``` -OLD: if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC) -NEW: if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC - || hello.node_status == DLT_NODE_STATUS_SYNC) -``` - -Added diagnostic log for non-aligned SYNC peers so operators can distinguish "needs snapshot" from "hostile fork". - -### New Expected Behavior — Per-Slave - -#### slaveA (head=800, master dlt=[1000-2000]) - -| Step | Before Fix | After Fix | -|------|-----------|-----------| -| Hello to master | fork_alignment=false (block 800 pruned) | fork_alignment=false (800+1 ≠ 1000) | -| Lifecycle on master | HANDSHAKING → 10s timeout → disconnect | **Transitions to ACTIVE** (hello.node_status == SYNC) | -| Blocks exchanged? | No — gets disconnected | No — exchange_enabled=false, but stays connected | -| Recovery path | Snapshot plugin stall detection → download | Same — needs snapshot from trusted peers | -| **Improvement** | Disconnect/reconnect loop with backoff | **Stays connected**, no backoff penalty | - -#### slaveB (head=999, master dlt=[1000-2000]) - -| Step | Before Fix | After Fix | -|------|-----------|-----------| -| Hello to master | fork_alignment=false (block 999 pruned) | **fork_alignment=true** — boundary link: block 1000.previous == H999 | -| Lifecycle on master | HANDSHAKING → timeout | **Transitions to ACTIVE** (exchange_enabled=true) | -| Blocks exchanged? | No | **Yes!** Requests blocks 1000-1199, master sends full range | -| Sync completion | N/A | Catches up 999→2000, transitions to FORWARD | -| **Improvement** | Had to snapshot-download from 1500 | **Direct P2P sync 999→2000** — zero snapshot overhead | - -#### slaveC (no blocks, empty node) - -| Step | Before Fix | After Fix | -|------|-----------|-----------| -| Hello to master | fork_alignment=false (zero_id not known) | **fork_alignment=true** — empty peer check | -| Lifecycle on master | HANDSHAKING → timeout | **Transitions to ACTIVE** (exchange_enabled=true) | -| Blocks exchanged? | No | No — peer_dlt_latest==0, no request sent | -| Recovery path | Snapshot plugin stall detection → download | Same — needs snapshot from trusted peers | -| **Improvement** | Disconnect/reconnect loop | **Stays connected**, doesn't waste bandwidth | - -### Problems Resolved - -``` -P1 [OK] check_fork_alignment now DLT-range-aware -P2 [OK] Range-overlap check added -P3 [OK] HANDSHAKING timeout loop fixed (SYNC peers always → ACTIVE) -P6 [OK] Boundary link check added -P7 [OK] check_fork_alignment receives full hello with range data -P8 [OK] Block number comparison via boundary link + range overlap -P9 [OK] Empty peer (head=0) recognized and accepted -P13 [OK] check_fork_alignment signature updated -P14 [OK] on_dlt_hello lifecycle logic fixed -P15 [OK] DLT range negotiation via boundary link check - -P4 [OK] Wasted range requests fixed — start clamped to peer_dlt_earliest -P5 [ ] Recovery still snapshot-plugin-driven (but no reconnect loop delays it) -P10 [ ] No "needs snapshot" signal yet -P11 [NA] Peer exchange works correctly — no fix needed -P12 [NA] No anti-spam needed — lifecycle fix eliminated reconnect loop -P16 [ ] No differentiation between reasons (improved via diagnostic log) - -P17 [OK] DLT block log corruption → auto-detected and reset on startup -P20 [OK] Dead fork blocks → DEAD_FORK result, soft-ban peer, no crash -P21 [OK] Dead fork crash loop → dead-fork blocks rejected, fork_db protected -P24 [OK] Snapshot lock isolation → periodic tasks skip DB locks, stall check skips -P27 [OK] Write lock diagnostic → overall + per-plugin timing, chainbase lock-holder ID -``` - ---- - -## New Problems Discovered (Post-Implementation) - -After deploying the fixes, the following new issues were observed in production (emergency consensus, DLT mode, 4-node network). - ---- - -### P17: DLT Block Log Corruption on Crash → Infinite Restart Loop - -**Observed on:** Master node (185.146.232.170) - -**Symptom:** Node crashes, then on restart the DLT block log opens with only **one block**: -``` -DLT block log: opened with blocks 79637600-79637600 -``` -Immediately after: -``` -terminate called after throwing an instance of 'boost::interprocess::lock_exception' - what(): boost::interprocess::lock_exception -``` -Node auto-restarts and loops with high CPU overhead. - -**Root cause hypothesis:** On crash, the DLT block log index/data files become inconsistent. The index truncates to a single entry while the database head is far ahead. The `boost::interprocess::lock_exception` is thrown when the database tries to acquire the shared memory lock that the previous (crashed) process held. - -**Severity:** **CRITICAL** — node cannot recover without manual intervention. - -**Related files:** -- [dlt_block_log.cpp](file:///d:/Work/viz-cpp-node/libraries/chain/dlt_block_log.cpp) -- [database.cpp](file:///d:/Work/viz-cpp-node/libraries/chain/database.cpp) (open path) - ---- - -### P18: Master Block Production Halt (No Clear Reason) - -**Observed on:** Master node (185.146.232.170, ~16-20 scheduled slots) - -**Symptom:** Node was producing blocks normally, then abruptly stopped. In logs: -``` -maybe_produce_block returned 3 (slot=0 — missed/not-my-slot) -``` -This repeated for hundreds of iterations (minutes of wall time) with no blocks produced. Eventually recovered on its own. During the halt, `slot=0` was returned every cycle, meaning `get_slot_at_time()` returned slot 0 — either the time was before the first slot, or the slot calculation was wrong. - -**Also observed:** After some restarts the pattern changed: -``` -maybe_produce_block returned 1 (not my slot, but slot had a value) -``` -Here `scheduled_witness=social` (a slave validator), so the master correctly doesn't produce, but the slave's block never arrived. - -**Severity:** **CRITICAL** — network stalls for minutes with no block production. - -**Related files:** -- [validator.cpp](file:///d:/Work/viz-cpp-node/plugins/validator/validator.cpp) — `maybe_produce_block` - ---- - -### P19: Slave Stuck in Sync — Never Catches Up - -**Observed on:** Slave node (80.87.202.57) - -**Symptom:** Slave connects to master, synopsis exchange happens, but slave never receives blocks. Stays in SYNC mode indefinitely. No `Got X transactions` lines appear. The synopsis response from master returns an empty reply because the synopsis anchor from the slave references blocks outside the master's DLT range. - -**Severity:** **HIGH** — slave never syncs, relies on snapshot plugin for recovery. - ---- - -### P20: Dead Fork Blocks Trigger Sync Status Loss → Silent Crash - -**Observed on:** Master node (185.146.232.170) - -**Symptom:** Master is producing blocks normally (head=79641907). Then peers send sync blocks 79641905-79641908 which are on a **different fork**: -``` -Block 79641905 is from a dead fork (parent not in fork_db, head=79641907) -Block 79641906 is from a dead fork (parent not in fork_db, head=79641907) -Block 79641907 is from a dead fork (parent not in fork_db, head=79641907) -``` -Block 79641908 (gap=0) is treated as "near-caught-up" and triggers: -``` -Sync mode ended: received normal block #79641908 (head: 79641907) -``` -This **resets sync status** on the master. Shortly after, the node silently crashes: -``` -json_rpc plugin: plugin_initialize() begin ← fresh restart -``` - -**Root cause:** A block from a different fork with `gap=0` triggers `accept_block` logic that treats it as a "normal block", ending sync mode. This corrupts the node's internal state. The crash is likely an OOM (from fork_db growing with unlinkable blocks) or an assertion failure. - -**Severity:** **CRITICAL** — malicious peers can crash the master by sending dead-fork blocks. - -**Related files:** -- [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) — `handle_block` -- [database.cpp](file:///d:/Work/viz-cpp-node/libraries/chain/database.cpp) — `_push_block` - ---- - -### P21: Dead Fork → Crash → Restart → Dead Fork Loop - -**Observed on:** Master node (185.146.232.170) - -**Symptom:** After recovering from P20's crash, the master restarts, produces a few blocks, then another dead-fork block arrives: -``` -Block from a different fork whose parent is not in fork_db (block 79641912, head=79641913) -``` -Note: **head (79641913) > block num (79641912)** — this means the node's head advanced past the block it's rejecting. The node crashes again. This creates an infinite restart loop. - -**Key observation:** `head > block_num` when rejecting — the node's own head has moved beyond the rejected block, which means the fork_db might be in an inconsistent state where unlinkable blocks accumulate but the chain head advances independently. - -**Severity:** **CRITICAL** — infinite crash loop, node cannot stay up. - ---- - -### P22: fork_db Rejection Cascade on Restart - -**Observed on:** Master node after restart - -**Symptom:** On restart with DLT range [79640201..79641912], peers send sync blocks 79641905-79641908. These are **older than the node's head** (79641912) but the fork_db doesn't contain their parent chain: -``` -Chain pushing sync block #79641905 (head: 79641912, gap: -8) -Rejecting block 79641905 from a different fork: parent not in fork_db (head=79641912) -``` -All 4 blocks rejected. Node crashes shortly after. - -**Root cause:** After restart, the fork_db is seeded from the DLT block log at block 79641912. Blocks 79641905-79641908 are in the DLT range [79640201..79641912] but their parent chain (below 79641905) is in the fork_db unlinked section or pruned. The gap of -8 means the node is being asked to process blocks it should already have. - -**Severity:** **HIGH** — restart recovery fails if peers send older blocks. - ---- - -### P23: fetch_branch_from Assertion in fork_db During Synopsis - -**Observed on:** Slave node (80.87.202.57) - -**Symptom:** When constructing a synopsis for peer 185.45.192.155:2001: -``` -Unable to construct a blockchain synopsis for reference hash 04bf3d5a... -assert_exception: Assert Exception -second_branch_itr != _index.get().end(): - {"first":"04bf3de9...","second":"04bf3d5a..."} - fork_database.cpp:201 fetch_branch_from -``` -The peer's reference block is on a fork that our fork_db doesn't know about. The `fetch_branch_from` function assumes both branches exist in the index, but the second branch doesn't. - -**Also causes:** Connection breakage with that peer, synopsis failure, lost sync time. - -**Severity:** **HIGH** — can crash the node or break sync with legitimate peers. - -**Related files:** -- [fork_database.cpp](file:///d:/Work/viz-cpp-node/libraries/chain/fork_database.cpp) — `fetch_branch_from` -- [database.cpp](file:///d:/Work/viz-cpp-node/libraries/chain/database.cpp) — `get_block_ids_on_fork` - ---- - -### P24: Snapshot Write Lock Freezes Entire Node - -**Observed on:** Slave node (80.87.202.57) - -**Symptom:** When a periodic snapshot fires at block 79642200: -``` -Periodic snapshot at block 79642200: /var/lib/vizd/snapshots/snapshot-block-79642200.vizjson -``` -Immediately, **all P2P connections** start failing: -``` -Read lock timeout -Read lock timeout -... (repeated hundreds of times) -Peer connection terminating (95.217.177.173:2001), now 5 active peers -Peer connection closing (95.217.177.173:2001): Disconnecting due to inactivity -... -Peer connection closing (185.146.232.170:2001): Disconnecting due to inactivity, now 0 active peers -``` - -**Timeline:** -| Time | Event | -|------|-------| -| T+0s | Snapshot starts, WRITE lock acquired | -| T+13s | `auto-clearing stuck peer_needs_sync_items_from_us` for master peer (30s timeout) | -| T+20s | `Skipping head_block_num read: Unable to acquire READ lock` | -| T+130s | All peers disconnected due to inactivity | -| T+150s | `Stalled sync detected while snapshot in progress — cancelling snapshot to release locks` | -| T+160s | `Snapshot still in progress after 10s wait, proceeding with recovery anyway` | - -**The stall detection itself fails** because it can't acquire the READ lock: -``` -trigger_resync: could not read head block (lock contention?): Unable to acquire READ lock -``` - -**Root cause:** The snapshot serialization holds a WRITE lock on the database for the entire duration. P2P needs READ locks for every operation (reading head, LIB, block IDs). The snapshot can take 2+ minutes for large state, during which the node is completely isolated from the network. - -**Severity:** **CRITICAL** — periodic snapshots cause complete network disconnection. Self-reinforcing: no peers → no blocks → stall detection → can't recover because of lock. - -**Related files:** -- [snapshot/plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/snapshot/plugin.cpp) — `on_applied_block` → snapshot creation - ---- - -### P25: Slave-Produced Block Ignored by Master → Fork Switch - -**Observed on:** Slave (80.87.202.57) + Master (185.146.232.170) - -**Symptom:** Slave produces block #79645211 (validator="social", slot time 10:20:24), sends it via P2P. Master never receives/processes it and produces its own block #79645211 (validator="committee", slot time 10:20:27) — 3 seconds later for the same block number: - -**Slave side:** -``` -Block num collision at block 79645211: 2 blocks with SAME parent (possible double-production) -Fork switch: new_head=#79645211, db_head=#79645211 -FORK-SWITCH-POP: popping head #79645211 (target=04bf4a1a...) -``` - -**Master side:** -``` -scheduled_witness=social -maybe_produce_block returned 2 (not our slot to produce) -scheduled_witness=committee -... produces block #79645211 by committee -``` - -**Root cause:** The slave sent its block but the master either: -1. Had the slave peer in a state where blocks from it weren't accepted -2. The block was delayed/lost in P2P transmission -3. The master's `exchange_enabled` flag for the slave was false - -The master then produced a competing block because it didn't see the slave's block within the slot window. - -**Severity:** **HIGH** — causes unnecessary fork switches, wastes blocks, confuses validators. - ---- - -### P26: Sync State Confusion on Slave — Blocks Received But Status Stays SYNC - -**Observed on:** Slave node (80.87.202.57) - -**Symptom:** Slave receives blocks normally via sync (`Chain pushing sync block #...`), processes them, even generates its own blocks, but the node status **never transitions from SYNC to FORWARD**. It stays in SYNC mode while actually having caught up. - -**Severity:** **MEDIUM** — node functions but reports wrong status, may affect peer selection logic. - ---- - -### P27: Write Lock Held 25+ Seconds During Block Application - -**Observed on:** Master node (185.146.232.170) - -**Symptom:** During `_apply_block`, the write lock is held for **25+ seconds**: - -``` -Read lock timeout [lock=READ waiter_tid=... wait_ms=500 readers=0 writer_tid=... writer_held_ms=503] -Read lock timeout [lock=READ waiter_tid=... wait_ms=1000 readers=0 writer_tid=... writer_held_ms=1003] -... -Read lock timeout [lock=READ waiter_tid=... wait_ms=2000 readers=0 writer_tid=... writer_held_ms=25670] -``` - -**All RPC calls fail** during this period: -``` -elapsed: 2.01s, error: 'Unable to acquire READ lock [writer_held_ms=22857]' -``` - -The lock is held between these DEBUG_CRASH markers: -``` -DEBUG_CRASH: notify_applied_block start ← lock acquired before this -... 25+ seconds pass ... -DEBUG_CRASH: notify_applied_block done ← lock released after this -``` - -**User explicitly requests:** "add normal logs that show WHO set lock, where in which method?" — the current `Read lock timeout` messages show the writer's TID but not which function/code line is holding the lock. - -**Severity:** **CRITICAL** — 25-second write lock makes the node unresponsive to all P2P and RPC traffic. The `notify_applied_block` plugin callbacks are the bottleneck — one of the plugins (likely `operation_history`, `account_history`, or `snapshot`) is doing heavy synchronous work under the write lock. - -**Related files:** -- [database.cpp](file:///d:/Work/viz-cpp-node/libraries/chain/database.cpp) — `_apply_block` -- All plugin `on_applied_block` handlers - ---- - -### P28: Build Error — multimap::erase and ip::address::data() API - -**Observed on:** Docker build (GCC 13) - -**Symptom:** Compilation fails in `dlt_p2p_node.cpp`: - -1. **multimap::erase with pair**: -```cpp -_mempool_by_expiry.erase(std::make_pair(it->second.trx.expiration, it->first)); -``` -Error: `no matching function for call to 'std::multimap<...>::erase(std::pair<...>)'` - -In C++17, `multimap::erase` no longer accepts a value/pair directly — it requires an iterator, key, or iterator range. **Fix:** Use `_mempool_by_expiry.erase(it_by_expiry)` where `it_by_expiry` is the iterator to the element. - -2. **ip::address::data() doesn't exist**: -```cpp -auto a_data = a.data(); // 'const class fc::ip::address' has no member named 'data' -``` -**Fix:** Use `fc::raw::pack(a)` or the appropriate fc serialization method. - -**Severity:** **MEDIUM** — blocks compilation with newer GCC. - -**Affected locations** (3 call sites): -- `transition_to_forward()` line 972 -- `remove_transactions_in_block()` line 1089 -- `prune_mempool_on_fork_switch()` line 1099 - -**Related files:** -- [dlt_p2p_node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/dlt_p2p_node.cpp) - ---- - -### P29: Build Error — Missing witness_plugin.hpp Header - -**Observed on:** Docker build - -**Symptom:** -``` -fatal error: graphene/plugins/validator/witness_plugin.hpp: No such file or directory -#include -``` - -**Root cause:** The include path is wrong. The actual file is at `plugins/validator/include/graphene/plugins/validator/validator.hpp` (note: `validator.hpp` not `witness_plugin.hpp`). The CMake include directories may not include the Validator Plugin's include path. - -**Severity:** **MEDIUM** — blocks compilation in certain build configurations. - -**Related files:** -- [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) line 9 - ---- - -### P30: Multiple API Mismatches in p2p_plugin.cpp - -**Observed on:** Docker build (after fixing P29) - -**Symptom:** Multiple compilation errors in `p2p_plugin.cpp`: - -| # | Error | Root Cause | -|---|-------|------------| -| 1 | `with_read_lock([&]{...})` — candidate expects 6 arguments, 1 provided | The `with_read_lock` API changed — now requires `(lock_type, timeout_ms, lambda, file, line, func)` | -| 2 | `'is_emergency_consensus' is not a member` | Field was renamed or removed from `dynamic_global_property_object` | -| 3 | `blocks.front()->id()` — no match for call to `(block_id_type)()` | `id` is a field, not a method. Should be `blocks.front()->id` (without `()`) | -| 4 | `catch (const unlinkable_block_exception&)` — expected unqualified-id before `&` | Missing exception variable name: `catch (const unlinkable_block_exception& e)` | -| 5 | `accept_transaction` doesn't exist, did you mean `apply_transaction`? | Method was renamed | -| 6 | `push_block(*block)` where `block` is `fork_item` — can't convert | `fork_item` is not a `signed_block`. Need `fork_item->data` or cast | -| 7 | `is_known_block(ref_block_num)` with `uint32_t` — expects `block_id_type` | Need to fetch block ID by number first | - -**Severity:** **HIGH** — blocks compilation, indicates the p2p_plugin.cpp was written against a different API version than what's in the codebase. - -**Related files:** -- [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) - ---- - -### P31: Linker Error — static constexpr ODR-use - -**Observed on:** Docker build - -**Symptom:** -``` -/usr/bin/ld: undefined reference to `graphene::network::dlt_peer_state::MAX_RECONNECT_BACKOFF_SEC' -``` - -**Root cause:** `MAX_RECONNECT_BACKOFF_SEC` is declared as `static constexpr` in the header but used in a context that requires a definition (ODR-use). The code takes its address or binds a reference to it. In C++17, `static constexpr` members still need an out-of-line definition if they're ODR-used. - -**Fix:** Add a definition in the `.cpp` file: -```cpp -constexpr uint32_t dlt_peer_state::MAX_RECONNECT_BACKOFF_SEC; -``` -Or change the usage to avoid ODR-use. - -**Severity:** **MEDIUM** — linker error, blocks final linking. - -**Related files:** -- [dlt_p2p_peer_state.hpp](file:///d:/Work/viz-cpp-node/libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp) -- [dlt_p2p_node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/dlt_p2p_node.cpp) - ---- - -## Implemented Fixes (2026-05-06) - -### Fix 1: Dead Fork Block Crash Protection (P20/P21) - -**Files:** `libraries/network/include/graphene/network/dlt_p2p_node.hpp`, `plugins/p2p/p2p_plugin.cpp`, `libraries/network/dlt_p2p_node.cpp` - -**Root cause:** When a FORWARD node receives sync blocks from a peer on a dead fork (parent not in fork_db, head already past the block), `_push_block` throws `unlinkable_block_exception`. But the P2P layer's `on_dlt_block_range_reply` didn't distinguish between "rejected because validation failed" and "rejected because dead fork." A block with `block_num == head` (gap=0) from a dead fork could trigger "sync mode ended" logic, corrupting internal state. The fork_db accumulated unlinked blocks until OOM/assertion crash. After restart, peers re-sent the same blocks, creating a crash loop. - -**Changes:** - -1. **`dlt_block_accept_result` enum** — Added `DEAD_FORK` value to distinguish dead-fork rejection from generic rejection (`dlt_p2p_node.hpp` line 34). - -2. **`dlt_delegate::accept_block()`** (`p2p_plugin.cpp` line 166-183) — When `push_block()` throws `unlinkable_block_exception`: - - If `block.block_num() <= head_block_num()`: returns `DEAD_FORK` (peer is on a competing fork that diverged before our fork_db window). Does NOT push to `fork_db._unlinked_index`, preventing unbounded growth. - - If `block.block_num() > head_block_num()`: returns `FORK_DB_ONLY` (block is ahead but has a gap — store in `_unlinked_index` for later linking). - -3. **`on_dlt_block_range_reply()`** (`dlt_p2p_node.cpp` line 760-768) — On `DEAD_FORK` result: soft-ban the peer immediately and break out of the block processing loop. No more blocks are accepted from that peer in the range. - -4. **`on_dlt_block_reply()`** (`dlt_p2p_node.cpp` line 856-862) — Same DEAD_FORK soft-ban for single-block replies. - -5. **`transition_to_forward()` guard** (`dlt_p2p_node.cpp` line 784) — Only transition from SYNC to FORWARD if `any_block_applied` is true. A range full of dead-fork rejects does NOT end sync mode. - -**Expected behavior after fix:** -- Dead-fork blocks are rejected immediately without polluting fork_db -- Peers sending dead-fork blocks are soft-banned (10 min) -- No more "sync mode ended" from dead-fork gap=0 blocks -- No more crash loops from fork_db OOM or state corruption - ---- - -### Fix 2: DLT Block Log Corruption Recovery (P17) - -**Files:** `libraries/chain/include/graphene/chain/dlt_block_log.hpp`, `libraries/chain/dlt_block_log.cpp`, `libraries/chain/database.cpp` - -**Root cause:** On crash, the DLT block log index/data files can become inconsistent — the index truncates to a single entry while the database head is far ahead. Opening a corrupted DLT block log and using it for fork_db seeding causes cascading P2P failures (dead forks, sync stalls, crash loops). - -**Changes:** - -1. **`dlt_block_log::is_consistent_with()`** (`dlt_block_log.cpp` line 604-652) — New method that compares the DLT block log state with the database head block number. Detection rules: - - Empty DB or empty DLT log → consistent (normal) - - 1 block in log but DB has thousands → **corrupted** (index truncated on crash) - - DLT head exceeds DB head → **corrupted** - - DB head > DLT head + 1000 and DLT has < 10 blocks → **corrupted** (partial wipe) - -2. **Corruption detection in `database::open()`** (`database.cpp` line 269-280) — Before seeding fork_db from the DLT block log, validate consistency. If corrupted: - - Log a clear error with database head block number - - Call `_dlt_block_log.reset()` to wipe the corrupted log - - P2P sync will rebuild the DLT block log from scratch - -**Expected behavior after fix:** -- Corrupted DLT block log is detected and auto-reset on startup -- No more infinite restart loops from inconsistent block log -- P2P sync rebuilds the log naturally after reset - ---- - -### Fix 3: Snapshot Lock Isolation Prevention (P24) - -**Files:** `libraries/network/dlt_p2p_node.cpp`, `plugins/snapshot/plugin.cpp` - -**Root cause:** Snapshot `create_snapshot()` runs on a background thread with a strong read lock on the database. During the 30-120s serialization, the P2P thread's periodic tasks (stats, peer exchange, hello broadcasts, stall detection) also need read locks but time out because the snapshot's lock blocks them. This cascades into all peer disconnections. - -**Changes:** - -1. **`periodic_task()` early-return guard** (`dlt_p2p_node.cpp` line 1682-1708) — When `_block_processing_paused` is true (snapshot in progress), skip periodic operations that need database read locks: - - Skip `sync_stagnation_check()` — stale blocks won't arrive anyway - - Skip `periodic_peer_exchange()` — can wait - - Skip `log_peer_stats()` — cosmetic only - - Still run: `periodic_reconnect_check()`, `periodic_lifecycle_timeout_check()`, `block_validation_timeout()`, `periodic_mempool_cleanup()`, banned-peer unban check - -2. **`check_stalled_sync_loop()` snapshot awareness** (`snapshot/plugin.cpp` line 1833-1843) — When `snapshot_in_progress` is true, skip the stall check entirely and reset the timer. The stall is caused by the snapshot (expected), not by actual network failure. Without this, the stall detector would trigger a false recovery (cancel the snapshot, attempt resync while still locked). - -3. **`serialize_state()` progress logging** (`snapshot/plugin.cpp` line 845-860) — The `EXPORT_INDEX` macro now logs progress every 5 seconds during long serialization. Operators can see the snapshot is progressing instead of assuming the node is frozen. - -**Expected behavior after fix:** -- Snapshot creation no longer causes peer disconnections -- Stall detection doesn't trigger false recovery during snapshots -- Progress logs give visibility into snapshot serialization - ---- - -### Fix 4: Write Lock Diagnostic Logging (P27) - -**Files:** `libraries/chain/database.cpp`, `plugins/mongo_db/mongo_db_plugin.cpp`, `plugins/operation_history/plugin.cpp`, `plugins/account_history/plugin.cpp` - -**Root cause:** `_apply_block` holds the WRITE lock through `notify_applied_block()` which calls ALL registered plugin callbacks synchronously. One plugin (likely `operation_history`, `account_history`, or `mongo_db`) does heavy work under the lock, blocking all P2P and RPC for 25+ seconds. - -**Changes:** - -1. **Overall timing in `notify_applied_block()`** (`database.cpp` line 2181-2199) — Times the total `applied_block` signal notification. If it exceeds 200ms, logs a warning with block number, duration, and number of connected plugins. This identifies when the bottleneck occurs without changing behavior. - -2. **Per-plugin self-timing** — Added self-timing to the 3 most likely slow `applied_block` handlers: - - `mongo_db_plugin.cpp` line 99-108: Times `on_block()`, logs if >100ms - - `operation_history/plugin.cpp` line 266-275: Times `purge_old_history()`, logs if >100ms - - `account_history/plugin.cpp` line 543-552: Times `purge_old_history()`, logs if >100ms - -3. **Lock-holder identification** — Already implemented in chainbase (`chainbase.hpp` line 1330-1342): `with_strong_write_lock` macro auto-captures `__FILE__`, `__LINE__`, `__func__` at every call site. Lock timeout messages now include `writer_at=file:line func`, `writer_held_ms`, and `writer_tid`. - -**Expected behavior after fix:** -- When a 25s write lock occurs, the log will show: - - Which block triggered it (`applied_block notification took Xms for block #N`) - - Which specific plugin is slow (`mongo_db on_block took Xms` or `operation_history purge_old_history took Xms`) - - Which write lock call site is holding it (from chainbase diagnostics) -- No behavior change — only diagnostic logging added - -**Next step (not implemented):** Once the slow plugin is identified, defer its heavy work to a background thread after the write lock is released. - ---- - -## Summary of New Problems - -``` -P17 [FIX] DLT block log corruption on crash → infinite restart loop -P18 [FIX] Master stops producing blocks for minutes (slot=0 loop) → stall detector + NTP force-sync -P19 [FIX] Slave stuck in SYNC → gap detection + multi-peer fallback + snapshot warning -P20 [FIX] Dead fork blocks trigger sync status loss → silent crash -P21 [FIX] Dead fork → crash → restart → dead fork loop -P22 [FIX] fork_db rejection cascade on restart → seed 100 blocks + 60s grace period -P23 [FIX] fetch_branch_from assertion failure → graceful empty-branch return -P24 [FIX] Snapshot write lock freezes entire node (all peers disconnect, self-isolation) -P25 [FIX] Slave-produced block ignored by master → exchange_enabled re-evaluated -P26 [FIX] Sync state confusion → check_sync_catchup() on block accept + periodic -P27 [FIX] Write lock held 25+ sec during _apply_block (notify_applied_block bottleneck) -P28 [MED] Build error: multimap::erase with pair, ip::address::data() missing -P29 [MED] Build error: missing witness_plugin.hpp include -P30 [HIGH] Build error: multiple API mismatches in p2p_plugin.cpp (6+ separate issues) -P31 [MED] Linker error: static constexpr ODR-use (MAX_RECONNECT_BACKOFF_SEC) -``` - ---- - -## New Problem Priority Matrix - -### Immediate (node stability) - -| # | Problem | Impact | Status | -|---|---------|--------|--------| -| P17 | DLT block log corruption → restart loop | Node can't recover | **Fixed** | -| P20 | Dead fork blocks → crash | Malicious peer can crash master | **Fixed** | -| P21 | Dead fork → crash loop | Node stays down | **Fixed** | -| P24 | Snapshot lock → network isolation | Node disappears from network | **Fixed** | -| P27 | 25s write lock → all RPC/P2P fails | Node unresponsive | **Fixed** (diagnostic) | - -### High (sync/catchup reliability) - -| # | Problem | Impact | Status | -|---|---------|--------|--------| -| P18 | Master stops producing blocks | Network stalls | **Fixed** | -| P19 | Slave stuck in SYNC | Never catches up | **Fixed** | -| P22 | fork_db rejection cascade | Restart recovery fails | **Fixed** | -| P23 | fetch_branch_from assertion | Sync with some peers breaks | **Fixed** | -| P25 | Slave block ignored by master | Fork switches | **Fixed** | - -### Build (must compile) - -| # | Problem | Impact | -|---|---------|--------| -| P28 | multimap::erase, ip::address API | Won't compile on GCC 13+ | -| P29 | Missing header include | Won't compile | -| P30 | 6+ API mismatches in p2p_plugin | Won't compile | -| P31 | static constexpr linker error | Won't link | - -### Lower - -| # | Problem | Impact | Status | -|---|---------|--------|--------| -| P26 | Sync state confusion | Cosmetic status bug | **Fixed** | diff --git a/.qoder/docs/dlt-forward-mode.md b/.qoder/docs/dlt-forward-mode.md deleted file mode 100644 index dbd922f402..0000000000 --- a/.qoder/docs/dlt-forward-mode.md +++ /dev/null @@ -1,502 +0,0 @@ -# DLT Forward Mode — Block & Transaction Exchange - -## Overview - -Forward mode (`DLT_NODE_STATUS_FORWARD`) is the normal operating state of a DLT P2P node after it catches up with the network. In this mode, nodes actively **push** new blocks and transactions to each other as they arrive, rather than pulling them via range requests. - -This document describes how exchange works in forward mode: what gets sent, to whom, and the filtering mechanisms that control it. - ---- - -## Two-Phase Lifecycle - -A DLT node operates in one of two modes: - -| Mode | Enum Value | Behavior | -|------|-----------|----------| -| **SYNC** | `DLT_NODE_STATUS_SYNC = 0` | Pull-based: requests block ranges from peers to catch up. Mempool entries are tagged *provisional* (not forwarded). | -| **FORWARD** | `DLT_NODE_STATUS_FORWARD = 1` | Push-based: broadcasts new blocks/transactions to all fork-aligned peers. Mempool entries are validated and forwarded. | - -The transition from SYNC → FORWARD happens via `transition_to_forward()` (see [SYNC→FORWARD Transition](#syncforward-transition) below). - ---- - -## Core Mechanism: `send_to_all_our_fork_peers` - -All broadcasting in forward mode funnels through a single method: - -```cpp -// dlt_p2p_node.cpp -void dlt_p2p_node::send_to_all_our_fork_peers(const message& msg, peer_id exclude = INVALID_PEER_ID, const block_id_type& block_id = block_id_type()) { - for (auto& [id, state] : _peer_states) { - if (id == exclude) continue; - if (state.exchange_enabled && state.lifecycle_state == DLT_PEER_LIFECYCLE_ACTIVE) { - if (block_id != block_id_type() && state.has_block(block_id)) continue; // echo suppression - send_message(id, msg); - if (block_id != block_id_type()) state.record_known_block(block_id); - } - } -} -``` - -**Two filters control delivery:** - -| Filter | Source | Meaning | -|--------|--------|---------| -| `exchange_enabled == true` | Set during hello handshake, combined via OR in hello_reply (P27 fix); re-evaluated on block accept and FORWARD transition (P25 fix) | Peer is on our fork — its head block is known to us | -| `lifecycle_state == ACTIVE` | Peer lifecycle FSM | Peer has completed handshake and is in normal operation | - -**`exclude` parameter:** When relaying a message received from a peer, that peer's ID is passed as `exclude` to avoid echoing the message back. - -**`block_id` parameter (echo suppression):** When broadcasting or relaying a block, the block's ID is passed as `block_id`. The function checks each peer's `known_blocks` ring buffer — if the peer already has this block, the send is skipped and the peer is counted in the `skipped_echo` diagnostic. After a successful send, the block ID is recorded in the peer's `known_blocks`. - ---- - -## Block Echo Suppression - -### Problem - -In a multi-peer mesh, a block can echo back to the node that produced it: - -``` -1. Master A generates block #N, broadcasts to B, V, G -2. V receives #N first, accepts it, retransmits to B and G (standard relay) -3. B receives #N from V, accepts it, retransmits to A and G -4. A receives its own block #N back from B — wasted bandwidth + log noise -``` - -The `exclude` parameter only filters the **direct sender**. It cannot filter peer B, which received the block from V (not from A) and relayed it to A. - -### Solution: Per-Peer `known_blocks` Ring Buffer - -Each peer state maintains a small ring buffer of recent block IDs that the peer is **known to have**: - -```cpp -// dlt_p2p_peer_state.hpp -static constexpr size_t KNOWN_BLOCKS_WINDOW = 20; // ~60 seconds of blocks at 3s/block -std::vector known_blocks; - -bool has_block(const block_id_type& id) const; -void record_known_block(const block_id_type& id); -``` - -A peer is recorded as having a block in two situations: - -| Signal | Why it means the peer has the block | -|--------|-------------------------------------| -| **We sent the block to the peer** | `send_to_all_our_fork_peers` records the block ID after each successful send | -| **The peer sent the block to us** | `on_dlt_block_reply` records the block ID from the incoming message | - -Before sending a block to a peer, `send_to_all_our_fork_peers` checks `has_block()`. If the peer already has it, the send is skipped. - -### Scope - -Echo suppression only applies to **block_reply** messages (the primary broadcast vector in FORWARD mode). Transactions, fork_status messages, and other P2P messages are not affected — the `block_id` parameter defaults to `block_id_type()` (null) for those calls. - -### Diagnostics - -The relay log line now includes echo-filtered count: -``` -Relay block_reply to 3 peers (0 skipped: no_exchange, 0 skipped: not_active, 1 skipped: echo) -``` - ---- - -## Block Broadcasting - -### Self-Produced Blocks - -When a validator produces a block, the flow is: - -``` -validator.cpp:1081 p2p().broadcast_block(block) - → p2p_plugin.cpp:482 my->node->broadcast_block(block) - → dlt_p2p_node.cpp:1117 -``` - -```cpp -void dlt_p2p_node::broadcast_block(const signed_block& block) { - dlt_block_reply_message reply; - reply.block = block; - reply.next_available = 0; - reply.is_last = true; - send_to_all_our_fork_peers(message(reply), INVALID_PEER_ID, block.id()); // NO exclude, WITH echo suppression -} -``` - -The block goes to **all** ACTIVE peers with `exchange_enabled=true`, **except** peers that already have this block (echo suppression). - -### Relaying Received Blocks - -When a block arrives from a peer (via `on_dlt_block_reply`), the node applies it and **retransmits** to all other fork-aligned peers: - -```cpp -// dlt_p2p_node.cpp -// Record that sender has this block (echo suppression) -state.record_known_block(reply.block.id()); -// Retransmit to our-fork peers (with echo suppression) -send_to_all_our_fork_peers(message(dlt_block_reply_message(reply)), peer, reply.block.id()); -``` - -The `peer` (sender) is excluded via the `exclude` parameter. Additionally, peers that already have this block in their `known_blocks` are skipped via echo suppression. The sender's `known_blocks` is updated so that if this block is later received from another peer, it won't be sent back to the original sender. - -### Block Post-Validation Broadcast - -`broadcast_block_post_validation()` sends a lightweight fork-status message (block ID + validator + signature) instead of the full block. This is called by the Validator Plugin for each block in the production round after validation completes. - ---- - -## Transaction Broadcasting - -### Self-Originated Transactions - -When a transaction arrives via API (`network_broadcast_api`): - -```cpp -// dlt_p2p_node.cpp -void dlt_p2p_node::broadcast_transaction(const signed_transaction& trx) { - add_to_mempool(trx, /*from_peer=*/false, INVALID_PEER_ID); - dlt_transaction_message msg; - msg.trx = trx; - dlog(DLT_LOG_DGRAY "Broadcasting transaction ${id} to fork peers" DLT_LOG_RESET, - ("id", trx.id())); - send_to_all_our_fork_peers(message(msg)); // NO exclude -} -``` - -The transaction is added to the local mempool and broadcast to **all** fork-aligned peers. - -### Relaying Received Transactions - -When a transaction arrives from a peer (via `on_dlt_transaction` → `add_to_mempool`): - -```cpp -// dlt_p2p_node.cpp — in add_to_mempool() -// Retranslate to our-fork peers (if from peer) -if (from_peer && sender != INVALID_PEER_ID) { - dlog(DLT_LOG_DGRAY "Relaying transaction ${id} to fork peers (excluding sender)" DLT_LOG_RESET, - ("id", trx_id)); - dlt_transaction_message msg; - msg.trx = trx; - send_to_all_our_fork_peers(message(msg), sender); // exclude sender -} -``` - -The sender is excluded; all other fork-aligned peers receive the relay. - -### Transaction Diagnostic Logging - -All transaction exchange events produce dark-gray `dlog` messages (visible at debug log level): - -| Event | Log Message | Level | -|-------|-------------|-------| -| Self-originated send (API) | `Broadcasting transaction ${id} to fork peers` | `dlog` DGRAY | -| Peer relay (retransmit) | `Relaying transaction ${id} to fork peers (excluding sender)` | `dlog` DGRAY | -| Relay stats in `send_to_all_our_fork_peers` | `Relay transaction to ${e} peers (${nx} skipped: no_exchange, ${na} skipped: not_active)` | `dlog` DGRAY | -| Received from peer (new) | `Got transaction ${id} from peer ${ep}` | `dlog` DGRAY | -| Received duplicate | *Silent — no log emitted* | — | - -Duplicate transactions (already in `_mempool_by_id`) are silently ignored in both `on_dlt_transaction` and `add_to_mempool`, same as duplicate blocks — no console spam. - -### Mempool Validation (Pre-Forwarding) - -Before a transaction is added to mempool or forwarded, it passes these checks: - -| Check | Failure Action | -|-------|---------------| -| **Dedup** (`_mempool_by_id`) | Silently skip | -| **Expired** (`trx.expiration < now`) | Reject, increment spam strike if from peer | -| **Expiration too far** (>24h) | Reject, increment spam strike if from peer | -| **Too large** (>64KB) | Reject, increment spam strike if from peer | -| **TaPoS invalid** (ref block unknown) | Reject, increment spam strike if from peer | -| **Mempool full** | Evict oldest-expiry entry, retry | - -During SYNC mode, accepted transactions are tagged `is_provisional = true` — they are stored but NOT forwarded to peers. On transition to FORWARD, provisional entries are revalidated (TaPoS check against current head) and the invalid ones are purged. - ---- - -## SYNC→FORWARD Transition - -The transition from SYNC to FORWARD is governed by `transition_to_forward()`: - -```cpp -// dlt_p2p_node.cpp:1247 -void dlt_p2p_node::transition_to_forward() { - if (_node_status == DLT_NODE_STATUS_FORWARD) return; - _node_status = DLT_NODE_STATUS_FORWARD; - _sync_stagnation_retries = 0; - // ... -} -``` - -### Transition Triggers - -| Trigger | Location | Condition | -|---------|----------|------------| -| **Block range complete** | `on_dlt_block_range_reply()` | `is_last == true` AND `any_block_applied == true` (P20 guard) | -| **Sync catchup** | `check_sync_catchup()` | `our_head >= all_active_peer_heads` AND at least one active peer exists | -| **Stagnation timeout** | `sync_stagnation_check()` | 30s no-block, 3 retries exhausted → FORWARD with warning | - -`check_sync_catchup()` is called from two places (P26 fix): -- `on_dlt_block_reply()` — after accepting a single block -- `periodic_task()` — every ~5 seconds - -**Isolation guard (P53 fix):** `check_sync_catchup()` does NOT claim "caught up" when zero active peers exist. With no peers to compare against, the node cannot determine whether it has actually caught up. Instead, it tracks isolation via `_isolation_detected_time` and after 60 seconds calls `emergency_peer_reset()` to force reconnection. See [Peer Isolation Recovery](#peer-isolation-recovery) below. - -### What Happens on Transition - -1. **Notify all connected peers**: Send `dlt_fork_status_message` with `node_status=FORWARD` to ALL active/syncing peers (not just exchange-enabled). This lets peers know we're now in FORWARD mode so they can re-evaluate `exchange_enabled` for us. -2. **Re-evaluate `exchange_enabled` for all peers** (P25 fix): peers whose head block is now recognized (because we synced past it) get `exchange_enabled = true` -3. **Revalidate provisional mempool entries**: entries tagged during SYNC are checked for TaPoS validity against the current head; invalid ones are purged -4. **Reset stagnation retries**: counter set to 0 for clean slate - ---- - -## FORWARD→SYNC Transition (P27) - -| Trigger | Location | Condition | -|---------|----------|------------| -| **Peer ahead in hello_reply** | `on_dlt_hello_reply()` | `peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD` while in FORWARD mode | -| **Periodic fallbehind check** | `check_forward_behind()` | Any active peer is ahead by > `FORWARD_FALLBEHIND_THRESHOLD` (2) blocks | -| **FORWARD stagnation** | `check_forward_stagnation()` | Head stuck for 30s with active peers and at least one peer ahead → SYNC (P37). No peer ahead → reset stagnation timer, stay in FORWARD (P55). Isolated (no active peers) → emergency reset after 60s (P53) | - ---- - -## The `exchange_enabled` Flag - -`exchange_enabled` is the primary gatekeeper for forward-mode traffic. It is set during hello handshake, combined via logical OR in hello_reply, and re-evaluated at key lifecycle events. - -### Initial Setting (Hello Handshake) - -During `build_hello_reply()`, `check_fork_alignment()` determines whether the peer's head/LIB blocks are known to us: - -| Check | Condition | Result | -|-------|-----------|--------| -| Empty peer (`head==0`) | Always | Aligned (new node) | -| Range overlap | `peer_head_num ∈ [our_earliest, our_latest]` | Use `is_block_known(head_id)` | -| Boundary link | `peer_head_num + 1 == our_earliest` | Check `our_earliest_block.previous == head_id` | -| LIB fallback | Always | `is_block_known(lib_id)` | - -If any check passes → `exchange_enabled = true`, `fork_alignment = true`. - -### P27 Fix: OR Combination in `on_dlt_hello_reply` - -When two nodes connect, **both sides send hello messages** to each other. Each side computes its own `exchange_enabled` in `on_dlt_hello` (local determination), then receives the other side's determination in `on_dlt_hello_reply`. - -**The bug (P27):** `on_dlt_hello_reply` used to **overwrite** `state.exchange_enabled` with the remote side's determination. This caused a critical failure: - -``` -Slave (head=79673001) connects to Master (head=79673101) - -1. Master's on_dlt_hello: - - check_fork_alignment(slave_head) → true (master has block 79673001) - - state.exchange_enabled = true ✅ - -2. Slave's on_dlt_hello: - - check_fork_alignment(master_head) → false (slave doesn't have block 79673101) - - state.exchange_enabled = false - - Sends hello_reply with exchange_enabled=false to master - -3. Master's on_dlt_hello_reply: - - state.exchange_enabled = reply.exchange_enabled (overwrite!) - - state.exchange_enabled = false ❌ BUG! - - Master stops broadcasting blocks to slave! -``` - -**The fix:** Use `state.exchange_enabled = state.exchange_enabled || reply.exchange_enabled`. If **either** side considers the peer fork-aligned, exchange is enabled: -- If we think the peer is on our fork → we should send blocks to them -- If they think we're on their fork → we should receive blocks from them -- If both are false → truly different forks, no exchange - -### Receiving FORWARD Transition from a Peer - -When `on_dlt_fork_status()` receives a status update from a peer that just transitioned SYNC→FORWARD, the node re-evaluates `exchange_enabled` for that peer. The peer's head block may now be within our known chain, meaning we should enable block/transaction exchange with it. - -### Re-Evaluation Triggers (P25 Fix) - -The original implementation set `exchange_enabled` once and never updated it, causing slave-produced blocks to be ignored by the master. Re-evaluation points: - -1. **`transition_to_forward()`**: Re-checks `is_block_known(peer_head_id)` for all peers with `exchange_enabled=false` -2. **`on_dlt_fork_status()`**: When a peer transitions SYNC→FORWARD, re-checks if the peer's head block is now known to us -3. **`on_dlt_block_range_reply()`**: When a non-exchange-enabled peer's block is ACCEPTED, enables exchange for that peer -4. **`on_dlt_block_reply()`**: Same — when a single block from a non-exchange-enabled peer is ACCEPTED - ---- - -## FORWARD→SYNC Fallback (P27 Fix) - -In FORWARD mode, blocks arrive via broadcast from fork-aligned peers. But if broadcast blocks are missed (e.g., connection dropped, `exchange_enabled` was incorrectly false), the node can fall behind with no recovery mechanism. - -Two detection points were added: - -### 1. In `on_dlt_hello_reply` (Reactive) - -When a FORWARD node receives a hello_reply from a peer that is significantly ahead (`peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD`), it transitions to SYNC and requests the missing range: - -```cpp -if (_node_status == DLT_NODE_STATUS_FORWARD) { - uint32_t our_head = _delegate->get_head_block_num(); - if (state.peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD) { - transition_to_sync(); - request_blocks_from_peer(peer); - } -} -``` - -### 2. In `check_forward_behind()` (Periodic) - -Called every ~5 seconds from `periodic_task()`. Iterates all active peers and checks if any is ahead by more than `FORWARD_FALLBEHIND_THRESHOLD` blocks: - -```cpp -void dlt_p2p_node::check_forward_behind() { - if (_node_status != DLT_NODE_STATUS_FORWARD) return; - for (const auto& [id, state] : _peer_states) { - if (state.peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD) { - transition_to_sync(); - // Request blocks from ALL exchange-enabled ahead peers - break; - } - } -} -``` - -The threshold is `FORWARD_FALLBEHIND_THRESHOLD = 2` (3+ blocks behind = ~9s at 3s/block). This avoids false triggers from normal 1-block broadcast latency. - ---- - -## Peer Isolation Recovery (P53) - -### Problem - -When all peers are disconnected or banned (e.g., after a snapshot pause), the node becomes **isolated** — no active peer connections exist. This caused a SYNC↔FORWARD oscillation loop: - -``` -1. All peers DISC → check_sync_catchup() sees 0 active peers → all_caught_up=true → FORWARD -2. In FORWARD with no connections → check_forward_stagnation() after 30s → SYNC -3. In SYNC with no connections → check_sync_catchup() sees 0 active → FORWARD -4. Repeat forever, head never advances, backoffs never expire (up to 3600s) -``` - -The root cause: `check_sync_catchup()` treated zero active peers as "caught up" (vacuously true for the `all_caught_up` check), and `check_forward_stagnation()` transitioned to SYNC without any peer to request blocks from. - -### Solution: Isolation Detection + Emergency Reset - -A new field `_isolation_detected_time` tracks when isolation was first detected. After 60 seconds (`ISOLATION_RESET_SEC`) of continuous isolation, `emergency_peer_reset()` fires: - -1. **Clears all soft bans** — BANNED peers are moved to DISCONNECTED state, `ban_reason` and `spam_strikes` are reset. -2. **Resets all backoffs** — Every DISCONNECTED peer gets `reconnect_backoff_sec = INITIAL_RECONNECT_BACKOFF_SEC` (30s) and `next_reconnect_attempt = now` (immediate). -3. **Resets stagnation counters** — `_sync_stagnation_retries` is cleared. -4. **Clears isolation timer** — `_isolation_detected_time` is reset so the timer can re-trigger if isolation recurs. - -On the next `periodic_task()` tick (5 seconds), `periodic_reconnect_check()` will attempt immediate reconnection to all peers. - -### Where Isolation Is Handled - -| Function | Behavior When Isolated | -|----------|----------------------| -| `check_sync_catchup()` | Returns early (does not claim caught up). Starts isolation timer or calls `emergency_peer_reset()` after 60s. | -| `check_forward_stagnation()` | Returns early (does not transition to SYNC). Starts isolation timer or calls `emergency_peer_reset()` after 60s. | -| `emergency_peer_reset()` | Clears bans, resets backoffs, enables immediate reconnection. | - ---- - -## FORWARD Stagnation When No Peer Is Ahead (P55) - -A second oscillation pattern was observed when the head is stuck but no connected peer has a higher block number: - -``` -1. Head stuck at block N, all 5 peers also at block N → check_forward_stagnation() after 30s → SYNC -2. In SYNC, sync_stagnation_check() fires immediately (stale timer, see below) -3. check_sync_catchup() sees our_head >= all peers → FORWARD -4. Repeat — SYNC never actually requests or processes any blocks -``` - -**Two root causes:** - -1. `transition_to_sync()` did not reset `_last_block_received_time`, so the sync stagnation timer inherited the stale timestamp from the last block received in FORWARD mode (~30s ago). `sync_stagnation_check()` fired on the very next periodic tick. - -2. `check_forward_stagnation()` transitioned to SYNC even when no peer was ahead. With no peer to sync from, SYNC mode was useless — `check_sync_catchup()` immediately returned to FORWARD. - -**Fixes:** - -1. `transition_to_sync()` now resets `_last_block_received_time = fc::time_point::now()`, giving the sync phase a full 30s window. - -2. `check_forward_stagnation()` now checks for peers ahead before transitioning to SYNC. If no peer has `peer_head_num > our_head`, the function resets the stagnation timer and stays in FORWARD instead of oscillating. - ---- - -## What Does NOT Get Forwarded - -### Peers on a Different Fork - -Peers whose `exchange_enabled=false` (fork not aligned) receive nothing. This is by design — blocks and transactions from one fork are meaningless on another. - -### SYNC-Mode Nodes - -While in SYNC mode, a node does NOT broadcast blocks or relay transactions to peers. The only outbound traffic is block range requests (`dlt_get_block_range_message`) and gap fill requests (`dlt_gap_fill_request`). Gap fill works in both SYNC and FORWARD modes — in SYNC mode, it provides an alternative path to request missing blocks when `request_blocks_from_peer()` cannot bridge a gap (e.g., blocks below the syncing peer's DLT range). Large gaps are served in 100-block chunks. - -### Block Processing Paused - -When `_block_processing_paused == true` (snapshot in progress), periodic tasks skip DB-accessing operations, but the node can still receive and broadcast blocks. The flag primarily prevents sync-stagnation false positives and lock contention. - ---- - -## Comparison: Old Graphene vs. DLT Forward Mode - -| Aspect | Old Graphene (`node.cpp`) | DLT (`dlt_p2p_node.cpp`) | -|--------|--------------------------|--------------------------| -| **Broadcast trigger** | Inventory gossip → peer requests items | Direct push of full block/transaction | -| **Filtering** | `peer_needs_sync_items_from_us` / `we_need_sync_items_from_peer` per-peer flags | `exchange_enabled` (fork alignment) + `lifecycle_state == ACTIVE` | -| **Block format** | `block_message` with synopsis negotiation | `dlt_block_reply_message` — full block, always | -| **Transaction format** | `trx_message` via inventory | `dlt_transaction_message` — direct push | -| **Relay exclusion** | Complex inventory tracking | Simple `exclude` parameter on `send_to_all_our_fork_peers` | -| **Mempool** | Chain's `_pending_tx` | Separate P2P mempool with expiry/TaPoS/size filtering | -| **Anti-spam** | Multiple counters (`unlinkable_block_strikes`, `sync_spam_strikes`, etc.) | Single `spam_strikes` counter, reset on good packet | - ---- - -## Summary: Who Gets What in Forward Mode - -| Event | Sent to | Excluded | Echo Filtered | -|-------|---------|----------|---------------| -| Node produces a block | All ACTIVE peers with `exchange_enabled=true` | *none* | Peers that already have the block (from a previous relay) | -| Node originates a transaction | All ACTIVE peers with `exchange_enabled=true` | *none* | N/A | -| Node receives a block from peer X | All ACTIVE peers with `exchange_enabled=true` | X | Peers that already have the block | -| Node receives a transaction from peer X | All ACTIVE peers with `exchange_enabled=true` | X | N/A | -| Peer has `exchange_enabled=false` | *nothing* | — | — | -| Node is in SYNC mode | *nothing* (only range requests and gap fill) | - | - | - ---- - -## Peer Stats: Stale `peer_head_num` Caveat - -The `peer_head_num` shown in the P2P stats table is **not real-time**. It is a snapshot from the last communication event: - -| Update Source | When `peer_head_num` Gets Updated | -|-------------|----------------------------------| -| `dlt_hello_message` | Initial handshake when connection is established | -| `dlt_fork_status_message` | Periodic fork_status exchanges between peers | -| `dlt_block_reply_message` | Updated if a peer sends us block #N, its head must be ≥ N | - -Between these events, the peer's actual chain head may advance significantly (e.g., the peer produces or receives blocks via broadcast). **Do not treat `peer_head_num` in the stats table as the peer's current chain state.** It is useful for relative ordering and sync progress estimation, but not as a real-time block height monitor. - ---- - -## Relevant Source Files - -| File | Content | -|------|---------| -| `libraries/network/dlt_p2p_node.cpp` | `broadcast_block()`, `broadcast_transaction()`, `send_to_all_our_fork_peers()`, `transition_to_forward()`, `check_sync_catchup()`, `check_forward_behind()`, `check_forward_stagnation()`, `emergency_peer_reset()`, `add_to_mempool()` | -| `libraries/network/include/graphene/network/dlt_p2p_node.hpp` | `dlt_p2p_node` class declaration, `dlt_node_status` enum | -| `libraries/network/include/graphene/network/dlt_p2p_messages.hpp` | `dlt_block_reply_message`, `dlt_transaction_message`, `dlt_message_type_enum` | -| `libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp` | `dlt_peer_state` (contains `exchange_enabled`, `fork_alignment`, `known_blocks` for echo suppression) | -| `plugins/p2p/p2p_plugin.cpp` | `dlt_delegate::accept_block()`, `p2p_plugin::broadcast_block()`, `p2p_plugin::broadcast_transaction()` | -| `plugins/validator/validator.cpp` | Calls `p2p().broadcast_block()` after block production | - ---- - -## Related Documents - -- [DLT P2P Network Redesign](./dlt-p2p-network-redesign.md) — Full implementation overview -- [DLT 4-Node Sync Scenarios](./dlt-4-node-sync-scenarios.md) — Problem analysis for SYNC/FORWARD edge cases -- [P2P Sync Workflow](./p2p-sync-workflow.md) — Old Graphene synopsis-based sync (pre-DLT, for comparison) diff --git a/.qoder/docs/dlt-hardfork-new-objects.md b/.qoder/docs/dlt-hardfork-new-objects.md deleted file mode 100644 index 4a3881cb12..0000000000 --- a/.qoder/docs/dlt-hardfork-new-objects.md +++ /dev/null @@ -1,383 +0,0 @@ -# DLT Hardforks with New Consensus Objects - -This document describes how to implement hardforks that introduce new consensus objects in DLT (Distributed Ledger Technology) mode, and how the snapshot system handles compatibility between nodes running different versions. - -## Overview - -In DLT mode, nodes can start from snapshots instead of replaying the entire blockchain. When a hardfork introduces new consensus objects, the system must ensure: - -1. **Forward compatibility**: Old nodes can load snapshots containing unknown objects -2. **Backward compatibility**: New nodes can load older snapshots missing new objects -3. **Consensus safety**: Nodes only participate in consensus for hardforks they understand - -## Adding New Consensus Objects in a Hardfork - -### Step 1: Define the Object Type - -Add the new object type to `libraries/chain/include/graphene/chain/chain_object_types.hpp`: - -```cpp -enum object_type { - // ... existing types ... - block_post_validation_object_type, // last existing type - prediction_market_object_type, // NEW: add new type at the end -}; - -// Forward declaration -class prediction_market_object; - -// Type alias -typedef object_id prediction_market_object_id_type; - -// Add to FC_REFLECT_ENUM -FC_REFLECT_ENUM(graphene::chain::object_type, - // ... existing types ... - (block_post_validation_object_type) - (prediction_market_object_type) // NEW -) -``` - -### Step 2: Create the Object Definition - -Create a new header file `libraries/chain/include/graphene/chain/prediction_market_object.hpp`: - -```cpp -#pragma once - -#include -#include - -namespace graphene { namespace chain { - -class prediction_market_object - : public object { -public: - prediction_market_object() = delete; - - template - prediction_market_object(Constructor&& c, allocator a) - : title(a), description(a) { - c(*this); - } - - id_type id; - account_name_type creator; - shared_string title; - shared_string description; - asset total_stake; - time_point_sec resolution_time; - uint16_t status = 0; // 0=open, 1=resolved, 2=cancelled -}; - -struct by_creator; -struct by_resolution_time; - -typedef multi_index_container< - prediction_market_object, - indexed_by< - ordered_unique, - member>, - ordered_non_unique, - member>, - ordered_non_unique, - member> - >, - allocator -> prediction_market_index; - -}} // graphene::chain - -FC_REFLECT((graphene::chain::prediction_market_object), - (id)(creator)(title)(description)(total_stake)(resolution_time)(status)) -CHAINBASE_SET_INDEX_TYPE(graphene::chain::prediction_market_object, graphene::chain::prediction_market_index) -``` - -### Step 3: Register the Index - -Add the index registration in `libraries/chain/database.cpp` in `initialize_indexes()`: - -```cpp -void database::initialize_indexes() { - // ... existing indexes ... - add_core_index(*this); - - // NEW: Register prediction market index - add_core_index(*this); - - _plugin_index_signal(); -} -``` - -### Step 4: Update Snapshot Plugin - -Add serialization support in `plugins/snapshot/plugin.cpp`: - -**In `serialize_state()`:** -```cpp -fc::mutable_variant_object snapshot_plugin::plugin_impl::serialize_state() { - fc::mutable_variant_object state; - - // ... existing EXPORT_INDEX calls ... - - // NEW: Export prediction market objects - EXPORT_INDEX(prediction_market_index, prediction_market_object, "prediction_market") - - #undef EXPORT_INDEX - return state; -} -``` - -**In `load_snapshot()` - add import logic:** -```cpp -// NEW: Import prediction market objects (in appropriate section) -if (state.contains("prediction_market")) { - auto n = detail::import_prediction_markets(db, state["prediction_market"].get_array()); - ilog("Imported ${n} prediction markets", ("n", n)); -} -``` - -**Create specialized import function (if using shared_string):** -```cpp -inline uint32_t import_prediction_markets( - graphene::chain::database& db, - const fc::variants& arr -) { - uint32_t count = 0; - for (const auto& v : arr) { - auto id_val = v["id"].as_int64(); - auto& mutable_idx = db.get_mutable_index(); - mutable_idx.set_next_id(prediction_market_object_id_type(id_val)); - - db.create([&](prediction_market_object& obj) { - obj.creator = v["creator"].as(); - from_string(obj.title, v["title"].as_string()); - from_string(obj.description, v["description"].as_string()); - obj.total_stake = v["total_stake"].as(); - obj.resolution_time = v["resolution_time"].as(); - obj.status = v["status"].as_uint64(); - }); - ++count; - } - return count; -} -``` - -### Step 5: Implement Hardfork Logic - -Add the hardfork case in `libraries/chain/database.cpp` in `apply_hardfork()`: - -```cpp -void database::apply_hardfork(uint32_t hardfork) { - switch (hardfork) { - // ... existing cases ... - - case CHAIN_HARDFORK_10: // NEW hardfork - { - // Initialize any required state for prediction markets - // This runs when the hardfork activates - - // Example: Set initial parameters, migrate data, etc. - const auto& props = get_dynamic_global_properties(); - modify(props, [&](dynamic_global_property_object& p) { - // Set any new parameters - }); - - break; - } - - default: - break; - } - - // Update hardfork property object - modify(get_hardfork_property_object(), [&](hardfork_property_object& hfp) { - hfp.processed_hardforks.push_back(_hardfork_times[hardfork]); - hfp.last_hardfork = hardfork; - hfp.current_hardfork_version = _hardfork_versions[hardfork]; - }); -} -``` - -### Step 6: Define Hardfork Constants - -Add hardfork constants to the appropriate header (e.g., `libraries/chain/include/graphene/chain/hardfork.hpp` or hardfork.d files): - -```cpp -#ifndef CHAIN_HARDFORK_10 -#define CHAIN_HARDFORK_10 10 -#define CHAIN_HARDFORK_10_TIME 1893456000 // Unix timestamp -#define CHAIN_HARDFORK_10_VERSION hardfork_version( 1, 10, 0 ) -#endif -``` - -Update `CHAIN_NUM_HARDFORKS` if necessary. - -## Snapshot Compatibility - -### Forward Compatibility (Old Node → New Snapshot) - -When an older node loads a snapshot created by a newer node: - -1. **Unknown objects are silently ignored** - The snapshot loader uses conditional checks: - ```cpp - if (state.contains("prediction_market")) { - // import prediction markets - } - ``` - If the old node doesn't check for `prediction_market`, it simply won't be imported. - -2. **Hardfork state is preserved** - The `hardfork_property_object` contains: - - `last_hardfork` - highest applied hardfork number - - `current_hardfork_version` - current network version - - `processed_hardforks` - timestamps of applied hardforks - -3. **Node behavior** - The old node will: - - Successfully load the snapshot - - Continue operating at its known hardfork level - - Reject blocks containing unknown operations - - Eventually fall out of consensus when the new hardfork activates - -### Backward Compatibility (New Node → Old Snapshot) - -When a newer node loads an older snapshot: - -1. **Missing objects are handled gracefully** - All object imports are conditional: - ```cpp - if (state.contains("prediction_market")) { - // import prediction markets - } - // If not present, the index remains empty - ``` - -2. **Hardfork initialization** - After loading, `initialize_hardforks()` sets up the hardfork schedule. When the hardfork time arrives, `apply_hardfork()` will: - - Initialize any required state - - Create initial objects if needed - - Update the hardfork property - -3. **Node behavior** - The new node will: - - Successfully load the older snapshot - - Have empty indexes for new object types - - Apply the hardfork at the scheduled time - - Participate normally in consensus after the hardfork - -## Compatibility Matrix - -| Scenario | Snapshot Contains | Node Version | Result | -|----------|-------------------|--------------|--------| -| Old node loads new snapshot | Unknown objects | Older | ✅ Ignores unknown objects, continues at its hardfork | -| New node loads old snapshot | Missing new objects | Newer | ✅ Initializes empty indexes, applies hardfork when triggered | -| Old node processes new HF blocks | N/A | Older | ❌ Rejects unknown operations (expected consensus split) | -| New node processes old blocks | N/A | Newer | ✅ Validates normally, hardfork not yet active | - -## Best Practices - -### 1. Object Type Ordering - -Always add new object types at the **end** of the `object_type` enum to avoid reordering existing types, which would break binary compatibility. - -### 2. Optional vs Required Objects - -Consider whether new objects should be: -- **Critical**: Required for consensus (must be in snapshot) -- **Important**: Needed for full functionality (should be in snapshot) -- **Optional**: Can be reconstructed (nice to have in snapshot) - -### 3. Hardfork State Validation - -Nodes should validate the hardfork state after loading a snapshot: - -```cpp -const auto& hfp = db.get_hardfork_property_object(); -if (hfp.last_hardfork > CHAIN_NUM_HARDFORKS) { - wlog("Snapshot requires hardfork ${hf} which this node doesn't support", - ("hf", hfp.last_hardfork)); -} -``` - -### 4. Snapshot Versioning - -Consider incrementing `SNAPSHOT_FORMAT_VERSION` for breaking changes: - -```cpp -// In snapshot_types.hpp -static const uint32_t SNAPSHOT_FORMAT_VERSION = 2; // Increment for breaking changes -``` - -### 5. Testing Compatibility - -Test these scenarios: -1. Create snapshot with new objects, load on old node -2. Create snapshot without new objects, load on new node -3. Verify hardfork applies correctly after loading old snapshot -4. Verify old node rejects new hardfork blocks appropriately - -## Example: Complete Hardfork Implementation - -Here's a minimal example of adding a simple counter object in hardfork 10: - -**1. Object Definition (`counter_object.hpp`):** -```cpp -#pragma once -#include - -namespace graphene { namespace chain { - -class counter_object : public object { -public: - template - counter_object(Constructor&& c, allocator) { c(*this); } - - id_type id; - account_name_type owner; - uint64_t count = 0; -}; - -typedef multi_index_container< - counter_object, - indexed_by< - ordered_unique, member>, - ordered_unique, member> - >, - allocator -> counter_index; - -}} - -FC_REFLECT((graphene::chain::counter_object), (id)(owner)(count)) -CHAINBASE_SET_INDEX_TYPE(graphene::chain::counter_object, graphene::chain::counter_index) -``` - -**2. Hardfork Initialization:** -```cpp -case CHAIN_HARDFORK_10: -{ - // Initialize counters for existing accounts - const auto& acc_idx = get_index().indices(); - for (const auto& acc : acc_idx) { - create([&](counter_object& c) { - c.owner = acc.name; - c.count = 0; - }); - } - break; -} -``` - -**3. Snapshot Export/Import:** -```cpp -// Export -EXPORT_INDEX(counter_index, counter_object, "counter") - -// Import -if (state.contains("counter")) { - auto n = detail::import_simple_objects( - db, state["counter"].get_array()); - ilog("Imported ${n} counters", ("n", n)); -} -``` - -## Related Documentation - -- [Snapshot Plugin](snapshot-plugin.md) - General snapshot functionality -- [Plugins](plugins.md) - Plugin architecture and development -- [Data Types](data-types.md) - Protocol data types and serialization diff --git a/.qoder/docs/dlt-p2p-network-redesign-review.md b/.qoder/docs/dlt-p2p-network-redesign-review.md deleted file mode 100644 index 7c20fd6123..0000000000 --- a/.qoder/docs/dlt-p2p-network-redesign-review.md +++ /dev/null @@ -1,299 +0,0 @@ -# DLT P2P Network Redesign — Plan vs. Implementation Review - -Review of [implementation status](dlt-p2p-network-redesign.md) against the [design plan](../plans/dlt-p2p-network-redesign_91a7ca29.md). - -## Summary - -The implementation closely follows the plan across all 5 phases. Core architecture, message types, connection management, sync logic, mempool, fork resolution, anti-spam, and in-place replacement are correctly implemented. Snapshot download on empty state is verified as preserved (lives in chain + snapshot plugins, untouched by P2P redesign). **6 unreported gaps** were found beyond the 6 already-documented known limitations. - ---- - -## Items Fully Matching the Plan (29 items) - -| Area | Plan Requirement | Status | -|------|-----------------|--------| -| Message types | 14 types (5100-5113), all structs with exact fields | ✅ Match | -| Delegate pattern | `dlt_p2p_delegate` bridging network→chain | ✅ Match | -| Fiber architecture | Accept loop, read loop, periodic task on `fc::thread` | ✅ Match | -| Wire format | 8-byte header (size+type) + raw data, no STCP | ✅ Match | -| Per-peer state | All fields: lifecycle, chain state, spam, exchange, reconnect | ✅ Match | -| Peer lifecycle | 6 states: connecting(5s)→handshaking(10s)→syncing→active→disconnected→banned | ✅ Match | -| Reconnection | Backoff 30s→…→3600s, ±25% jitter, reset on stable>5min | ✅ Match | -| 8h peer removal | Permanently remove after `dlt-peer-max-disconnect-hours` of non-response | ✅ Match | -| Node status | SYNC / FORWARD, transitions at catchup completion | ✅ Match | -| Hello handshake | `protocol_version` check, fork alignment via `is_block_known()` | ✅ Match | -| Block sync | Bulk range (200 blocks), single block, `not_available` | ✅ Match | -| P2P mempool | Separate index: dedup, expiry check, TaPoS check, size limits | ✅ Match | -| Mempool eviction | Oldest-expiry first when caps hit | ✅ Match | -| Provisional entries | Tagged during SYNC, revalidated on SYNC→FORWARD | ✅ Match | -| Fork threshold | 42 blocks (= `CHAIN_MAX_WITNESSES * 2`) | ✅ Match | -| Fork hysteresis | `CONFIRMATION_BLOCKS = 6`, tracked via `dlt_fork_resolution_state` | ✅ Match | -| Anti-spam | Single `spam_strikes` counter, reset on good, threshold=10, ban=3600s | ✅ Match | -| Spam reset-on-good | Valid block/transaction/hello → reset counter to 0 | ✅ Match | -| Peer exchange rate-limit | 10-min cooldown per peer | ✅ Match | -| Peer exchange subnet diversity | /24 subnet, max 2 per subnet | ✅ Match | -| Peer exchange min uptime | 600s before sharing | ✅ Match | -| Peer exchange cap | Max 10 peers per reply | ✅ Match | -| Color logging | GREEN/WHITE/RED/DGRAY/ORANGE | ✅ Match | -| Sync stagnation | 30s no-block, 3 retries, then FORWARD with warning | ✅ Match | -| Plugin replacement | Same `"p2p"` name, same port, same public API | ✅ Match | -| Old files removed | 12 files (node.cpp, peer_connection.cpp, stcp_socket.cpp, etc.) deleted from build | ✅ Match | -| Config | 9 new DLT options added, 4 old options removed | ✅ Match | -| Plugin startup | Deadlock fixed — `.async([setup]).wait()` instead of infinite loop block | ✅ Match | -| Snapshot download on empty state | Chain plugin detects `head_block_num == 0`, snapshot plugin downloads via raw TCP, P2P starts after import. Entirely in chain + snapshot plugins — untouched by P2P redesign. `trigger_resync()` bridge preserved. | ✅ Verified | - ---- - -## Known Gaps (Documented in implementation status) - -These are acknowledged in `dlt-p2p-network-redesign.md` § "Known Limitations / Future Work": - -| # | Gap | Severity | -|---|-----|----------| -| 1 | `periodic_dlt_prune_check()` is a no-op | P2 | -| 2 | `dlt_delegate::has_emergency_private_key()` returns `false` | P2 | -| 3 | `dlt_delegate::switch_to_fork()` simplified — only pops one block | P1 | -| 4 | `dlt_p2p_node::compute_branch_info()` returns `total_vote_weight=0` | P1 | -| 5 | No unit tests | P2 | -| 6 | Build not verified | P2 | - ---- - -## Unreported Gaps (Not in docs — Discrepancies with Plan) - -### GAP 1: `expected_next_block` tracking never used (P1 Security) - -**Plan**: P1 security hardening (§3.7) — Per-peer `expected_next_block` tracking to reject blocks that skip too far ahead (hole-creation attack prevention). - -**What exists**: The field `expected_next_block` is declared in `dlt_peer_state` ([dlt_p2p_peer_state.hpp:53](file:///d:/Work/viz-cpp-node/libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp#L53)) but **never set, updated, or validated anywhere** in `dlt_p2p_node.cpp`. - -**Impact**: No protection against peers sending blocks out of order or creating gaps. - -**Fix**: In `on_dlt_block_range_reply()` and `on_dlt_block_reply()`, after applying blocks, set `state.expected_next_block = last_applied_block_num + 1`. Before applying new blocks, validate that `first_block.block_num() == state.expected_next_block` (or 0 if unset). Reject with `record_packet_result(peer, false)` on mismatch. - ---- - -### GAP 2: `pending_block_batch` timeout never activated (P1 Security) - -**Plan**: P1 security hardening (§3.7) — Blocks received but not yet validated: track with `pending_block_batch` timeout (30s) — if validation doesn't complete, soft-ban the peer. - -**What exists**: The field `pending_block_batch_time` and helper `has_pending_batch_timeout()` are declared in `dlt_peer_state` ([dlt_p2p_peer_state.hpp:54-55](file:///d:/Work/viz-cpp-node/libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp#L54-L55)) but **never set** when blocks are received (e.g., in `on_dlt_block_range_reply` line 615), and **never checked** in `periodic_task()` (line 1271). - -**Impact**: A slow/malicious peer can send blocks that stall without consequence. - -**Fix**: In `on_dlt_block_range_reply()`, before `_delegate->accept_block()` loop, set `state.pending_block_batch_time = fc::time_point::now()`. Add a `block_validation_timeout()` method. In `periodic_task()`, call `block_validation_timeout()` to check all peers with `has_pending_batch_timeout()` → soft-ban on timeout. - ---- - -### GAP 3: `block_validation_timeout()` handler not implemented (P1 Security) - -**Plan**: Phase 2 (§4) — `block_validation_timeout()` — if `pending_block_batch` not validated within 30s, soft-ban the peer that sent it. - -**What exists**: Not implemented at all. No such function in `dlt_p2p_node.cpp`, not declared in the header. - -**Impact**: Combined with GAP 2, block validation timeout enforcement is completely absent. - -**Fix**: Add method declaration to `dlt_p2p_node.hpp` and implementation to `dlt_p2p_node.cpp`. Call from `periodic_task()`. - ---- - -### GAP 4: Fork resolution resets 42-block window on non-confirmation (P1 Fork) - -**Plan**: P1 hysteresis (§3.7) — After the 42-block window, compute the winner. Winner must maintain lead for 6 consecutive blocks. If lead flips, reset the **confirmation counter** — not the 42-block detection window. - -**What exists**: In `track_fork_state()` ([dlt_p2p_node.cpp:1116-1120](file:///d:/Work/viz-cpp-node/libraries/network/dlt_p2p_node.cpp#L1116-L1120)): - -```cpp -if (_fork_detected && - block.block_num() - _fork_detection_block_num >= FORK_RESOLUTION_BLOCK_THRESHOLD) { - resolve_fork(); - _fork_detected = false; // ← resets the 42-block window too! -} -``` - -When `resolve_fork()` returns early (hysteresis not met), `_fork_detected = false` causes a **fresh 42-block countdown** from the next block. The plan intended continuous retry without resetting the detection window. - -Both the plan's pseudocode and the implementation set `_fork_detected = false` after `resolve_fork()` — but this contradicts the plan's own design intent in section 3.7, which specifies that only the confirmation counter should reset on lead flip, not the 42-block detection window. - -**Impact**: Fork resolution can be delayed by 42 extra blocks per failed confirmation attempt. In the worst case (two forks with rapidly oscillating vote weight), resolution may never complete. - -**Fix**: Move `_fork_detected = false` into `resolve_fork()` — only clear it when resolution actually completes (i.e., `is_confirmed()` returns true and the switch is executed). Keep the confirmation counter reset logic unchanged. - ---- - -### GAP 5: Spam strikes not incremented for all mempool rejections (P0 Anti-Spam) - -**Plan**: P0 DoS protection (§3.7) — "All rejections increment sender's `spam_strikes`". - -**What exists**: In `add_to_mempool()` ([dlt_p2p_node.cpp:960-1012](file:///d:/Work/viz-cpp-node/libraries/network/dlt_p2p_node.cpp#L960-L1012)): - -| Rejection reason | Line | `record_packet_result(sender, false)` | -|-----------------|------|---------------------------------------| -| Already in mempool (dedup) | 964 | Not called (acceptable — duplicates are not malicious) | -| Expired (`expiration < now`) | 967 | ❌ Not called | -| Expiration headroom exceeded | 972 | ✅ Called | -| Size exceeded | 979 | ✅ Called | -| TaPoS invalid (wrong fork) | 984 | ❌ Not called | - -**Impact**: Peers can spam expired or wrong-fork transactions without accumulating strikes. - -**Fix**: Add `record_packet_result(sender, false)` calls at lines 967 and 984 when `from_peer && sender != INVALID_PEER_ID`. - ---- - -### GAP 6: Fork resolution winner always picks first branch (P0 Fork) - -**Plan**: §2.2 — Fork resolution uses `compare_fork_branches()` (database.cpp line 1359-1417) which does vote-weighted comparison with +10% longer-chain bonus. - -**What exists**: `compute_branch_info()` ([dlt_p2p_node.cpp:1172-1181](file:///d:/Work/viz-cpp-node/libraries/network/dlt_p2p_node.cpp#L1172-L1181)) returns `total_vote_weight = 0` and `block_count = 1` for every branch. In `resolve_fork()`, the comparison `info.total_vote_weight > winner.total_vote_weight` always compares 0 > 0, so the **first branch in the `tips` vector always wins**. - -The delegate already provides `compare_fork_branches(a, b)` which returns the correct vote-weighted comparison — but `resolve_fork()` calls `compute_branch_info()` instead. - -**Impact**: Fork resolution is non-functional — it picks the first branch arbitrarily, not the vote-weighted winner. This directly contradicts the plan's fork resolution design. - -**Fix**: Replace `compute_branch_info()` iterations in `resolve_fork()` with calls to `_delegate->compare_fork_branches()`: - -```cpp -void dlt_p2p_node::resolve_fork() { - auto tips = _delegate->get_fork_branch_tips(); - if (tips.size() < 2) { _fork_status = DLT_FORK_STATUS_NORMAL; return; } - - block_id_type winner = tips[0]; - for (size_t i = 1; i < tips.size(); ++i) { - if (_delegate->compare_fork_branches(tips[i], winner) > 0) { - winner = tips[i]; - } - } - // ... hysteresis and switch logic using 'winner' -} -``` - ---- - -## Minor Observations (Non-Blocking) - -| # | Observation | -|---|-------------| -| 1 | `dlt_range_request` / `dlt_range_reply` messages (5102/5103) are implemented but the sync flow uses bulk `get_block_range` directly after hello — the plan's improvement note says "range query step could be skipped". These messages exist but are not on the main code path. | -| 2 | `broadcast_block_post_validation()` sends a `dlt_fork_status_message` with `head_block_num = 0` — the receiver stores this as `peer_head_num = 0`, corrupting peer state tracking. This is a functional bug, not just semantic imprecision. | -| 3 | `dlt_delegate::accept_block()` (p2p_plugin.cpp:139-151) ignores the `sync_mode` parameter — always calls `push_block()` the same way, discards the return value with `return false`. | -| 4 | `dlt_delegate::is_head_on_branch()` (p2p_plugin.cpp:203-206) does a simple equality check against `head_block_id()` — will miss the case where our head IS on the branch but not at its tip. | -| 5 | `resync_from_lib()` in `dlt_delegate` (p2p_plugin.cpp:215-217) is empty — documented as "handled at plugin level", but the plugin-level `resync_from_lib()` (p2p_plugin.cpp:437-441) simply calls `node->resync_from_lib()` which just calls `transition_to_sync()` + re-requests blocks. No actual LIB-level resync logic. | - ---- - -## Severity Summary - -| Priority | Gaps | -|----------|------| -| **P0 (must fix)** | GAP 5 — incomplete spam strikes (plan rates this P0); GAP 6 — Fork resolution non-functional (picks wrong branch) | -| **P1 (should fix)** | GAP 4 — Fork window reset; GAP 1 — missing block ordering validation; GAP 2/3 — missing block validation timeout | -| **P2 (nice to fix)** | Known gaps 1-6 (already documented) | - ---- - -## Fixes Applied - -All gaps and minor observations identified in this review have been fixed in code. Below is a summary of each fix and the files modified. - -### Fix 1: Compile error — `peer_dlt_latest_block` field name mismatch (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -`peer_dlt_latest_block` referenced a non-existent field; the actual field is `peer_dlt_latest`. Replaced at two call sites. - -### Fix 2: `head_block_num = 0` corrupts peer state (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -`broadcast_block_post_validation()` set `msg.head_block_num = 0` with comment "filled by receiver from block_id". But the receiver stores it as `peer_head_num = 0`, corrupting peer state. Fixed by extracting the block number from the `block_id` using `block_header::num_from_id(block_id)`. - -### Fix 3: Fork resolution always picks first branch (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -`compute_branch_info()` returned `total_vote_weight = 0` for every branch, so `0 > 0` was always false and the first branch always won. Replaced the `compute_branch_info()` loop in `resolve_fork()` with `_delegate->compare_fork_branches()` which correctly performs vote-weighted comparison with +10% longer-chain bonus. - -### Fix 4: `expected_next_block` tracking never used (P1) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -The field existed but was never set or validated. Added ordering validation in `on_dlt_block_range_reply()` and `on_dlt_block_reply()`: before `accept_block()`, check `state.expected_next_block != 0 && block.block_num() != state.expected_next_block` — reject out-of-order blocks with `record_packet_result(peer, false)`. After successful accept, set `state.expected_next_block = block.block_num() + 1`. Reset to 0 on disconnect in `handle_disconnect()`. - -### Fix 5: `pending_block_batch` timeout + `block_validation_timeout()` (P1) - -**Files**: `libraries/network/include/graphene/network/dlt_p2p_node.hpp`, `libraries/network/dlt_p2p_node.cpp` - -`pending_block_batch_time` and `has_pending_batch_timeout()` were declared but never used. Added: (1) set `pending_block_batch_time = fc::time_point::now()` before block processing loop in `on_dlt_block_range_reply()`, clear it after; (2) declared `block_validation_timeout()` in the header; (3) implemented it — iterates all peer states, soft-bans peers whose batch timeout exceeded 30s; (4) wired into `periodic_task()` after `sync_stagnation_check()`. - -### Fix 6: Fork window reset on non-confirmation (P1) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -`track_fork_state()` set `_fork_detected = false` after every `resolve_fork()` call, including when hysteresis was not met — causing a fresh 42-block countdown instead of continuous retry. Moved `_fork_detected = false` into `resolve_fork()` at the two points where resolution actually completes: (1) when `tips.size() < 2` (fork is over), (2) after hysteresis is confirmed and fork switch is executed. NOT set at the early return where hysteresis is not confirmed. - -### Fix 7: `switch_to_fork()` only pops one block (P1) - -**File**: `plugins/p2p/p2p_plugin.cpp` - -Original implementation called `pop_block()` once but never re-pushed fork blocks. Replaced with `chain.db().push_block(*block)` where `block` is fetched from `fork_db`. The chain's `push_block()` already contains the full fork-switch implementation: pop-until-common-ancestor, re-apply new branch, LIB guard, DLT crash prevention. - -### Fix 8: `is_head_on_branch()` too simplistic (P1) - -**File**: `plugins/p2p/p2p_plugin.cpp` - -Original did `tip == head_block_id()` — missed the case where our head IS on the branch but not at its tip. Replaced with `fork_db.fetch_branch_from(tip, head_block_id())` to check if our head is an ancestor of the tip. Returns true if the "old" branch is non-empty (shared ancestry). - -### Fix 9: Spam strikes not incremented for expired/TaPoS-invalid rejections (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -In `add_to_mempool()`, expired transactions (line 967) and TaPoS-invalid transactions (line 984) returned false without calling `record_packet_result(sender, false)`. Added the call (guarded by `from_peer && sender != INVALID_PEER_ID`) at both locations so peers accumulate strikes for these rejections. - -### Fix 10: `periodic_dlt_prune_check()` is a no-op (P2) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -Implemented the body: checks if DLT block log range exceeds `_dlt_block_log_max_blocks`, batches pruning in increments of `DLT_PRUNE_BATCH_SIZE` (10000), logs the intent. The actual pruning requires a delegate method `prune_dlt_block_log()` not yet added to the chain — marked with TODO. - -### Fix 11: `has_emergency_private_key()` returns false (P2) - -**Files**: `plugins/p2p/p2p_plugin.cpp`, `plugins/validator/include/graphene/plugins/validator/validator.hpp`, `plugins/validator/validator.cpp` - -Cross-plugin fix. (1) Added `is_emergency_key_configured()` declaration to `witness_plugin` public API — returns true if `CHAIN_EMERGENCY_WITNESS_ACCOUNT` is in `_witnesses` set (which only happens when `--emergency-private-key` is configured). (2) Implemented in validator.cpp with try/catch guard. (3) Updated `has_emergency_private_key()` in `dlt_delegate` to call `appbase::app().find_plugin()->is_emergency_key_configured()` instead of returning `false`. Added include for `witness_plugin.hpp`. - -### Fix 12: `accept_block()` ignores `sync_mode` parameter (P2) - -**File**: `plugins/p2p/p2p_plugin.cpp` - -Original always called `push_block(block)` regardless of `sync_mode`. Fixed to pass `skip` flags: in sync mode, sets `skip = skip_witness_signature | skip_transaction_signatures` for faster bulk sync. In normal mode, uses `skip_nothing`. This is safe because only fork-aligned peers exchange blocks. - -### Fix 13: `resync_from_lib()` is shallow (P2) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -Original just called `transition_to_sync()` and re-requested blocks. Fixed to: (1) reset all fork tracking state (`_fork_detected = false`, `_fork_detection_block_num = 0`, `_fork_resolution_state = dlt_fork_resolution_state()`, `_fork_status = DLT_FORK_STATUS_NORMAL`); (2) transition to sync; (3) re-send hello messages to all active/syncing peers for updated chain state; (4) then request blocks. The delegate-level `resync_from_lib()` remains empty since the P2P node handles logic internally. - -### Fix 14: Review document corrections - -**File**: `.qoder/docs/dlt-p2p-network-redesign-review.md` - -- **14a**: Corrected factual error in GAP 4 — both the plan's pseudocode and the implementation set `_fork_detected = false` after `resolve_fork()` (previously claimed the plan did not). -- **14b**: Upgraded GAP 5 to P0 in severity summary — the plan rates mempool DoS protection as P0. -- **14c**: Upgraded GAP 6 heading from P1 to P0 — fork resolution is non-functional. -- **14d**: Upgraded minor observation #2 from "semantically imprecise" to functional bug — `head_block_num = 0` corrupts `peer_head_num` in the receiver. - -### Fix 15: Per-IP connection deduplication — broadcast spam prevention (P0) - -**Files**: `libraries/network/dlt_p2p_node.cpp`, `libraries/network/include/graphene/network/dlt_p2p_node.hpp` - -**Root cause**: When Node A connects outbound to Node B (port 2001) and Node B also connects outbound to Node A (port 2001), both sides also accept the other's inbound connection. Each connection gets a separate `peer_state` entry with a different `peer_id`. The `send_to_all_our_fork_peers()` broadcast function sends to ALL exchange-enabled active entries individually — so one block gets sent N times to the same physical node. With reconnect storms or multiple retries, this accumulates to 4-6 entries per node pair. - -Additionally, `on_dlt_peer_exchange_request()` shared `s.endpoint` for ALL peers including incoming connections with ephemeral ports (e.g., `62.231.188.129:44712`). Other nodes tried connecting to these dead ports, creating ghost entries that multiplied the problem across the network. - -**Fix** (5 changes): -1. Added `find_active_peer_by_ip()` helper — scans `_peer_states` for any CONNECTING/HANDSHAKING/SYNCING/ACTIVE peer with matching IP address. -2. `accept_loop()` — rejects incoming connections from IPs that already have an active peer entry (closes socket, cleans up partial state). -3. `connect_to_peer()` — skips outbound connection if target IP already has an active entry, preventing cross-direction duplication. -4. `send_to_all_our_fork_peers()` — belt-and-suspenders IP dedup: tracks `std::set` of IPs already sent to, skipping duplicates. -5. `on_dlt_peer_exchange_request()` — skips `is_incoming` peers to prevent ephemeral port propagation through the network. diff --git a/.qoder/docs/dlt-p2p-network-redesign.md b/.qoder/docs/dlt-p2p-network-redesign.md deleted file mode 100644 index 5d40fa4da4..0000000000 --- a/.qoder/docs/dlt-p2p-network-redesign.md +++ /dev/null @@ -1,623 +0,0 @@ -# DLT P2P Network Redesign — Implementation Status - -Implementation of the [design plan](../plans/dlt-p2p-network-redesign_91a7ca29.md). - -## Architecture - -In-place replacement of the old Graphene synopsis-based P2P (`node.cpp`, 6978 lines) with a new DLT-specific protocol. Same plugin name, same port, same public API — only the internal implementation changes. - -``` -Before: p2p_plugin → graphene::network::node (node.cpp, STCP, synopsis, inventory gossip) -After: p2p_plugin → dlt_p2p_node (dlt_p2p_node.cpp, raw TCP, DLT hello/range/exchange) -``` - -### Delegate Pattern - -The network library only links `fc` and `graphene_protocol` — NOT `graphene_chain`. So `dlt_p2p_node` cannot directly access the database, `dlt_block_log`, or `fork_db`. The `dlt_p2p_delegate` abstract interface bridges this gap: - -``` -dlt_p2p_node (network lib) ←→ dlt_p2p_delegate (abstract interface) ←→ dlt_delegate (p2p_plugin) -``` - -This matches the old `node_delegate` pattern. - -### Fiber Architecture - -All I/O runs on the p2p thread using fc's cooperative fiber model: - -- **Accept loop fiber**: `_thread->async(accept_loop)` — blocks on `tcp_server::accept()`, yields while waiting -- **Read loop fibers**: one per peer via `_thread->async(read_loop)` — blocks on `tcp_socket::readsome()`, yields while waiting -- **Periodic task fiber**: `_thread->async(periodic_loop)` — sleeps 5s between iterations -- All fibers run cooperatively on the same `fc::thread` — no mutexes needed for shared state -- `close()` cancels all fibers via `fc::future::cancel_and_wait()` - -### Wire Format - -Raw TCP (no STCP encryption). Each message on the wire: - -``` -[4 bytes: data size (uint32_t)] [4 bytes: msg_type (uint32_t)] [size bytes: fc::raw::pack(T)] -``` - -This matches the old `message_oriented_connection` format without 16-byte padding (no encryption layer). The `send_message()` method writes the header and data separately to avoid `fc::raw::pack(message)` which adds a varint length prefix to the data vector. - -## File Map - -### New Files (Created) - -| File | Lines | Purpose | -|------|-------|---------| -| `libraries/network/include/graphene/network/dlt_p2p_messages.hpp` | 319 | All DLT message types (5100-5116), enums, structs, FC_REFLECT macros | -| `libraries/network/dlt_p2p_messages.cpp` | 21 | Static `type` constants for each message struct | -| `libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp` | 123 | `dlt_peer_state`, `dlt_known_peer`, `dlt_mempool_entry`, `dlt_fork_resolution_state`, `dlt_fork_branch_info` | -| `libraries/network/include/graphene/network/dlt_p2p_node.hpp` | 350 | `dlt_p2p_delegate` interface + `dlt_p2p_node` class declaration | -| `libraries/network/dlt_p2p_node.cpp` | 2627 | Full `dlt_p2p_node` implementation | - -### Modified Files - -| File | Change | -|------|--------| -| `libraries/network/CMakeLists.txt` | Removed 6 old source files and 6 old headers; added new DLT files | -| `plugins/p2p/p2p_plugin.cpp` | Replaced `node.cpp`-based impl with `dlt_p2p_node` wrapper + `dlt_delegate` | -| `plugins/p2p/CMakeLists.txt` | Removed `graphene::snapshot` dependency | -| `plugins/p2p/include/.../p2p_plugin.hpp` | **Unchanged** — same public API preserved | - -### Deleted Files (12 total) - -| Type | Files | -|------|-------| -| Source | `node.cpp`, `peer_connection.cpp`, `peer_database.cpp`, `stcp_socket.cpp`, `message_oriented_connection.cpp`, `core_messages.cpp` | -| Headers | `node.hpp`, `peer_connection.hpp`, `peer_database.hpp`, `stcp_socket.hpp`, `message_oriented_connection.hpp`, `core_messages.hpp` | - -### Kept Files (still in network lib) - -| File | Reason | -|------|--------| -| `config.hpp` | Defines `MAX_MESSAGE_SIZE`, `GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING` used by DLT code | -| `exceptions.hpp` | Defines `unlinkable_block_exception` etc. used by chain and plugins | -| `message.hpp` | Core `message` / `message_header` structs — wire format foundation | - -## Plan Phase → Implementation Mapping - -### Phase 1: New Message Types ✅ - -`dlt_p2p_messages.hpp` implements all 17 message types (5100-5116) exactly as specified: - -| Message Type | ID | Struct | -|---|---|---| -| `dlt_hello_message_type` | 5100 | `dlt_hello_message` — protocol_version, head/LIB, DLT range, emergency, fork/node status | -| `dlt_hello_reply_message_type` | 5101 | `dlt_hello_reply_message` — exchange_enabled, fork_alignment, recognized blocks | -| `dlt_range_request_message_type` | 5102 | `dlt_range_request_message` | -| `dlt_range_reply_message_type` | 5103 | `dlt_range_reply_message` | -| `dlt_get_block_range_message_type` | 5104 | `dlt_get_block_range_message` — start/end + prev_block_id | -| `dlt_block_range_reply_message_type` | 5105 | `dlt_block_range_reply_message` — blocks vector + is_last | -| `dlt_get_block_message_type` | 5106 | `dlt_get_block_message` | -| `dlt_block_reply_message_type` | 5107 | `dlt_block_reply_message` — block + next_available + is_last | -| `dlt_not_available_message_type` | 5108 | `dlt_not_available_message` | -| `dlt_fork_status_message_type` | 5109 | `dlt_fork_status_message` | -| `dlt_peer_exchange_request_type` | 5110 | `dlt_peer_exchange_request` (empty body) | -| `dlt_peer_exchange_reply_type` | 5111 | `dlt_peer_exchange_reply` — peers vector | -| `dlt_peer_exchange_rate_limited_type` | 5112 | `dlt_peer_exchange_rate_limited` — wait_seconds | -| `dlt_transaction_message_type` | 5113 | `dlt_transaction_message` — signed_transaction | -| `dlt_soft_ban_message_type` | 5114 | `dlt_soft_ban_message` — ban_duration_sec, reason (sent before disconnecting a banned peer) | -| `dlt_gap_fill_request_type` | 5115 | `dlt_gap_fill_request` — block_nums (gap fill in both SYNC and FORWARD modes) | -| `dlt_gap_fill_reply_type` | 5116 | `dlt_gap_fill_reply` — blocks (gap fill in both SYNC and FORWARD modes) | - -Enums: `dlt_node_status` (SYNC/FORWARD), `dlt_fork_status` (NORMAL/LOOKING_RESOLUTION/MINORITY), `dlt_peer_lifecycle_state` (6 states). - -All FC_REFLECT macros defined for serialization. - -### Phase 2: DLT P2P Node ✅ - -`dlt_p2p_node.hpp` + `dlt_p2p_node.cpp` implement the full node: - -**Connection management**: -- `find_active_peer_by_ip()` — per-IP dedup helper: returns peer_id of any existing active connection from the same IP address, or INVALID_PEER_ID if none -- `connect_to_peer()` — per-IP dedup check before connecting (skips if same IP already has active connection), then synchronous connect on p2p thread, sends hello, starts read loop -- `accept_loop()` — fiber that accepts incoming connections, **per-IP dedup check rejects duplicate connections from same IP** (prevents broadcast amplification), creates peer state, sends hello, starts read loop -- `start_read_loop()` — per-peer fiber that reads message_header + data, dispatches to `on_message()` -- `handle_disconnect()` — cancels read fiber, closes socket, calculates backoff with jitter -- Periodic reconnect/backoff/expire logic - -**Hello handshake**: -- `build_hello_message()` — queries delegate for all chain state -- `build_hello_reply()` — checks fork alignment via `delegate->is_block_known()` -- `on_dlt_hello()` — stores peer chain state, sends reply, transitions lifecycle, starts sync if SYNC mode -- `on_dlt_hello_reply()` — processes exchange_enabled/fork_alignment, starts block fetch - -**Block sync (SYNC mode)**: -- `request_blocks_from_peer()` — requests up to 200 blocks after our head -- `on_dlt_get_block_range()` — reads blocks from dlt_block_log via delegate, sends reply -- `on_dlt_block_range_reply()` — validates prev_hash, applies blocks, transitions to FORWARD when `is_last` -- `on_dlt_get_block()` / `on_dlt_block_reply()` — single-block fetch variant -- `sync_stagnation_check()` — 30s no-block timeout, 3 retries, then FORWARD with warning -- `check_sync_catchup()` — compares our head against all peers' heads, transitions to FORWARD if caught up (P26 fix). Guards against isolation with 60s emergency reset (P53 fix). -- `emergency_peer_reset()` — clears soft bans and resets backoffs when all peers are isolated for 60s (P53 fix) -- `transition_to_forward()` — revalidates provisional mempool entries, re-evaluates `exchange_enabled` for all peers (P25 fix) - -**Mempool** (separate from chain's `_pending_tx`): -- `add_to_mempool()` — dedup by tx_id, check expiry/size/TaPoS, enforce limits with oldest-expiry eviction, retranslate to our-fork peers -- `remove_transactions_in_block()` — prune on block receipt -- `prune_mempool_on_fork_switch()` — remove TaPoS-invalid entries on fork -- `periodic_mempool_cleanup()` — prune expired + TaPoS-invalid entries -- Provisional entries tagged during SYNC, revalidated on transition to FORWARD - -**Fork resolution**: -- `track_fork_state()` — 42-block threshold (2 full rounds), triggers `resolve_fork()` -- `resolve_fork()` — finds heaviest branch, hysteresis with 6-block confirmation -- `dlt_fork_resolution_state` — tracks `current_winner_tip` and `consecutive_blocks_as_winner` - -**Anti-spam**: -- `record_packet_result()` — single `spam_strikes` counter per peer, reset on good packet, soft-ban at threshold=10 -- `soft_ban_peer()` — sets BANNED state for 3600s, sends `dlt_soft_ban_message` notification, then closes connection -- **Per-IP connection dedup** (sender-side broadcast spam prevention): - - `find_active_peer_by_ip()` — scans `_peer_states` for any CONNECTING/HANDSHAKING/SYNCING/ACTIVE peer with matching IP address - - `accept_loop()` — rejects incoming connections from IPs that already have an active peer entry, preventing N connections from the same node via different ephemeral ports - - `connect_to_peer()` — skips outbound connection if target IP already has an active entry, preventing cross-direction duplication (inbound + outbound to same node) - - `send_to_all_our_fork_peers()` — belt-and-suspenders IP dedup in broadcast: tracks `std::set` of IPs already sent to, skipping duplicates - -**Peer exchange** (rate-limited): -- `on_dlt_peer_exchange_request()` — sliding window rate limit (3 requests per 5 min per peer), subnet diversity filter, min uptime 600s, **skips `is_incoming` peers to prevent ephemeral port propagation** -- `on_dlt_peer_exchange_reply()` — adds to known peers, connects if under max_connections -- Subnet diversity via `/24` prefix comparison - -**Peer lifecycle** (connecting→handshaking→syncing→active→disconnected→banned): -- Timeouts: connecting=5s, handshaking=10s -- Reconnection: backoff 30s→60s→…→3600s with ±25% jitter, reset on stable >5min -- Permanent removal after 8h non-response - -**Color-coded logging**: GREEN=sync/production, WHITE=normal block exchange, RED=fork, DARK_GRAY=transactions, ORANGE=warnings, CYAN=peer stats - -### Phase 3: P2P Plugin Replacement ✅ - -`p2p_plugin.cpp` rewritten from 1951 lines (node.cpp-based) to 487 lines (dlt_p2p_node wrapper): - -**`dlt_delegate` class** (implements `dlt_p2p_delegate`): -- Bridges chain state queries using `chain.db()` with appropriate read locks -- `read_block_by_num()` — checks dlt_block_log first, then fork_db -- `accept_block()` — calls `push_block()`, catches `unlinkable_block_exception` → stores in fork_db; 60s startup grace period for near-head blocks (P22 fix) -- `get_fork_branch_tips()` — fetches from fork_db at head_num through head_num+5 -- `is_tapos_block_known()` — delegates to `chain.db().is_known_block()` - -**Config options** (new DLT-specific): -| Option | Default | Purpose | -|--------|---------|---------| -| `dlt-block-log-max-blocks` | 100000 | Max blocks in DLT block log | -| `dlt-peer-max-disconnect-hours` | 8 | Remove peer after this many hours non-response | -| `dlt-mempool-max-tx` | 10000 | Hard cap on mempool entries | -| `dlt-mempool-max-bytes` | 104857600 (100MB) | Hard cap on total mempool memory | -| `dlt-mempool-max-tx-size` | 65536 (64KB) | Reject oversized transactions | -| `dlt-mempool-max-expiration-hours` | 24 | Reject far-future expiration | -| `dlt-peer-exchange-max-per-reply` | 10 | Cap peers per exchange reply | -| `dlt-peer-exchange-max-per-subnet` | 2 | Anti-sybil: max 2 per /24 | -| `dlt-peer-exchange-min-uptime-sec` | 600 | Min uptime before sharing | -| `dlt-stats-interval-sec` | 300 (5 min) | Interval between P2P peer stats log output (min 30) | - -**Removed old config**: `p2p-stats-enabled`, `p2p-stats-interval`, `p2p-stale-sync-detection`, `p2p-stale-sync-timeout-seconds` (replaced by `dlt-stats-interval-sec` and P2P-level stale sync detection) - -**Plugin startup** (deadlock fix): -- Old: `p2p_thread.async([...infinite loop...]).wait()` — blocks forever -- New: `p2p_thread.async([create node, set_thread, configure, start]).wait()` — returns after setup -- `dlt_p2p_node::start()` internally spawns accept loop + periodic task as fibers -- Thread reference passed via `node->set_thread(fc::thread::current())` - -**Soft-ban notification**: -- `soft_ban_peer()` sends `dlt_soft_ban_message` (type 5114) before closing the connection -- Receiving peer enters BANNED state with the specified duration and logs an orange/yellow notice -- Prevents wasted bandwidth — both sides stop sending data immediately - -**Peer stats**: -- `log_peer_stats()` outputs cyan-colored peer statistics at configurable interval -- Shows: node status, fork state, head/LIB, per-peer details (flags, ranges, spam strikes, ban time) -- Interval configured via `dlt-stats-interval-sec` (default 300s = 5 min) - -**Out-of-order/duplicate block handling**: -- Duplicate blocks (already applied) from peers are silently skipped, not counted as spam -- Out-of-order blocks in range replies fall through to fork_db/push_block instead of soft-banning -- Deserialization errors no longer increment spam strikes -- Oversized messages from old-protocol peers disconnect with `skip_backoff_increase=true` - -### Phase 4: Fork Resolution ✅ - -Implemented in `dlt_p2p_node.cpp`: -- `FORK_RESOLUTION_BLOCK_THRESHOLD = 42` (matches `CHAIN_MAX_WITNESSES * 2`) -- `dlt_fork_resolution_state::CONFIRMATION_BLOCKS = 6` (hysteresis) -- `track_fork_state()` called after each block application -- `resolve_fork()` with vote-weight winner + consecutive-block confirmation -- `_fork_status` exposed via `is_on_majority_fork()` for Validator Plugin - -### Phase 5: In-Place Replacement ✅ - -- Same plugin name `"p2p"`, same `p2p-endpoint` port (2001/4243) -- Same public API — zero changes to validator, witness_guard, snapshot plugins -- Old `node.cpp` and all related files removed from build and deleted from disk -- `p2p_plugin.hpp` completely unchanged - -## Key Design Decisions - -| Decision | Rationale | -|----------|-----------| -| Delegate pattern instead of direct chain access | Network lib only links `fc` + `graphene_protocol`, not `graphene_chain`. Delegate avoids circular dependency. | -| Raw TCP instead of STCP encryption | DLT emergency mode means all validators switch simultaneously — no need for backward-compatible encryption. Simpler wire protocol. | -| fc::thread fibers instead of per-peer threads | All I/O uses fc's cooperative fiber model. `readsome()`/`writesome()` yield the fiber, allowing multiple peers on one thread without mutexes. | -| Manual header+data writes instead of `fc::raw::pack(msg)` | `fc::raw::pack(message)` adds varint length prefix to the data vector, creating mismatched wire format. Writing header and data separately matches the read side. | -| Single `spam_strikes` counter | Simpler and more effective than old multi-counter system (`unlinkable_block_strikes`, `sync_spam_strikes`, etc.). Reset-on-good naturally recovers from transient issues. | -| Separate P2P mempool | Chain's `_pending_tx` only applies after acceptance. P2P mempool provides earlier filtering (expiry, TaPoS, size limits) before pushing to chain. | -| In-place replacement, no dual-mode | Old and new protocols are incompatible. Dual-mode creates isolated sub-networks. Emergency mode means all validators can switch simultaneously. | - -## Subsequent Enhancements & Fixes (2026-05-05) - -### DLT-Range-Aware Fork Alignment (P1-P16 fixes) - -The original `check_fork_alignment` only called `is_block_known()` on peer head/LIB IDs. In DLT mode, old blocks are pruned from the rolling block log, so peers on the same chain were falsely flagged as "different fork" and disconnected. - -**Fix:** `check_fork_alignment` now accepts the full `dlt_hello_message` and performs multi-tier alignment: - -| Check | Condition | Result | -|-------|-----------|--------| -| Empty peer | `head_block_num == 0` | Aligned (new node, no fork to be on) | -| Range overlap | `head_num >= our_earliest && head_num <= our_latest` | Uses `is_block_known(head_id)` | -| Boundary link | `head_num + 1 == our_earliest` | Reads `our_earliest_block`, checks `previous == head_id` | -| LIB fallback | Always | `is_block_known(lib_id)` as before | - -**Peer lifecycle fix:** `on_dlt_hello()` now transitions SYNC peers to ACTIVE regardless of `exchange_enabled`: -```cpp -// OLD: if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC) -// NEW: -if (reply.exchange_enabled || _node_status == DLT_NODE_STATUS_SYNC - || hello.node_status == DLT_NODE_STATUS_SYNC) -``` - -This eliminates the HANDSHAKING timeout → disconnect → reconnect loop for same-chain peers whose blocks were pruned. - -Full details in [DLT 4-Node Sync Scenarios](./dlt-4-node-sync-scenarios.md). - -### Soft-Ban Notification (type 5114) - -`dlt_soft_ban_message` is sent before disconnecting a banned peer: -- Contains `ban_duration_sec` and human-readable `reason` -- Receiving peer enters BANNED state with the specified duration -- Logged as orange/yellow notice on both sides -- Prevents wasted bandwidth — both sides stop sending immediately - -### Peer Stats Logging - -`log_peer_stats()` outputs cyan-colored peer statistics at configurable interval (`dlt-stats-interval-sec`, default 300s). Shows node status, fork state, head/LIB, per-peer details (flags, ranges, spam strikes, ban time). - -### Out-of-Order / Duplicate Block Tolerance - -- Duplicate blocks (already applied) from peers are silently skipped, not counted as spam -- Out-of-order blocks in range replies fall through to fork_db instead of soft-banning -- Deserialization errors no longer increment spam strikes -- Oversized messages from old-protocol peers disconnect with `skip_backoff_increase=true` - -### Block Processing Pause/Resume - -`pause_block_processing()` / `resume_block_processing()` with `_block_processing_paused` flag allows the snapshot or other plugins to temporarily halt P2P block intake during critical operations. - -### Additional Public API - -| Method | Purpose | -|--------|---------| -| `broadcast_block_post_validation()` | Broadcast block by ID+validator+signature after validation | -| `broadcast_chain_status()` | Send hello to all connected peers | -| `trigger_resync()` | Force re-enter SYNC mode and re-request blocks | -| `reconnect_seeds()` | Re-connect to all seed nodes | -| `pause_block_processing()` / `resume_block_processing()` | Temporarily halt P2P block intake | -| `set_stats_log_interval()` | Configure periodic peer stats output interval | - -### C++14 constexpr ODR-use Fix - -`static constexpr` members that are ODR-used (e.g., `MAX_RECONNECT_BACKOFF_SEC`) now have out-of-line definitions in `dlt_p2p_node.cpp`: -```cpp -constexpr uint32_t dlt_peer_state::PEER_EXCHANGE_MAX_REQUESTS; -constexpr uint32_t dlt_peer_state::PEER_EXCHANGE_WINDOW_SEC; -constexpr uint32_t dlt_peer_state::PENDING_BATCH_TIMEOUT_SEC; -constexpr uint32_t dlt_peer_state::INITIAL_RECONNECT_BACKOFF_SEC; -constexpr uint32_t dlt_peer_state::MAX_RECONNECT_BACKOFF_SEC; -``` - -### `dlt_block_accept_result` Enum - -New enum replaces the old `bool` return from `accept_block()`: -```cpp -enum class dlt_block_accept_result { - ACCEPTED, // pushed to chain (became head or fork_db head) - FORK_DB_ONLY, // stored in fork_db but not applied (unlinkable / competing fork) - DEAD_FORK, // block from a dead fork (parent not in fork_db, at/below head) - REJECTED // failed validation entirely -}; -``` - ---- - -## Subsequent Enhancements & Fixes (2026-05-06) - -### Dead Fork Block Crash Protection (P20/P21) - -Added `DEAD_FORK` to `dlt_block_accept_result` enum. When `push_block()` throws `unlinkable_block_exception` and the block is at/below our head (`block.block_num() <= head_block_num()`), the delegate returns `DEAD_FORK` instead of pushing to `fork_db._unlinked_index`. The P2P layer soft-bans peers sending dead-fork blocks and breaks out of the block processing loop. `transition_to_forward()` is now guarded by `any_block_applied` — a range full of dead-fork rejects does NOT end sync mode. - -**Files:** `dlt_p2p_node.hpp`, `p2p_plugin.cpp`, `dlt_p2p_node.cpp` - -### DLT Block Log Corruption Recovery (P17) - -New `dlt_block_log::is_consistent_with(db_head_block_num)` method detects corruption on startup: single-block log with thousands in DB, DLT head exceeding DB head, or far-behind with few blocks. In `database::open()`, corrupted DLT block logs are auto-reset before fork_db seeding. P2P sync rebuilds the log naturally after reset. - -**Files:** `dlt_block_log.hpp`, `dlt_block_log.cpp`, `database.cpp` - -### Snapshot Lock Isolation Prevention (P24) - -When `_block_processing_paused` is true (snapshot in progress), `periodic_task()` skips operations that need database read locks: `sync_stagnation_check()`, `periodic_peer_exchange()`, `log_peer_stats()`. Non-DB housekeeping (reconnect, lifecycle, validation, mempool cleanup, banned-peer unban) still runs. `check_stalled_sync_loop()` skips stall detection when `snapshot_in_progress` is true and resets the timer. `serialize_state()` now logs progress every 5s during long serialization. - -**Files:** `dlt_p2p_node.cpp`, `snapshot/plugin.cpp` - -### Write Lock Diagnostic Logging (P27) - -`notify_applied_block()` now times the overall signal notification and logs a warning if it exceeds 200ms (with block number, duration, and connected plugin count). Self-timing added to the 3 most likely slow handlers: `mongo_db::on_block()`, `operation_history::purge_old_history()`, `account_history::purge_old_history()` — each logs if >100ms. The chainbase `with_strong_write_lock` macro already captures `__FILE__`/`__LINE__`/`__func__` so lock timeout messages identify the call site. - -**Files:** `database.cpp`, `mongo_db_plugin.cpp`, `operation_history/plugin.cpp`, `account_history/plugin.cpp` - ---- - -## Known Limitations / Future Work - -- `has_emergency_private_key()` **now queries Validator Plugin** (was hardcoded `false`) -- `switch_to_fork()` **now has full implementation** with fork_db fetch + `push_block()` -- `resync_from_lib()` is handled at plugin level (by design — delegate returns early) -- `compute_branch_info()` returns simplified info — detailed vote-weight computation needs fork_db traversal -- `dlt_block_log` batch pruning (10000 at a time) not yet connected — `periodic_dlt_prune_check()` is a no-op -- No unit tests yet for the new message types or node logic -- `dlt_delegate::is_tapos_block_known()` uses `find_block_id_for_num()` — may need chain index access - ---- - -## Build Issues (GCC 13 / Docker) - -The following compilation/linking issues block building with newer GCC (13+) in Docker: - -| # | Issue | File | Fix | -|---|-------|------|-----| -| P28 | `multimap::erase` with `std::pair` — C++17 removed value-erase overload | `dlt_p2p_node.cpp` | Use iterator-erase: `_mempool_by_expiry.erase(it_by_expiry)` | -| P28b | `ip::address::data()` doesn't exist in fc | `dlt_p2p_node.cpp` | Use `fc::raw::pack()` | -| P29 | Missing `witness_plugin.hpp` — actual file is `validator.hpp` | `p2p_plugin.cpp` | Fix include path | -| P30 | 6+ API mismatches in `p2p_plugin.cpp` (see below) | `p2p_plugin.cpp` | Update delegate calls | -| P31 | Linker error: `static constexpr` ODR-use | `dlt_p2p_peer_state.hpp` | **Fixed** — out-of-line definitions added | - -**P30 API mismatch details:** -| Error | Fix Needed | -|-------|------------| -| `with_read_lock([&]{...})` — expects 6 args, 1 provided | Add `lock_type, timeout_ms, file, line, func` params | -| `is_emergency_consensus` is not a member | Field renamed; find new name in `dynamic_global_property_object` | -| `blocks.front()->id()` — `id` is field, not method | Change to `blocks.front()->id` | -| `catch (const unlinkable_block_exception&)` — missing var name | Add variable: `catch (const unlinkable_block_exception& e)` | -| `accept_transaction` doesn't exist | Renamed to `apply_transaction` or similar | -| `push_block(*block)` with `fork_item` — not `signed_block` | Use `fork_item->data` | -| `is_known_block(ref_block_num)` with `uint32_t` — expects `block_id_type` | Fetch block ID by number first | - ---- - -## Known Runtime Issues - -Post-implementation issues observed in production (4-node DLT emergency consensus network): - -| # | Severity | Problem | -|---|----------|--------| -| P17 | ~~CRITICAL~~ **Fixed** | DLT block log corruption on crash → auto-detected and reset | -| P18 | ~~CRITICAL~~ **Fixed** | Master stops producing blocks for minutes (`slot=0` loop) → stall detector + NTP force-sync | -| P19 | ~~HIGH~~ **Fixed** | Slave stuck in SYNC → gap detection + multi-peer fallback + snapshot warning | -| P20 | ~~CRITICAL~~ **Fixed** | Dead fork blocks → DEAD_FORK result, soft-ban, no crash | -| P21 | ~~CRITICAL~~ **Fixed** | Dead fork crash loop → blocks rejected, fork_db protected | -| P22 | ~~HIGH~~ **Fixed** | fork_db rejection cascade on restart → seed 100 blocks + 60s grace period | -| P23 | ~~HIGH~~ **Fixed** | `fetch_branch_from` assertion failure → graceful empty-branch return | -| P24 | ~~CRITICAL~~ **Fixed** | Snapshot lock isolation → periodic tasks skip DB, stall check aware | -| P25 | ~~HIGH~~ **Fixed** | Slave-produced block ignored → exchange_enabled re-evaluated on block accept + FORWARD transition | -| P26 | ~~MED~~ **Fixed** | Sync state confusion → `check_sync_catchup()` on block accept + periodic task | -| P27 | ~~CRITICAL~~ **Fixed** (diag) | Write lock diagnostic — overall + per-plugin timing, lock-holder ID | -| P36 | ~~HIGH~~ **Fixed** | `block_too_old_exception` during SYNC range processing + FORWARD mode gap fill | -| P37 | ~~HIGH~~ **Fixed** | Gap fill never triggers: stale peer_head_num + no FORWARD stagnation detection | -| P39 | ~~CRITICAL~~ **Fixed** | `_push_next` cascade blocks not applied to database + gap fill requires exchange peers | -| P40 | ~~HIGH~~ **Fixed** | FORWARD transition not announced to peers + exchange status not visible in stats | -| P41 | ~~HIGH~~ **Fixed** | Stale peer state on reconnect: exchange_enabled, spam_strikes, peer_head_num leak from old session | -| P53 | ~~HIGH~~ **Fixed** | Peer isolation oscillation: emergency_peer_reset() clears bans/backoffs after 60s isolation | -| P54 | ~~HIGH~~ **Fixed** | Gap fill disabled in SYNC mode + large gaps silently ignored: mode-agnostic gap fill + chunked requests | - -Full analysis in [DLT 4-Node Sync Scenarios](./dlt-4-node-sync-scenarios.md#new-problems-discovered-post-implementation). - ---- - -## Subsequent Enhancements & Fixes (2026-05-07) - -### P23: fetch_branch_from Assertion Safety - -**Files:** `libraries/chain/fork_database.cpp` - -**Root cause:** `fetch_branch_from()` had 6 `FC_ASSERT` calls that crashed the node when block IDs weren't in the fork_db index. In production, peers on different forks triggered these assertions, crashing the node. - -**Fix:** Replaced all `FC_ASSERT` calls with graceful early returns (empty branches + `wlog`). Callers already handle empty branches (e.g., `is_head_on_branch()` catches exceptions, `compare_fork_branches()` has try-catch with fork_db reset). - -### P26: SYNC→FORWARD Transition Fix - -**Files:** `libraries/network/dlt_p2p_node.cpp`, `libraries/network/include/graphene/network/dlt_p2p_node.hpp` - -**Root cause:** `transition_to_forward()` was only called in `on_dlt_block_range_reply()` and `sync_stagnation_check()`. If a slave caught up via individual block replies, the transition never triggered. - -**Fix:** Added `check_sync_catchup()` that compares `our_head` against all active peers' `peer_head_num`. Called from `on_dlt_block_reply()` after accepting a block, and from `periodic_task()`. - -### P25: exchange_enabled Re-evaluation - -**Files:** `libraries/network/dlt_p2p_node.cpp` - -**Root cause:** `exchange_enabled` was set once during hello handshake and never updated. Slaves that caught up still had `exchange_enabled=false` on the master side, so their block broadcasts were ignored. - -**Fix:** Three re-evaluation triggers: (1) in `transition_to_forward()` re-check `is_block_known(peer_head_id)`, (2) in `on_dlt_block_range_reply()` enable exchange when a non-exchange-enabled peer's block is ACCEPTED, (3) same in `on_dlt_block_reply()`. - -### P22: fork_db Restart Recovery - -**Files:** `libraries/chain/database.cpp`, `plugins/p2p/p2p_plugin.cpp` - -**Root cause:** After restart, fork_db was seeded with only the head block. Peers sending sync blocks near the head were rejected as "dead fork" because their parent chain wasn't in fork_db. - -**Fix:** (1) Seed the last 100 blocks from block_log/dlt_block_log into fork_db on startup. (2) Dead-fork grace period: for the first 60s after startup, blocks within 10 of the head are treated as `FORK_DB_ONLY` instead of `DEAD_FORK`. - -### P18: slot=0 Production Stall Detector - -**Files:** `plugins/validator/validator.cpp` - -**Root cause:** `get_slot_at_time()` returns 0 when NTP time is behind `head_block_time()`. After crash/restart with NTP desync, the master could loop on `not_time_yet` for minutes. - -**Fix:** Added `_slot_zero_streak` counter: at streak=10 (~3s) logs warning + forces NTP resync; at streak=120 (~30s) logs CRITICAL error. Counter resets on any non-stall result. - -### P19: Sync Gap Detection + Multi-Peer Fallback - -**Files:** `libraries/network/dlt_p2p_node.cpp` - -**Root cause:** When `our_head + 1 < peer_dlt_earliest` (gap between our head and what the peer can serve), blocks were clamped but didn't link to our head. No attempt was made to find a peer with the missing blocks. - -**Fix:** When a gap is detected in `request_blocks_from_peer()`: (1) search other peers for one whose DLT range covers the missing blocks, (2) if found, defer current peer and sync from the bridging peer, (3) if no peer can bridge, log "Snapshot may be required" warning and still attempt the clamped request. - ---- - -## Subsequent Enhancements & Fixes (2026-05-08) - -### P36: block_too_old_exception During SYNC Range Processing + Gap Fill Exchange Packet - -**Files:** `plugins/p2p/p2p_plugin.cpp`, `libraries/network/include/graphene/network/dlt_p2p_messages.hpp`, `libraries/network/dlt_p2p_messages.cpp`, `libraries/network/include/graphene/network/dlt_p2p_node.hpp`, `libraries/network/dlt_p2p_node.cpp` - -**Problem B — Root cause:** When processing a block range reply in SYNC mode, the first block can trigger `fork_db._push_next()` which cascades and links previously-deferred blocks from a competing fork. This advances `fork_db._head` far beyond the database head. Subsequent blocks in the range (from a different fork or the same chain) are then rejected with `block_too_old_exception` because they fall outside fork_db's `_max_size=2` sliding window. The exception fell through to the generic `catch (const fc::exception&)` which returned `REJECTED`, causing the P2P layer to skip updating `expected_next_block`. This made every subsequent block in the range appear "out of order", creating an unresolvable gap. - -**Problem B — Fix:** Added `catch (const graphene::chain::block_too_old_exception& e)` in `dlt_delegate::accept_block()` before the generic `fc::exception` catch. Returns `ALREADY_KNOWN` instead of `REJECTED`. This is correct because fork_db already has a better chain at that height (that's why it considers the block "too old"). The `ALREADY_KNOWN` result allows `expected_next_block` to be updated properly, preventing the cascading gap. - -**Gap fill — Root cause:** When transitioning SYNC→FORWARD with a 1-2 block gap, the only recovery mechanism was the FORWARD→SYNC fallbehind detection (threshold=2 blocks), causing oscillation between modes. Broadcast blocks arrive out-of-order and get deferred to fork_db._unlinked_index, but there's no way to proactively request the specific missing blocks. - -**Gap fill — Fix:** Added two new exchange-only message types: - -| Type | ID | Purpose | -|------|----|---------| -| `dlt_gap_fill_request` | 5115 | Request specific block numbers from exchange-enabled peers | -| `dlt_gap_fill_reply` | 5116 | Return requested blocks from dlt_block_log or fork_db | - -Protocol: -- Only exchanged between exchange-enabled peers (`on_dlt_gap_fill_request` rejects non-exchange peers) -- Maximum 100 blocks per request (`GAP_FILL_MAX_BLOCKS`); larger gaps are served in 100-block chunks -- 5-second cooldown between requests (`GAP_FILL_COOLDOWN_SEC`) -- Works in both SYNC and FORWARD modes (not FORWARD-only) -- SYNCING lifecycle peers are also eligible as candidates (not just ACTIVE) -- Requesting peer selects the exchange-enabled peer with the highest head block -- Requested blocks must be within the serving peer's DLT log range - -Gap fill is triggered in three places: -1. `on_dlt_block_reply()` — when an out-of-order block is detected (any mode) -2. `periodic_task()` — proactive gap detection every 5s cycle (any mode) -3. `resume_block_processing()` — after snapshot pause, tries gap fill before falling back to SYNC - -### P37: Gap Fill Never Triggers — Stale peer_head_num + No FORWARD Stagnation Detection - -**Files:** `libraries/network/dlt_p2p_node.cpp`, `libraries/network/include/graphene/network/dlt_p2p_node.hpp` - -**Root cause (3 bugs):** - -1. **`peer_head_num` never updated from received blocks.** Only set during hello/fork_status exchange. When peers broadcast blocks #79678587+ but their recorded `peer_head_num` stays at #79678585, `request_gap_fill()` sees `max_peer_head <= our_head` and silently returns — the gap grows forever. - -2. **`check_forward_behind()` depends on stale `peer_head_num`.** The fallbehind detection checks `peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD`, which never triggers for the same reason. - -3. **No FORWARD-mode head-progress stagnation detection.** There was no "our head hasn't advanced in N seconds" check — the node stays stuck in FORWARD mode indefinitely. - -**Fix 1 — Update `peer_head_num` from received blocks:** In both `on_dlt_block_reply()` and `on_dlt_block_range_reply()`, when a block with number N is received from a peer, update `state.peer_head_num = max(state.peer_head_num, N)`. A peer that can send us block #N must have applied it, so its head is ≥ N. - -**Fix 2 — Track `_highest_seen_block_num`:** Global high-water mark updated whenever we see any block with a higher number. Used in `request_gap_fill()` as a fallback gap ceiling when no peer reports a higher head: `gap_ceiling = max(max_peer_head, _highest_seen_block_num)`. Also sends gap fill to ANY exchange-enabled peer if none has a higher reported head. - -**Fix 3 — Gap fill timeout:** `_gap_fill_in_progress` now times out after 15s (`GAP_FILL_TIMEOUT_SEC`). Prevents the flag from getting permanently stuck if the target peer disconnects. - -**Fix 4 — FORWARD stagnation detection:** New `check_forward_stagnation()` called from `periodic_task()`. If in FORWARD mode and our head hasn't advanced in 30 seconds (`FORWARD_STAGNATION_SEC`), transitions to SYNC mode and requests blocks from all exchange-enabled peers. This is the safety net when gap fill fails (no peer has the missing blocks). - -### P39: `_push_next` Cascade Blocks Not Applied to Database + Gap Fill Requires Exchange Peers - -**Files:** `libraries/chain/database.cpp`, `plugins/p2p/p2p_plugin.cpp`, `libraries/network/dlt_p2p_node.cpp` - -**Root cause (2 bugs):** - -1. **`_push_block` doesn't apply `_push_next` cascade blocks after linear extension.** When a block that directly extends `head_block_id()` is pushed to fork_db, `_push_next` may link previously-deferred blocks from `_unlinked_index`, advancing `fork_db._head` far beyond the database head. But `_push_block` only applies the original block — the cascaded blocks are never applied to the database. Subsequent blocks in the range reply are rejected as "too old" by fork_db's sliding window (`max_size=2`), the P36 fix returns ALREADY_KNOWN, `expected_next_block` advances past them, and the node thinks it's caught up while the database head is stuck behind. This causes FORWARD\u2192SYNC oscillation. - -2. **Gap fill requires exchange-enabled peers on both sides.** Right after SYNC\u2192FORWARD transition, no peer may have `exchange_enabled=true` yet (the hello exchange may not have re-evaluated fork alignment). The gap fill request fails with "no exchange-enabled peer available", the gap grows, and the node must wait for stagnation detection (30s) before transitioning to SYNC. - -**Fix 1 — Apply cascade blocks in `_push_block`:** After the linear extension block is applied, check if `new_head` (returned by `fork_db.push_block()`) is ahead of the applied block. If so, `fetch_branch_from` to get the cascade blocks and apply them as linear extensions. On failure, reset fork_db to database head to prevent subsequent "too old" rejections. - -**Fix 2 — Gap fill uses any active peer:** `request_gap_fill()` now prefers exchange-enabled peers but falls back to any active peer with a higher head. The serving side (`on_dlt_gap_fill_request`) also accepts requests from any peer (not just exchange-enabled), since gap fill is a lightweight block_log read. - -**Fix 3 — Gap fill failure transitions to SYNC:** When no peer at all is available for gap fill, immediately transition to SYNC mode instead of waiting for stagnation detection. - -**Fix 4 — Belt-and-suspenders in `accept_block`:** When `block_too_old_exception` is caught and the database is behind fork_db._head, reset fork_db to the database head and retry the push. This handles edge cases where the primary cascade fix (Fix 1) doesn't apply (e.g., non-linear cascade). - -### P40: FORWARD Transition Peer Notification + Exchange Status Visibility - -**Files:** `libraries/network/dlt_p2p_node.cpp`, `libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp` - -**Root cause:** When a node transitions from SYNC→FORWARD, it locally re-evaluates `exchange_enabled` for its peers (P25 fix), but does NOT notify peers. The peer still sees this node as SYNC with `exchange_enabled=false`, and won't send blocks/transactions to it. This delays exchange activation until the next periodic `broadcast_chain_status()` cycle (which only targets exchange-enabled peers — a catch-22). - -**Fix 1 — Notify ALL peers on FORWARD transition:** `transition_to_forward()` now sends a `dlt_fork_status_message` with `node_status=FORWARD` to ALL active/syncing peers, not just exchange-enabled ones. This ensures peers know we're ready for exchange. - -**Fix 2 — Re-evaluate exchange on received FORWARD status:** `on_dlt_fork_status()` now detects SYNC→FORWARD transitions in the peer's status. When a peer transitions to FORWARD, it re-checks `is_block_known(peer_head_id)` and enables exchange if the peer's head is now recognized. - -**Fix 3 — Exchange status in peer stats:** `log_peer_stats()` now shows `exch=YES/no` explicitly in the per-peer stats line, making it easy to see which peers are exchange-enabled at a glance. - -**Fix 4 — Document stale `peer_head_num`:** Added comments in `dlt_peer_state` and `log_peer_stats()` clarifying that `peer_head_num` is a stale snapshot (from hello/fork_status/block relay), NOT real-time. The peer's actual head may be significantly higher. This prevents AI assistants and developers from misinterpreting the stats table as showing real-time peer state. - -### P41: Stale Peer State on Reconnect — exchange_enabled, spam_strikes, peer_head_num Leak - -**Files:** `libraries/network/dlt_p2p_node.cpp` - -**Root cause:** When `connect_to_peer()` reuses a `DISCONNECTED` peer state entry for reconnection, it only overwrites `endpoint`, `lifecycle_state`, and `state_entered_time`. All other fields from the previous session persist: -- `exchange_enabled=true` leaks — combined with the OR in `on_dlt_hello_reply`, this makes exchange appear enabled even if the peer switched forks while disconnected. -- `peer_head_num` / `peer_head_id` are stale — used by `check_forward_behind()`, `check_sync_catchup()`, `request_gap_fill()` with outdated data. -- `spam_strikes` carries over — unfair penalty for the new connection. -- `fork_alignment`, `peer_fork_status`, `peer_node_status`, `expected_next_block` are all stale. - -**Fix — Full state reset on reconnect:** `connect_to_peer()` now saves only cross-session fields (`reconnect_backoff_sec`, `last_connection_duration`, `node_id`) and then resets the entire `dlt_peer_state` via `state = dlt_peer_state()`. All per-session fields start fresh. The hello handshake re-establishes `exchange_enabled`, `peer_head_num`, etc. from scratch. - -### P53: Peer Isolation Oscillation — Emergency Peer Reset - -**Files:** `libraries/network/dlt_p2p_node.cpp`, `libraries/network/include/graphene/network/dlt_p2p_node.hpp` - -**Root cause:** After a snapshot pause, all peers could be in DISCONNECTED state with high backoffs (up to 3600s). `check_sync_catchup()` treated zero active peers as vacuously "all caught up" → transitioned to FORWARD. Then `check_forward_stagnation()` detected 30s without progress → transitioned to SYNC. Then `check_sync_catchup()` again saw zero peers → FORWARD. This oscillation loop prevented the node from ever reconnecting. - -**Fix 1 — Isolation guard in `check_sync_catchup()`:** When zero active peers exist, the function returns early without claiming caught up. It tracks `_isolation_detected_time` and after 60 seconds (`ISOLATION_RESET_SEC`) calls `emergency_peer_reset()`. - -**Fix 2 — Isolation-aware `check_forward_stagnation()`:** When the head is stuck AND zero active peers exist, the function does NOT transition to SYNC (useless without peers). Instead, it starts the same 60s isolation timer and calls `emergency_peer_reset()` when it expires. - -**Fix 3 - `emergency_peer_reset()` method:** Iterates all `_peer_states`: clears all soft bans (BANNED to DISCONNECTED, resets `spam_strikes`), resets all DISCONNECTED peer backoffs to `INITIAL_RECONNECT_BACKOFF_SEC` with `next_reconnect_attempt = now` (immediate). Also clears `_sync_stagnation_retries` and `_isolation_detected_time`. - -### P54: Gap Fill Disabled in SYNC Mode + Large Gaps Silently Ignored - -**Files:** `libraries/network/dlt_p2p_node.cpp` - -**Root cause (2 bugs):** - -1. **`request_gap_fill()` gated to FORWARD mode only.** The function had `if (_node_status != DLT_NODE_STATUS_FORWARD) return;` at the top, so when a node was stuck in SYNC mode with a growing gap (e.g., head=79737668, network at 79738507, gap=839), gap fill never fired. Blocks arrived via broadcast, were stored in fork_db as unlinkable, and the gap kept growing. - -2. **Gaps > `GAP_FILL_MAX_BLOCKS` (100) silently ignored.** When `gap > 100`, the function returned with no log, no request, and no fallback. Combined with Bug 1, even after a SYNC to FORWARD transition, the gap (still >100) would prevent gap fill from doing anything. - -Additionally, the peer candidate loop only considered `DLT_PEER_LIFECYCLE_ACTIVE` peers, but in SYNC mode the best candidate is typically in `DLT_PEER_LIFECYCLE_SYNCING` state (set by `request_blocks_from_peer`). - -**Fix 1 - Remove FORWARD-only guard:** Gap fill now works in both SYNC and FORWARD modes. In SYNC mode, when `request_blocks_from_peer()` cannot bridge a gap (blocks below the syncing peer's DLT range), gap fill provides an alternative path. - -**Fix 2 - Chunked requests for large gaps:** Instead of silently returning when `gap > GAP_FILL_MAX_BLOCKS`, the function now requests the first 100 blocks. Subsequent chunks are requested on the next periodic call after the current chunk completes or times out. - -**Fix 3 - Include SYNCING peers:** The peer candidate loop now includes `DLT_PEER_LIFECYCLE_SYNCING` peers alongside `DLT_PEER_LIFECYCLE_ACTIVE`. - -**Fix 4 - Mode-agnostic out-of-order trigger:** In `on_dlt_block_reply()`, the gap fill trigger for out-of-order blocks no longer requires FORWARD mode. Any mode with a real gap triggers gap fill. - -### P55: SYNC↔FORWARD Oscillation When No Peer Is Ahead - -**Files:** `libraries/network/dlt_p2p_node.cpp` - -**Root cause (2 bugs):** - -1. **`transition_to_sync()` did not reset `_last_block_received_time`.** When `check_forward_stagnation()` triggered a FORWARD→SYNC transition after 30s without head progress, the sync stagnation timer inherited a stale `_last_block_received_time` (set when the last block was received in FORWARD mode, ~30s ago). On the very next periodic tick (~5s later), `sync_stagnation_check()` saw the timestamp was already 35s old (> `SYNC_STAGNATION_SEC` 30s) and immediately triggered a stagnation retry. - -2. **`check_forward_stagnation()` transitioned to SYNC even when no peer was ahead.** When head was stuck at block N and all connected peers also reported head=N, transitioning to SYNC mode was pointless — there was nothing to sync. `check_sync_catchup()` would immediately detect `our_head >= all peers` on the next tick and transition back to FORWARD, completing the oscillation loop in a single tick cycle. - -**Observed behavior:** Node at block #79740459 with 5 active peers all at #79740459. At 444006ms, `check_forward_stagnation` transitions to SYNC. At 449006ms (next tick), `sync_stagnation_check` fires retry 1/3 and `check_sync_catchup` transitions back to FORWARD. No actual sync attempt occurs. - -**Fix 1 — Reset stagnation timer on SYNC entry:** `transition_to_sync()` now sets `_last_block_received_time = fc::time_point::now()`. This gives the sync phase a full 30s window to receive blocks before stagnation detection kicks in. - -**Fix 2 — Peer-ahead guard in `check_forward_stagnation()`:** Before transitioning to SYNC, the function now checks whether at least one active/SYNCING peer has `peer_head_num > our_head`. If no peer is ahead, SYNC mode cannot help, so the function logs the situation and resets the stagnation timer (`_last_forward_head_num` and `_last_forward_progress_time`) instead of oscillating. diff --git a/.qoder/docs/dlt-p2p-stats-reference-ru.md b/.qoder/docs/dlt-p2p-stats-reference-ru.md deleted file mode 100644 index da2b18d5b3..0000000000 --- a/.qoder/docs/dlt-p2p-stats-reference-ru.md +++ /dev/null @@ -1,375 +0,0 @@ -# Справочник по статистике DLT P2P - -Этот документ объясняет вывод статистики DLT P2P — что означает каждое поле, почему оно имеет текущее значение и какие действия (если нужны) должен предпринять оператор. - -`dlt_p2p_node` выводит **две различных строки** в лог периодически: - -| Префикс лога | Частота | Назначение | -|---|---|---| -| `DLT Status \|` | Каждые ~30с | Компактная строка для оперативного мониторинга | -| `=== DLT P2P Stats \|` | Каждые ~120с | Детальная статистика с данными по каждому пиру | - ---- - -## DLT Status Log (компактная строка) - -``` -DLT Status | FORWARD | head=#79881136 lib=#79881130 | dlt_range=79000000-79881136 | peers=6active/8conn | uptime=2h15m43s | flags=... -``` - -Выводится каждые ~30 секунд. Предназначена для мониторинга через `tail`/`grep` без лишнего шума. - -### Поля - -| Поле | Пример | Описание | -|---|---|---| -| Режим | `FORWARD` | Режим работы ноды (`SYNC` или `FORWARD`) | -| `head=#N` | `head=#79881136` | Номер текущего head-блока | -| `lib=#N` | `lib=#79881130` | Номер последнего необратимого блока | -| `dlt_range=A-B` | `dlt_range=79000000-79881136` | Диапазон блоков в DLT block log | -| `peers=Xactive/Yconn` | `peers=6active/8conn` | Активных (обменивающихся) / всего подключённых пиров | -| `uptime=XhYmZs` | `uptime=2h15m43s` | Время с момента запуска ноды | -| `flags=...` | Различные | Активные флаги состояния (snapshot, pause, catchup и т.д.) | - -**`uptime`** вычисляется как `(now - _node_start_time)`, где `_node_start_time` фиксируется в конструкторе `dlt_p2p_node`. Используйте это поле для корреляции событий в логе с возрастом ноды — особенно полезно при диагностике проблем с продакшеном: началась ли проблема сразу после запуска или через некоторое время работы. - ---- - -## Сводка на уровне ноды (заголовочная строка) - -``` -=== DLT P2P Stats | status=FWD fork=NORMAL head=79881136 lib=79881130 peers=6 conn=4 paused=no uptime=0h20m30s === -``` - -### `status` — Режим работы ноды - -| Значение | Описание | -|----------|----------| -| `SYNC` | Нода синхронизируется — загружает и применяет блоки от пиров. Транзакции в этом режиме не транслируются. | -| `FWD` | Нода синхронизирована и работает в режиме forward — производит/ретранслирует блоки и транзакции в реальном времени. | - -**Почему может быть `SYNC`:** -- Нода только запустилась и загружает блокчейн -- Нода отстала от сети (пропустила блоки во время простоя или потери соединения) -- Нода обнаружила, что находится на миноритарном форке, и пересинхронизируется с мажоритарной цепью -- Стагнация обработки блоков — head не продвигается в течение настроенного периода - -**Почему может быть `FWD`:** -- Нода догнала сеть и работает нормально -- Все блоки принимаются через вещание в реальном времени от пиров - -### `fork` — Состояние форка - -| Значение | Описание | -|----------|----------| -| `NORMAL` | Нода находится на мажоритарном форке — конфликтов форков не обнаружено. | -| `LOOKING` | Нода обнаружила несколько конкурирующих вершин форка и активно сравнивает ветки, чтобы определить мажоритарную. | -| `MINORITY` | Нода определила, что находится на миноритарном форке (меньше свидетелей производят на этой ветке). Вероятен переключением на другой форк. | - -**Почему может быть не `NORMAL`:** -- Два или более свидетеля произвели блоки в одном слоте, создав временный форк -- Сетевой разраздел вызвал то, что разные подмножества свидетелей строили разные вершины -- Нода только что получила блок из альтернативного форка, который не связывается с её текущим head - -### `head` / `lib` - -- **head** — Номер блока текущей вершины цепи ноды (последний блок в цепи). -- **lib** — Номер последнего необратимого блока (Last Irreversible Block). Блоки на уровне LIB или ниже являются финализированными и не могут быть отменены. - -Разрыв между head и lib показывает, сколько блоков находится в обратимой части цепи. При нормальной работе с DLT этот разрыв небольшой (обычно 1-10 блоков). - -### `peers` / `conn` - -- **peers** — Общее количество известных ноде пиров (включает активных, подключающихся и отключенных пиров, отслеживаемых для переподключения). -- **conn** — Количество активных TCP-соединений, открытых в данный момент. - -Если `peers` значительно выше, чем `conn`, у ноды есть отключенные пиры, к которым она ожидает переподключиться (с экспоненциальной задержкой). - -### `paused` - -| Значение | Описание | -|----------|----------| -| `no` | Обработка блоков активна — входящие блоки применяются немедленно. | -| `YES` | Обработка блоков временно приостановлена. Входящие блоки ставятся в очередь и будут обработаны при возобновлении. | - -**Почему может быть `YES`:** -- Создается снимок (snapshot) — база данных заблокирована для экспорта согласованного состояния -- Внутренняя операция требует исключительного доступа к базе данных - -Когда обработка приостановлена, нода продолжает принимать P2P-соединения и получать сообщения, но блоки не применяются к цепи до возобновления обработки. - -### `uptime` - -Время с момента запуска P2P-ноды. Формат: `XhYmZs`. - ---- - -## Статистика по каждому пиру - -Каждый пир отображается на отдельной строке с детальной информацией о состоянии. - -### Отключенные пиры - -``` -138.201.117.201:2001 | DISC | disconnected=74s | backoff=480s | reconnect_in=502s | spam=0 -``` - -| Поле | Описание | -|------|----------| -| `DISC` | Пир в данный момент отключен. Нода попытается переподключиться. | -| `disconnected` | Секунд с момента потери соединения. | -| `backoff` | Текущий интервал задержки переподключения в секундах. Начинается с 30s и удваивается при каждой неудачной попытке, до 3600s (1 час). | -| `reconnect_in` | Секунд до следующей попытки переподключения. | -| `spam` | Счётчик spam-ударов. Сбрасывается при успешном переподключении. | - -**Поведение backoff:** -- Первое отключение: задержка 30s -- Каждая последующая неудачная попытка удваивает задержку (30 → 60 → 120 → 240 → 480 → ... → 3600) -- Если соединение остаётся стабильным более 5 минут, задержка сбрасывается до 30s -- Максимальная задержка — 3600s (1 час) - -### Активные пиры - -``` -62.109.17.82:2001 | ACTIVE | exch=YES | head=79881136 lib=79880729 | range=79869724-79880729 | peer_fork=NORM peer_node=FWD | spam=0 | +align+emrg+sync -``` - -#### Состояние жизненного цикла - -| Состояние | Описание | -|-----------|----------| -| `CONNECT` | Устанавливается TCP-соединение (таймаут: 5s). | -| `HANDSHAKE` | Идёт обмен hello/hello-reply (таймаут: 10s). | -| `SYNCING` | Активно загружается диапазон блоков от этого пира. | -| `ACTIVE` | Handshake завершён, exchange установлен, пир работает. | -| `DISC` | Отключён — см. формат отключенных пиров выше. | -| `BANNED` | Временно заблокирован — пир не будет контактировать до истечения бана (по умолчанию: 1 час). | - -#### `exch` — Статус обмена - -| Значение | Описание | -|----------|----------| -| `YES` | Обмен блоками и транзакциями включён с этим пиром. Обе стороны распознают друг друга как находящиеся на одном форке. | -| `no` | Обмен отключён. Нода не знает, находится ли этот пир на том же форке, поэтому не отправляет и не запрашивает блоки. | - -**Почему exchange может быть `no`:** -- Handshake только завершился, и выравнивание форка ещё не проверено -- Head/LIB пира не распознаётся нашей нодой (разные форки) -- Пир явно сообщил, что наша цепь не на его форке во время handshake -- Пир прислал блок из нераспознанного форка - -**Как exchange становится `YES`:** -- Во время handshake: если наша нода распознаёт head-блок пира, или пир распознаёт наш head-блок -- После принятия блока от пира: если блок применяется к нашей цепи, exchange включается автоматически -- Когда пир переходит из SYNC в FWD и его head становится распознанным - -#### `head` / `lib` (пира) - -Сообщённые пиrom номера head и LIB. **Это снимки** с последней коммуникации (hello, сообщение fork_status или ретрансляция блоков), а не значения в реальном времени. Фактический head цепи пира может быть значительно выше, особенно если пир работает в режиме FWD и быстро производит блоки. - -#### `range` — Диапазон блоков DLT - -Диапазон блоков, доступных этому пиру в его DLT block log: `earliest-latest`. - -- **earliest** — Номер самого старого блока, всё ещё сохранённого в DLT-логе пира -- **latest** — Номер самого нового блока в DLT-логе пира - -Если нашей ноде нужны блоки ниже `earliest` пира, этот пир не может их предоставить. Это важно при операциях gap fill, когда нужны исторические блоки. - -#### `peer_fork` — Статус форка пира - -Что пир сообщает о своей ситуации с форком: - -| Значение | Описание | -|----------|----------| -| `NORM` | Пир считает, что находится на мажоритарном форке. | -| `LOOK` | Пир активно разрешает конфликт форков. | -| `MINO` | Пир считает, что находится на миноритарном форке. | - -Это самоотчет пира через сообщения fork_status. Если пир сообщает `MINO`, он вероятно в процессе переключения на мажоритарный форк и скоро будет иметь другой head. - -#### `peer_node` — Режим работы пира - -| Значение | Описание | -|----------|----------| -| `SYNC` | Пир синхронизируется — загружает блоки. Не будет транслировать транзакции. | -| `FWD` | Пир синхронизирован — работает в режиме forward, производит и ретранслирует блоки. | - -**Почему это важно:** -- Если наша нода в режиме FWD, а пир в SYNC, мы не ожидаем вещания блоков в реальном времени от этого пира -- Если наша нода в режиме SYNC, мы запрашиваем блоки от всех пиров с enabled exchange независимо от их режима -- Если пир переходит из SYNC в FWD, он может пересмотреть exchange и начать отправлять нам блоки - -#### `spam` — Счётчик анти-спам ударов - -Количество spam-ударов, накопленных этим пиром. Каждое недействительное или malformed сообщение увеличивает этот счётчик. Когда он достигает 10, пир мягко блокируется на 1 час. - -**Что вызывает spam strike:** -- Malformed сообщения, которые не проходят десериализацию -- Сообщения, нарушающие правила протокола (например, блоки вне порядка вне ожидаемой синхронизации) -- Повторяющиеся недействительные данные от пира - -**Как сбрасывается:** -- При получении валидного пакета счётчик сбрасывается до 0 -- При успешном переподключении после отключения -- Когда бан снимается - -#### Флаги - -| Флаг | Описание | -|------|----------| -| `+align` | Выравнивание форка проверено — цепь этого пира связывается с нашей. Блоки от этого пира применяются к нашей цепи чисто. | -| `+emrg` | У пира активен emergency consensus — он производит блоки используя механизм emergency key. | -| `+ekey` | Пир владеет emergency private key — он может участвовать в emergency consensus при необходимости. | -| `+sync` | Диапазон блоков синхронизации в данный момент ожидается или в процессе с этим пиром. Нода запросила блоки и ждёт ответ. | - -**Почему флаги важны:** -- `+align` самый важный — подтверждает, что пир является валидным источником для блоков -- `+emrg` + `+ekey` вместе указывают, что пир является участником emergency validator -- `+sync` указывает на активную синхронизацию — пир используется как источник блоков - -### Заблокированные пиры - -``` -1.2.3.4:2001 | BANNED | ban_remaining=1800s | reason=spam strike threshold exceeded -``` - -| Поле | Описание | -|------|----------| -| `BANNED` | Пир временно заблокирован. Попытки соединения не будут предприниматься. | -| `ban_remaining` | Секунд осталось до истечения бана. Длительность бана по умолчанию — 3600s (1 час). | -| `reason` | Почему пир был заблокирован. Распространённые причины: превышен порог spam, нарушение протокола. | - -После истечения бана состояние пира сбрасывается на DISCONNECTED и начинается нормальная задержка переподключения. - ---- - -## Распространённые сценарии и интерпретации - -### Сценарий 1: Все пиры показывают `exch=no` - -**Что это значит:** Нода не распознаёт цепь любого пира как находящуюся на том же форке. - -**Вероятные причины:** -- Нода только запустилась и ещё не завершила handshake ни с одним пиром -- Нода находится на другом форке, чем все подключённые пиры -- Пиры подключились, пока нода была в режиме SYNC, и их head ещё не были распознаны - -**Что делать:** -- Дождитесь завершения handshake — exchange может включиться автоматически -- Проверьте статус `fork` в заголовке — если там `MINORITY` или `LOOKING`, нода разрешает конфликт форков -- Если нода остаётся в этом состоянии долго, возможно, ей нужно пересинхронизироваться с другим набором пиров - -### Сценарий 2: `status=SYNC` со многими активными пирами - -**Что это значит:** Нода активно загружает блоки от нескольких пиров. - -**Вероятные причины:** -- Запуск ноды после offline -- Нода отстала от сети -- Нода пересинхронизируется после переключения форка - -**Что делать:** -- Нормальное поведение — дождитесь завершения синхронизации -- Проверьте прогресс `head` в последовательных выводах статистики, чтобы подтвердить применение блоков -- Если `head` не продвигается в течение длительного периода, проверьте ошибки валидации блоков в логах - -### Сценарий 3: `peer_fork=MINO` у нескольких пиров - -**Что это значит:** Несколько пиров считают, что находятся на миноритарном форке. - -**Вероятные причины:** -- Общесетевое событие форка — свидетели разделены между двумя конкурирующими цепями -- Мажоритарный форк строится другим набором свидетелей - -**Что делать:** -- Мониторьте статус `fork` в заголовке — если он перейдёт в `MINORITY`, наша нода также переключит форк -- Дождитесь разрешения форка — протокол в конечном итоге сойдётся на одной цепи -- Если это сохраняется, проверьте активность свидетелей и сетевое подключение - -### Сценарий 4: Высокие значения `backoff` у отключённых пиров - -**Что это значит:** Пиры отключались несколько раз, и интервал переподключения вырос. - -**Вероятные причины:** -- Нестабильность сети между нашей нодой и пирами -- Пиры перезапускаются или испытывают downtime -- Проблемы с фаерволом или NAT, блокирующие постоянные соединения - -**Что делать:** -- Проверьте сетевое подключение к адресам пиров -- Убедитесь, что правила фаервола разрешают исходящие соединения на порт 2001 -- Если пиры известны как онлайн, высокий backoff нормален и сбросится при успешном соединении - -### Сценарий 5: `paused=YES` - -**Что это значит:** Обработка блоков временно приостановлена. - -**Вероятные причины:** -- Создание снимка в процессе — база данных заблокирована для экспорта согласованного состояния -- Внутренняя операция обслуживания, требующая исключительного доступа к базе данных - -**Что делать:** -- Нормально во время операций снимка — дождитесь завершения -- Блоки, полученные во время паузы, ставятся в очередь и будут обработаны при возобновлении -- Если пауза сохраняется неожиданно, проверьте застрявшие операции снимка - -### Сценарий 6: Rate-limiting обмена пирами в логах - -**Сообщение в логе:** `Peer rate-limited our exchange request, wait s` - -**Что это значит:** Наша нода отправила `dlt_peer_exchange_request` пиру, но этот пир ответил `dlt_peer_exchange_rate_limited`, потому что удалённый пир уже обслужил 3 запроса обмена от нас за последние 5 минут. - -**Механизм:** -- Каждый пир отслеживает `peer_exchange_request_count` и `peer_exchange_window_start` для каждого удалённого пира. -- Когда приходит `dlt_peer_exchange_request`, принимающий пир проверяет `is_peer_exchange_rate_limited()` — если получено 3 или более запросов в течение 5-минутного окна (`PEER_EXCHANGE_WINDOW_SEC`), он отвечает `dlt_peer_exchange_rate_limited{wait_seconds}` вместо списка пиров. -- На нашей стороне получение этого ответа помечает нас как rate-limited локально (`peer_exchange_request_count = MAX`), что предотвращает выбор этого пира в `periodic_peer_exchange()` до истечения окна. -- Это двусторонний механизм: обе ноды независимо обеспечивают одно и то же скользящее окно (3 запроса за 5 минут). - -**Зачем это нужно:** -- Предотвращает избыточный трафик обнаружения пиров в стабильных сетях -- Снижает излишнюю нагрузку сообщениями, когда топология пиров не меняется -- Дополняет фильтр `dlt-peer-exchange-min-uptime-sec` (600с), который гарантирует передачу только стабильных пиров - -**Что делать:** -- Это нормальное, ожидаемое поведение — никаких действий не требуется -- Сообщение появляется максимум после 4-го запроса в 5-минутном окне на пира -- Если сообщение появляется очень часто для множества пиров, это может указывать на слишком агрессивный интервал периодического обмена или слишком малое количество уникальных пиров в сети - ---- - -## Быстрый справочник - -### Перечисления - -**Статус ноды:** -- `DLT_NODE_STATUS_SYNC` (0) = Синхронизация -- `DLT_NODE_STATUS_FORWARD` (1) = Синхронизирована, работа в реальном времени - -**Статус форка:** -- `DLT_FORK_STATUS_NORMAL` (0) = На мажоритарном форке -- `DLT_FORK_STATUS_LOOKING_RESOLUTION` (1) = Разрешение конфликта форков -- `DLT_FORK_STATUS_MINORITY` (2) = На миноритарном форке - -**Состояния жизненного цикла пира:** -- `DLT_PEER_LIFECYCLE_CONNECTING` (0) = Установка TCP-соединения -- `DLT_PEER_LIFECYCLE_HANDSHAKING` (1) = Обмен сообщениями hello -- `DLT_PEER_LIFECYCLE_SYNCING` (2) = Загрузка диапазона блоков -- `DLT_PEER_LIFECYCLE_ACTIVE` (3) = Работает, exchange установлен -- `DLT_PEER_LIFECYCLE_DISCONNECTED` (4) = Отключён, будет переподключён -- `DLT_PEER_LIFECYCLE_BANNED` (5) = Временно заблокирован - -### Пороги и константы - -| Константа | Значение | Описание | -|-----------|----------|----------| -| `SPAM_STRIKE_THRESHOLD` | 10 | Spam-ударов до мягкого бана | -| `BAN_DURATION_SEC` | 3600 | Длительность бана по умолчанию (1 час) | -| `INITIAL_RECONNECT_BACKOFF_SEC` | 30 | Начальная задержка переподключения | -| `MAX_RECONNECT_BACKOFF_SEC` | 3600 | Максимальная задержка переподключения (1 час) | -| `PEER_EXCHANGE_MAX_REQUESTS` | 3 | Максимум запросов обмена пирами к одному пиру в рамках скользящего окна | -| `PEER_EXCHANGE_WINDOW_SEC` | 300 | Длительность скользящего окна обмена пирами (5 мин) — обе стороны обеспечивают ограничение; ответ rate-limited содержит `wait_seconds` с оставшимся временем окна | -| `SEND_QUEUE_MAX_DEPTH` | 100 | Максимум queued сообщений на пира | -| `KNOWN_BLOCKS_WINDOW` | 20 | Размер кольцевого буфера ID блоков для подавления эха | -| `CONNECTING_TIMEOUT` | 5s | Таймаут установления TCP-соединения | -| `HANDSHAKING_TIMEOUT` | 10s | Таймаут обмена hello/hello-reply | diff --git a/.qoder/docs/dlt-p2p-stats-reference.md b/.qoder/docs/dlt-p2p-stats-reference.md deleted file mode 100644 index 62bc173fa0..0000000000 --- a/.qoder/docs/dlt-p2p-stats-reference.md +++ /dev/null @@ -1,375 +0,0 @@ -# DLT P2P Statistics Reference - -This document explains the DLT P2P statistics output — what each field means, why it has its current value, and what actions (if any) an operator should take. - -There are **two distinct log lines** emitted periodically by `dlt_p2p_node`: - -| Log prefix | Frequency | Purpose | -|---|---|---| -| `DLT Status \|` | Every ~30s | Compact one-liner for quick health monitoring | -| `=== DLT P2P Stats \|` | Every ~120s | Detailed stats including per-node fork/peer data | - ---- - -## DLT Status Log (Compact Line) - -``` -DLT Status | FORWARD | head=#79881136 lib=#79881130 | dlt_range=79000000-79881136 | peers=6active/8conn | uptime=2h15m43s | flags=... -``` - -Emitted every ~30 seconds. Intended for tail/grep monitoring without noise. - -### Fields - -| Field | Example | Meaning | -|---|---|---| -| Mode | `FORWARD` | Node operating mode (`SYNC` or `FORWARD`) | -| `head=#N` | `head=#79881136` | Current head block number | -| `lib=#N` | `lib=#79881130` | Last irreversible block number | -| `dlt_range=A-B` | `dlt_range=79000000-79881136` | Block range stored in DLT block log | -| `peers=Xactive/Yconn` | `peers=6active/8conn` | Active (exchanging) / total connected peers | -| `uptime=XhYmZs` | `uptime=2h15m43s` | Time since node startup | -| `flags=...` | Various | Active operational flags (snapshot, pause, catchup, etc.) | - -**`uptime`** is computed as `(now - _node_start_time)` where `_node_start_time` is recorded in the `dlt_p2p_node` constructor. Use this to correlate log events with node age — particularly useful when checking if a production issue started immediately after startup vs. after running for a while. - ---- - -## Node-Level Summary (Header Line) - -``` -=== DLT P2P Stats | status=FWD fork=NORMAL head=79881136 lib=79881130 peers=6 conn=4 paused=no uptime=0h20m30s === -``` - -### `status` — Node Operating Mode - -| Value | Meaning | -|-------|---------| -| `SYNC` | Node is catching up — downloading and applying blocks from peers. Transactions are not broadcast during this mode. | -| `FWD` | Node is caught up and in forward mode — producing/relaying blocks and transactions in real-time. | - -**Why it might be `SYNC`:** -- Node just started and is downloading the blockchain -- Node fell behind the network (missed blocks during downtime or connection loss) -- Node detected it is on a minority fork and is re-syncing from the majority chain -- Block processing stagnation — head hasn't advanced for a configured period - -**Why it might be `FWD`:** -- Node has caught up to the network and is operating normally -- All blocks are being received via real-time broadcast from peers - -### `fork` — Fork Status - -| Value | Meaning | -|-------|---------| -| `NORMAL` | Node is on the majority fork — no fork conflict detected. | -| `LOOKING` | Node detected multiple competing fork tips and is actively comparing branches to determine which is the majority. | -| `MINORITY` | Node determined it is on a minority fork (fewer validators producing on this branch). A fork switch is likely pending. | - -**Why it might not be `NORMAL`:** -- Two or more validators produced blocks at the same slot, creating a temporary fork -- Network partition caused different subsets of validators to build on different tips -- Node just received a block from an alternative fork that doesn't link to its current head - -### `head` / `lib` - -- **head** — Block number of the node's current head (latest block in the chain). -- **lib** — Last Irreversible Block number. Blocks at or below LIB are finalized and cannot be reverted. - -The gap between head and lib indicates how many blocks are on the reversible part of the chain. In normal operation with DLT, this gap is small (typically 1-10 blocks). - -### `peers` / `conn` - -- **peers** — Total number of peer entries known to the node (includes active, connecting, and disconnected peers tracked for reconnection). -- **conn** — Number of active TCP connections currently open. - -If `peers` is significantly higher than `conn`, the node has disconnected peers it is waiting to reconnect to (with exponential backoff). - -### `paused` - -| Value | Meaning | -|-------|---------| -| `no` | Block processing is active — incoming blocks are being applied immediately. | -| `YES` | Block processing is temporarily paused. Incoming blocks are queued and will be processed when resumed. | - -**Why it might be `YES`:** -- A snapshot is being created — the database is locked for consistent state export -- An internal operation requires exclusive database access - -When paused, the node continues to accept P2P connections and receive messages, but blocks are not applied to the chain until processing resumes. - -### `uptime` - -Time since the P2P node was started. Format: `XhYmZs`. - ---- - -## Per-Peer Statistics - -Each peer is shown on a separate line with detailed state information. - -### Disconnected Peers - -``` -138.201.117.201:2001 | DISC | disconnected=74s | backoff=480s | reconnect_in=502s | spam=0 -``` - -| Field | Meaning | -|-------|---------| -| `DISC` | Peer is currently disconnected. The node will attempt to reconnect. | -| `disconnected` | Seconds since the connection was lost. | -| `backoff` | Current reconnection backoff interval in seconds. Starts at 30s and doubles on each failed attempt, up to 3600s (1 hour). | -| `reconnect_in` | Seconds until the next reconnection attempt. | -| `spam` | Spam strike counter. Reset on successful reconnection. | - -**Backoff behavior:** -- First disconnect: 30s backoff -- Each subsequent failed reconnect doubles the backoff (30 → 60 → 120 → 240 → 480 → ... → 3600) -- If a connection stays stable for more than 5 minutes, the backoff resets to 30s -- Maximum backoff is 3600s (1 hour) - -### Active Peers - -``` -62.109.17.82:2001 | ACTIVE | exch=YES | head=79881136 lib=79880729 | range=79869724-79880729 | peer_fork=NORM peer_node=FWD | spam=0 | +align+emrg+sync -``` - -#### Lifecycle State - -| State | Meaning | -|-------|---------| -| `CONNECT` | TCP connection is being established (timeout: 5s). | -| `HANDSHAKE` | Hello/hello-reply exchange is in progress (timeout: 10s). | -| `SYNCING` | Actively downloading a block range from this peer. | -| `ACTIVE` | Handshake complete, exchange is established, peer is operational. | -| `DISC` | Disconnected — see disconnected peer format above. | -| `BANNED` | Temporarily banned — peer will not be contacted until ban expires (default: 1 hour). | - -#### `exch` — Exchange Status - -| Value | Meaning | -|-------|---------| -| `YES` | Block and transaction exchange is enabled with this peer. Both sides recognize each other as being on the same fork. | -| `no` | Exchange is disabled. The node does not know if this peer is on the same fork, so it does not send or request blocks. | - -**Why exchange might be `no`:** -- Handshake just completed and fork alignment has not been verified yet -- Peer's head/LIB is not recognized by our node (different forks) -- Peer explicitly reported that our chain is not on its fork during handshake -- Peer sent a block from an unrecognized fork - -**How exchange becomes `YES`:** -- During handshake: if our node recognizes the peer's head block, or the peer recognizes our head block -- After accepting a block from the peer: if the block applies to our chain, exchange is automatically enabled -- When a peer transitions from SYNC to FWD and its head becomes recognized - -#### `head` / `lib` (peer) - -The peer's reported head and LIB block numbers. **These are snapshots** from the last communication (hello, fork_status message, or block relay), not real-time values. The peer's actual chain head may be significantly higher, especially if the peer is in FWD mode and producing blocks rapidly. - -#### `range` — DLT Block Range - -The block range this peer has available in its DLT block log: `earliest-latest`. - -- **earliest** — Oldest block number still retained in the peer's DLT log -- **latest** — Newest block number in the peer's DLT log - -If our node needs blocks below the peer's `earliest`, this peer cannot serve them. This is relevant during gap fill operations when historical blocks are needed. - -#### `peer_fork` — Peer's Fork Status - -What the peer reports about its own fork situation: - -| Value | Meaning | -|-------|---------| -| `NORM` | Peer believes it is on the majority fork. | -| `LOOK` | Peer is actively resolving a fork conflict. | -| `MINO` | Peer believes it is on a minority fork. | - -This is self-reported by the peer via fork_status messages. If a peer reports `MINO`, it is likely in the process of switching to the majority fork and may soon have a different head. - -#### `peer_node` — Peer's Operating Mode - -| Value | Meaning | -|-------|---------| -| `SYNC` | Peer is catching up — downloading blocks. It will not broadcast transactions. | -| `FWD` | Peer is caught up — operating in forward mode, producing and relaying blocks. | - -**Why this matters:** -- If our node is in FWD mode and a peer is in SYNC, we do not expect real-time block broadcasts from that peer -- If our node is in SYNC mode, we request blocks from all exchange-enabled peers regardless of their mode -- If a peer transitions from SYNC to FWD, it may re-evaluate exchange and start sending us blocks - -#### `spam` — Anti-Spam Strike Counter - -Number of spam strikes accumulated by this peer. Each invalid or malformed message increments this counter. When it reaches 10, the peer is soft-banned for 1 hour. - -**What triggers a spam strike:** -- Malformed messages that fail deserialization -- Messages that violate protocol rules (e.g., out-of-order blocks outside of expected sync) -- Repeated invalid data from the peer - -**How it resets:** -- On receiving a valid packet, the counter resets to 0 -- On successful reconnection after a disconnect -- When a ban is lifted - -#### Flags - -| Flag | Meaning | -|------|---------| -| `+align` | Fork alignment verified — this peer's chain links to ours. Blocks from this peer apply cleanly to our chain. | -| `+emrg` | Peer has emergency consensus active — it is producing blocks using the emergency key mechanism. | -| `+ekey` | Peer possesses an emergency private key — it can participate in emergency consensus if needed. | -| `+sync` | A block range sync is currently pending or in progress with this peer. The node has requested blocks and is waiting for the response. | - -**Why flags matter:** -- `+align` is the most important — it confirms the peer is a valid source for blocks -- `+emrg` + `+ekey` together indicate the peer is an emergency validator participant -- `+sync` indicates active synchronization — the peer is being used as a block source - -### Banned Peers - -``` -1.2.3.4:2001 | BANNED | ban_remaining=1800s | reason=spam strike threshold exceeded -``` - -| Field | Meaning | -|-------|---------| -| `BANNED` | Peer is temporarily banned. No connection attempts will be made. | -| `ban_remaining` | Seconds remaining until the ban expires. Default ban duration is 3600s (1 hour). | -| `reason` | Why the peer was banned. Common reasons: spam threshold exceeded, protocol violation. | - -After the ban expires, the peer state resets to DISCONNECTED and normal reconnection backoff begins. - ---- - -## Common Scenarios and Interpretations - -### Scenario 1: All Peers Show `exch=no` - -**What it means:** The node does not recognize any peer's chain as being on the same fork. - -**Likely causes:** -- Node just started and hasn't completed handshake with any peer -- Node is on a different fork than all connected peers -- Peers connected while the node was in SYNC mode and their heads were not yet recognized - -**What to do:** -- Wait for handshake to complete — exchange may enable automatically -- Check the `fork` status in the header — if it says `MINORITY` or `LOOKING`, the node is resolving a fork conflict -- If the node stays in this state for a long time, it may need to re-sync from a different peer set - -### Scenario 2: `status=SYNC` with Many Active Peers - -**What it means:** The node is actively downloading blocks from multiple peers. - -**Likely causes:** -- Node startup after being offline -- Node fell behind the network -- Node is re-syncing after a fork switch - -**What to do:** -- Normal behavior — wait for sync to complete -- Check `head` progression in successive stats outputs to confirm blocks are being applied -- If `head` does not advance for an extended period, check for block validation errors in logs - -### Scenario 3: `peer_fork=MINO` on Multiple Peers - -**What it means:** Multiple peers believe they are on a minority fork. - -**Likely causes:** -- Network-wide fork event — validators are split between two competing chains -- The majority fork is being built by a different set of validators - -**What to do:** -- Monitor the `fork` status in the header — if it transitions to `MINORITY`, our node will also switch forks -- Wait for fork resolution — the protocol will eventually converge on a single chain -- If this persists, check validator activity and network connectivity - -### Scenario 4: High `backoff` Values on Disconnected Peers - -**What it means:** Peers have disconnected multiple times and the reconnection interval has grown. - -**Likely causes:** -- Network instability between our node and the peers -- Peers are restarting or experiencing downtime -- Firewall or NAT issues blocking persistent connections - -**What to do:** -- Check network connectivity to the peer addresses -- Verify firewall rules allow outbound connections on port 2001 -- If peers are known to be online, high backoff is normal and will reset on successful connection - -### Scenario 5: `paused=YES` - -**What it means:** Block processing is temporarily suspended. - -**Likely causes:** -- Snapshot creation in progress — database is locked for consistent state export -- Internal maintenance operation requiring exclusive database access - -**What to do:** -- Normal during snapshot operations — wait for completion -- Blocks received during pause are queued and will be processed when resumed -- If pause persists unexpectedly, check for stuck snapshot operations - -### Scenario 6: Peer Exchange Rate-Limiting in Logs - -**Log message:** `Peer rate-limited our exchange request, wait s` - -**What it means:** Our node sent a `dlt_peer_exchange_request` to a peer, but that peer responded with a `dlt_peer_exchange_rate_limited` message because the remote peer has already served 3 exchange requests from us within the last 5-minute window. - -**Mechanism:** -- Each peer tracks `peer_exchange_request_count` and `peer_exchange_window_start` per remote peer. -- When a `dlt_peer_exchange_request` arrives, the receiving peer checks `is_peer_exchange_rate_limited()` — if 3 or more requests were received within the 5-minute window (`PEER_EXCHANGE_WINDOW_SEC`), it responds with `dlt_peer_exchange_rate_limited{wait_seconds}` instead of a peer list. -- On our side, receiving this response marks us as rate-limited locally (`peer_exchange_request_count = MAX`), which prevents `periodic_peer_exchange()` from selecting this peer for future requests until the window expires. -- This is a two-sided mechanism: both nodes independently enforce the same sliding window (3 requests per 5 minutes). - -**Why this exists:** -- Prevents excessive peer discovery traffic on stable networks -- Reduces unnecessary message overhead when peer topology is not changing -- Complements the `dlt-peer-exchange-min-uptime-sec` (600s) filter which ensures only stable peers are shared - -**What to do:** -- This is normal, expected behavior — no action required -- The message appears at most after the 4th request within a 5-minute window per peer -- If this message appears very frequently for many peers, it may indicate that the periodic exchange interval is configured too aggressively or the network has very few unique peers - ---- - -## Quick Reference - -### Enumerations - -**Node Status:** -- `DLT_NODE_STATUS_SYNC` (0) = Catching up -- `DLT_NODE_STATUS_FORWARD` (1) = Caught up, real-time operation - -**Fork Status:** -- `DLT_FORK_STATUS_NORMAL` (0) = On majority fork -- `DLT_FORK_STATUS_LOOKING_RESOLUTION` (1) = Resolving fork conflict -- `DLT_FORK_STATUS_MINORITY` (2) = On minority fork - -**Peer Lifecycle States:** -- `DLT_PEER_LIFECYCLE_CONNECTING` (0) = Establishing TCP connection -- `DLT_PEER_LIFECYCLE_HANDSHAKING` (1) = Exchanging hello messages -- `DLT_PEER_LIFECYCLE_SYNCING` (2) = Downloading block range -- `DLT_PEER_LIFECYCLE_ACTIVE` (3) = Operational, exchange established -- `DLT_PEER_LIFECYCLE_DISCONNECTED` (4) = Disconnected, will reconnect -- `DLT_PEER_LIFECYCLE_BANNED` (5) = Temporarily banned - -### Thresholds and Constants - -| Constant | Value | Description | -|----------|-------|-------------| -| `SPAM_STRIKE_THRESHOLD` | 10 | Spam strikes before soft-ban | -| `BAN_DURATION_SEC` | 3600 | Default ban duration (1 hour) | -| `INITIAL_RECONNECT_BACKOFF_SEC` | 30 | Initial reconnection delay | -| `MAX_RECONNECT_BACKOFF_SEC` | 3600 | Maximum reconnection delay (1 hour) | -| `PEER_EXCHANGE_MAX_REQUESTS` | 3 | Maximum peer exchange requests allowed per peer within the sliding window | -| `PEER_EXCHANGE_WINDOW_SEC` | 300 | Peer exchange sliding window duration (5 min) — both requesting and serving sides enforce this; a rate-limited response carries `wait_seconds` indicating remaining window time | -| `SEND_QUEUE_MAX_DEPTH` | 100 | Maximum queued messages per peer | -| `KNOWN_BLOCKS_WINDOW` | 20 | Block ID ring buffer size for echo suppression | -| `CONNECTING_TIMEOUT` | 5s | Timeout for TCP connection establishment | -| `HANDSHAKING_TIMEOUT` | 10s | Timeout for hello/hello-reply exchange | diff --git a/.qoder/docs/emergency-consensus-review.md b/.qoder/docs/emergency-consensus-review.md deleted file mode 100644 index 859d5becf0..0000000000 --- a/.qoder/docs/emergency-consensus-review.md +++ /dev/null @@ -1,613 +0,0 @@ -# Emergency Consensus Recovery — Implementation Review - -## Status: Implemented (Hardfork 12, version 3.1.0) — Bugs Found and Fixed (B1–B17) - -Research source: [consensus-emergency-recovery.md](../research/consensus-emergency-recovery.md) - ---- - -## System Overview - -Hardfork 12 adds an on-chain **Emergency Consensus Mode** that activates automatically when the VIZ network stalls for >1 hour (no LIB advancement). The activation check is fully deterministic — it uses only block timestamps from the chain state (`b.timestamp - lib_block.timestamp`), ensuring identical results on every node and during every replay. A well-known committee key (`VIZ75CRHVHPwYiUESy1bgN3KhVFbZCQQRA9jT6TnpzKAmpxMPD6Xv`) becomes the block producer, keeping the chain alive until real validators return. - -On activation, **all real validators are disabled** (`signing_key` set to null, penalties reset to zero, `current_run` reset). Only the committee produces blocks initially. Operators must manually re-enable validators via `witness_update_operation` transactions. This ensures a clean start where only intentionally re-registered validators participate. - -The committee validator is a **neutral voter**: it copies the current median chain properties and votes for the currently applied hardfork version (not a future one). This ensures that committee slots in the schedule don't skew governance parameters or push unvoted hardforks. - -### Key Files Modified - -| File | Role | -|---|---| -| `libraries/chain/hardfork.d/12.hf` | Hardfork 12 definition | -| `libraries/protocol/include/graphene/protocol/config.hpp` | Emergency constants, version 3.1.0 | -| `libraries/chain/include/graphene/chain/database.hpp` | DGP fields, skip flags | -| `libraries/chain/database.cpp` | Activation, hybrid schedule, LIB advancement (capped), startup recovery, vote-weighted fork comparison | -| `libraries/chain/include/graphene/chain/fork_database.hpp` | Emergency mode flag, size increase 1024→2400 | -| `libraries/chain/fork_database.cpp` | Hash tie-breaking, `set_emergency_mode()` | -| `plugins/validator/validator.cpp` | Three-state safety, emergency key config, fork collision | -| `libraries/network/include/graphene/network/peer_connection.hpp` | `fork_rejected_until` soft-ban field, `sync_spam_strikes` counter | -| `libraries/network/node.cpp` | P2P anti-spam (soft-ban vs disconnect), sync ping-pong loop fix, sync spam soft-ban | -| `plugins/snapshot/plugin.cpp` | Forward-compatible DGP import | -| `share/vizd/config/config_witness.ini` | `emergency-private-key` option | - ---- - -## Architecture Diagram - -``` - ┌──────────────────────────────┐ - │ update_global_dynamic_data │ - │ (every block) │ - │ │ - │ HF12 active? │ - │ emergency_active == false? │ - │ ├── NO ─────────────────│── Skip check - │ └── YES │ - │ lib_block available? │ - │ ├── NO ─────────────────│── Skip check (snapshot restore) - │ └── YES │ - │ seconds_since_lib │ - │ = b.timestamp - │ - │ lib_block.timestamp │ - │ ≥ 3600? │ - │ ├── YES ─────────────│── Activate Emergency - │ │ │ • emergency_consensus_active = true - │ │ │ • Create/update committee validator - │ │ │ • Disable ALL real validators - │ │ │ (signing_key = null, penalties = 0) - │ │ │ • Override schedule → committee - │ │ │ • next_shuffle_block_num = now+N - │ │ │ • fork_db.set_emergency_mode(true) - │ └── NO │ - └──────────────────────────────┘ - │ - ▼ - ┌──────────────────────────────┐ - │ update_witness_schedule │ - │ (every round) │ - │ │ - │ 1. Normal schedule build │ - │ (may zero all slots if no │ - │ validators have valid keys) │ - │ 2. Hybrid schedule override: │ - │ • Real validators keep slots │ - │ • Committee fills gaps │ - │ • Expand to full 21 slots │ - │ • Sync committee props/vote │ - │ 3. update_median_witness_props │ - │ │ - │ Skip committee in: │ - │ • Hardfork vote tally │ - │ • Median props computation │ - │ │ - │ Exit check: │ - │ real_witness_slots >= 75% │ - │ of CHAIN_MAX_WITNESSES? │ - │ ├── YES ─────────────►│── Deactivate Emergency - │ │ │ • emergency_consensus_active = false - │ │ │ • fork_db.set_emergency_mode(false) - │ └── NO │ - └──────────────────────────────┘ - │ - ▼ - ┌──────────────────────────────┐ - │ update_last_irreversible_block│ - │ │ - │ During emergency: │ - │ • All schedule validators │ - │ used (including committee) │ - │ • 75% nth_element threshold │ - │ • LIB capped at HEAD−1 │ - │ (preserves undo session │ - │ for current block) │ - │ • LIB advances every block │ - │ (1 block behind head) │ - └──────────────────────────────┘ - │ - ▼ - ┌──────────────────────────────┐ - │ Startup Recovery │ - │ (database::open) │ - │ │ - │ If schedule has empty slots: │ - │ • Fill all with committee │ - │ • Ensure emergency active │ - │ • Restore fork_db flag │ - └──────────────────────────────┘ -``` - ---- - -## Failure & Rollback Procedures - -### F1: Emergency Activated Erroneously - -**When**: A bug or time desync causes `seconds_since_lib >= 3600` while the network is actually healthy. - -**Why this is extremely unlikely**: Emergency activation is fully deterministic: `seconds_since_lib = b.timestamp - lib_block.timestamp`. For a false trigger, LIB must genuinely stall for 1 hour (1200 blocks at 3s intervals). No config flags or skip-flags gate the check — it relies solely on signed block timestamps embedded in the chain, ensuring identical results on every node and during every replay. - -**Special case — snapshot restore**: After `open_from_snapshot()`, the block_log is empty, so `fetch_block_by_number(LIB)` returns invalid. If we fell back to `genesis_time`, emergency would activate immediately. This is now prevented: when the LIB block is unavailable, the emergency check is skipped entirely (B7 fix). - -**If it happens** (legitimate false activation): -1. Emergency activates, all real validators are **disabled** (`signing_key` zeroed). -2. Committee produces blocks alone initially. -3. Since all validators had valid keys before, operators quickly re-register via `witness_update_operation`. -4. Once **16+ real validators** (75% of 21) have re-registered with valid signing keys in the hybrid schedule, **emergency exits automatically**. -5. **Manual intervention required**: operators must re-register validators via transactions. - -**Worst case**: If all 21 validator operators are available, recovery takes as fast as they can broadcast `witness_update_operation` transactions. - -### F2: Emergency Exit Does Not Trigger - -**When**: Emergency is active but the exit condition (75% real validators in schedule) never becomes true. - -**Root cause**: Fewer than 75% of the 21 schedule slots (i.e., <16) are occupied by real validators with valid `signing_key`. Since all validators are disabled on activation, operators must manually re-enable them. - -**Recovery procedure**: -1. **Check how many validators are active**: `get_dynamic_global_properties` → `participation_count`, plus inspect `witness_schedule` to see how many non-committee slots exist. -2. **Activate more validators**: Each validator operator re-registers via `witness_update_operation` from CLI wallet or service. The validator only needs a valid `signing_key` — the hybrid schedule will automatically assign their slot on the next schedule update. -3. **No config changes needed**: The `enable-stale-production` and `required-participation` settings are auto-bypassed during emergency. validators just need to be connected to P2P and have their signing key registered. -4. **Threshold**: Once 16+ validators have valid signing keys in the schedule, emergency exits on the next `update_witness_schedule()` call. - -**If validators cannot re-register** (e.g., lost master keys): The network remains in emergency mode indefinitely but still produces blocks. Governance intervention (committee proposals) would be needed to resolve validator account recovery. - -### F3: Committee Chain Split (Multiple Emergency Producers) - -**When**: Multiple nodes with the emergency private key produce competing blocks at the same slot during emergency. - -**Why this is expected and handled**: -1. **Hash tie-breaking** (`fork_database::_push_block()`): When two blocks are at the same height during emergency, the one with the lower `block_id` (SHA256 hash) wins deterministically. All nodes converge to the same block within 1 P2P propagation round. -2. **Fork collision check** (`validator.cpp`): After the first slot, nodes detect that a competing block already exists at the target height and skip production. This reduces multi-producer collisions to a transient 1–2 slot artifact. -3. **No permanent split**: Because all emergency blocks use the same `committee` validator account and the schedule is identical on all nodes, there is no ongoing disagreement. Convergence is guaranteed within seconds. - -**If a persistent split occurs** (e.g., network partition during emergency): -- LIB is 1 block behind head on all partitions → most emergency blocks are irreversible. -- When partitions reconnect, **vote-weighted chain comparison** (`push_block()`) resolves the fork: - - Branches with real validators (non-committee) win over pure-committee branches. - - Among branches with only committee blocks, the longer chain wins, with hash tie-breaking as the final tiebreaker. -- The losing partition unwinds its reversible blocks and syncs from the winner. - -### F4: Emergency Lasts Longer Than Undo Window - -**When**: This failure mode is now **largely mitigated**. LIB advances every block during emergency (capped at HEAD−1), so the gap between HEAD and LIB stays at exactly 1 block. The 10,000-block undo limit cannot be reached. - -**Previous behavior (before LIB advancement fix)**: LIB was frozen during emergency, and the gap grew at 1 block per 3 seconds. After ~8.3 hours (10,000 blocks), the undo limit was hit and block production halted. - -**Current behavior**: LIB = HEAD−1 at all times during emergency. `fork_db` size stays at 2 blocks. Emergency can run indefinitely without hitting any undo limits. - -**Remaining risk**: If a bug prevents LIB from advancing despite the cap, the old failure mode would resurface. The startup recovery mechanism (B12) provides an additional safety net. - -### F5: validators Disabled On Emergency Activation - -**When**: On emergency activation, **all real validators are disabled**: `signing_key` is set to `public_key_type()` (null), `penalty_percent` reset to 0, `counted_votes` restored to `votes`, `current_run` reset to 0. All `witness_penalty_expire_object` entries are removed. - -**Emergency behavior**: -1. On activation, **all real validators are immediately disabled** by zeroing their `signing_key`. This is intentional — it ensures a clean start where only the committee produces blocks. Operators must explicitly re-register validators. -2. **During emergency, offline validators do NOT accumulate new missed-block penalties**. The `update_global_dynamic_data()` penalty/shutdown logic is skipped for validators that are not the block producer and not the committee account. -3. validators must broadcast `witness_update_operation` to re-register their signing key. - -**Recovery**: Emergency blocks **allow transactions** (not forced empty). So validators can: -1. Connect their node to the P2P network. -2. Use CLI wallet or web services to broadcast `witness_update_operation` with their signing key. -3. The transaction enters the next emergency block. -4. On the next schedule update, the validator gets their slot back in the hybrid schedule. - -**This is intentional**: Disabling all validators on activation ensures that only intentionally re-registered validators participate. This prevents stale/crashed validators from being included in the schedule and causing production failures. - ---- - -## Bugs Found and Fixed - -The following bugs were discovered during code review of the emergency consensus implementation, specifically for the scenario of **1 node running 11 top validators** with other validators expected to join later. - -### B1 (Critical): Hybrid Schedule Doesn't Expand `num_scheduled_witnesses` - -**Problem**: `update_witness_schedule()` sets `num_scheduled_witnesses` to the count of validators with valid signing keys (e.g., 11). The hybrid override loop only iterated up to `num_scheduled_witnesses`, so empty slots at indices 11-20 were never visited and never assigned to committee. After the first schedule round, committee disappeared from production entirely. - -**Fix**: The hybrid override now iterates the full `CHAIN_MAX_WITNESSES` range, reads entries beyond `num_scheduled_witnesses` as empty, assigns committee to all empty/unavailable slots, and sets `num_scheduled_witnesses = CHAIN_MAX_WITNESSES * CHAIN_BLOCK_WITNESS_REPEAT`. - -### B2 (Critical): Committee Over-Counted in Hardfork Vote Tally - -**Problem**: With `CHAIN_BLOCK_WITNESS_REPEAT = 1`, the hardfork vote tally iterates every schedule slot. Committee filling 10 slots caused `get_witness("committee")` to be called 10 times, incrementing the committee's vote count by 10. The committee's default `hardfork_version_vote = 0.0.0` dominated the tally and blocked any hardfork from reaching `CHAIN_HARDFORK_REQUIRED_WITNESSES = 17`. - -**Fix**: The hardfork vote tally now skips `CHAIN_EMERGENCY_WITNESS_ACCOUNT` during emergency mode. Only real validators' votes count toward hardfork adoption. - -### B3 (High): Committee Skews Median Chain Properties - -**Problem**: `update_median_witness_props()` collected all schedule entries including 10 committee copies. The committee's default `chain_properties` (zero fees, zero sizes, zero penalties) skewed the median, enabling spam attacks and removing miss penalties. - -**Fix** (two-part): -1. The committee validator is initialized with `props = median_props` (current median), and re-synced every schedule update. This makes committee entries neutral — they reinforce the existing median rather than distorting it. -2. As defense-in-depth, `update_median_witness_props()` skips committee entries during emergency mode. This ensures the median reflects only real validators' preferences. - -### B4 (High): Offline validators Accumulate Penalties During Emergency - -**Problem**: When committee produced a block, the `missed_blocks` loop in `update_global_dynamic_data()` applied penalties to offline validators for every missed slot. After 200 missed blocks (~10 minutes), their `signing_key` was set to null again — the same problem that emergency activation's penalty reset tried to solve. - -**Fix** (consensus-based, deterministic): During emergency mode, the penalty/shutdown logic is split: -- **Skipped**: `total_missed++`, `penalty_percent` increment, and `counted_votes` recalculation for offline validators. Only `current_run` is reset. -- **Applied (consensus check)**: Key-blanking uses `head_block_num() - last_confirmed_block_num > CHAIN_EMERGENCY_MAX_WITNESS_MISSED_BLOCKS` (105 blocks = 5 full rounds). Both `head_block_num()` and `last_confirmed_block_num` are on-chain consensus fields stored in `witness_object` shared memory, so all nodes compute identical results. This replaces the old non-consensus approach that used a local `_emergency_round_start_gap` map (which could diverge between nodes). The threshold (105) is tighter than the normal-mode threshold (200) because real validators should re-enable faster during emergency recovery. - -### B5 (Medium): Committee Could Be Selected as Top/Support validator - -**Problem**: The top/support validator selection iterated by `counted_votes`. If the committee validator ever received votes, it could compete for a production slot, displacing a real validator. - -**Fix**: Both top and support validator selection loops now explicitly exclude `CHAIN_EMERGENCY_WITNESS_ACCOUNT`. - -### B6 (Medium): Committee Hardfork Vote Auto-Injected via Block Extensions - -**Problem**: `_generate_block()` auto-injects a `hardfork_version_vote` extension when the validator's on-chain vote doesn't match the binary's configured next hardfork. For the committee validator (which votes for `current_hardfork_version`), this extension overwrote the on-chain vote to the next hardfork version via `process_header_extensions()`, defeating the neutral-voter design. - -**Fix**: When the block producer is the emergency committee, hardfork vote auto-injection is skipped entirely. The committee's on-chain vote stays at `current_hardfork_version` and is re-synced every schedule update. - -### B7 (Critical): False Emergency Activation After Snapshot Restore - -**Problem**: In `update_global_dynamic_data()`, when `fetch_block_by_number(LIB)` returns invalid (block_log empty after `open_from_snapshot()`), `lib_time` fell back to `_dgp.genesis_time`. This made `seconds_since_lib = block_timestamp - genesis_time` — millions of seconds — causing emergency activation on the very first block after snapshot restore. - -The false activation triggers catastrophic side effects: -1. Schedule overridden to all-committee, but `next_shuffle_block_num` not updated → hybrid override can't run until the next shuffle boundary. -2. Blocks from real validators (via p2p) are rejected because the schedule expects `committee` → `head_block_num()` doesn't advance. -3. `next_shuffle_block_num` is never reached → **deadlock: the node permanently stops syncing**. Probability: ~20/21 (~95%) depending on how close the next shuffle was. -4. Side effects: all validator penalties reset, committee validator object created, consensus state corrupted. - -**Fix** (two-part): -1. When `lib_block` is not found (block_log empty after snapshot restore), `lib_time_available` stays `false` and the emergency check is skipped entirely. Emergency cannot activate without a valid LIB timestamp. -2. On emergency activation, `next_shuffle_block_num` is now set to `head_block_num() + num_scheduled_witnesses`, ensuring the hybrid override runs on the next schedule update even after a legitimate activation. - -### B8 (Medium): `inhibit_fetching_sync_blocks` Never Reset After Soft-Ban Expires - -**Problem**: In `node.cpp`, when a soft-ban is applied (`fork_rejected_until = now + 1h`), `inhibit_fetching_sync_blocks` is also set to `true`. After 1 hour, `fork_rejected_until` expires and blocks are accepted again, but `inhibit_fetching_sync_blocks` remains `true` forever — the node never requests sync inventory from that peer again. During extended emergency operation, the number of peers available for sync gradually decreases as each soft-banned peer's inhibit flag becomes permanent. - -**Fix**: In `process_block_during_normal_operation()`, after the `fork_rejected_until` check passes (ban expired), if `inhibit_fetching_sync_blocks` is `true` and `fork_rejected_until` is set and has expired, reset `inhibit_fetching_sync_blocks = false`. The check is targeted: it only resets the flag when `fork_rejected_until` is non-default (i.e., was set by a soft-ban), so `inhibit_fetching_sync_blocks` set for other reasons (missing sync items, old fork) is not affected. - -### B9 (Medium): `unlinkable_block_exception` Causes Infinite Resync Instead of Soft-Ban - -**Problem**: In `process_block_during_normal_operation()` (`node.cpp`), `unlinkable_block_exception` inherits from `fc::exception` but is caught by a more specific `catch` handler before the general `fc::exception` handler. The specific handler unconditionally sets `restart_sync_exception`, which triggers `start_synchronizing_with_peer()`. The `e.code() == unlinkable_block_exception::code_enum::code_value` check in the `fc::exception` handler was dead code — it could never be reached for unlinkable blocks. - -When a peer on a stale fork sends an unlinkable block at or below our head block number, the node enters an infinite resync loop: it requests blocks from the stale peer, cannot link them, requests again, etc. - -**Fix** (two-part): -1. In the `unlinkable_block_exception` catch handler, compare the peer's block number against our head. If the block is at or below our head, the peer is on a stale fork — soft-ban for 1 hour and set `inhibit_fetching_sync_blocks = true`. If the block is ahead of us, resync is justified (keep original behavior). -2. Remove the dead `unlinkable_block_exception::code_enum::code_value` check from the `fc::exception` handler, leaving only the `block_num <= head` comparison for the soft-ban decision. - -### B10 (Critical): All Real validators Must Be Disabled On Emergency Activation - -**Problem**: When emergency consensus activated, real validators retained their `signing_key` values. If they had valid signing keys but were offline, the hybrid schedule assigned them slots — but they could never produce blocks at those slots. The schedule had a mix of online committee and offline real validators, making block production unreliable. - -**Fix**: On emergency activation, **all real validators are immediately disabled**: `signing_key` set to `public_key_type()` (null), `penalty_percent` reset to 0, `counted_votes` restored to `votes`, `current_run` reset to 0. All `witness_penalty_expire_object` entries are removed. This ensures the initial schedule is all-committee (100% available). Operators must explicitly re-register validators via `witness_update_operation` transactions. - -### B11 (Critical): Schedule Crash — `get_witness("")` on Empty Schedule - -**Problem**: During emergency, the normal schedule build in `update_witness_schedule()` iterates all validators but skips any with null `signing_key`. Since B10 zeroes all keys, `sum_witnesses_count = 0` → all 21 slots are set to `account_name_type()` (empty string). The execution order was: -1. `modify(wso, ...)` — builds normal schedule (all slots zeroed) -2. `update_median_witness_props()` — iterates schedule, calls `get_witness("")` → **crash: `unknown key`** -3. Emergency hybrid override — would have filled empty slots with committee, but runs too late - -**Fix**: Moved the emergency hybrid schedule override to execute **before** `update_median_witness_props()`. The hybrid override fills empty/unavailable slots with committee first, so by the time `update_median_witness_props()` runs, all 21 slots contain valid validator names. - -### B12 (Critical): Permanently Corrupted Schedule After LIB=HEAD Commit - -**Problem**: During emergency, all 21 schedule slots point to the same committee validator. LIB computation via `nth_element` yields `last_supported_block_num == HEAD`. In `_apply_block`, the execution order is: -1. `update_last_irreversible_block()` (line ~4455) — calls `commit(HEAD)`, which **destroys the undo session** for the current block -2. `update_witness_schedule()` (line ~4466) — zeros all slots (pre-B11 fix), then crashes at `get_witness("")` - -Since `commit(HEAD)` already consumed the undo session, the zeroed schedule from step 2 was **permanently written** to shared memory with no rollback possible. On node restart, the schedule contained all empty slots, emergency mode was not re-entered, and the node couldn't produce blocks. - -**Fix** (three-part): -1. **LIB cap to HEAD−1**: During emergency, `new_last_irreversible_block_num` is capped to `head_block_number - 1`. This ensures `commit()` never catches up to the current block, preserving the undo session. -2. **Schedule override execution order** (B11 fix): Hybrid override runs before `update_median_witness_props()`, preventing the crash entirely. -3. **Startup schedule recovery**: In `database::open()`, after `init_hardforks()`, the schedule is scanned for empty slots. If found: all 21 slots are filled with committee, `emergency_consensus_active` is set to `true`, and `fork_db.set_emergency_mode(true)` is called. If the schedule is OK but emergency is active, the fork_db flag is restored (it's in-memory only and lost on restart). - -### B13 (Critical): Stack Buffer Overflow in `get_block_post_validations()` - -**Problem**: During emergency mode, the committee account fills 18 of 21 schedule slots. `get_block_post_validations()` iterates all `block_post_validation_object` entries and matches each against the schedule. For each match, it writes an entry to a fixed-size array of `CHAIN_MAX_BLOCK_POST_VALIDATION_COUNT = 20`. With the committee occupying 18 slots, each validation object generates up to 18 matches — a single object with its matching validators could write up to 18 entries per iteration. With 20 objects, this produced up to 360 writes into a 20-element array, causing a stack buffer overflow and silent segfault. - -**Fix** (two-part): -1. **Bounds check**: Break the inner loop when the result array reaches `CHAIN_MAX_BLOCK_POST_VALIDATION_COUNT`. -2. **One match per object**: Once a `block_post_validation_object` matches any schedule slot, skip remaining slots for that object. Each validation object contributes at most one entry. - -### B14 (High): P2P Sync Ping-Pong Loop in Emergency Mode - -**Problem**: In `on_fetch_blockchain_item_ids_message()` (`node.cpp`), when node A responds to node B's sync request, it checks whether it has B's last block. During emergency mode, competing forks produce different blocks at the same height — so A doesn't have B's block (and vice versa). Both nodes then call `start_synchronizing_with_peer()` for each other, which resets all tracking state and sends a new request, creating an infinite ping-pong loop. This generated hundreds of `get_block_ids()` calls per second, flooding logs and wasting CPU. - -**Fix**: Before calling `start_synchronizing_with_peer()`, compare the peer's block number (extracted via `block_header::num_from_id()`) against our head block number. Only restart sync if `peer_block_num > our_head_num` (peer is genuinely ahead). When `peer_block_num <= our_head_num`, the peer is on a competing fork at the same height — skip the restart. - -### B15 (Medium): Sync Spam Soft-Ban for Old Peers - -**Problem**: After the B14 fix, the local node no longer amplifies the sync loop. However, old peers (without the B14 fix) continue flooding the node with `fetch_blockchain_item_ids_message` requests from their own unpatched ping-pong loops. Each request triggers `get_block_ids()` on the server side — harmless but wasteful (CPU, log noise). - -**Fix**: Added sync spam detection with soft-ban in `on_fetch_blockchain_item_ids_message()`: -1. At the top of the handler, check `fork_rejected_until` — silently discard requests from already-banned peers (skips `get_block_ids()` entirely). -2. In both competing-fork branches (where `peer_block_num <= our_head_num`), increment a per-peer `sync_spam_strikes` counter. -3. After 50 strikes, set `fork_rejected_until = now + 300s` (5 minute soft-ban). Reset strikes on legitimate sync (peer genuinely ahead). -4. At the observed spam rate (~23 requests per 4ms burst), the threshold is hit in under 1 second. - -### B16 (High): Null validator Signing Key Crashes Block Validation - -**Problem**: When a validator has a null/empty `signing_key` (disabled via `shutdown_witness_operation` or emergency activation), and a block from that validator arrives via P2P, `validate_block_header()` calls `validate_signee(validator.signing_key)` with the empty key. The elliptic curve library crashes with `my->_key != empty_pub` deep inside `serialize()` — an opaque assertion that gives no indication of the root cause. The node then enters an infinite retry loop: gap fill requests the same block, receives it, rejects it with the crash, requests it again. - -**Fix** (two parts): -1. **Clear error message**: Added a pre-check in `validate_block_header()` before `validate_signee()` that catches `validator.signing_key == public_key_type()` and throws a descriptive `FC_ASSERT` naming the validator and block number. -2. **Gap fill blacklist**: Added rejection tracking in the DLT P2P node. When the same block is rejected 3 times (from any combination of broadcast + gap fill), gap fill is blacklisted for 120 seconds, breaking the infinite retry loop. - -### B17 (Critical): Emergency validator Blanking Was Non-Consensus - -**Problem**: The emergency validator blanking logic in `update_witness_schedule()` used `_emergency_round_start_gap` — a local `std::map` that is NOT consensus state. It was populated at runtime and not stored in shared memory. Different nodes could compute different values depending on startup timing, replay history, and when they entered emergency mode. This meant two nodes could reach different conclusions about whether to blank a validator's signing key, causing chain divergence. - -**Fix**: Replaced the non-consensus approach with the same deterministic pattern used in normal mode. Key-blanking now happens in `update_global_dynamic_data()` (the consensus path) and checks `head_block_num() - last_confirmed_block_num > CHAIN_EMERGENCY_MAX_WITNESS_MISSED_BLOCKS` (105). Both values are on-chain consensus fields in `witness_object` shared memory — all nodes compute identical results. Removed `_emergency_round_start_gap` entirely. - -### Committee Neutral Voter Design - -After all fixes, the committee validator has these properties: - -| Field | Value | Rationale | -|---|---|---| -| `signing_key` | `CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY` | Required for block production | -| `running_version` | `CHAIN_VERSION` | Matches the binary | -| `hardfork_version_vote` | `current_hardfork_version` | Votes for status quo, not future hardforks | -| `hardfork_time_vote` | `processed_hardforks[last_hardfork]` | Time the current hardfork was applied | -| `props` | `median_props` (current) | Copies current median, doesn't skew it | -| `schedule` | `top` | Required for hybrid schedule assignment | - -The committee's props and hardfork vote are re-synced every schedule update (`update_witness_schedule()`) to stay aligned with the latest median and hardfork state. - ---- - -## Emergency Key Threat Model - -### Key Distribution - -The emergency private key is **publicly known**. It is not a secret. Any node operator can configure it via `emergency-private-key` in their config. The corresponding public key is hardcoded in `config.hpp`: - -``` -VIZ75CRHVHPwYiUESy1bgN3KhVFbZCQQRA9jT6TnpzKAmpxMPD6Xv -``` - -### Who Has the Key - -**Everyone who wants it.** The key is published in: -- Source code (`config.hpp`) -- Config template (`config_witness.ini`) -- Documentation - -Any node running the Validator Plugin with `emergency-private-key` configured will attempt to produce blocks during emergency mode. This is by design — the goal is maximum availability during a network stall, not access control. - -### How Many Nodes Have It - -In practice: **every public validator node** should have it configured. During emergency: -- Multiple nodes may attempt to produce at the same slot. -- Hash tie-breaking (lowest `block_id` wins) ensures deterministic convergence. -- Fork collision check causes all but one node to back off after the first slot. -- The result is functionally equivalent to a single producer, achieved through distributed consensus rather than central coordination. - -### Compromise Scenario - -**The key cannot be "compromised" in the traditional sense** because it is already public. However, there are attack scenarios: - -| Attack | Impact | Mitigation | -|---|---|---| -| Attacker produces emergency blocks during normal operation | **None.** Emergency mode only activates when `seconds_since_lib >= 3600`. During normal operation, the schedule does not contain `committee`, so the attacker's blocks are invalid (wrong scheduled validator). | Consensus-level gating | -| Attacker produces blocks during real emergency | Blocks are valid but **compete equally** with other emergency producers. Hash tie-breaking resolves conflicts deterministically. The attacker cannot produce *more* blocks than any other node with the key. | Hash tie-breaking + fork collision check | -| Attacker produces blocks with invalid transactions | **Rejected.** Full consensus validation still applies to emergency blocks. Invalid operations, double-spends, etc. are caught by `apply_block()`. | Standard block validation | -| Attacker floods P2P with emergency blocks | **Mitigated.** P2P anti-spam (`fork_rejected_until`) soft-bans peers sending blocks on rejected forks for 15 minutes (5 minutes for trusted peers). Sync request spam is detected separately: 50 repeated competing-fork sync requests trigger a 5-minute soft-ban, silently discarding further requests. Fork collision check limits production to 1 block per slot. After soft-ban expires, `inhibit_fetching_sync_blocks` is automatically reset (B8 fix) so the peer remains available for sync. | P2P soft-ban + sync spam ban + fork collision + auto-reset | - -### Key Rotation - -**No rotation needed or supported.** Rationale: -- The key has no value outside emergency mode (it cannot sign normal blocks). -- Rotation would require a hardfork (the public key is in `config.hpp` as a consensus constant). -- There is zero security benefit from rotation — the key is public by design. -- All nodes must agree on the same key, so any change requires coordinated network upgrade. - -### Can Emergency Produce Non-Empty Blocks? - -**Yes, and this is required.** Emergency blocks must allow transactions because: -1. validators need to broadcast `witness_update_operation` to re-register their signing key during recovery. -2. If emergency blocks were forced empty, validators couldn't re-activate → deadlock where emergency never exits. -3. In practice, most emergency blocks will be empty (low transaction volume during a stall), but the mechanism **must not** prohibit transactions. - -### Summary - -The emergency key is a **public coordination mechanism**, not a secret. Its security model is: -- **Deterministic activation**: Uses only signed block timestamps (`b.timestamp - lib_block.timestamp ≥ 3600`), no config flags or wall-clock time — identical results on every node and during every replay -- **Activation gating**: Only usable when `emergency_consensus_active == true` (requires 1-hour LIB stall) -- **Snapshot safety**: When LIB block is unavailable (post-snapshot restore), emergency check is skipped to prevent false activation -- **Convergence**: Hash tie-breaking + fork collision → single effective producer -- **Validation**: Full consensus rules apply to emergency blocks -- **Deactivation**: Automatic when 75% (16/21) of schedule slots are real validators with valid signing keys -- **Scope**: Delegates can immediately re-activate through wallets/services once their nodes reconnect to the P2P network, because the emergency chain is public and accepts transactions - ---- - -## Test Matrix - -All scenarios should be tested before deployment. Each test specifies preconditions, actions, expected outcomes, and the emergency subsystem components exercised. - -### T1: Simple Stall → Emergency → Recovery - -| | | -|---|---| -| **Precondition** | 21 validators active, network healthy | -| **Action** | Shut down all 21 validators. Wait >1 hour. Start 1 node with emergency key. Gradually restart validators. | -| **Expected** | Emergency activates at LIB+3600s. All real validators disabled (signing_key zeroed). Committee produces blocks (full 21-slot schedule). LIB advances every block (capped at HEAD−1). Offline validators do NOT accumulate penalties. Committee props synced to median, hardfork vote synced to current version. validators re-register via `witness_update_operation`. When 16+ real validators have valid signing keys in schedule (75%) → emergency exits automatically. | -| **Components** | Activation, validator disabling, hybrid schedule (full expansion), LIB advancement (capped at HEAD−1), penalty skip, committee neutral voter, exit condition (75% real validators) | - -### T2: 2-Way Partition (Majority/Minority) - -| | | -|---|---| -| **Precondition** | 21 validators, healthy network | -| **Action** | Partition: 16 validators on side A, 5 on side B. | -| **Expected** | Side A: participation >75% → continues normally, no emergency. Side B: participation drops to ~24% → production stops (below 33% threshold). After 1 hour, emergency activates on side B. On reconnect, side A's chain wins (higher vote weight from 16 real validators). Side B unwinds emergency blocks. | -| **Components** | Three-state safety (healthy vs distressed), vote-weighted comparison, fork resolution | - -### T3: 2-Way Partition (Even Split, Both Enter Emergency) - -| | | -|---|---| -| **Precondition** | 21 validators, healthy network | -| **Action** | Partition: 10 validators on A, 11 on B. Neither side has 75% → both stall. Wait 1 hour → both enter emergency. Reconnect after 2 hours. | -| **Expected** | Both sides produce emergency+hybrid blocks. LIB advances (capped at HEAD−1) on both. On reconnect, vote-weighted comparison: side with higher total `votes` wins. Losing side unwinds. Emergency exits when 16+ real validators have valid keys in the merged schedule. | -| **Components** | LIB advancement (capped), vote-weighted comparison, fork_db expansion, partition merge | - -### T4: 3-Way Partition - -| | | -|---|---| -| **Precondition** | 21 validators, healthy network | -| **Action** | Partition into 3 groups: 7+7+7 validators. All stall → all enter emergency. Reconnect sequentially (A↔B first, then AB↔C). | -| **Expected** | Each partition produces emergency blocks independently. LIB advances (capped at HEAD−1) on all. First merge (A↔B): vote-weighted comparison picks winner. Second merge (AB↔C): vote-weighted comparison again. Final chain has highest cumulative vote weight. | -| **Components** | Multi-partition merge, vote-weighted comparison, cascading fork resolution, LIB advancement (capped) | - -### T5: Late Peer Rejoin - -| | | -|---|---| -| **Precondition** | Emergency active for 2 hours. 10 validators already back. | -| **Action** | validator #11 comes online with stale chain (2 hours behind). | -| **Expected** | Peer syncs from the emergency chain. Once synced, validator re-registers via `witness_update_operation`. Their slot appears in hybrid schedule on next update. validator produces at its assigned slot. LIB on the emergency chain is at HEAD−1, so only 1 reversible block exists — sync is fast. | -| **Components** | P2P sync during emergency, hybrid schedule, LIB advancement (capped), validator re-registration | - -### T6: Conflicting Emergency Producers - -| | | -|---|---| -| **Precondition** | Emergency active. 5 nodes with emergency key, well-connected. | -| **Action** | All 5 attempt to produce at slot N simultaneously. | -| **Expected** | Each produces a valid block with different `block_id`. Nodes receive competing blocks. Hash tie-breaking: lowest `block_id` wins on all nodes. Fork collision check: at slot N+1, nodes see existing block → only 1 (or 0) produces. By slot N+2, a single producer dominates. | -| **Components** | Hash tie-breaking, fork collision check, deterministic convergence | - -### T7: Long Emergency > Undo Horizon - -| | | -|---|---| -| **Precondition** | All validators offline. Emergency active. | -| **Action** | Let emergency run for 8+ hours (well beyond old undo limit). | -| **Expected** | LIB advances every block (capped at HEAD−1), so HEAD−LIB gap stays at exactly 1. fork_db size stays at 2 blocks. Emergency runs indefinitely without hitting any undo limits. No degradation over time. | -| **Components** | LIB advancement (capped at HEAD−1), fork_db sizing, indefinite emergency operation | - -### T8: validator Shutdown + Re-registration During Emergency - -| | | -|---|---| -| **Precondition** | Emergency active. All validators had `signing_key` nullified by missed-block shutdown. | -| **Action** | validator operator broadcasts `witness_update_operation` via CLI wallet during emergency. | -| **Expected** | Transaction included in emergency block. validator object updated with new signing key. Next schedule update: validator gets their slot in hybrid schedule instead of committee. validator begins producing. Offline validators do NOT get `signing_key` nullified again during emergency (penalty/shutdown skipped). | -| **Components** | Transaction processing during emergency, hybrid schedule update, penalty skip during emergency | - -### T9: Snapshot Restore + Emergency Interaction - -| | | -|---|---| -| **Precondition** | Snapshot taken during emergency mode (`emergency_consensus_active = true`). | -| **Action** | Restore snapshot on a fresh node. Start node with Validator Plugin and emergency key. | -| **Expected** | Snapshot import reads `emergency_consensus_active` and `emergency_consensus_start_block` from DGP (forward-compatible). Node resumes in emergency mode. Produces emergency blocks. Standard exit condition applies. **No false activation**: block_log is empty after snapshot restore, so `fetch_block_by_number(LIB)` returns invalid, but the emergency check is skipped (not triggered by fallback to genesis_time). | -| **Components** | Snapshot import (forward-compatible fields), emergency state persistence, B7 fix (no false activation on empty block_log) | - -### T10: Snapshot From Pre-HF12 Node - -| | | -|---|---| -| **Precondition** | Snapshot taken before HF12 (no emergency fields in DGP). | -| **Action** | Restore on HF12 node. | -| **Expected** | Snapshot import defaults `emergency_consensus_active = false`, `emergency_consensus_start_block = 0`. Node starts in normal mode. Emergency detection works normally after HF12 activation. | -| **Components** | Snapshot forward-compatibility, default field handling | - -### T11: Healthy Network After HF12 (No Regression) - -| | | -|---|---| -| **Precondition** | HF12 active. All 21 validators online. Network healthy. | -| **Action** | Normal operation for extended period. Occasional validator restarts. | -| **Expected** | Emergency never activates (LIB advances every few seconds). Three-state safety: healthy mode enforces safe defaults regardless of `enable-stale-production` config. Vote-weighted comparison active but functionally equivalent to longest-chain (same validators on both sides of any micro-fork). Committee exclusion in hardfork tally and median props is a no-op (committee not in schedule). | -| **Components** | No-regression, three-state safety (healthy mode), vote-weighted comparison | - -### T12: `enable-stale-production` Ignored in Healthy Mode - -| | | -|---|---| -| **Precondition** | HF12 active. All validators online. Operator has `enable-stale-production = true` (forgot to revert from pre-HF12). | -| **Action** | Network partition isolates this validator. | -| **Expected** | Participation rate ≥33% → healthy mode → `enable-stale-production` is **ignored**. validator stops producing when it detects it's isolated (no recent blocks). **This is the core micro-fork prevention feature.** | -| **Components** | Three-state safety (healthy mode auto-enforces safe defaults) | - -### T13: Partial validator Set (11 Top validators on 1 Node) - -| | | -|---|---| -| **Precondition** | Network stalled >1 hour. 1 node with 11 top validators + emergency key. 10 other validators offline. | -| **Action** | Emergency activates. 11 real validators produce at their slots. Committee fills the other 10 slots. Over time, other validators re-join. | -| **Expected** | All validators initially disabled (signing_key zeroed). 11 validators re-register via `witness_update_operation`. Hybrid schedule expands to full 21 slots (11 real + 10 committee). Committee validator has `props = median_props` and `hardfork_version_vote = current_hardfork_version` — neutral voter. Hardfork vote tally excludes committee (only 11 real votes counted). Median props computed from real validators only. Offline validators don't accumulate penalties. When 16+ real validators have valid signing keys in schedule (75% of 21) → emergency exits automatically. | -| **Components** | validator disabling, hybrid schedule expansion, committee neutral voter, hardfork tally exclusion, median props exclusion, penalty skip, exit condition (75% real validators) | - -### T14: Committee Hardfork Vote Neutrality - -| | | -|---|---| -| **Precondition** | Emergency active. Binary version includes a pending hardfork (e.g., HF13) that has not been applied on-chain yet. 11 real validators running HF13 binary. | -| **Action** | Committee produces blocks. Verify committee's on-chain `hardfork_version_vote` stays at the current applied version. | -| **Expected** | Committee's block headers do NOT contain `hardfork_version_vote` extensions. `process_header_extensions()` does not update the committee's on-chain vote. Committee vote stays at `current_hardfork_version` (e.g., HF12). Only real validators' votes count toward HF13 adoption (need 17 of them). Committee props/hardfork vote re-synced every schedule update. | -| **Components** | Hardfork vote auto-injection skip, process_header_extensions, committee props sync | - -### T15: Snapshot Restore Does Not False-Activate Emergency - -| | | -|---|---| -| **Precondition** | Network healthy. Snapshot taken from a healthy state (`emergency_consensus_active = false`). | -| **Action** | Restore snapshot on a fresh node (block_log empty). Start syncing from p2p. | -| **Expected** | First block from p2p: `fetch_block_by_number(LIB)` returns invalid (block_log empty). `lib_time_available = false`. Emergency check is **skipped**. Node processes the block normally. No false activation, no committee validator created, no penalties reset. Node syncs normally. | -| **Components** | B7 fix (lib_time_available guard), snapshot restore, block_log interaction | - -### T16: Soft-Ban `inhibit_fetching_sync_blocks` Reset After Expiry - -| | | -|---|---| -| **Precondition** | Emergency active. Node soft-banned a peer 1 hour ago (`fork_rejected_until` set, `inhibit_fetching_sync_blocks = true`). | -| **Action** | The soft-ban expires. Peer sends a valid block. | -| **Expected** | `fork_rejected_until` is in the past → block is processed (not discarded). `inhibit_fetching_sync_blocks` is reset to `false` (B8 fix). Node resumes requesting sync inventory from this peer. Gradual peer loss during extended emergency is prevented. | -| **Components** | B8 fix (inhibit flag reset on soft-ban expiry), P2P soft-ban lifecycle, sync operations | - -### T17: Startup Schedule Recovery (B12 Fix) - -| | | -|---|---| -| **Precondition** | Node crashed during emergency with corrupted schedule (all empty validator slots in shared memory). | -| **Action** | Restart the node. | -| **Expected** | `database::open()` detects empty slots in schedule. Fills all 21 slots with committee. Sets `emergency_consensus_active = true` if not already set. Restores `fork_db.set_emergency_mode(true)`. Node resumes producing emergency blocks. LIB advances normally (capped at HEAD−1). | -| **Components** | Startup recovery (B12), schedule repair, fork_db flag restoration | - -### T18: Emergency fork_db Flag Restored On Normal Restart - -| | | -|---|---| -| **Precondition** | Emergency active, schedule is healthy (all committee slots). Node restarted cleanly. | -| **Action** | Restart the node. | -| **Expected** | `database::open()` sees schedule is OK but `emergency_consensus_active == true` in DGP. Calls `fork_db.set_emergency_mode(true)` to restore the in-memory flag. Node continues in emergency mode without interruption. | -| **Components** | Startup recovery (fork_db flag), DGP state persistence | - -### T19: Block Post-Validation During Emergency (B13 Fix) - -| | | -|---|---| -| **Precondition** | Emergency active. Committee fills 18/21 schedule slots. Multiple `block_post_validation_object` entries exist. | -| **Action** | Call `get_block_post_validations()` (triggered by P2P block validation). | -| **Expected** | Result array stays within `CHAIN_MAX_BLOCK_POST_VALIDATION_COUNT = 20` bounds. Each validation object produces at most one entry (no duplicate committee matches). No stack overflow, no segfault. | -| **Components** | B13 fix (bounds check + one-match-per-object), `get_block_post_validations()` | - -### T20: P2P Sync Ping-Pong Loop Prevention (B14 Fix) - -| | | -|---|---| -| **Precondition** | Emergency active. Two nodes (A, B) with competing forks at the same height. Both have the B14 fix. | -| **Action** | A sends sync request to B. B responds. Observe whether `start_synchronizing_with_peer()` is called. | -| **Expected** | B sees A's last block is at the same height as B's head (`peer_block_num == our_head_num`). B does NOT call `start_synchronizing_with_peer()`. No ping-pong loop. `get_block_ids()` is called once per request, not hundreds of times per second. | -| **Components** | B14 fix (block-number comparison guard), `on_fetch_blockchain_item_ids_message()` | - -### T21: Sync Spam Soft-Ban (B15 Fix) - -| | | -|---|---| -| **Precondition** | Emergency active. Node has B14+B15 fixes. Connected to old peers without B14 fix. | -| **Action** | Old peers flood the node with `fetch_blockchain_item_ids_message` requests (competing fork at same height). | -| **Expected** | First 50 requests are processed normally (each incrementing `sync_spam_strikes`). At strike 50, peer is soft-banned for 5 minutes (`fork_rejected_until` set). Subsequent requests are silently discarded at the top of `on_fetch_blockchain_item_ids_message()` — no `get_block_ids()` calls. After 5 minutes, ban expires and strikes reset if peer sends legitimate sync requests. | -| **Components** | B15 fix (sync spam strikes + soft-ban), `fork_rejected_until` reuse, `on_fetch_blockchain_item_ids_message()` | diff --git a/.qoder/docs/emergency-consensus-workflow.md b/.qoder/docs/emergency-consensus-workflow.md deleted file mode 100644 index 35a4662a1f..0000000000 --- a/.qoder/docs/emergency-consensus-workflow.md +++ /dev/null @@ -1,711 +0,0 @@ -# Emergency Consensus Mode — Full Workflow Tree - -Comprehensive analysis of the emergency consensus system introduced in Hardfork 12. Covers all processes, code paths, component interactions, and guard conditions across the entire node codebase. - ---- - -## 1. Overview - -Emergency consensus mode activates when the network has stalled for 1 hour (no blocks have been accepted since the Last Irreversible Block timestamp). During emergency, a special "committee" validator produces blocks to maintain chain continuity. Once enough real validators re-enable their signing keys (\(\ge\) 75% of schedule slots), emergency mode auto-deactivates. - -### Key Constants - -| Constant | Value | Meaning | -|----------|-------|---------| -| `CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC` | 3600 | Seconds since LIB before activation | -| `CHAIN_EMERGENCY_WITNESS_ACCOUNT` | `"committee"` | Emergency block producer | -| `CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY` | `VIZ75CR...` | Deterministic signing key | -| `CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS` | 21 | Consecutive real-validator blocks for exit | -| `CHAIN_IRREVERSIBLE_THRESHOLD` | 75% (`75 * CHAIN_1_PERCENT`) | Required to advance LIB / exit emergency | -| `CHAIN_MAX_WITNESSES` | 21 | Maximum unique validator slots | -| `CHAIN_HARDFORK_12` | Hardfork #12 | Gates all emergency logic | - -### System State - -Two dynamic global property fields track emergency state: - -| Field | Type | Default | Meaning | -|-------|------|---------|---------| -| `emergency_consensus_active` | `bool` | `false` | Is emergency mode active? | -| `emergency_consensus_start_block` | `uint32_t` | `0` | Block number at activation | - ---- - -## 2. Complete Workflow Trees - -### 2A. Emergency Activation - -**Location:** [`database::update_global_dynamic_data()`](file://libraries/chain/database.cpp#L5059-L5200) — runs on every applied block. - -``` -Block applied (update_global_dynamic_data) - │ - ├── Gate: has_hardfork(CHAIN_HARDFORK_12)? - │ └── No → RETURN (emergency mode not possible) - │ - ├── Gate: _dgp.emergency_consensus_active? - │ └── Yes → RETURN (already active, skip re-activation) - │ - ├── Gate: LIB block available in block_log? - │ ├── LIB num == 0 → skip (no LIB) - │ ├── fetch_block_by_number(LIB) invalid → skip (snapshot restore) - │ └── LIB block found in block_log → proceed - │ Reason: DLT nodes with empty block_log after snapshot would - │ see millions of seconds since genesis → false activation → deadlock - │ - ├── Compute: seconds_since_lib = b.timestamp - lib_block.timestamp - │ - ├── Gate: seconds_since_lib >= CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC (3600)? - │ └── No → RETURN (network still within grace period) - │ - └── === ACTIVATION SEQUENCE === - │ - ├── 1. Set emergency flags on DGP: - │ dgp.emergency_consensus_active = true - │ dgp.emergency_consensus_start_block = b.block_num() - │ - ├── 2. Create/Update emergency validator object: - │ ├── validator "committee" exists? - │ │ ├── No → create: - │ │ │ owner = "committee" - │ │ │ signing_key = CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY - │ │ │ running_version = CHAIN_VERSION - │ │ │ hardfork votes = current applied hf version (neutral) - │ │ │ props = current median_props - │ │ └── Yes → modify existing: - │ │ signing_key = CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY - │ │ schedule = top - │ │ sync version + hardfork votes + props - │ │ - │ ├── 3. Disable ALL real validators: - │ │ For each validator (except committee): - │ │ signing_key = zero (public_key_type()) - │ │ penalty_percent = 0 - │ │ counted_votes = votes - │ │ current_run = 0 - │ │ - │ ├── 4. Remove ALL penalty expiration objects - │ │ - │ ├── 5. Override validator schedule: - │ │ All num_scheduled_witnesses slots → "committee" - │ │ next_shuffle_block_num = head + num_scheduled - │ │ - │ ├── 6. Notify fork_db: - │ │ _fork_db.set_emergency_mode(true) - │ │ → enables deterministic hash tie-breaking - │ │ - │ └── 7. Log: - │ "EMERGENCY CONSENSUS MODE activated at block #N. - │ No blocks for X seconds since LIB Y." - │ - └── Determinism guarantee: uses ONLY block-embedded data - (b.timestamp, lib_block.timestamp). No wall-clock, no skip flags. -``` - ---- - -### 2B. Emergency Deactivation (Exit Condition) - -**Location:** [`database::update_witness_schedule()`](file://libraries/chain/database.cpp#L2689-L2791) — runs on every schedule boundary. - -``` -update_witness_schedule() - │ - ├── 1. Build normal validator schedule - │ (may have empty slots if validators have zero signing_key) - │ - ├── Gate: has_hardfork(HF12) && emergency_consensus_active? - │ └── No → skip hybrid override, proceed with normal schedule - │ - └── === HYBRID SCHEDULE OVERRIDE === - │ - ├── For each slot i in [0, CHAIN_MAX_WITNESSES) (by CHAIN_BLOCK_WITNESS_REPEAT): - │ ├── Slot name == "" ? - │ │ └── Fill slot + repeats with "committee" → committee_slots++ - │ ├── validator has signing_key != zero ? - │ │ └── Keep validator → real_witness_slots++ - │ └── validator unavailable (key=0 or not found)? - │ └── Fill slot + repeats with "committee" → committee_slots++ - │ - ├── Expand: num_scheduled_witnesses = CHAIN_MAX_WITNESSES × REPEAT - ├── Set: next_shuffle_block_num = head + num_scheduled - ├── Log: "Emergency hybrid schedule: R real, C committee slots" - │ - ├── Sync committee validator: - │ props = current median_props - │ hardfork votes = current applied version (neutral voter) - │ - └── === EXIT CHECK === - ├── exit_threshold = (CHAIN_MAX_WITNESSES × 75%) / 100 (= 15) - ├── real_witness_slots >= exit_threshold? - │ ├── No → emergency continues (not enough real validators) - │ └── Yes → DEACTIVATE: - │ ├── dgp.emergency_consensus_active = false - │ ├── _fork_db.set_emergency_mode(false) - │ └── Log: "EMERGENCY CONSENSUS MODE deactivated at block #N. - │ R real validators active (threshold: T)." - │ - └── After deactivation: validators with restored keys produce normally -``` - ---- - -### 2C. validator Block Production in Emergency Mode - -**Location:** [`witness_plugin::maybe_produce_block()`](file://plugins/validator/validator.cpp#L468-L870) - -``` -maybe_produce_block() {now = NTP + 250ms} - │ - ├── === DLT MODE SYNC CHECK === - │ │ - │ ├── NOT DLT mode OR NOT syncing? → continue - │ │ - │ └── DLT mode AND syncing? - │ ├── IS emergency master? (emergency key in _witnesses) - │ │ └── Yes → BYPASS sync check (deadlock prevention — p18.log) - │ └── NOT emergency master (slave) - │ └── Return not_synced (producing on stale head creates - │ double-production collisions — p32.log) - │ - ├── === HARDFORK 12 THREE-STATE SAFETY === - │ │ - │ ├── emergency_consensus_active? - │ │ └── Yes → EMERGENCY PATH: - │ │ ├── IS emergency master? (emergency key in _witnesses) - │ │ │ └── Yes → _production_enabled = true (bypass stale + participation) - │ │ ├── NOT emergency master (slave): - │ │ │ ├── _production_enabled already? → continue - │ │ │ └── Else: check get_slot_time(1) >= now - │ │ │ ├── Yes → _production_enabled = true - │ │ │ └── No → return not_synced - │ │ └── _witnesses.empty()? - │ │ └── Yes → ERROR: "no validators configured" - │ │ - │ ├── Not emergency, participation >= 33%? - │ │ └── Yes → HEALTHY PATH: - │ │ ├── Clear stale-production skip flag - │ │ └── Enable production if slot_time(1) >= now - │ │ - │ └── Not emergency, participation < 33%? - │ └── DISTRESSED PATH: - │ ├── Honor enable-stale-production override - │ └── Else: check sync + participation normally - │ - ├── Block post-validation broadcast (every validator we have, scheduled only) - │ - ├── === MINORITY FORK DETECTION === - │ │ - │ ├── NOT emergency: standard 21-block check - │ │ └── All 21 blocks from our validators? - │ │ ├── stale-production enabled → continue - │ │ └── stale-production disabled → resync_from_lib() - │ │ - │ ├── Emergency + DLT + IS MASTER: - │ │ └── SKIP minority fork check entirely - │ │ (all blocks being "ours" is expected for master) - │ │ - │ └── Emergency + DLT + NOT MASTER (follower): - │ └── 21-block check (1 full round via CHAIN_MAX_WITNESSES) - │ └── All 21 blocks from our validators? - │ └── Yes → DLT EMERGENCY MINORITY FORK: - │ resync_from_lib() [but see guard in §2G below] - │ - ├── Slot assignment check: - │ ├── get_slot_at_time(now) == 0? → not_time_yet - │ ├── scheduled_witness not ours? → not_my_turn - │ ├── scheduled_key == zero? → not_my_turn - │ └── Don't have private key? → no_private_key - │ - ├── Timing: |scheduled_time - now| > 500ms? → lag - │ - ├── === FORK COLLISION === - │ │ - │ ├── Emergency mode: ANY block at this height IS competing - │ │ → defer to fork_db deterministic hash resolution - │ │ - │ └── Normal mode: only different validator + different parent - │ → vote-weight comparison - │ - └── generate_block() with committee private key -``` - -#### Deferred Block Notifications - -`notify_applied_block()` is deferred outside the write-lock scope in `push_block()`: - -``` -push_block(new_block) - │ - ├── _defer_block_notifications = true - │ - ├── with_strong_write_lock(): - │ └── _push_block() → _apply_block() - │ └── notify_applied_block deferred: - │ _pending_block_notifications.push_back(block) - │ - ├── [on exception]: - │ └── Clear pending, re-throw - │ - ├── _defer_block_notifications = false - │ - └── flush_pending_block_notifications() - └── For each pending block: - notify_applied_block(block) ← no write lock held -``` - -**Why:** Plugin callbacks (MongoDB, operation_history, account_history) can take -seconds. Holding the write lock during notification blocks ALL P2P and RPC -threads (p32.log: 13.8s lock hold). Deferring releases the lock first, allowing -concurrent reads while plugins process at their own pace. - -**Replay path:** `apply_block()` (public, called during replay) does NOT set -`_defer_block_notifications`, so notifications are delivered immediately — same -as before (no contention during replay). - -**Exception safety:** If `push_block()` throws, pending notifications are discarded -(those blocks were rolled back by the undo stack). - -#### Master/Follower Detection (DLT Emergency) - -``` -Is this node the DLT emergency master? - │ - ├── Condition A: CHAIN_EMERGENCY_WITNESS_ACCOUNT ("committee") in _witnesses? - │ └── Only true when --emergency-private-key is configured - │ - ├── Condition B: "committee" is in the current schedule? - │ (check current_shuffled_witnesses for committee account) - │ - └── A AND B → IS MASTER (produces blocks, others sync from us) - NOT A OR NOT B → IS FOLLOWER (relies on P2P sync from master) -``` - ---- - -### 2D. Fork Database — Emergency Deterministic Tie-Breaking - -**Location:** [`fork_database::_push_block()`](file://libraries/chain/fork_database.cpp#L77-L88) - -``` -fork_db._push_block(item) - │ - ├── Validate linkage (parent must be in _index) - ├── Insert into _index - │ - └── Update _head: - ├── item->num > _head->num? - │ └── Yes → _head = item (longer chain wins, normal) - │ - └── item->num == _head->num AND _emergency_consensus_active? - ├── item->id < _head->id? - │ └── Yes → _head = item (lower hash wins, deterministic) - └── No → keep current _head -``` - -**Purpose:** When multiple emergency nodes produce at the same height (all using the same committee key), arrival order varies by P2P topology. Deterministic hash comparison ensures all nodes converge on the same chain tip regardless of which block they saw first. - ---- - -### 2E. LIB Advancement During Emergency - -**Location:** [`database::update_last_irreversible_block()`](file://libraries/chain/database.cpp#L5686-L5740) - -``` -update_last_irreversible_block() - │ - ├── Normal LIB computation: - │ ├── Collect validator objects for all schedule slots - │ ├── nth_element by last_supported_block_num - │ └── new_lib = wit_objs[offset]->last_supported_block_num - │ where offset = (100% - 75%) × num_witnesses / 100% - │ - ├── === EMERGENCY LIB CAP === - │ │ - │ └── emergency AND new_lib >= head_block_num? - │ ├── Yes → cap to head - 1 - │ │ Reason: During emergency all slots = committee. - │ │ Committee's last_supported_block_num == HEAD. - │ │ nth_element returns HEAD → commit(HEAD) destroys - │ │ current block's undo session → crash during _apply_block - │ │ would leave permanently corrupted state (zeroed schedule). - │ └── No → use computed value - │ - ├── Committee validator: current_run advances by CHAIN_BLOCK_WITNESS_REPEAT each block - │ → after 3 blocks (CHAIN_IRREVERSIBLE_SUPPORT_MIN_RUN), LIB moves every block - │ → gap between LIB and HEAD stays small → fork_db won't overflow - │ - └── === BLOCK POST VALIDATION CHAIN === - └── emergency? → return early (don't advance LIB via BPV during emergency) -``` - ---- - -### 2F. Startup Recovery — Schedule Repair - -**Location:** [`database::open()`](file://libraries/chain/database.cpp#L315-L356) - -``` -database::open() — after replay/reindex - │ - ├── Read startup DGP + witness_schedule_object - │ - ├── Scan schedule for empty slots: - │ └── Any current_shuffled_witnesses[i] == "" ? - │ │ - │ ├── Yes → EMERGENCY SCHEDULE RECOVERY: - │ │ ├── If !emergency_active → activate emergency + fork_db flag - │ │ ├── Fill ALL slots (CHAIN_MAX_WITNESSES × REPEAT) with "committee" - │ │ ├── num_scheduled_witnesses = CHAIN_MAX_WITNESSES × REPEAT - │ │ ├── next_shuffle_block_num = head + num_scheduled - │ │ └── Log: "schedule repaired, all N slots set to committee" - │ │ - │ └── No, but emergency_active → restore fork_db.set_emergency_mode(true) - │ - └── Continue normal startup -``` - ---- - -### 2G. P2P Stale Sync Detection — Emergency Awareness - -**Location:** [`p2p_plugin::stale_sync_check_task()`](file://plugins/p2p/p2p_plugin.cpp#L1056-L1090) - -``` -stale_sync_check_task() {every 30s} - │ - ├── elapsed = now - _last_block_received_time - ├── elapsed > 120s? - │ └── No → reschedule - │ - ├── === EMERGENCY GUARD === - │ │ - │ ├── Read emergency_consensus_active from DGP - │ │ - │ └── emergency active? - │ ├── Yes + current_head > _last_stale_check_head: - │ │ └── Head is advancing → MASTER is producing - │ │ ├── Reset _last_block_received_time = now - │ │ ├── _last_stale_check_head = current_head - │ │ └── skip_recovery = true - │ │ - │ └── Yes + head is STUCK: - │ └── FOLLOWER lost sync with master - │ └── Allow recovery to proceed - │ (logs warning: "head is stuck — triggering recovery") - │ - └── Recovery (if !skip_recovery): - ├── sync_from(LIB block ID) - ├── resync() → full peer state reset + start_synchronizing() - └── Reconnect seed nodes -``` - ---- - -### 2H. `resync_from_lib()` Emergency Guard - -**Location:** [`p2p_plugin::resync_from_lib()`](file://plugins/p2p/p2p_plugin.cpp#L1458-L1484) - -``` -resync_from_lib() — called from minority fork detection - │ - ├── === EMERGENCY GUARD === - │ │ - │ └── emergency_consensus_active? - │ ├── Yes → SKIP entirely, log warning: - │ │ "SKIPPING during emergency consensus mode. - │ │ Emergency fork must not be unwound." - │ │ - │ │ Reason: During emergency, LIB is close to HEAD. - │ │ Popping blocks + fork_db reset → peer blocks from - │ │ real network may link to re-seeded LIB → fork switch - │ │ → pop below committed LIB → infinite loop or crash. - │ │ - │ └── No → continue with normal resync flow - │ - └── Normal flow: - ├── Pop all reversible blocks back to LIB - ├── Reset fork_db, seed with LIB block - ├── sync_from(LIB block ID) - ├── resync() → peer state reset - └── Reconnect seed nodes -``` - ---- - -### 2I. Block Validation — Relaxed Slot Mapping - -**Location:** [`database::verify_signing_witness()`](file://libraries/chain/database.cpp#L4884-L4896) - -``` -verify_signing_witness(next_block) - │ - ├── Normal mode: FC_ASSERT(validator.owner == scheduled_witness) - │ └── "validator produced block at wrong time" - │ - └── Emergency mode: - └── If block.validator != scheduled_witness: - └── dlog (debug, not assertion) - "Emergency mode: accepting block from BW at slot scheduled for SW" - → Block accepted regardless of slot-to-validator mapping - → Signature still validated against block.validator's signing_key -``` - ---- - -### 2J. Snapshot Plugin — Emergency State Handling - -**Location:** [`snapshot::plugin.cpp`](file://plugins/snapshot/plugin.cpp#L127-L181) and stalled sync detection - -``` -=== SNAPSHOT IMPORT === - -dynamic_global_property_object from snapshot JSON: - ├── emergency_consensus_active field present? → use value - ├── emergency_consensus_active field absent? → default false - ├── emergency_consensus_start_block present? → use value - └── emergency_consensus_start_block absent? → default 0 - -This is forward-compatible: older snapshots without these fields -import correctly (no emergency), and new snapshots with emergency -state preserve it. - - -=== STALLED SYNC DETECTION (snapshot plugin) === - -check_stalled_sync_loop() {every 30s} - │ - ├── elapsed > stalled_sync_timeout_minutes (default 5 min)? - │ └── No → continue - │ - ├── === EMERGENCY GUARD === - │ │ - │ ├── emergency AND head advancing? → skip + reset timer - │ └── emergency AND head stuck? → allow recovery (follower) - │ - ├── First trigger: P2P recovery (trigger_resync + reconnect seeds) - └── Second trigger: download new snapshot from trusted peers -``` - ---- - -### 2K. P2P Block Handling — DLT Emergency Near-Caught-Up - -**Location:** [`p2p_plugin::handle_block()`](file://plugins/p2p/p2p_plugin.cpp#L202-L226) - -``` -handle_block(blk_msg, sync_mode) - │ - ├── Track _last_block_received_time + _last_stale_check_head - │ - ├── === DLT EMERGENCY NEAR-CAUGHT-UP === - │ │ - │ └── sync_mode AND gap 0-2 AND dlt_mode AND block_age < 30s? - │ ├── Yes → treat as NORMAL block (sync_mode = false) - │ │ Reason: Prevents "Syncing Blockchain started" triggers - │ │ when only a few blocks behind. Emergency validators must - │ │ continue producing — entering full sync mode would set - │ │ currently_syncing=true and disrupt the production loop. - │ └── No → keep sync_mode - │ - ├── Skip dead-fork blocks (>100 behind head in DLT sync mode) - │ - └── chain.accept_block() with appropriate skip flags -``` - ---- - -### 2L. validator guard — Emergency-Aware Key Restoration - -**Location:** [`witness_guard::plugin.cpp`](file://plugins/witness_guard/witness_guard.cpp#L87-L107) - -``` -validator key auto-restore check: - │ - ├── stale_production_config override active? - │ ├── Non-emergency + participation >= 33% → auto-clear stale flag - │ ├── Non-emergency + participation < 33% → skip restoration - │ └── Emergency → do NOT skip - │ "Emergency consensus handles its own recovery and key - │ restoration may still be needed." - │ - ├── Node synced? (head_time within 2 × CHAIN_BLOCK_INTERVAL) - ├── LIB not too old? (< 200s) - └── Proceed with key restoration if conditions met -``` - ---- - -### 2M. Hardfork Voting — Committee Exclusion - -**Location:** [`database::update_witness_schedule()`](file://libraries/chain/database.cpp#L2553-L2564) - -``` -During emergency mode, committee validator is excluded from: - ├── running_version (majority_version) tally - └── hardfork_version_vote tally - -Reason: Committee occupies many slots but is a single entity. -Counting it per-slot would inflate its vote weight and drag -majority_version to 0.0.0, blocking hardfork progression. -``` - ---- - -### 2N. Median validator Props — Committee Exclusion - -**Location:** [`database::update_median_witness_props()`](file://libraries/chain/database.cpp#L2796-L2820) - -``` -update_median_witness_props(): - └── Excludes CHAIN_EMERGENCY_WITNESS_ACCOUNT from the active set - when emergency_consensus_active is true. - -Reason: Committee validator copies current median_props and should -not skew the median computation. Its entries are invisible to -the median — they reinforce the existing value. -``` - ---- - -## 3. Full State Diagram - -```mermaid -graph TB - subgraph "NORMAL OPERATION" - N1[Blocks produced by validators] - N2[LIB advances normally] - N3[validator schedule normal] - end - - subgraph "ACTIVATION TRIGGER" - A1[Block applied] - A2{seconds_since_LIB >= 3600?} - A3{LIB block available?} - end - - subgraph "ACTIVATION SEQUENCE" - AS1[Set emergency_consensus_active=true] - AS2[Create/Update committee validator] - AS3[Disable all real validators] - AS4[Reset all penalties] - AS5[Override schedule → all committee] - AS6[Notify fork_db] - AS7[Log activation] - end - - subgraph "EMERGENCY OPERATION" - E1[Committee produces blocks] - E2[Hybrid schedule: real + committee] - E3[LIB capped HEAD-1] - E4[Fork DB hash tie-breaking] - E5[Relaxed slot validation] - E6[Stale sync guards active] - end - - subgraph "EXIT CONDITION" - X1[Build hybrid schedule] - X2{real_witness_slots >= 75%?} - X3[Set emergency_consensus_active=false] - X4[Notify fork_db] - X5[Log deactivation] - end - - subgraph "RECOVERY GUARDS" - RG1[resync_from_lib: skip during emergency] - RG2[stale_sync_check: skip if head advancing] - RG3[minority_fork: DLT master skips check] - RG4[startup: repair empty schedule] - end - - N1 --> A1 - A1 --> A2 - A1 --> A3 - A2 -->|Yes| AS1 - A2 -->|No| N1 - A3 -->|No| N1 - AS1 --> AS2 --> AS3 --> AS4 --> AS5 --> AS6 --> AS7 - AS7 --> E1 - E1 --> E2 --> E3 --> E4 --> E5 --> E6 - E2 --> X1 - X1 --> X2 - X2 -->|Yes| X3 --> X4 --> X5 --> N1 - X2 -->|No| E1 - E6 --> RG1 - E6 --> RG2 - E6 --> RG3 - RG4 --> E1 -``` - ---- - -## 4. Component Interaction Map - -``` - ┌──────────────────────┐ - │ P2P Plugin │ - │ • stale sync check │ - │ • resync_from_lib │ - │ • block handling │ - │ • near-caught-up │ - └──────┬───────────────┘ - │ emergency guards - ▼ - ┌──────────┐ ┌──────────────────────┐ ┌──────────────┐ - │ validator │◄───│ Database (chain) │───►│ Fork DB │ - │ Plugin │ │ • activation │ │ • hash tie- │ - │ │ │ • deactivation │ │ breaking │ - │ • prod. │ │ • hybrid schedule │ │ • emergency │ - │ loop │ │ • LIB cap │ │ flag │ - │ • master/│ │ • slot validation │ └──────────────┘ - │ follr │ │ • startup recovery │ - │ • min.fk │ │ • hardfork voting │ - └────┬─────┘ │ • median props │ ┌──────────────┐ - │ └──────────┬───────────┘ │ Snapshot │ - │ │ │ Plugin │ - ┌────┴─────┐ ┌────┴──────┐ │ • import │ - │ validator │ │ Dynamic │ │ • stall │ - │ Guard │ │ Global │ │ detection │ - │ • key │ │ Properties│ └──────────────┘ - │ rest. │ │ • flags │ - └──────────┘ └───────────┘ -``` - ---- - -## 5. Guard Summary — All Emergency Checks - -| # | Location | File | Guard | -|---|----------|------|-------| -| 1 | `update_global_dynamic_data` | `database.cpp` | Only activate if HF12 + !already_active + LIB available | -| 2 | `update_witness_schedule` | `database.cpp` | Hybrid override + exit check via 75% real validator slots | -| 3 | `update_last_irreversible_block` | `database.cpp` | Cap LIB to HEAD-1 during emergency | -| 4 | `check_block_post_validation_chain` | `database.cpp` | Skip BPV-based LIB advancement during emergency | -| 5 | `verify_signing_witness` | `database.cpp` | Relax slot-to-validator mapping during emergency | -| 6 | `fork_db._push_block` | `fork_database.cpp` | Deterministic hash tie-breaking during emergency | -| 7 | `maybe_produce_block` | `validator.cpp` | Emergency master: bypass sync+stale+participation, skip minority fork; slave: must sync first, standard production gate | -| 8 | `resync_from_lib` | `p2p_plugin.cpp` | SKIP entirely during emergency (prevent crash) | -| 9 | `stale_sync_check_task` | `p2p_plugin.cpp` | Skip recovery if master's head advancing; allow if follower stuck | -| 10 | `handle_block` | `p2p_plugin.cpp` | Near-caught-up blocks treated as normal in DLT emergency | -| 11 | `database::open` | `database.cpp` | Startup schedule repair: fill empty slots, re-activate emergency if needed | -| 12 | `witness_guard` | `witness_guard.cpp` | Don't skip key restoration during emergency | -| 13 | `snapshot import` | `plugin.cpp` | Forward-compatible emergency field handling | -| 14 | `snapshot stalled sync` | `plugin.cpp` | Skip if master's head advancing | -| 15 | `update_witness_schedule` | `database.cpp` | Exclude committee from hardfork version voting | -| 16 | `update_median_witness_props` | `database.cpp` | Exclude committee from median computation | -| 17 | `_push_block` fork switch | `database.cpp` | Direct-extension bypass + fork_db head-seeding (protects emergency after stale sync resets) | -| 18 | `update_global_dynamic_data` | `database.cpp` | Skip emergency activation if LIB block not in block_log (DLT snapshot safety) | -| 19 | `push_block` | `database.cpp` | Deferred applied_block notification: plugin callbacks run outside write lock to avoid blocking P2P/RPC | - ---- - -## 6. Key Invariants - -1. **Deterministic activation**: `seconds_since_lib` uses only block-embedded timestamps — identical on every node, every replay. -2. **DLT snapshot safety**: Activation skipped when LIB block is unavailable in block_log (empty after snapshot restore). -3. **Emergency fork immutability**: `resync_from_lib()` refuses to unwind during emergency, protecting against LIB-close-to-HEAD crashes. -4. **Master/Follower distinction**: DLT nodes with `--emergency-private-key` are masters (bypass sync checks, skip minority fork detection); followers must sync before producing and run 21-block isolation check (1 round). -5. **Fork DB convergence**: Deterministic hash tie-breaking ensures all nodes pick the same block when multiple emergency producers compete. -6. **LIB safety**: Capped at HEAD-1 to preserve undo protection for the current `_apply_block`. -7. **Neutral committee voting**: Committee votes for currently-applied hardfork version (not binary version), copies median props — does not skew governance or chain properties. -8. **Stale sync protection**: Master nodes skip stale sync recovery while head is advancing; followers trigger recovery when head is stuck. diff --git a/.qoder/docs/expected-next-block.md b/.qoder/docs/expected-next-block.md deleted file mode 100644 index 29886a3290..0000000000 --- a/.qoder/docs/expected-next-block.md +++ /dev/null @@ -1,303 +0,0 @@ -# DLT P2P `expected_next_block` — Design, Data Flow, and Fixes - -## 1. Overview - -`expected_next_block` is a per-peer tracking field in `dlt_peer_state` that records the next sequential block number we expect to receive **from this specific peer**. Its purpose is: - -1. **Detect out-of-order blocks** — when a peer sends a block whose `block_num` doesn't match `expected_next_block`, something unexpected happened (duplicate, gap, competing fork). -2. **Distinguish harmless duplicates from suspicious out-of-order** — if the block is a known duplicate from another peer, it's harmless; otherwise it may indicate a fork or gap. - -**Key invariant (design intent):** `expected_next_block` should always equal `head_block_num + 1` for peers that are in sync with us. In practice, it only tracks what *this peer* has sent us, not our global chain head. - -**Field definition:** `dlt_p2p_peer_state.hpp:65` -```cpp -uint32_t expected_next_block = 0; // 0 = "not tracking" (no active sync session) -``` - ---- - -## 2. Lifecycle — Every Write Site - -### 2.1 Initialization to 0 (tracking disabled) - -| Site | File:Line | When | -|------|-----------|------| -| Struct default | `dlt_p2p_peer_state.hpp:65` | Peer state constructed | -| `connect_to_peer` | `dlt_p2p_node.cpp:222` | `state = dlt_peer_state()` — full zero-init on reconnect | -| `handle_disconnect` | `dlt_p2p_node.cpp:321` | `state.expected_next_block = 0` on disconnect | -| `request_blocks_from_peer` | `dlt_p2p_node.cpp:945` | Reset before sending range request | - -**Meaning of `0`:** We are not actively tracking block ordering from this peer. The out-of-order check is skipped entirely (see §3.1). - -### 2.2 Write: `expected_next_block = max(expected_next_block, block_num + 1)` - -This is the **per-peer advance** pattern. It appears in two places: - -| Site | File:Line | Context | -|------|-----------|--------| -| `on_dlt_block_range_reply` | `dlt_p2p_node.cpp:1150` | After processing each block in a range reply | -| `on_dlt_block_reply` | `dlt_p2p_node.cpp:1381` | After processing a single block reply | - -Both use `std::max()` so the value only moves forward, never backward. - -### 2.3 Write: Bulk advance for ALL peers in `on_block_applied()` (Fix 8.1) - -**File:** `dlt_p2p_node.cpp:2457-2469` - -After any block is applied (from any source), all peers whose `expected_next_block` is behind the new chain head are advanced: - -```cpp -uint32_t next = block.block_num() + 1; -for (auto& item : _peer_states) { - if (item.second.expected_next_block != 0 && - item.second.expected_next_block < next) { - item.second.expected_next_block = next; - } -} -``` - -This is called from: -- `on_dlt_block_range_reply` (SYNC mode) — `dlt_p2p_node.cpp:1117` -- `on_dlt_block_reply` (FORWARD mode) — `dlt_p2p_node.cpp:1345` -- `on_dlt_gap_fill_reply` (gap fill) — `dlt_p2p_node.cpp:1678` -- `broadcast_block()` (self-produced blocks) — `dlt_p2p_node.cpp:1860` (Fix 8.2) - ---- - -## 3. Lifecycle — Every Read Site (Decision Points) - -### 3.1 Out-of-order check in `on_dlt_block_range_reply` (SYNC mode) - -**File:** `dlt_p2p_node.cpp:1070-1094` - -``` -if (state.expected_next_block != 0 && block.block_num() != state.expected_next_block) -``` - -**Branches:** -- `expected_next_block == 0` → skip check (no tracking active) -- `block_num == expected_next_block` → in order, proceed normally -- `block_num < expected_next_block && block is known` → duplicate, skip with debug log -- `block_num != expected_next_block && (not duplicate)` → **3-tier log** (Fix 8.3, see §6): - - `block_num <= head` → `dlog` (stale tracking) - - `block_num == head + 1` → `dlog` (stale tracking) - - `block_num > head + 1` → `wlog` (genuine gap) - -### 3.2 Out-of-order check in `on_dlt_block_reply` (FORWARD mode single-block) - -**File:** `dlt_p2p_node.cpp:1225-1291` - -Same 3-tier log structure as 3.1 (Fix 8.3), plus additional logic: - -- **Gap fill trigger (P36/P40/P54):** If `block_num > head + 1`, request gap fill. Works in both SYNC and FORWARD modes. If `block_num == head + 1`, there's no real gap - it's just stale `expected_next_block`. -- **Competing fork detection:** If the block's `previous` hash differs from our head at the same height, request the competing parent block. - ---- - -## 4. Data Flow Diagram - -``` - ┌──────────────────────────────────────────┐ - │ Peer Connect / Reconnect │ - │ expected_next_block = 0 │ - └─────────────┬────────────────────────────┘ - │ - ┌─────────────▼────────────────────────────┐ - │ request_blocks_from_peer() │ - │ expected_next_block = 0 (reset again) │ - └─────────────┬────────────────────────────┘ - │ - ┌──────────────────┴──────────────────┐ - │ │ - ┌──────────▼──────────┐ ┌────────────▼───────────┐ - │ Range Reply (SYNC) │ │ Single Block Reply │ - │ (batch of blocks) │ │ (FORWARD / broadcast) │ - └──────────┬──────────┘ └────────────┬───────────┘ - │ │ - For each block: 1. Out-of-order check (3-tier log) - 1. Out-of-order check (3-tier) - duplicate? → return - 2. accept_block() - gap? → request_gap_fill() - 3. on_block_applied() ◄── Fix 8.1 - competing fork? → request parent - └─ advances ALL peers' enb 2. accept_block() - 4. expected_next_block = 3. on_block_applied() ◄── Fix 8.1 - max(enb, block_num + 1) └─ advances ALL peers' enb - 4. expected_next_block = - max(enb, block_num + 1) - │ │ - └──────────────────┬──────────────────┘ - │ - ┌──────────────────┴──────────────────┐ - │ │ - ┌──────────▼──────────┐ ┌────────────▼───────────┐ - │ broadcast_block() │ │ on_dlt_gap_fill_reply │ - │ (self-produced) │ │ (gap fill blocks) │ - │ Fix 8.2: calls │ │ │ - │ on_block_applied() │ │ on_block_applied() │ - └──────────┬──────────┘ └────────────┬───────────┘ - │ │ - └──────────────────┬──────────────────┘ - │ - ┌─────────────▼────────────────────────────┐ - │ handle_disconnect() │ - │ expected_next_block = 0 │ - └──────────────────────────────────────────┘ -``` - ---- - -## 5. Residual Gaps (after Fixes 8.1–8.3) - -Fixes 8.1, 8.2, and 8.3 eliminate the primary sources of false "out of order" warnings. The following gaps still exist but have reduced impact: - -### 5.1 ~~Self-produced blocks~~ FIXED (Fix 8.2) - -`broadcast_block()` now calls `on_block_applied()`, which advances all peers' `expected_next_block`. - -### 5.2 ~~Blocks received from other peers~~ FIXED (Fix 8.1) - -`on_block_applied()` now iterates all peers and advances stale `expected_next_block` values. - -### 5.3 ~~`on_block_applied()` does not touch peer states~~ FIXED (Fix 8.1) - -`on_block_applied()` now includes the peer advancement loop at `dlt_p2p_node.cpp:2457-2469`. - -### 5.4 Fork switches - -When a fork switch happens (minority → majority), the fix 8.1 loop in `on_block_applied()` will advance peers to the new head **if** `on_block_applied()` is called during the fork switch path. This is handled by the chain layer calling `broadcast_block` or the P2P accept path after switch_to_fork. - -### 5.5 `transition_to_forward()` / `transition_to_sync()` - -Neither transition function updates `expected_next_block` for any peer. This is mitigated by fix 8.1 since any block application during/after transition will correct stale values. - -### 5.6 Narrow race window - -There is a small race between when a block is applied and when the next block arrives from another peer. If both blocks arrive nearly simultaneously (before the first block's `on_block_applied()` completes the peer iteration), a single stale "out of order" may still fire. Fix 8.3 demotes this to `dlog`. - ---- - -## 6. Active Mitigations - -| Mitigation | File:Line | Description | -|-----------|-----------|-------------| -| Bulk advance all peers (8.1) | `dlt_p2p_node.cpp:2457-2469` | `on_block_applied()` advances stale `expected_next_block` for all peers | -| `broadcast_block` → `on_block_applied` (8.2) | `dlt_p2p_node.cpp:1860` | Self-produced blocks trigger peer advancement | -| 3-tier log demotion (8.3) | `dlt_p2p_node.cpp:1083-1092, 1240-1249` | Stale tracking → `dlog`; genuine gap → `wlog` | -| Reset to 0 on range request | `dlt_p2p_node.cpp:945` | Fresh range request resets tracking | -| Duplicate detection | `dlt_p2p_node.cpp:1071,1227` | If block_num < expected and block is known → skip silently | -| P40 gap fill guard | `dlt_p2p_node.cpp:1258` | Only trigger gap fill if block_num > head + 1 (not just stale tracking) | -| std::max on write | `dlt_p2p_node.cpp:1150,1381` | Value only moves forward, never backward | - ---- - -## 7. Concrete Bug Scenario (from production logs) - -``` -206774ms validator.cpp:431 Generated block #79720273 ... by creativity -... -209818ms dlt_p2p_node.cpp:1208 Block #79720274 from 80.87.202.57 out of order (expected #79720273) -209822ms dlt_p2p_node.cpp:1298 Got block #79720274 ... by validator m0ssa99 [80.87.202.57] -``` - -**Trace:** -1. Peer 80.87.202.57 sent us block #79720272 → its `expected_next_block` = 79720273 -2. We generated #79720273 ourselves → `expected_next_block` for this peer stays at 79720273 -3. Peer sends #79720274 → `79720274 != 79720273` → false "out of order" -4. Block is still accepted (no real gap, `block_num == head + 1`) — but the warning is misleading noise - ---- - -## 8. Implemented Fixes - -### 8.1 IMPLEMENTED: Update all peers' `expected_next_block` in `on_block_applied()` - -After any block is applied to our chain (from any source), advance `expected_next_block` for all peers whose value is behind our new head. - -**Location:** `dlt_p2p_node.cpp:2457-2469` (`on_block_applied`) - -```cpp -// Advance stale expected_next_block for all peers. -uint32_t next = block.block_num() + 1; -for (auto& item : _peer_states) { - if (item.second.expected_next_block != 0 && - item.second.expected_next_block < next) { - item.second.expected_next_block = next; - } -} -``` - -**Call sites:** `on_dlt_block_range_reply:1117`, `on_dlt_block_reply:1345`, `on_dlt_gap_fill_reply:1678`, `broadcast_block:1860`. - -### 8.2 IMPLEMENTED: Call `on_block_applied()` from `broadcast_block()` - -Self-produced blocks now trigger mempool cleanup, fork state tracking, and peer `expected_next_block` advancement. - -**Location:** `dlt_p2p_node.cpp:1856-1860` - -```cpp -// Track our own block application: clean mempool of included -// transactions, advance fork state, and update all peers' -// expected_next_block so the next incoming block from any peer -// is not falsely flagged as "out of order". -on_block_applied(block, /*caused_fork_switch=*/false); -``` - -### 8.3 IMPLEMENTED: Demote stale "out of order" to debug level - -Replaced unconditional `wlog` with 3-tier logic at both out-of-order check sites: - -| Condition | Log level | Meaning | -|-----------|-----------|---------| -| `block_num <= head` | `dlog` | Block already applied — stale per-peer tracker | -| `block_num == head + 1` | `dlog` | Block links to head — stale per-peer tracker, no gap | -| `block_num > head + 1` | `wlog` | Genuine gap — real out-of-order concern | - -**Locations:** `dlt_p2p_node.cpp:1083-1092` (SYNC), `dlt_p2p_node.cpp:1240-1249` (FORWARD) - ---- - -## 9. Remaining Proposals (Not Yet Implemented) - -### 9.1 Reset `expected_next_block` on `transition_to_forward()` - -When we complete SYNC and transition to FORWARD, all peers' `expected_next_block` is based on the last range reply. But in FORWARD mode, blocks arrive via broadcast from any peer, not in sequence from one peer. Resetting to 0 (or to `head + 1`) on transition would eliminate stale values: - -```cpp -// In transition_to_forward(): -for (auto& item : _peer_states) { - item.second.expected_next_block = 0; // broadcast mode = no sequential tracking -} -``` - -### 9.2 Long-term: Replace per-peer `expected_next_block` with global head comparison - -The fundamental design issue is that `expected_next_block` tries to track per-peer sequential ordering, but in FORWARD mode blocks come from multiple peers simultaneously. The real question is: "does this block follow our chain head?" — not "does this block follow what we last received from this peer?" - -A simpler and more robust approach: - -```cpp -// Replace out-of-order check with: -bool is_out_of_order = (block_num > _delegate->get_head_block_num() + 1); -bool is_behind = (block_num <= _delegate->get_head_block_num()); -``` - -This eliminates the per-peer tracking entirely for FORWARD mode and uses the only authoritative source of truth — our chain head. - ---- - -## 10. Source File Reference - -| File | Relevance | -|------|-----------| -| `libraries/network/include/graphene/network/dlt_p2p_peer_state.hpp:65` | Field definition | -| `libraries/network/dlt_p2p_node.cpp:321` | Reset on disconnect | -| `libraries/network/dlt_p2p_node.cpp:945` | Reset on range request | -| `libraries/network/dlt_p2p_node.cpp:1070-1094` | Out-of-order check (range reply, 3-tier log) | -| `libraries/network/dlt_p2p_node.cpp:1117` | `on_block_applied()` call in range reply | -| `libraries/network/dlt_p2p_node.cpp:1150` | Per-peer advance after range block | -| `libraries/network/dlt_p2p_node.cpp:1225-1291` | Out-of-order check (single block, 3-tier log) | -| `libraries/network/dlt_p2p_node.cpp:1345` | `on_block_applied()` call in single block reply | -| `libraries/network/dlt_p2p_node.cpp:1381` | Per-peer advance after single block | -| `libraries/network/dlt_p2p_node.cpp:1678` | `on_block_applied()` call in gap fill reply | -| `libraries/network/dlt_p2p_node.cpp:1849-1861` | `broadcast_block()` — now calls `on_block_applied()` (Fix 8.2) | -| `libraries/network/dlt_p2p_node.cpp:2457-2469` | `on_block_applied()` — bulk peer advance (Fix 8.1) | -| `plugins/validator/validator.cpp:1092-1099` | Block production → broadcast | diff --git a/.qoder/docs/fork-collision-hardfork-proposal.md b/.qoder/docs/fork-collision-hardfork-proposal.md deleted file mode 100644 index 0c50a1d30e..0000000000 --- a/.qoder/docs/fork-collision-hardfork-proposal.md +++ /dev/null @@ -1,282 +0,0 @@ -# Fork Collision Reduction Hardfork Proposal - -## Problem Statement - -The VIZ blockchain experiences recurring "block num collision" events — situations where two validators produce blocks at the same height on different chain tips, creating a fork. With a 3-second block interval (`CHAIN_BLOCK_INTERVAL = 3`), there is minimal margin for block propagation, making collisions frequent during periods of network latency or clock drift. - -### Observed Pattern (Block 79162800–79162802) - -``` -Block 79162800: mad-max @ 11:53:12 (latency 10160ms) | jackvote @ 11:53:15 (latency 7160ms) -Block 79162801: lexai @ 11:53:24 (latency 47ms) | denis-skripnik @ 11:53:21 (latency 8357ms) -Block 79162802: micu @ 11:53:30 (latency 78ms) | creativity @ 11:53:27 (latency 4044ms) -``` - -Three consecutive blocks had collisions, indicating a sustained network partition between two validator subsets. The high latency values (7–10 seconds) on one fork branch confirm severe propagation delay. - -### Root Causes - -1. **Tight block interval with no propagation margin** — 3-second slots leave no buffer for cross-region propagation (typical P2P propagation is 1–4 seconds for a global network). - -2. **Deterministic validator ordering without shuffling** — The validator shuffle was [commented out](../libraries/chain/database.cpp) (`// VIZ remove randomization`), making the schedule predictable. If two consecutive validators have poor connectivity, collisions recur every round. - -3. **No on-chain fork telemetry** — The current `_maybe_warn_multiple_production()` only logs a console warning. There is no on-chain metric for fork frequency, making it impossible to monitor network health programmatically. - -4. **No production-time fork awareness** — validators produce blocks on whatever chain tip they see, even if a competing block already exists in their fork database for the same height. - -5. **Clock drift susceptibility** — `get_slot_at_time()` uses wall-clock time. NTP drift between validator nodes can cause slot mismatches. API load can indirectly degrade NTP precision due to thread contention. - ---- - -## Proposal: Hardfork 12 — Fork Resilience Improvements - -### Change 1: Fork Collision Counter in `dynamic_global_property_object` - -**Type**: Consensus-breaking (new serialized field) - -Add a rolling fork collision counter to `dynamic_global_property_object`, making fork frequency observable via the `get_dynamic_global_properties` API. - -**File**: `libraries/chain/include/graphene/chain/global_property_object.hpp` - -```cpp -class dynamic_global_property_object - : public object { -public: - // ... existing fields ... - - /** - * Total number of fork collisions (block num collisions) detected - * since genesis. Incremented each time _maybe_warn_multiple_production() - * finds multiple blocks at the same height in the fork database. - * This counter never decreases, providing a cumulative metric - * for monitoring network fork health. - */ - uint32_t fork_collision_count = 0; - - /** - * Block number of the most recent fork collision. - * Zero if no collision has ever occurred. - * Useful for detecting recent fork events via API polling. - */ - uint32_t last_fork_collision_block_num = 0; -}; -``` - -**FC_REFLECT update**: - -```cpp -FC_REFLECT((graphene::chain::dynamic_global_property_object), - // ... existing fields ... - (fork_collision_count) - (last_fork_collision_block_num) -) -``` - -**Increment logic** in `database::_maybe_warn_multiple_production()`: - -```cpp -void database::_maybe_warn_multiple_production(uint32_t height) const { - auto blocks = _fork_db.fetch_block_by_number(height); - if (blocks.size() > 1) { - // Increment on-chain counter (non-const via modify) - const auto& dgp = get_dynamic_global_properties(); - modify(dgp, [&](dynamic_global_property_object& obj) { - obj.fork_collision_count++; - obj.last_fork_collision_block_num = height; - }); - - // ... diagnostic logging (already implemented) ... - } -} -``` - -**Why this requires a hardfork**: Adding new fields to `dynamic_global_property_object` changes its serialized representation. All nodes must agree on the object layout for consensus. The snapshot plugin's export/import must also be updated. - -**Consensus benefit**: Enables monitoring dashboards, alerting, and data-driven decisions about network topology. validators with high collision rates can be identified and their connectivity improved. - ---- - -### Change 2: Fork-Aware Block Production Deferral - -**Type**: Non-consensus-breaking (Validator Plugin behavior only) - -Already implemented in `plugins/validator/validator.cpp` — before producing a block, check if a competing block already exists in the fork database for the target height. If so, defer production to allow fork resolution. - -This does **not** require a hardfork because: -- It only affects the Validator Plugin's production timing -- It does not change block validation rules -- A deferred block will be produced in the next available slot - -However, the hardfork proposal could make this behavior **mandatory** by adding a consensus rule: if a validator observes a fork collision for the current slot, they MUST wait for the competing block's next validator to produce before building on top. This would formalize the deferral as a consensus rule rather than a best-effort optimization. - -**Formalized version** (requires hardfork): - -Add to `validate_block_header()`: - -```cpp -if (has_hardfork(CHAIN_HARDFORK_12)) { - // After a fork collision at height H, the next block must be - // built on top of the longest chain's block at height H. - // validators must not produce on the shorter fork's tip. - auto existing = _fork_db.fetch_block_by_number(next_block.block_num()); - if (existing.size() > 1) { - // There was a collision at this height; verify this block - // builds on the winner (longest chain) - auto winner = _fork_db.head(); - FC_ASSERT(next_block.previous == winner->data.previous || - next_block.block_num() > winner->num, - "Block produced on losing fork after collision"); - } -} -``` - -**Consensus benefit**: Prevents validators from extending a minority fork after a collision is detected, reducing the duration and depth of forks. - ---- - -### Change 2a: Minority Fork Detection & Auto-Resync - -**Type**: Non-consensus-breaking (validator + P2P plugin behavior only) - -Implemented in `plugins/validator/validator.cpp` and `plugins/p2p/p2p_plugin.cpp` — before producing a block, the Validator Plugin walks back the last `CHAIN_MAX_WITNESSES` (21) blocks in fork_db and checks if ALL were produced by the node's own configured validators. If so, the node is stuck on a minority fork (no external validators are participating on this chain). - -**Behavior by configuration:** - -| Condition | Action | -|---|---| -| `enable-stale-production=false` (default) | Trigger recovery: pop blocks to LIB, reset fork_db, re-initiate P2P sync, reconnect seed nodes | -| `enable-stale-production=true` | Log and continue producing (operator override for bootstrap/testnet/recovery) | -| Emergency consensus active | Skip detection entirely (emergency mode blocks are all from committee account) | - -**Recovery flow (`resync_from_lib()`):** - -1. Pop all reversible blocks from head back to LIB via `pop_block()` loop -2. Clear pending transactions -3. Reset fork_db and re-seed with LIB block -4. Call `node->sync_from()` + `node->resync()` to re-initiate P2P sync -5. Reconnect all configured seed nodes -6. Set `_production_enabled = false` (node must receive a recent block to re-enable) - -This replicates the effect of a manual docker stop/start without node downtime. - -**Files modified:** `validator.hpp` (new enum value `minority_fork`), `validator.cpp` (detection logic + switch case), `p2p_plugin.hpp` (new `resync_from_lib()` method), `p2p_plugin.cpp` (implementation) - ---- - -### Change 3: Production Delay Buffer - -**Type**: Consensus-breaking (changes block timing expectations) - -Add a configurable production delay of `CHAIN_PRODUCTION_DELAY_MS` milliseconds (e.g., 500ms) that a validator must wait after receiving a new block before producing its own. This gives the network time to propagate the latest block before the next validator builds on it. - -**Implementation**: - -```cpp -// In config.hpp: -#define CHAIN_PRODUCTION_DELAY_MS 500 // Wait 500ms after receiving block before producing - -// In validator.cpp maybe_produce_block(): -fc::time_point_sec earliest_production_time = db.head_block_time() + - fc::milliseconds(CHAIN_PRODUCTION_DELAY_MS); -if (now < earliest_production_time) { - capture("earliest", earliest_production_time)("now", now); - return block_production_condition::not_time_yet; -} -``` - -**Why this requires a hardfork**: Changes the timing semantics of when blocks are expected. The current consensus assumes validators produce as close to their scheduled slot time as possible. A mandatory delay effectively shortens the usable production window. - -**Consensus benefit**: A 500ms delay on a 3-second interval gives the P2P network 500ms to propagate the previous block to all validators before the next one starts building. This dramatically reduces the probability of two validators building on different chain tips simultaneously. - -**Trade-off**: Block latency increases by up to 500ms per block. Over a day (28,800 blocks), this adds ~4 hours of total latency, but the actual user-perceived latency increase is only 500ms per transaction confirmation. - ---- - -## Implementation Plan - -### Phase 1: Non-Breaking Changes (No Hardfork Required) - -These changes are already implemented and can be deployed immediately: - -| Change | File | Status | -|--------|------|--------| -| Enhanced collision diagnostics with fork topology classification | `database.cpp` `_maybe_warn_multiple_production()` | Done | -| Rate-limited collision warnings (prevent log spam) | `database.cpp` `_maybe_warn_multiple_production()` | Done | -| Parent block ID logging for fork topology analysis | `database.cpp` `_maybe_warn_multiple_production()` | Done | -| Pre-production fork collision check in Validator Plugin | `validator.cpp` `maybe_produce_block()` | Done | -| `fork_collision` block production condition | `validator.hpp` enum, `validator.cpp` handler | Done | -| NTP re-sync on fork collision detection | `validator.cpp` `block_production_loop()` | Done | -| Minority fork detection & auto-resync | `validator.cpp`, `p2p_plugin.cpp/.hpp`, `validator.hpp` | Done | - -### Phase 2: Hardfork 12 (Requires Network-Wide Upgrade) - -| Change | Breaking | Files Modified | -|--------|----------|----------------| -| Fork collision counter in DGP | Yes (serialized object) | `global_property_object.hpp`, `database.cpp`, `snapshot/plugin.cpp` | -| Production delay buffer | Yes (timing) | `config.hpp`, `validator.cpp` | -| Mandatory fork deferral rule | Yes (validation) | `database.cpp` `validate_block_header()` | - -### Hardfork 12 Definition - -**File**: `libraries/chain/hardfork.d/12.hf` - -```cpp -// 12 Hardfork — Fork Resilience Improvements -#ifndef CHAIN_HARDFORK_12 -#define CHAIN_HARDFORK_12 12 -#define CHAIN_HARDFORK_12_TIME // To be determined by validator vote -#define CHAIN_HARDFORK_12_VERSION hardfork_version( version(3, 1, 0) ) -#endif -``` - -**Update**: `0-preamble.hf` → `#define CHAIN_NUM_HARDFORKS 12` - ---- - -## Impact Analysis - -### Fork Collision Rate Reduction (Estimated) - -| Scenario | Current | After HF12 | Reduction | -|----------|---------|------------|-----------| -| Consecutive validator pair with poor connectivity | ~95% collision/round | ~5% collision/round | ~19× | -| Random single-slot collision (network hiccup) | 100% produces fork | ~30% (delay absorbs transient) | ~3× | -| Sustained partition (2+ round) | 100% collision every block | 100% (delay can't help) | 0× | -| NTP drift <500ms | ~50% collision | ~10% (delay + re-sync) | ~5× | - -### API Compatibility - -The `get_dynamic_global_properties` API will return two additional fields. Clients that ignore unknown fields (most JSON parsers) will be unaffected. Clients with strict schema validation must be updated. - -### Snapshot Compatibility - -Snapshots created before HF12 will not contain `fork_collision_count` or `last_fork_collision_block_num`. The snapshot import logic must handle missing fields with defaults (0 for both). See [dlt-hardfork-new-objects.md](dlt-hardfork-new-objects.md) for the standard procedure. - ---- - -## Alternative Approaches Considered - -### A. Increase Block Interval to 5 Seconds - -Would eliminate most collisions by giving 2+ seconds of propagation margin. **Rejected** because it increases transaction confirmation latency by 67% and reduces throughput proportionally. - -### B. Batch Block Production (Produce 2+ Blocks per Slot) - -Similar to EOS's approach where a validator produces a batch of consecutive blocks. **Rejected** because it increases centralization (longer production windows favor better-connected validators) and complicates missed-block accounting. - -### C. Fork Choice by validator Priority - -Instead of longest-chain, use validator priority (e.g., higher-voted validator's block wins). **Rejected** because it breaks the fundamental longest-chain consensus rule and could enable voting attacks. - -### D. P2P Block Prefetch / Fast Relay Network - -A dedicated relay network for block propagation (similar to Bitcoin's FIBRE). **Not a hardfork** — can be implemented as a P2P plugin improvement. Recommended as a complementary non-consensus change. - ---- - -## Related Documentation - -- [DLT Hardfork New Objects](dlt-hardfork-new-objects.md) — Procedure for adding consensus objects in hardforks -- [Block Processing](block-processing.md) — Block application flow and fork resolution -- [validator Operations](op-validator.md) — validator update, vote, chain properties -- [Plugins](plugins.md) — Plugin architecture including validator and P2P plugins diff --git a/.qoder/docs/hardfork-guide.md b/.qoder/docs/hardfork-guide.md deleted file mode 100644 index 7d140aefbb..0000000000 --- a/.qoder/docs/hardfork-guide.md +++ /dev/null @@ -1,259 +0,0 @@ -# Hardfork Implementation Guide - -Checklist and rules for implementing a new hardfork in the VIZ node. -Covers protocol, chain, plugins, deployment, and schema versioning. - ---- - -## 1. Define the hardfork - -### 1.1. Hardfork `.hf` file - -Create `libraries/chain/hardfork.d/N.hf`: - -```cpp -#ifndef CHAIN_HARDFORK_N -#define CHAIN_HARDFORK_N N -#define CHAIN_HARDFORK_N_TIME 1234567890 // Unix timestamp — must be in the future -#define CHAIN_HARDFORK_N_VERSION hardfork_version(3, N, 0) -#endif -``` - -### 1.2. Bump CHAIN_NUM_HARDFORKS - -In `libraries/chain/hardfork.d/0-preamble.hf`: -```cpp -#define CHAIN_NUM_HARDFORKS N // was N-1 -``` - -### 1.3. Bump CHAIN_VERSION (if protocol-visible) - -In `libraries/protocol/include/graphene/protocol/config.hpp`: -```cpp -#define CHAIN_VERSION (version(3, N, 0)) -``` - ---- - -## 2. Protocol changes - -### 2.1. New operation - -Add to `libraries/protocol/include/graphene/protocol/chain_operations.hpp`: -- Struct definition with `validate()` and authority getters -- Forward-declare any new `chain_properties_hfN` struct - -Add to `libraries/protocol/include/graphene/protocol/operations.hpp`: -- Entry in the `static_variant` for the new op -- Guard comment `// VIZ HF N: ...` - -Implement `validate()` in `libraries/protocol/chain_operations.cpp`. - -### 2.2. New virtual operation - -Add to `libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp`: -```cpp -struct my_new_virtual_operation : public virtual_operation { - my_new_virtual_operation() {} - my_new_virtual_operation(args...) : ... {} - // fields -}; -FC_REFLECT(...) -``` - -Add to `libraries/protocol/include/graphene/protocol/operations.hpp` in the virtual op section. - ---- - -## 3. Chain objects - -### 3.1. Adding fields to existing chainbase objects - -> **⚠ SCHEMA RULE — mandatory step:** -> Whenever you add, remove, or resize a field in any chainbase-managed struct -> (`witness_object`, `account_object`, etc.), you MUST increment -> `CHAIN_SCHEMA_VERSION` in `config.hpp`. -> -> ```cpp -> // config.hpp — increment this for every field layout change: -> #define CHAIN_SCHEMA_VERSION uint32_t(N) -> ``` -> -> The chain plugin reads `/schema_version` at startup and compares it with -> `CHAIN_SCHEMA_VERSION`. A mismatch triggers proactive `shared_memory.bin` wipe -> before `db.open()` is called, preventing corrupt reads. Without this, old shared -> memory can be opened with incorrect object sizes, leading to silent data corruption -> or crashes. -> -> **History format** — add a comment entry: -> ```cpp -> /// N — HFN: describe every affected object and field -> ``` - -New fields should always have zero-value defaults: -```cpp -uint16_t my_new_field = 0; -share_type my_reward = 0; -``` - -Zero defaults mean `apply_hardfork(N)` requires no migration — all existing objects -are in a valid pre-HF state without any extra writes. - -Add new fields to `FC_REFLECT`. - -### 3.2. Adding new chainbase objects - -Follow [dlt-hardfork-new-objects.md](dlt-hardfork-new-objects.md) for: -- `object_type` enum entry -- Object + index header -- `initialize_indexes()` registration -- Snapshot plugin `serialize_state` / `load_snapshot` support - -> **Schema rule applies here too**: adding a new index is a layout change. -> Increment `CHAIN_SCHEMA_VERSION`. - ---- - -## 4. Evaluators - -### 4.1. Declare evaluator - -In `libraries/chain/include/graphene/chain/chain_evaluator.hpp`: -```cpp -DEFINE_EVALUATOR(my_new_op) -``` - -### 4.2. Implement evaluator - -In `libraries/chain/chain_properties_evaluators.cpp` (or a dedicated file): -```cpp -void my_new_op_evaluator::do_apply(const my_new_op_operation& o) { - ASSERT_REQ_HF(CHAIN_HARDFORK_N, "my_new_op_operation"); - // ... -} -``` - -### 4.3. Register evaluator - -In `libraries/chain/database.cpp`, `initialize_evaluators()`: -```cpp -_my->_evaluator_registry.register_evaluator(); -``` - ---- - -## 5. database.cpp wiring - -### 5.1. Register hardfork - -In `init_hardforks()` (or hardfork array initialisation): -```cpp -FC_ASSERT(CHAIN_HARDFORK_N == N); -_hardfork_times[N] = fc::time_point_sec(CHAIN_HARDFORK_N_TIME); -_hardfork_versions[N] = hardfork_version(CHAIN_HARDFORK_N_VERSION); -``` - -### 5.2. apply_hardfork case - -```cpp -case CHAIN_HARDFORK_N: { - // Migration for any data that cannot be expressed via field defaults. - // If all new fields default to zero → leave this block empty with a comment. - break; -} -``` - -### 5.3. chain_properties median (if new consensus property) - -Add the new property to the median computation in `update_witness_schedule()` or the -equivalent location where `wso.median_props` is built: -```cpp -wso.median_props.my_new_param = median_value; -``` - ---- - -## 6. Plugins - -### 6.1. account_history - -Add impact extractor for any new virtual operation: -```cpp -void operator()(const my_new_virtual_operation& op) { - impacted.insert(op.sender); - impacted.insert(op.receiver); -} -``` - -### 6.2. witness_api - -If new fields appear on `witness_object` → add them to `witness_api_object`: -- `libraries/api/include/graphene/api/witness_api_object.hpp` — field declaration + `FC_REFLECT` -- `libraries/api/witness_api_object.cpp` — initialise in constructor - -### 6.3. chain_properties_update visitor - -In `chain_properties_evaluators.cpp`, add the new `chain_properties_hfN` visitor: -```cpp -result_type operator()(const chain_properties_hfN& p) const { - FC_ASSERT(_db.has_hardfork(CHAIN_HARDFORK_N), "chain_properties_hfN"); - _wprops = p; -} -``` - ---- - -## 7. Deployment checklist - -Before merging / deploying: - -- [ ] `CHAIN_NUM_HARDFORKS` incremented -- [ ] `CHAIN_VERSION` bumped if protocol-visible change -- [ ] `CHAIN_SCHEMA_VERSION` incremented if ANY chainbase object layout changed -- [ ] Hardfork `.hf` file created with future timestamp -- [ ] All new fields have zero defaults; `apply_hardfork` comment explains why no migration needed -- [ ] New evaluator registered in `initialize_evaluators()` -- [ ] New virtual op registered in `account_history` plugin -- [ ] `witness_api_object` updated if `witness_object` changed -- [ ] Snapshot plugin updated if new chainbase objects added - ---- - -## 8. Schema version — full lifecycle - -``` -First run (fresh node or missing schema_version file): - stored = 0, compiled = N → mismatch - wipe shared_memory (no-op if not present) - write schema_version = N - db.open() → init_genesis → revision=0, head=0 → no exception - -Upgrade (old binary had schema version M < N): - stored = M, compiled = N → mismatch - wipe shared_memory.bin - write schema_version = N - db.open() → init_genesis → revision=0, head=head_block_log - → database_revision_exception - → auto-recovery: snapshot import + dlt_block_log replay - OR replay-if-corrupted: full block_log replay - -Normal restart (schema unchanged): - stored = N, compiled = N → match - db.open() proceeds normally - write schema_version = N (confirms success) -``` - -**Key files:** -- `libraries/protocol/include/graphene/protocol/config.hpp` — `CHAIN_SCHEMA_VERSION` -- `plugins/chain/plugin.cpp` — `read_schema_version()`, `write_schema_version()`, - schema check block in `plugin_startup()` -- `/schema_version` — plain text file, single `uint32_t` - ---- - -## 9. Related documentation - -- [dlt-hardfork-new-objects.md](dlt-hardfork-new-objects.md) — adding new chainbase indexes -- [hf13-validator-reward-sharing.md](hf13-validator-reward-sharing.md) — reference implementation (HF13) -- [snapshot-plugin.md](snapshot-plugin.md) — snapshot format and import/export -- [shared-memory.md](shared-memory.md) — chainbase shared memory internals diff --git a/.qoder/docs/hf13-validator-reward-sharing.md b/.qoder/docs/hf13-validator-reward-sharing.md deleted file mode 100644 index bacacc166a..0000000000 --- a/.qoder/docs/hf13-validator-reward-sharing.md +++ /dev/null @@ -1,423 +0,0 @@ -# HF13: Validator Reward Sharing - -## Overview - -Hardfork 13 introduces **stakeholder reward sharing**: validators can configure a percentage of -their block reward to be accumulated and periodically distributed among the accounts that voted -for them (stakeholders). - -**Key design principle**: Stakeholder rewards are accumulated and distributed in **TOKEN (VIZ)** -atomic units, not SHARES. Stakeholders receive SHARES only after `create_vesting()` converts -their token share at the time of distribution. The `witness_reward_operation` virtual op for the -validator itself continues to carry SHARES as before, but only for the validator's own portion. - ---- - -## New Chain Property: `distribution_epoch_length` - -| Property | Type | Default | Range | -|---|---|---|---| -| `distribution_epoch_length` | `uint32_t` | `28800` (1 day) | `[21, ~10.5M]` | - -Validators vote on this parameter via `versioned_chain_properties_update_operation` using the new -`chain_properties_hf13` struct. The median across all scheduled validators becomes the consensus -value. At every block whose number is divisible by `distribution_epoch_length`, accumulated -stakeholder rewards are paid out. - ---- - -## New Operation: `set_reward_sharing_operation` - -Validators use this operation to set their **sharing rate** — the fraction of their block reward -that will be set aside for stakeholder distribution. - -```json -{ - "type": "set_reward_sharing_operation", - "value": { - "owner": "alice", - "sharing_rate": 5000 - } -} -``` - -| Field | Type | Description | -|---|---|---| -| `owner` | `account_name_type` | Validator account name | -| `sharing_rate` | `uint16_t` | Basis points: 0 = 0%, 10000 = 100% | - -**Required authority**: active key of `owner`. -**Requires HF13**: the operation is rejected before HF13 activates. -**Validator must exist**: account must have a registered `witness_object`. - ---- - -## Block Reward Split (process_funds) - -With HF13 active, when a validator (`sharing_rate > 0`) produces a block: - -``` -witness_reward = CHAIN_DIGITAL_ASSET_ISSUED_PER_BLOCK * inflation_witness_percent - (TOKEN, computed in process_funds) - -stakeholder_token = witness_reward * sharing_rate / CHAIN_100_PERCENT -validator_token = witness_reward - stakeholder_token - -# Validator receives their SHARES immediately: -create_vesting(validator_account, validator_token) -→ emits witness_reward_operation(validator, validator_shares) - -# Stakeholder pool accumulates TOKEN: -witness_object.pending_stakeholder_reward += stakeholder_token -``` - -When `sharing_rate == 0`, the full reward goes to the validator as usual (no change). - ---- - -## Epoch Distribution (process_validator_epoch_distribution) - -Called at the end of `_apply_block` on every block where -`head_block_num % distribution_epoch_length == 0`. - -For each `witness_object` with `pending_stakeholder_reward > 0`: - -``` -epoch_start_block = head_block_num - epoch_length + 1 - -# Time-weighted contribution per stakeholder: -for each stakeholder: - first_block = max(stakeholder.vote_created_block, epoch_start_block) - blocks_in_epoch = head_block_num - first_block + 1 - weighted[stakeholder] = stakeholder.witness_vote_weight() * blocks_in_epoch - -total_weighted = Σ weighted[stakeholder] - -# Distribution: -for each stakeholder: - stakeholder_token = total_token * weighted[stakeholder] / total_weighted - - if stakeholder_token < CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT: - skip (dust) - else: - stakeholder_shares = create_vesting(stakeholder_account, stakeholder_token) - emit stakeholder_reward_operation(validator, stakeholder, stakeholder_shares) - -dust_token = total_token - Σ(distributed stakeholder_token) -if dust_token > 0: - dust_shares = create_vesting(validator_account, dust_token) - emit witness_reward_operation(validator, dust_shares) - -wit.pending_stakeholder_reward = 0 -``` - -### Important notes - -- **Vote weight** used: `stakeholder.validator_vote_fair_weight()` — total stake (own vesting - shares + shares proxied by accounts that delegated voting to this stakeholder) divided by - `validators_voted_for`. This matches the per-validator weight used in consensus scheduling: - a stakeholder who splits their vote across N validators contributes 1/N of their stake to - each validator's distribution pool. -- **Time-weighted distribution**: each stakeholder's weight is multiplied by the number of blocks - they were actually voting for this validator within the current epoch (see `vote_created_block` - on `validator_vote_object`). A stakeholder who joined mid-epoch receives a proportionally smaller - share. -- **Pre-HF13 votes** (`vote_created_block == 0`) are treated as having voted since epoch start - (`first_block = epoch_start_block`), so they receive a full-epoch weight. No penalty for - existing stakeholders. -- **Min payout**: `CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT = 1` TOKEN atomic unit (0.001 VIZ). - Stakeholders with a computed share below this threshold receive nothing; their portion is - returned to the validator as dust (see design rationale below). -- **No-stakeholder case**: if a validator has no stakeholders, the entire accumulated pool is - returned to the validator via `validator_reward_operation`. - -#### Dust design rationale - -The accumulated reward pool belongs to the validator — sharing it with stakeholders is entirely -the validator's voluntary decision, expressed via `sharing_rate`. A stakeholder who fails to -accumulate a share above `CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT` (e.g. they hold very little stake, -or voted mid-epoch) has simply not earned a viable payout. The responsibility for that outcome -lies with the stakeholder, not with the validator. - -Consequently, unclaimed dust is **not burned**. After all eligible stakeholders are paid, any -remainder (dust from integer rounding, plus skipped sub-threshold shares) is transferred back to -the validator via `validator_reward_operation`. The validator retains what their stakeholders -collectively failed to claim — no TOKEN leaves the system, and the validator is not penalised for -stakeholders with negligible weight. - ---- - -## Mid-Epoch Sharing Rate Change - -If a validator calls `set_reward_sharing_operation` while an epoch is in progress, the new rate -takes effect **immediately on the next block**. The accumulated `pending_stakeholder_reward` is -NOT retroactively recalculated. - -Concretely, if an epoch spans N blocks and the rate changes after block K: - -``` -pending_stakeholder_reward = - Σ(block 1..K) reward_i * old_rate / CHAIN_100_PERCENT - + Σ(block K+1..N) reward_i * new_rate / CHAIN_100_PERCENT -``` - -The pool that stakeholders receive at epoch end is thus a weighted mix of both rates. This is the -intended behaviour: the validator controls their own split on a per-block basis, and stakeholders -observe the change immediately on-chain via the `sharing_rate` field of the `validator_object`. - ---- - -## Flash-Voter Protection - -Flash-voting (voting just before epoch end to capture the full accumulated pool) is mitigated by -the **time-weighted distribution** described above. - -### How it works - -`validator_vote_object` stores `vote_created_block` — the block number when the vote was cast. At -epoch end, each stakeholder's effective weight is scaled by: - -``` -blocks_in_epoch = head_block_num - max(vote_created_block, epoch_start_block) + 1 -``` - -A flash voter who votes in the last `k` blocks of a `N`-block epoch receives only `k/N` of their -stake-proportional share. For a 1-day epoch (28800 blocks), voting in the last block yields -`1/28800 ≈ 0.003%` of their proportional share — economically insignificant. - -### Residual risk - -The protection covers **new votes** only. If a stakeholder already held a vote from a previous -epoch, they receive a full-epoch weight regardless of their stake changes within the epoch. This -is acceptable: they were genuine stakeholders. - -Vote removal before epoch end results in the stakeholder not appearing in `validator_vote_index` at -distribution time, so they receive nothing — no exploit in this direction. - ---- - -## New Virtual Operation: `stakeholder_reward_operation` - -Emitted once per stakeholder per distribution epoch when they receive a non-dust reward. - -| Field | Type | Description | -|---|---|---| -| `validator` | `account_name_type` | Validator that produced the accumulated rewards | -| `stakeholder` | `account_name_type` | Stakeholder account receiving the reward | -| `shares` | `asset` | SHARES credited to the stakeholder | - ---- - -## Token Accounting - -``` -Before HF13: - current_supply += witness_reward (TOKEN) [issued] - total_vesting_fund += witness_reward (TOKEN) [via create_vesting] - validator.vesting_shares += validator_shares [via create_vesting] - -After HF13 with sharing_rate > 0: - current_supply += witness_reward (TOKEN) [issued, same as before] - total_vesting_fund += validator_token (TOKEN) [via create_vesting — immediate] - validator.vesting_shares += validator_shares [via create_vesting — immediate] - wit.pending_stakeholder_reward += stakeholder_token [TOKEN, pending] - - ...at epoch end: - total_vesting_fund += stakeholder_token (TOKEN) [via create_vesting per stakeholder] - stakeholder.vesting_shares += stakeholder_shares [via create_vesting per stakeholder] - wit.pending_stakeholder_reward = 0 [cleared] -``` - -The floating `pending_stakeholder_reward` TOKEN is already counted in `current_supply`. The -`total_vesting_fund` balance is updated when the TOKEN converts to SHARES via `create_vesting` at -distribution time. - ---- - -## Configuration Example - -```ini -# Set sharing rate to 30% (3000 basis points) -# Submit set_reward_sharing_operation via API or wallet - -# Set epoch length to 5 days via versioned_chain_properties_update_operation: -# chain_properties_hf13.distribution_epoch_length = 144000 (5 * 28800 blocks) -``` - ---- - -## Deployment: Shared Memory Compatibility and Replay - -### Why replay is required - -HF13 adds three new fields to chainbase-managed objects: - -| Object | New field | Default | -|---|---|---| -| `witness_object` | `sharing_rate` (`uint16_t`) | `0` | -| `witness_object` | `pending_stakeholder_reward` (`share_type`) | `0` | -| `witness_vote_object` | `vote_created_block` (`uint32_t`) | `0` | - -Chainbase stores objects as raw binary in a memory-mapped file (`shared_memory.bin`). -Adding a field changes `sizeof()`, so the old shared memory is **binary-incompatible** with -the new binary. If the new binary opens old shared memory, chainbase reads objects at -incorrect offsets, producing corrupt data and ultimately a `database_revision_exception` -(revision counter in shared memory will not match `head_block_num`). - -**No apply_hardfork migration is needed**: the new fields default to `0`, which is the -correct pre-HF13 state. Once the node replays or restores from snapshot, all objects -are initialised correctly. - ---- - -### Automatic recovery (existing mechanism) - -The chain plugin already handles this case. On `database_revision_exception` at startup, -it executes one of two recovery paths depending on config: - -``` -Startup - └─ db.open(shared_memory) - └─ EXCEPTION: database_revision_exception - │ - ├─ [auto-recover-from-snapshot = true AND snapshot exists] - │ wipe shared_memory - │ → import latest snapshot (state restored to block N) - │ → replay dlt_block_log blocks N+1..dlt_head - │ (bridges gap between snapshot and last known block) - │ → continue P2P sync for remaining blocks - │ - └─ [replay-if-corrupted = true, no snapshot] - → replay from block_log (slow, may take hours) -``` - -The dlt_block_log replay step (handled in `snapshot_plugin.cpp:2104–2121`) is automatic: -after the snapshot is imported, the node calls `db.reindex_from_dlt(snapshot_head + 1)` -to re-apply any local blocks that are newer than the snapshot. This minimises P2P sync -work after upgrade. - -**Recommended config for production validators:** - -```ini -# config.ini (or config_witness.ini) -replay-if-corrupted = true -replay-from-snapshot = true -snapshot-auto-latest = true -snapshot-dir = /path/to/snapshots -``` - -With this config, upgrading to the HF13 binary requires no manual intervention: -1. Stop the node. -2. Replace the binary. -3. Start the node — auto-recovery fires, wipes shared memory, loads the latest snapshot, - replays `dlt_block_log`, then syncs the remaining blocks via P2P. - ---- - -### Manual recovery procedure - -If auto-recovery is not configured, or the recovery path fails, perform the following: - -```bash -# 1. Stop vizd -systemctl stop vizd - -# 2. Delete shared memory (forces clean open on next start) -rm -f /path/to/node_data_dir/shared_memory.bin - -# Option A — restore from snapshot + replay dlt_block_log: -./vizd --replay-from-snapshot /path/to/snapshot-block-XXXXXXXX.json \ - --data-dir /path/to/node_data_dir - -# Option B — full replay from block_log (slow): -./vizd --replay --data-dir /path/to/node_data_dir -``` - -After Option A the node replays `dlt_block_log` automatically (same path as auto-recovery), -then syncs remaining blocks via P2P. Typical recovery time with a recent snapshot: a few -minutes. Full replay (Option B) may take several hours depending on blockchain height. - ---- - -### How chainbase detects the mismatch - -Chainbase does **not** compare `sizeof()` at open time — it opens the memory-mapped file -and begins reading. The mismatch surfaces as a revision inconsistency: - -``` -database::open() - chainbase::database::open(shared_mem_dir) ← objects read at wrong offsets - undo_all() ← traverses corrupt undo state - revision() != head_block_num() ← counter mismatch detected here - → throws database_revision_exception -``` - -Alternatively, `undo_all()` may trigger a `boost::interprocess::lock_exception` if the -process crashed while holding a shared-memory mutex (unrelated to HF13, but the same -recovery path applies). - -**Implication**: deleting `shared_memory.bin` before starting is the safest option. The -file is always rebuilt from snapshot or block_log; nothing of value is lost. - ---- - -### Proactive schema version check (implemented in HF13) - -Rather than waiting for a corrupt read to surface as a `database_revision_exception`, -the chain plugin now performs a **proactive schema version check** before calling -`db.open()`: - -``` -plugin_startup() - │ - ├─ read /schema_version (0 if file absent = pre-HF13 node) - ├─ compare with CHAIN_SCHEMA_VERSION (compile-time constant, currently 13) - │ - ├─ MISMATCH → wipe shared_memory.bin immediately (no corrupt read occurs) - │ write new schema_version to disk - │ → fall through to normal db.open() - │ → revision=0 ≠ head_block_num → database_revision_exception - │ → existing recovery: auto-snapshot or replay - │ - └─ MATCH → proceed normally - db.open() succeeds → write schema_version (confirm success) -``` - -**Key constant** (`config.hpp`): -```cpp -// Increment whenever a chainbase-managed object gains, loses, or resizes a field. -#define CHAIN_SCHEMA_VERSION uint32_t(13) -``` - -**Key file**: `/schema_version` — a plain text file containing a single -`uint32_t`. Absent file is treated as version `0` (pre-HF13). - -This mechanism is completely transparent: nodes that don't have `schema_version` -(old deployments upgrading to HF13 for the first time) automatically get `stored=0`, -mismatch is detected, shared memory is wiped, and recovery proceeds. - -See [hardfork-guide.md](hardfork-guide.md) for the rule: **increment -`CHAIN_SCHEMA_VERSION` in every hardfork that adds, removes, or resizes a field in -any chainbase-managed object**. - ---- - -## Files Changed - -| File | Change | -|---|---| -| `libraries/chain/hardfork.d/13.hf` | New hardfork definition | -| `libraries/chain/hardfork.d/0-preamble.hf` | `CHAIN_NUM_HARDFORKS` 12 → 13 | -| `libraries/protocol/include/graphene/protocol/config.hpp` | `CHAIN_VERSION` 3.2.0; `CHAIN_VALIDATOR_MAX_SHARING_RATE`, `CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT`, `CHAIN_SCHEMA_VERSION` | -| `libraries/protocol/include/graphene/protocol/chain_operations.hpp` | `chain_properties_hf13`, `set_reward_sharing_operation` | -| `libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp` | `stakeholder_reward_operation` | -| `libraries/protocol/include/graphene/protocol/operations.hpp` | New ops in variant | -| `libraries/protocol/chain_operations.cpp` | `validate()` for `set_reward_sharing_operation` | -| `libraries/chain/include/graphene/chain/witness_objects.hpp` | `chain_properties` alias → `hf13`; `sharing_rate`, `pending_stakeholder_reward` fields; `vote_created_block` on `witness_vote_object` | -| `libraries/chain/include/graphene/chain/chain_evaluator.hpp` | `DEFINE_EVALUATOR(set_reward_sharing)` | -| `libraries/chain/include/graphene/chain/database.hpp` | `process_validator_epoch_distribution()` declaration | -| `libraries/chain/chain_properties_evaluators.cpp` | HF13 visitor case; `set_reward_sharing_evaluator::do_apply` | -| `libraries/chain/chain_evaluator.cpp` | Set `vote_created_block` in all `create` paths | -| `libraries/chain/database.cpp` | Hardfork registration; evaluator registration; median; `process_funds` split; `process_validator_epoch_distribution` (time-weighted); `_apply_block` call; `apply_hardfork` case | -| `plugins/account_history/plugin.cpp` | Impacted accounts for `stakeholder_reward_operation` | -| `plugins/chain/plugin.cpp` | `CHAIN_SCHEMA_VERSION` check + proactive wipe before `db.open()`; `read_schema_version` / `write_schema_version` helpers; `#include ` | diff --git a/.qoder/docs/index.md b/.qoder/docs/index.md deleted file mode 100644 index 75f63249e3..0000000000 --- a/.qoder/docs/index.md +++ /dev/null @@ -1,175 +0,0 @@ -# VIZ Blockchain — Operations & Structures Spec - -Full specification and implementation checklist for building VIZ blockchain client libraries in PHP, Node.js, and other languages. - ---- - -## Files in This Directory - -### Research (`../research/`) - -| File | Contents | -|---|---| -| [consensus-emergency-recovery.md](../research/consensus-emergency-recovery.md) | Research: automatic emergency consensus mode for network stall recovery & micro-fork prevention | - -### Documentation (`./`) - -| File | Contents | -|---|---| -| [cli-wallet.md](cli-wallet.md) | Complete CLI wallet command reference with examples | -| [data-types.md](data-types.md) | Primitive types, `asset`, `authority`, `public_key_type`, operation type index | -| [plugins.md](plugins.md) | All node plugins, dependencies, status, JSON-RPC method tables | -| [block-processing.md](block-processing.md) | Block application flow, pending transactions, postponed tx mechanism | -| [shared-memory.md](shared-memory.md) | Shared memory architecture, locking model, resize workflow, config parameters, corruption risks | -| [block-log-spec.md](block-log-spec.md) | Block log binary format, index files, operation serialization, **tools: reader/viewer, bitmask, search & export** | -| [fork-collision-hardfork-proposal.md](fork-collision-hardfork-proposal.md) | Fork collision analysis, HF12 proposal for consensus improvements | -| [consensus-emergency-params.md](consensus-emergency-params.md) | Emergency restart parameters (enable-stale-production, required-participation, fork_db) & micro-fork risks | -| [emergency-consensus-review.md](emergency-consensus-review.md) | HF12 implementation review: failure/rollback procedures, threat model, test matrix | -| [op-account.md](op-account.md) | Account create, update, metadata operations | -| [op-transfer-vesting.md](op-transfer-vesting.md) | Transfer, transfer_to_vesting, withdraw_vesting, set route, delegate | -| [op-validator.md](op-validator.md) | validator update, vote, proxy, chain properties | -| [op-content.md](op-content.md) | Content, vote, delete_content (deprecated), custom | -| [op-recovery.md](op-recovery.md) | Request/recover account, change recovery account | -| [op-escrow.md](op-escrow.md) | Escrow transfer, approve, dispute, release | -| [op-committee.md](op-committee.md) | Committee worker create/cancel request, vote | -| [op-invite.md](op-invite.md) | Create invite, claim balance, register, use balance | -| [op-award.md](op-award.md) | Award, fixed_award operations | -| [op-subscription.md](op-subscription.md) | Set paid subscription, paid subscribe | -| [op-account-market.md](op-account-market.md) | Set account/subaccount price, buy account, target sale | -| [op-proposal.md](op-proposal.md) | Proposal create, update, delete (multi-sig) | -| [virtual-operations.md](virtual-operations.md) | All virtual operations (read-only, blockchain-generated) | - ---- - -## Quick Reference: All Operations - -### Regular Operations (user-broadcast) - -| ID | Name | Auth | Key File | -|---|---|---|---| -| 0 | `vote_operation` *(deprecated)* | regular | op-content.md | -| 1 | `content_operation` *(deprecated)* | regular | op-content.md | -| 2 | `transfer_operation` | active (VIZ) / master (SHARES) | op-transfer-vesting.md | -| 3 | `transfer_to_vesting_operation` | active | op-transfer-vesting.md | -| 4 | `withdraw_vesting_operation` | active | op-transfer-vesting.md | -| 5 | `account_update_operation` | master/active | op-account.md | -| 6 | `witness_update_operation` | active | op-validator.md | -| 7 | `account_witness_vote_operation` | active | op-validator.md | -| 8 | `account_witness_proxy_operation` | active | op-validator.md | -| 9 | `delete_content_operation` *(deprecated)* | regular | op-content.md | -| 10 | `custom_operation` | active/regular | op-content.md | -| 11 | `set_withdraw_vesting_route_operation` | active | op-transfer-vesting.md | -| 12 | `request_account_recovery_operation` | active | op-recovery.md | -| 13 | `recover_account_operation` | master (×2) | op-recovery.md | -| 14 | `change_recovery_account_operation` | master | op-recovery.md | -| 15 | `escrow_transfer_operation` | active | op-escrow.md | -| 16 | `escrow_dispute_operation` | active | op-escrow.md | -| 17 | `escrow_release_operation` | active | op-escrow.md | -| 18 | `escrow_approve_operation` | active | op-escrow.md | -| 19 | `delegate_vesting_shares_operation` | active | op-transfer-vesting.md | -| 20 | `account_create_operation` | active | op-account.md | -| 21 | `account_metadata_operation` | regular | op-account.md | -| 22 | `proposal_create_operation` | active | op-proposal.md | -| 23 | `proposal_update_operation` | varies | op-proposal.md | -| 24 | `proposal_delete_operation` | active | op-proposal.md | -| 25 | `chain_properties_update_operation` | active | op-validator.md | -| 35 | `committee_worker_create_request_operation` | regular | op-committee.md | -| 36 | `committee_worker_cancel_request_operation` | regular | op-committee.md | -| 37 | `committee_vote_request_operation` | regular | op-committee.md | -| 43 | `create_invite_operation` | active | op-invite.md | -| 44 | `claim_invite_balance_operation` | active | op-invite.md | -| 45 | `invite_registration_operation` | active | op-invite.md | -| 46 | `versioned_chain_properties_update_operation` | active | op-validator.md | -| 47 | `award_operation` | regular | op-award.md | -| 50 | `set_paid_subscription_operation` | active | op-subscription.md | -| 51 | `paid_subscribe_operation` | active | op-subscription.md | -| 54 | `set_account_price_operation` | master | op-account-market.md | -| 55 | `set_subaccount_price_operation` | master | op-account-market.md | -| 56 | `buy_account_operation` | active | op-account-market.md | -| 58 | `use_invite_balance_operation` | active | op-invite.md | -| 60 | `fixed_award_operation` | regular | op-award.md | -| 61 | `target_account_sale_operation` | master | op-account-market.md | - -### Virtual Operations (read-only, not broadcastable) - -| ID | Name | Trigger | Key File | -|---|---|---|---| -| 26 | `author_reward_operation` | Content payout | virtual-operations.md | -| 27 | `curation_reward_operation` | Content payout | virtual-operations.md | -| 28 | `content_reward_operation` | Content payout | virtual-operations.md | -| 29 | `fill_vesting_withdraw_operation` | Withdrawal interval | virtual-operations.md | -| 30 | `shutdown_witness_operation` | validator deactivated | virtual-operations.md | -| 31 | `hardfork_operation` | Hardfork activation | virtual-operations.md | -| 32 | `content_payout_update_operation` | Content payout update | virtual-operations.md | -| 33 | `content_benefactor_reward_operation` | Content payout | virtual-operations.md | -| 34 | `return_vesting_delegation_operation` | Delegation limbo ends | virtual-operations.md | -| 38 | `committee_cancel_request_operation` | Request expires | virtual-operations.md | -| 39 | `committee_approve_request_operation` | Request approved | virtual-operations.md | -| 40 | `committee_payout_request_operation` | Payout processed | virtual-operations.md | -| 41 | `committee_pay_request_operation` | Worker paid | virtual-operations.md | -| 42 | `witness_reward_operation` | Block produced | virtual-operations.md | -| 48 | `receive_award_operation` | Award given | virtual-operations.md | -| 49 | `benefactor_award_operation` | Award with beneficiary | virtual-operations.md | -| 52 | `paid_subscription_action_operation` | Subscription payment | virtual-operations.md | -| 53 | `cancel_paid_subscription_operation` | Subscription ends | virtual-operations.md | -| 57 | `account_sale_operation` | Account sold | virtual-operations.md | -| 59 | `expire_escrow_ratification_operation` | Escrow deadline missed | virtual-operations.md | -| 62 | `bid_operation` | Bid placed (HF11) | virtual-operations.md | -| 63 | `outbid_operation` | Outbid (HF11) | virtual-operations.md | - ---- - -## Library Implementation Master Checklist - -### Serialization -- [ ] Operations serialized as `[type_id, object]` (2-element array) -- [ ] `asset` values as string `"10.000 VIZ"` or as `{"amount": int, "symbol": int}` depending on API -- [ ] `authority` serialized with `weight_threshold`, `account_auths`, `key_auths` -- [ ] `public_key_type` as VIZ-prefixed base58check string -- [ ] `time_point_sec` as ISO 8601 UTC string `"2024-01-15T12:00:00"` (no timezone suffix) -- [ ] `optional` as `null` when absent, or the value when present -- [ ] `extensions_type` always `[]` -- [ ] `flat_set` and `vector` as JSON arrays -- [ ] `flat_map` as JSON array of `[key, value]` pairs - -### Transaction Construction -- [ ] Fetch current block header for `ref_block_num` and `ref_block_prefix` -- [ ] Set `expiration` = current time + desired TTL (max 60 seconds recommended) -- [ ] Sign transaction with required keys (see each operation's authority requirements) -- [ ] `ref_block_num` = `head_block_number & 0xFFFF` -- [ ] `ref_block_prefix` = first 4 bytes (little-endian uint32) of `block_id` starting at byte 4 -- [ ] Chain ID must match target network (mainnet vs testnet) - -### Key Management -- [ ] Private keys in WIF format (Base58Check with version byte 0x80) -- [ ] Public keys in VIZ-prefixed compressed base58 format -- [ ] Derive public key from private key using secp256k1 -- [ ] Sign: sha256d(chain_id + serialized_tx) → compact ECDSA signature - -### Energy System -- [ ] Energy is in basis points (0–10000 = 0%–100%) -- [ ] Energy regenerates at 100% per day (`CHAIN_ENERGY_REGENERATION_SECONDS = 86400`) -- [ ] Formula: `current_energy = min(10000, last_energy + elapsed_seconds / REGEN_RATE)` -- [ ] Spending 1000 energy (10%) costs proportional share of reward pool - -### Asset Formatting -- [ ] VIZ: 3 decimal places, e.g. `"10.000 VIZ"` -- [ ] SHARES: 6 decimal places, e.g. `"10.000000 SHARES"` -- [ ] Parse: split on space, parse amount with decimal, check symbol - -### Account Name Validation -- [ ] Length: 3–16 characters -- [ ] Only lowercase letters, digits, hyphens, dots -- [ ] Each segment (dot-separated) starts with letter, ends with letter/digit -- [ ] Each segment >= 3 characters - -### Authority Validation -- [ ] `weight_threshold` must be satisfiable (sum of weights >= threshold) -- [ ] `key_auths` entries: `[public_key_string, uint16]` -- [ ] `account_auths` entries: `[account_name_string, uint16]` - -### Bandwidth & Fees -- [ ] Operations consume bandwidth proportional to their serialized byte size -- [ ] Data operations (`custom_operation.json` etc.) have additional bandwidth cost -- [ ] Account creation requires `fee` >= chain `account_creation_fee` -- [ ] Committee/invite/subscription operations charge network fees from chain properties diff --git a/.qoder/docs/jsonrpc-api-spec.json b/.qoder/docs/jsonrpc-api-spec.json deleted file mode 100644 index 4d821d5bdf..0000000000 --- a/.qoder/docs/jsonrpc-api-spec.json +++ /dev/null @@ -1,1353 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VIZ Blockchain JSON-RPC API Specification", - "description": "Complete specification of all JSON-RPC methods exposed by VIZ node plugins. Intended as a machine-readable spec for API explorer generation.", - "version": "1.0.0", - "plugins": [ - { - "name": "validator_api", - "description": "Provides read-only access to validator (witness) data: schedules, votes, and validator registration info.", - "methods": [ - { - "method": "get_active_validators", - "description": "Returns the list of currently active validator account names that are participating in block production.", - "aliases": ["get_active_witnesses"], - "params": [], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of account names of currently active validators." - } - }, - { - "method": "get_validator_schedule", - "description": "Returns the current validator schedule object, including the shuffled list of validators and their timeshares.", - "aliases": ["get_witness_schedule"], - "params": [], - "returns": { - "type": "object", - "description": "The validator_schedule_object containing current_shuffled_validators, timeshare, and related scheduling data." - } - }, - { - "method": "get_validators", - "description": "Returns a list of validator objects by their database IDs. For each ID, returns either the validator_api_object or null if not found.", - "aliases": ["get_witnesses"], - "params": [ - { - "name": "validator_ids", - "caption": "Validator IDs", - "description": "Array of validator object database IDs to look up.", - "type": "array", - "items": { "type": "integer" }, - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of optional validator_api_object entries, one per requested ID." - } - }, - { - "method": "get_validator_by_account", - "description": "Returns the validator object registered under a specific account name, or null if the account is not a validator.", - "aliases": ["get_witness_by_account"], - "params": [ - { - "name": "account_name", - "caption": "Account Name", - "description": "The account name to look up as a validator.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The validator_api_object for the account, or null if not a validator.", - "nullable": true - } - }, - { - "method": "get_validators_by_vote", - "description": "Returns validators sorted by total votes (descending). Starts from a given account name. Only returns validators with votes > 0. Maximum 100 results.", - "aliases": ["get_witnesses_by_vote"], - "params": [ - { - "name": "from", - "caption": "From Account", - "description": "The account name to start from. Use empty string to start from the top.", - "type": "string", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 100.", - "type": "integer", - "required": true, - "maximum": 100 - } - ], - "returns": { - "type": "array", - "description": "Array of validator_api_object entries sorted by vote count." - } - }, - { - "method": "get_validators_by_counted_vote", - "description": "Returns validators sorted by counted votes (descending). Starts from a given account name. Only returns validators with counted_votes > 0. Maximum 100 results.", - "aliases": ["get_witnesses_by_counted_vote"], - "params": [ - { - "name": "from", - "caption": "From Account", - "description": "The account name to start from. Use empty string to start from the top.", - "type": "string", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 100.", - "type": "integer", - "required": true, - "maximum": 100 - } - ], - "returns": { - "type": "array", - "description": "Array of validator_api_object entries sorted by counted vote." - } - }, - { - "method": "get_validator_count", - "description": "Returns the total number of registered validators on the blockchain.", - "aliases": ["get_witness_count"], - "params": [], - "returns": { - "type": "integer", - "description": "Total count of registered validators." - } - }, - { - "method": "lookup_validator_accounts", - "description": "Looks up validator account names starting from a lower bound. Returns up to 1000 results alphabetically.", - "aliases": ["lookup_witness_accounts"], - "params": [ - { - "name": "lower_bound_name", - "caption": "Lower Bound Name", - "description": "The lower bound of the first account name to return. Use empty string to start from the beginning.", - "type": "string", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 1000.", - "type": "integer", - "required": true, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Set of validator account names matching the query." - } - } - ] - }, - { - "name": "account_history", - "description": "Tracks operations by account and provides per-account operation history queries.", - "methods": [ - { - "method": "get_account_history", - "description": "Returns a map of operations for a given account in the sequence range [from-limit, from]. Each account operation has a sequence number starting from 0. Use from=-1 (4294967295) to get the most recent operations.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name whose operation history to retrieve.", - "type": "string", - "required": true - }, - { - "name": "from", - "caption": "From Sequence", - "description": "The absolute sequence number. Use -1 (4294967295) for the most recent operation.", - "type": "integer", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of operations to return. Must be between 1 and 1000. Must be less than 'from' unless from is -1.", - "type": "integer", - "required": true, - "minimum": 1, - "maximum": 1000 - } - ], - "returns": { - "type": "object", - "description": "Map of sequence number to applied_operation objects for the account." - } - } - ] - }, - { - "name": "operation_history", - "description": "Tracks all blockchain operations and provides block-level and transaction-level operation queries.", - "methods": [ - { - "method": "get_ops_in_block", - "description": "Returns the sequence of operations included or generated within a particular block. Virtual operations are generated by the blockchain (e.g. rewards) as opposed to user-submitted operations.", - "params": [ - { - "name": "block_num", - "caption": "Block Number", - "description": "Height of the block whose operations should be returned.", - "type": "integer", - "required": true - }, - { - "name": "only_virtual", - "caption": "Only Virtual", - "description": "Whether to only include virtual operations in the returned results.", - "type": "boolean", - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of applied_operation objects from the specified block." - } - }, - { - "method": "get_transaction", - "description": "Returns a transaction by its ID, including block number and transaction index within the block.", - "params": [ - { - "name": "id", - "caption": "Transaction ID", - "description": "The hash (SHA-256 / ripemd160) of the transaction to retrieve.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "annotated_signed_transaction with block_num and transaction_num fields added." - } - } - ] - }, - { - "name": "database_api", - "description": "The core read-only API for the blockchain database. Provides access to blocks, accounts, chain properties, authority validation, vesting delegations, and more.", - "methods": [ - { - "method": "get_block_header", - "description": "Retrieves a block header by block number.", - "params": [ - { - "name": "block_num", - "caption": "Block Number", - "description": "Height of the block whose header should be returned.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The block header, or null if no matching block was found.", - "nullable": true - } - }, - { - "method": "get_block", - "description": "Retrieves a full, signed block by block number.", - "params": [ - { - "name": "block_num", - "caption": "Block Number", - "description": "Height of the block to be returned.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The full signed block, or null if no matching block was found.", - "nullable": true - } - }, - { - "method": "get_irreversible_block_header", - "description": "Retrieves a block header only if the block is irreversible. Returns null if the block has not yet been finalized.", - "params": [ - { - "name": "block_num", - "caption": "Block Number", - "description": "Height of the block whose header should be returned.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The block header if the block is irreversible, or null.", - "nullable": true - } - }, - { - "method": "get_irreversible_block", - "description": "Retrieves a full, signed block only if it is irreversible. Returns null if the block has not yet been finalized.", - "params": [ - { - "name": "block_num", - "caption": "Block Number", - "description": "Height of the block to be returned.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The full signed block if irreversible, or null.", - "nullable": true - } - }, - { - "method": "set_block_applied_callback", - "description": "Sets a callback function that is triggered on each newly generated block. Used for real-time block notifications via WebSocket.", - "params": [ - { - "name": "callback", - "caption": "Callback", - "description": "Callback function to invoke when a new block is applied.", - "type": "function", - "required": true - } - ], - "returns": { - "type": "null", - "description": "No return value (callback-based)." - } - }, - { - "method": "get_config", - "description": "Retrieves compile-time constants and configuration values of the blockchain (e.g., chain ID, symbol, precision).", - "params": [], - "returns": { - "type": "object", - "description": "Object containing blockchain compile-time configuration constants." - } - }, - { - "method": "get_dynamic_global_properties", - "description": "Retrieves the current dynamic global properties object, which contains real-time chain state such as head block number, total supply, and other dynamic metrics.", - "params": [], - "returns": { - "type": "object", - "description": "The dynamic_global_property_api_object with current chain state." - } - }, - { - "method": "get_chain_properties", - "description": "Retrieves the chain properties as set by the median validator schedule (chain-wide constraints like account creation fee, maximum block size, etc.).", - "params": [], - "returns": { - "type": "object", - "description": "chain_api_properties object with median chain parameters." - } - }, - { - "method": "get_hardfork_version", - "description": "Returns the current hardfork version of the blockchain.", - "params": [], - "returns": { - "type": "string", - "description": "The current hardfork version string (e.g. '0.23.0')." - } - }, - { - "method": "get_next_scheduled_hardfork", - "description": "Returns the next scheduled hardfork version and the time it is planned to go live.", - "params": [], - "returns": { - "type": "object", - "description": "Object with hf_version (string) and live_time (ISO timestamp)." - } - }, - { - "method": "get_accounts", - "description": "Returns full account objects for a list of account names. Includes balances, vesting, authority, and validator votes.", - "params": [ - { - "name": "names", - "caption": "Account Names", - "description": "Array of account names to look up.", - "type": "array", - "items": { "type": "string" }, - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of account_api_object entries. Only accounts that exist are returned." - } - }, - { - "method": "lookup_account_names", - "description": "Looks up accounts by their names. Returns an optional account object for each name; null if the account does not exist.", - "params": [ - { - "name": "account_names", - "caption": "Account Names", - "description": "Array of account names to look up.", - "type": "array", - "items": { "type": "string" }, - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of optional account_api_object entries. Each element may be null." - } - }, - { - "method": "lookup_accounts", - "description": "Looks up account names starting from a lower bound. Returns a set of account names in alphabetical order.", - "params": [ - { - "name": "lower_bound_name", - "caption": "Lower Bound Name", - "description": "The lower bound of the first account name to return.", - "type": "string", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 1000.", - "type": "integer", - "required": true, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Set of account names matching the query." - } - }, - { - "method": "get_account_count", - "description": "Returns the total number of accounts registered on the blockchain.", - "params": [], - "returns": { - "type": "integer", - "description": "Total number of registered accounts." - } - }, - { - "method": "get_master_history", - "description": "Returns the master authority change history for a given account, useful for account recovery audits.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name whose master authority history to retrieve.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of master_authority_history_api_object entries." - } - }, - { - "method": "get_recovery_request", - "description": "Returns the current account recovery request for an account, if one exists.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name whose recovery request to check.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The account_recovery_request_api_object, or null if no request exists.", - "nullable": true - } - }, - { - "method": "get_escrow", - "description": "Returns the escrow object for a given sender and escrow ID.", - "params": [ - { - "name": "from", - "caption": "From Account", - "description": "The account name of the escrow sender.", - "type": "string", - "required": true - }, - { - "name": "escrow_id", - "caption": "Escrow ID", - "description": "The numeric escrow ID to look up.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "The escrow_api_object, or null if not found.", - "nullable": true - } - }, - { - "method": "get_withdraw_routes", - "description": "Returns vesting withdrawal routes for a given account. Can filter by direction (incoming, outgoing, or all).", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name whose withdrawal routes to retrieve.", - "type": "string", - "required": true - }, - { - "name": "type", - "caption": "Route Type", - "description": "Filter direction: 'incoming', 'outgoing', or 'all'.", - "type": "string", - "enum": ["incoming", "outgoing", "all"], - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of withdraw_route objects with from_account, to_account, percent, auto_vest." - } - }, - { - "method": "get_vesting_delegations", - "description": "Returns vesting delegation objects for a given account. Supports pagination and filtering by delegated or received.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The delegator or delegatee account name.", - "type": "string", - "required": true - }, - { - "name": "from", - "caption": "From", - "description": "The account name to start from for pagination.", - "type": "string", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results. Defaults to 100. Must not exceed 1000.", - "type": "integer", - "required": false, - "default": 100, - "maximum": 1000 - }, - { - "name": "type", - "caption": "Delegation Type", - "description": "Filter type: 'delegated' (sent) or 'received'. Defaults to 'delegated'.", - "type": "string", - "enum": ["delegated", "received"], - "required": false, - "default": "delegated" - } - ], - "returns": { - "type": "array", - "description": "Array of vesting_delegation_api_object entries." - } - }, - { - "method": "get_expiring_vesting_delegations", - "description": "Returns expiring vesting delegation objects for a given account, starting from a given date.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The delegator account name.", - "type": "string", - "required": true - }, - { - "name": "from", - "caption": "From Date", - "description": "Start date/time for expiration lookup (ISO timestamp).", - "type": "string", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results. Defaults to 100. Must not exceed 1000.", - "type": "integer", - "required": false, - "default": 100, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "description": "Array of vesting_delegation_expiration_api_object entries." - } - }, - { - "method": "get_transaction_hex", - "description": "Returns a hexadecimal dump of the serialized binary form of a transaction.", - "params": [ - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction object to serialize.", - "type": "object", - "required": true - } - ], - "returns": { - "type": "string", - "description": "Hex-encoded serialized transaction." - } - }, - { - "method": "get_required_signatures", - "description": "Given a partially signed transaction and a set of available public keys, returns the minimal subset of public keys that should add signatures to authorize the transaction.", - "params": [ - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction to analyze.", - "type": "object", - "required": true - }, - { - "name": "available_keys", - "caption": "Available Keys", - "description": "Array/set of public keys that the caller can sign with.", - "type": "array", - "items": { "type": "string" }, - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Set of public keys that are required to sign the transaction." - } - }, - { - "method": "get_potential_signatures", - "description": "Returns the set of all public keys that could possibly sign for a given transaction. Useful for wallets to filter their key set before calling get_required_signatures.", - "params": [ - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction to analyze.", - "type": "object", - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Set of all public keys that could potentially authorize the transaction." - } - }, - { - "method": "verify_authority", - "description": "Verifies that a transaction has all of the required signatures. Returns true if valid, otherwise throws an exception.", - "params": [ - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction to verify.", - "type": "object", - "required": true - } - ], - "returns": { - "type": "boolean", - "description": "true if the transaction has all required signatures." - } - }, - { - "method": "verify_account_authority", - "description": "Verifies that a set of public keys has sufficient authority to authorize actions on behalf of an account.", - "params": [ - { - "name": "name_or_id", - "caption": "Account Name", - "description": "The account name to check authority for.", - "type": "string", - "required": true - }, - { - "name": "signers", - "caption": "Signer Keys", - "description": "Array/set of public keys to verify against the account's authority.", - "type": "array", - "items": { "type": "string" }, - "required": true - } - ], - "returns": { - "type": "boolean", - "description": "true if the signers have enough authority to authorize the account." - } - }, - { - "method": "get_database_info", - "description": "Returns database shared memory usage information including total size, free size, reserved size, used size, and per-index record counts.", - "params": [], - "returns": { - "type": "object", - "description": "Object with total_size, free_size, reserved_size, used_size, and index_list (array of {name, record_count})." - } - }, - { - "method": "get_proposed_transactions", - "description": "Returns proposed transactions (proposals) associated with a given account, both authored and requiring approval.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name whose proposals to retrieve.", - "type": "string", - "required": true - }, - { - "name": "from", - "caption": "From Offset", - "description": "Offset for pagination (number of results to skip).", - "type": "integer", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of proposals to return. Must not exceed 100.", - "type": "integer", - "required": true, - "maximum": 100 - } - ], - "returns": { - "type": "array", - "description": "Array of proposal_api_object entries." - } - }, - { - "method": "get_accounts_on_sale", - "description": "Returns a list of accounts currently on sale (direct sale, not auction). Only accounts whose sale start time has passed are included.", - "params": [ - { - "name": "from", - "caption": "From Offset", - "description": "Number of results to skip for pagination.", - "type": "integer", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 1000.", - "type": "integer", - "required": true, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "description": "Array of account_on_sale_api_object entries." - } - }, - { - "method": "get_accounts_on_auction", - "description": "Returns a list of accounts currently on auction (no target buyer set). Only accounts whose sale start time has passed are included.", - "params": [ - { - "name": "from", - "caption": "From Offset", - "description": "Number of results to skip for pagination.", - "type": "integer", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 1000.", - "type": "integer", - "required": true, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "description": "Array of account_on_sale_api_object entries for auction listings." - } - }, - { - "method": "get_subaccounts_on_sale", - "description": "Returns a list of subaccounts currently on sale.", - "params": [ - { - "name": "from", - "caption": "From Offset", - "description": "Number of results to skip for pagination.", - "type": "integer", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 1000.", - "type": "integer", - "required": true, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "description": "Array of subaccount_on_sale_api_object entries." - } - } - ] - }, - { - "name": "account_by_key", - "description": "Provides a lookup from public keys to the accounts that reference those keys in their authority.", - "methods": [ - { - "method": "get_key_references", - "description": "Returns all account names that reference the given public keys in their master, active, or regular authority.", - "params": [ - { - "name": "keys", - "caption": "Public Keys", - "description": "Array of public keys to look up.", - "type": "array", - "items": { "type": "string" }, - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of arrays of account names. Each inner array corresponds to one input key and contains all accounts referencing that key." - } - } - ] - }, - { - "name": "network_broadcast_api", - "description": "Provides transaction and block broadcasting capabilities. This is the write API for submitting transactions to the network.", - "methods": [ - { - "method": "broadcast_transaction", - "description": "Broadcasts a signed transaction to the network. The transaction is accepted into the pending pool and propagated to P2P peers. Optionally checks that the blockchain is not too far behind.", - "params": [ - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction to broadcast.", - "type": "object", - "required": true - }, - { - "name": "max_block_age", - "caption": "Max Block Age", - "description": "Optional. Maximum allowed age of the head block in seconds. If the blockchain is behind by more than this, the call will fail. Use -1 to disable.", - "type": "integer", - "required": false - } - ], - "returns": { - "type": "null", - "description": "No return value on success." - } - }, - { - "method": "broadcast_transaction_synchronous", - "description": "Broadcasts a signed transaction and waits for confirmation. Returns the transaction ID, block number, and transaction index once included in a block. The callback includes whether the transaction expired.", - "params": [ - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction to broadcast.", - "type": "object", - "required": true - }, - { - "name": "max_block_age", - "caption": "Max Block Age", - "description": "Optional. Maximum allowed age of the head block in seconds. Use -1 to disable.", - "type": "integer", - "required": false - } - ], - "returns": { - "type": "object", - "description": "Object with id (transaction hash), block_num, trx_num, and expired fields." - } - }, - { - "method": "broadcast_block", - "description": "Broadcasts a signed block to the network. Typically used by validators to propagate newly produced blocks.", - "params": [ - { - "name": "block", - "caption": "Block", - "description": "The signed block to broadcast.", - "type": "object", - "required": true - } - ], - "returns": { - "type": "null", - "description": "No return value on success." - } - }, - { - "method": "broadcast_transaction_with_callback", - "description": "Broadcasts a signed transaction with a confirmation callback. The first argument is the callback, followed by the transaction. Similar to broadcast_transaction_synchronous but with custom callback handling.", - "params": [ - { - "name": "callback", - "caption": "Callback", - "description": "Confirmation callback function.", - "type": "function", - "required": true - }, - { - "name": "trx", - "caption": "Transaction", - "description": "The signed transaction to broadcast.", - "type": "object", - "required": true - }, - { - "name": "max_block_age", - "caption": "Max Block Age", - "description": "Optional. Maximum allowed age of the head block in seconds. Use -1 to disable.", - "type": "integer", - "required": false - } - ], - "returns": { - "type": "null", - "description": "No direct return; result delivered via callback." - } - } - ] - }, - { - "name": "committee_api", - "description": "Provides access to committee worker proposal requests and their voting state.", - "methods": [ - { - "method": "get_committee_request", - "description": "Returns a committee request by its ID, optionally including votes.", - "params": [ - { - "name": "request_id", - "caption": "Request ID", - "description": "The numeric ID of the committee request to retrieve.", - "type": "integer", - "required": true - }, - { - "name": "votes_count", - "caption": "Votes Count", - "description": "Number of votes to include. Use 0 for no votes, -1 for all votes, or a positive number to limit.", - "type": "integer", - "required": false, - "default": 0 - } - ], - "returns": { - "type": "object", - "description": "committee_api_object with optional embedded votes array." - } - }, - { - "method": "get_committee_request_votes", - "description": "Returns all votes for a specific committee request.", - "params": [ - { - "name": "request_id", - "caption": "Request ID", - "description": "The numeric ID of the committee request whose votes to retrieve.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "array", - "description": "Array of committee_vote_state objects." - } - }, - { - "method": "get_committee_requests_list", - "description": "Returns a list of committee request IDs filtered by status.", - "params": [ - { - "name": "status", - "caption": "Status", - "description": "The status code to filter by (e.g. 0=pending, 1=approved, etc.).", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "integer" }, - "description": "Array of committee request IDs matching the given status." - } - } - ] - }, - { - "name": "invite_api", - "description": "Provides access to invite objects used for account registration via invite keys.", - "methods": [ - { - "method": "get_invites_list", - "description": "Returns a list of invite IDs filtered by status.", - "params": [ - { - "name": "status", - "caption": "Status", - "description": "The status code to filter invites by.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "integer" }, - "description": "Array of invite database IDs matching the given status." - } - }, - { - "method": "get_invite_by_id", - "description": "Returns an invite object by its database ID.", - "params": [ - { - "name": "id", - "caption": "Invite ID", - "description": "The database ID of the invite to retrieve.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "invite_api_object with invite details (key, creator, balance, etc.)." - } - }, - { - "method": "get_invite_by_key", - "description": "Returns an invite object by its public key.", - "params": [ - { - "name": "key", - "caption": "Invite Key", - "description": "The public key associated with the invite.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "invite_api_object matching the given key." - } - } - ] - }, - { - "name": "paid_subscription_api", - "description": "Provides access to paid subscription data: subscription options set by content creators, subscription status of subscribers, and active/inactive subscription lists.", - "methods": [ - { - "method": "get_paid_subscription_options", - "description": "Returns the paid subscription settings for a given account (creator).", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name of the subscription creator.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "paid_subscription_state with subscription details (price, period, etc.)." - } - }, - { - "method": "get_paid_subscriptions", - "description": "Returns a paginated list of all paid subscription objects.", - "params": [ - { - "name": "from", - "caption": "From Offset", - "description": "Number of results to skip for pagination.", - "type": "integer", - "required": true - }, - { - "name": "limit", - "caption": "Limit", - "description": "Maximum number of results to return. Must not exceed 1000.", - "type": "integer", - "required": true, - "maximum": 1000 - } - ], - "returns": { - "type": "array", - "description": "Array of paid_subscription_object entries." - } - }, - { - "method": "get_paid_subscription_status", - "description": "Returns the subscription status of a specific subscriber for a given creator account.", - "params": [ - { - "name": "subscriber", - "caption": "Subscriber", - "description": "The account name of the subscriber.", - "type": "string", - "required": true - }, - { - "name": "account", - "caption": "Creator Account", - "description": "The account name of the subscription creator.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "paid_subscribe_state with subscription status details." - } - }, - { - "method": "get_active_paid_subscriptions", - "description": "Returns a list of creator account names that a given subscriber has active subscriptions to.", - "params": [ - { - "name": "subscriber", - "caption": "Subscriber", - "description": "The account name of the subscriber.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of creator account names with active subscriptions." - } - }, - { - "method": "get_inactive_paid_subscriptions", - "description": "Returns a list of creator account names that a given subscriber has inactive (expired) subscriptions to.", - "params": [ - { - "name": "subscriber", - "caption": "Subscriber", - "description": "The account name of the subscriber.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of creator account names with inactive subscriptions." - } - } - ] - }, - { - "name": "custom_protocol_api", - "description": "Provides access to account data enriched with custom protocol sequence information. Custom protocols allow third-party applications to track per-account custom operations.", - "methods": [ - { - "method": "get_account", - "description": "Returns an account object enriched with custom protocol sequence data for a specific custom protocol ID. Populates custom_sequence and custom_sequence_block_num fields.", - "params": [ - { - "name": "account", - "caption": "Account Name", - "description": "The account name to look up.", - "type": "string", - "required": true - }, - { - "name": "custom_protocol_id", - "caption": "Custom Protocol ID", - "description": "The custom protocol ID string to retrieve the sequence for. Use empty string to skip custom protocol lookup.", - "type": "string", - "required": true - } - ], - "returns": { - "type": "object", - "description": "account_api_object with custom_sequence and custom_sequence_block_num populated for the given protocol." - } - } - ] - }, - { - "name": "auth_util", - "description": "Provides utility methods for verifying account authority signatures against arbitrary data digests.", - "methods": [ - { - "method": "check_authority_signature", - "description": "Verifies that the provided signatures are valid for the given account's authority at a specified level (master, active, or regular). Returns the public keys derived from the signatures.", - "params": [ - { - "name": "account_name", - "caption": "Account Name", - "description": "The account name whose authority to check.", - "type": "string", - "required": true - }, - { - "name": "level", - "caption": "Authority Level", - "description": "The authority level to verify against: 'master' (or 'm'), 'active' (or 'a'), 'regular' (or 'r'). Empty string defaults to 'active'.", - "type": "string", - "required": true - }, - { - "name": "dig", - "caption": "Digest", - "description": "The SHA-256 hash of the data that was signed.", - "type": "string", - "required": true - }, - { - "name": "sigs", - "caption": "Signatures", - "description": "Array of signatures to verify.", - "type": "array", - "items": { "type": "string" }, - "required": true - } - ], - "returns": { - "type": "array", - "items": { "type": "string" }, - "description": "Array of public keys recovered from the valid signatures." - } - } - ] - }, - { - "name": "block_info", - "description": "Tracks block metadata (size, average block size, slot info) and provides queries to retrieve this information for ranges of blocks.", - "methods": [ - { - "method": "get_block_info", - "description": "Returns block metadata (block_id, block_size, average_block_size, aslot, last_irreversible_block_num) for a range of blocks starting from start_block_num.", - "params": [ - { - "name": "start_block_num", - "caption": "Start Block Number", - "description": "The first block number to return info for. Must be greater than 0.", - "type": "integer", - "required": true, - "minimum": 1 - }, - { - "name": "count", - "caption": "Count", - "description": "Number of blocks to return info for. Must not exceed 10000.", - "type": "integer", - "required": true, - "maximum": 10000 - } - ], - "returns": { - "type": "array", - "description": "Array of block_info objects. Entries may be empty if no info is stored (e.g. blocks before snapshot)." - } - }, - { - "method": "get_blocks_with_info", - "description": "Returns full signed blocks with attached metadata for a range. Limits total response size to 8 MB. Stops early if no info is stored for a block.", - "params": [ - { - "name": "start_block_num", - "caption": "Start Block Number", - "description": "The first block number to return. Must be greater than 0.", - "type": "integer", - "required": true, - "minimum": 1 - }, - { - "name": "count", - "caption": "Count", - "description": "Maximum number of blocks to return. Must not exceed 10000. Response is capped at 8 MB total.", - "type": "integer", - "required": true, - "maximum": 10000 - } - ], - "returns": { - "type": "array", - "description": "Array of block_with_info objects, each containing a signed block and its block_info metadata." - } - } - ] - }, - { - "name": "raw_block", - "description": "Provides access to raw (base64-encoded) serialized block data for low-level block inspection or re-import.", - "methods": [ - { - "method": "get_raw_block", - "description": "Returns a raw block by block number, including the base64-encoded serialized binary, block ID, previous block ID, and timestamp.", - "params": [ - { - "name": "block_num", - "caption": "Block Number", - "description": "Height of the block to retrieve in raw form.", - "type": "integer", - "required": true - } - ], - "returns": { - "type": "object", - "description": "Object with block_id, previous, timestamp, and raw_block (base64-encoded string) fields." - } - } - ] - } - ] -} diff --git a/.qoder/docs/onix-protocol-paper-ru.md b/.qoder/docs/onix-protocol-paper-ru.md deleted file mode 100644 index 3fd104a67e..0000000000 --- a/.qoder/docs/onix-protocol-paper-ru.md +++ /dev/null @@ -1,769 +0,0 @@ ---- -title: "Объединение тотализаторного расчёта с автоматическими маркет-мейкерами для рынков предсказаний с гарантией ликвидности: протокол Onix" -author: | - Анатолий Пискунов\ - Независимый исследователь — распределённый реестр VIZ\ - anatoly.piskunov@gmail.com\ - ORCID: [0009-0000-8883-4111](https://orcid.org/0009-0000-8883-4111) -date: Июнь 2026 -lang: ru-RU -header-includes: | - \usepackage{titling} - \pretitle{\begin{center}\LARGE\bfseries} - \posttitle{\par\end{center}\vspace{1.4em}} - \setlength{\droptitle}{-1.5em} - \setlength{\abovedisplayskip}{8pt} - \setlength{\belowdisplayskip}{8pt} -abstract: | - Рынки предсказаний агрегируют рассеянную информацию в вероятностные прогнозы, которые устойчиво превосходят опросы и экспертные панели, однако их распространение сдерживается структурной проблемой ликвидности: поставщики рыночной глубины несут риск неблагоприятного отбора, отпугивающий розничных участников. Мы представляем **протокол Onix** — гибридную архитектуру, которая разделяет функцию *ценообразования* и функцию *расчёта* на рынках предсказаний. Сочетая ценообразование автоматического маркет-мейкера — маркет-мейкера с постоянным произведением (CPMM) для бинарных исходов и логарифмического правила рыночного скоринга (LMSR) для многоисходных рынков — с тотализаторным (parimutuel) расчётом, мы достигаем **структурной гарантии** того, что основной капитал поставщика ликвидности никогда не подвергается риску из-за исходов ставок. Мы формализуем экономические инварианты протокола, доказываем гарантию основного капитала LP для обоих типов рынков, описываем «ленивый» пул ликвидности, обеспечивающий пассивное участие розничных пользователей, анализируем механизм разрешения споров под управлением DAO и обсуждаем экспериментальные гипотезы, для проверки которых спроектирована эта система. Протокол реализован как операции консенсусного уровня в распределённом реестре VIZ. - - \vspace{0.9\baselineskip}\noindent - **Ключевые слова:** рынки предсказаний, автоматические маркет-мейкеры, тотализатор, LMSR, предоставление ликвидности, управление через DAO, дизайн механизмов ---- - -\clearpage - -\tableofcontents - -\clearpage - -## 1. Введение - -### 1.1 Проблема ликвидности рынков предсказаний - -Рынки предсказаний — одни из наиболее надёжных механизмов агрегирования рассеянной информации в применимые вероятностные прогнозы. Эмпирические данные из политического прогнозирования (Berg, Forsythe, Nelson & Rietz, 2008), спортивных ставок (Levitt, 2004) и внутрикорпоративных рынков (Cowgill, Wolfers & Zitzewitz, 2009) показывают, что рыночные цены устойчиво превосходят альтернативные методы прогнозирования. - -Несмотря на этот послужной список, развёртывание рынков предсказаний остаётся ограниченным. Фундаментальное ограничение — не спрос на прогнозы, а *предложение ликвидности*. Каждая существующая архитектура рынка предсказаний требует, чтобы поставщики ликвидности (LP) приняли один из трёх профилей риска: - -1. **Непостоянные потери** (impermanent loss) на рынках на основе AMM (Adams, Zinsmeister & Robinson, 2020; Adams et al., 2021), где LP систематически проигрывают информированным трейдерам; -2. **Неблагоприятный отбор** на рынках с книгой лимитных заявок, где маркет-мейкеры несут инвентарный риск против лучше информированных контрагентов; -3. **Ограниченные потери субсидии** на рынках LMSR (Hanson, 2003), где маркет-мейкер рискует суммой до $b \ln N$ против корректно информированной «толпы». - -Эти профили риска ограничивают предоставление ликвидности кругом искушённых участников со способностью к активному управлению, порождая тонкие рынки с высоким проскальзыванием, которые отпугивают игроков, — самоподдерживающийся цикл: - -$$\text{тонкие рынки} \rightarrow \text{высокое проскальзывание} \rightarrow \text{плохой UX} \rightarrow \text{мало игроков} \rightarrow \text{низкие комиссии} \rightarrow \text{нет стимула для LP} \rightarrow \text{тонкие рынки}$$ - -### 1.2 Исследовательские вопросы - -Эта работа ставит и стремится ответить на следующие вопросы через развёрнутую экспериментальную систему: - -**Q1.** *Можно ли архитектурно разделить функции ценообразования и расчёта рынка предсказаний так, чтобы основной капитал LP был структурно изолирован от исходов ставок?* - -**Q2.** *Устраняет ли тотализаторный механизм расчёта, где проигравшие исключительно финансируют выигравших, проблему неблагоприятного отбора, присущую рынкам предсказаний на основе AMM и LMSR?* - -**Q3.** *Способно ли автоматическое размещение ликвидности через пуловый механизм («ленивый» пул) снизить барьер входа для LP настолько, чтобы запустить рыночную глубину без активного выбора рынков отдельными LP?* - -**Q4.** *Является ли механизм разрешения споров под управлением DAO, со взвешенным по стейку публичным голосованием и оракулами с залогом, жизнеспособной альтернативой централизованному арбитражу или оптимистичным оракулам?* - -**Q5.** *Какая полезность токена управления возникает в протоколе рынка предсказаний, где позиции LP несут вес голоса, и создаёт ли это устойчивую петлю обратной связи спроса на токен?* - -### 1.3 Вклад - -Наш вклад состоит в следующем: - -1. **Формальное доказательство** того, что сочетание ценообразования AMM с тотализаторным расчётом даёт структурную гарантию основного капитала LP для обоих типов рынков — бинарного (CPMM) и многоисходного (LMSR). -2. **Механизм «ленивого» пула** — система автоматического размещения капитала с градуированным отзывом и защитой от издержек упущенных возможностей, обеспечивающая пассивное участие розничных LP, плюс опциональная, выключенная по умолчанию подсистема кредитного плеча, финансируемая из пула, чья экспозиция *ограничена* (худший случай недостачи ограничен залогом заёмщика) и изолирована от гарантии LP по Теореме 1. -3. **Двухрежимная система разрешения споров**, сочетающая оракулов с залогом с арбитражем комитета DAO, включая полный теоретико-игровой анализ совместимости стимулов оракула. -4. **Экспериментальный протокол**, реализованный как операции консенсусного уровня в продакшен-реестре, с явными гипотезами и измеримыми результатами. - ---- - -## 2. Связанные работы - -### 2.1 Правила рыночного скоринга - -Hanson (2003) ввёл логарифмическое правило рыночного скоринга (LMSR), в котором маркет-мейкер котирует цены через функцию softmax над параметрами количеств. LMSR гарантирует ограниченные потери маркет-мейкера в размере $b \ln N$ и обеспечивает сумму цен, равную единице. Однако эта граница одновременно является и максимальным риском LP: если «толпа» верно предсказывает исход, маркет-мейкер теряет всю субсидию. Это ограничило применение LMSR корпоративными средами (Microsoft, Inkling), где оператор поглощает потери. - -### 2.2 Маркет-мейкеры с постоянным произведением - -Формула постоянного произведения $x \cdot y = k$, популяризированная Uniswap (Adams, Zinsmeister & Robinson, 2020), обеспечивает непрерывное ценообразование для пар из двух активов. В применении к бинарным рынкам предсказаний резервы представляют противоположные исходы, и инвариант даёт естественное вероятностное ценообразование. Однако стандартные LP в AMM несут непостоянные потери — эмпирический анализ показывает, что более 50% LP в Uniswap v3 уступают стратегии «купи и держи» (Adams et al., 2021). - -### 2.3 Фреймворки условных токенов - -Polymarket использует фреймворк условных токенов Gnosis (CTF), где позиции — это токены ERC-1155 с операциями разделения/слияния для обеспечения согласованности цен ($\sum p_i = 1$). Это добавляет компонуемость, но вносит сложность и требует внешнего арбитражного механизма. Оптимистичный оракул UMA обеспечивает разрешение споров через игру «вызов–ответ» с экономическими залогами. - -### 2.4 Тотализаторные ставки - -Тотализаторная (parimutuel) модель, восходящая к тотализатору Пьера Олле (1865), объединяет все ставки и распределяет ставки проигравших пропорционально между выигравшими. Это гарантирует, что «дом» никогда не платит из собственного капитала. Однако традиционные тотализаторные системы лишены ценообразования в реальном времени — коэффициенты окончательны лишь при закрытии пула, — что делает их непригодными для непрерывного агрегирования информации. - -### 2.5 Наш подход - -Протокол Onix сочетает способность AMM/LMSR к ценообразованию в реальном времени со свойством нулевого риска LP, присущим тотализаторному расчёту. AMM или LMSR служит исключительно *механизмом ценообразования* — определяет подразумеваемые вероятности и назначает веса, — тогда как расчёт следует тотализаторной модели, где проигравшие финансируют выигравших, а основной капитал LP архитектурно отделён от потока выплат. - -Это позиционирует Onix иначе, чем два других гибридных направления в литературе. Во-первых, *чувствительные к ликвидности* маркет-мейкеры (Othman, Pennock, Reeves & Sandholm, 2013; Abernethy, Chen & Vaughan, 2011) сохраняют оператора в роли контрагента, но ограничивают или формируют его потери через геометрию функции издержек; оператор всё ещё может проиграть. Onix же полностью убирает роль контрагента у LP — кривая лишь *ценообразует*, она никогда не *платит*. Во-вторых, гибриды *CLOB+AMM* (например, книги заявок, подстрахованные AMM, как в ряде дизайнов DEX и в CLOB Polymarket с AMM-резервом) смешивают две площадки ценообразования, но наследуют инвентарный риск и неблагоприятный отбор со стороны маркет-мейкинга. Onix сохраняет единую непрерывную площадку ценообразования и полностью выносит несение риска из ценообразования в тотализаторный пул. Насколько нам известно, сочетание непрерывного ценообразователя AMM/LMSR с тотализаторным расчётом (проигравшие финансируют выигравших) для получения безусловной гарантии основного капитала LP ранее не формализовалось. - ---- - -## 3. Модель протокола - -### 3.1 Участники и роли - -Мы определяем следующее множество участников $\mathcal{P}$: - -| Роль | Символ | Функция | -|------|--------|----------| -| Создатель рынка | $c \in \mathcal{P}$ | Задаёт параметры рынка, предоставляет начальную ликвидность | -| Оракул | $o \in \mathcal{P}$ | Вносит страховой залог $I_o$, принимает рынки, разрешает исходы | -| Игрок | $b_i \in \mathcal{P}$ | Делает ставки на исходы, получает токены исхода | -| Поставщик ликвидности | $\ell_j \in \mathcal{P}$ | Вносит капитал в пулы рынков или в «ленивый» пул | -| Арбитр спора | $\mathcal{D}$ | Комитет (взвешенное по стейку голосование) или именованный аккаунт | - -### 3.2 Определение рынка - -Рынок $\mathcal{M}$ — это кортеж: - -$$\mathcal{M} = (o, \tau, \mathbf{O}, t_{\text{bet}}, t_{\text{res}}, \boldsymbol{\theta}, L)$$ - -где $o$ — назначенный оракул, $\tau \in \{0, 1\}$ — тип рынка (бинарный или многоисходный), $\mathbf{O} = \{O_1, \ldots, O_N\}$ — множество исходов ($N = 2$ для бинарного, $3 \leq N \leq 10$ для многоисходного), $t_{\text{bet}}$ и $t_{\text{res}}$ — времена истечения приёма ставок и разрешения, $\boldsymbol{\theta}$ — вектор параметров комиссий, $L$ — начальная ликвидность. - -### 3.3 Структура комиссий - -Все комиссии выражены в базисных пунктах (bp), где $10000 \text{ bp} = 100\%$. Вектор комиссий: - -$$\boldsymbol{\theta} = (\theta_{\text{oracle}}, \theta_{\text{creator}}, \theta_{\text{liq}})$$ - -Комиссии вычисляются исключительно при разрешении из совокупной ставки проигравшей стороны $S_{\text{lose}}$ и никогда не удерживаются в момент размещения ставки: - -$$f_{\text{oracle}} = \lfloor S_{\text{lose}} \cdot \theta_{\text{oracle}} / 10000 \rfloor$$ -$$f_{\text{creator}} = \lfloor S_{\text{lose}} \cdot \theta_{\text{creator}} / 10000 \rfloor$$ -$$f_{\text{liq}} = \lfloor S_{\text{lose}} \cdot \theta_{\text{liq}} / 10000 \rfloor$$ -$$W = S_{\text{lose}} - f_{\text{oracle}} - f_{\text{creator}} - f_{\text{liq}}$$ - -где $W$ — **пул выигрыша**, доступный для распределения выигравшим игрокам. - -### 3.4 Конечный автомат рынка - -Жизненный цикл $\mathcal{M}$ следует конечному автомату: - -$$q_0 \xrightarrow{\text{оракул принимает}} q_1 \xrightarrow{t \geq t_{\text{bet}}} q_2 \xrightarrow{\text{оракул разрешает}} q_3 \xrightarrow{\text{льготный период}} \text{выплачено}$$ - -с путём отклонения $q_0 \xrightarrow{\text{оракул отклоняет}} q_{-1}$ (удалён, ликвидность возвращена). - ---- - -## 4. Механизмы ценообразования - -### 4.1 Бинарные рынки: маркет-мейкер с постоянным произведением - -Для бинарных рынков ($N = 2$) протокол поддерживает два резерва $R_A$ и $R_B$, подчинённых инварианту: - -$$k = R_A \cdot R_B$$ - -**Инициализация.** При начальной ликвидности $L$: - -$$R_A = \lfloor L / 2 \rfloor, \quad R_B = L - R_A, \quad k = R_A \cdot R_B$$ - -**Размещение ставки.** Когда игрок $b_i$ ставит сумму $a$ на исход $A$: - -$$R'_A = R_A + a$$ -$$R'_B = \lfloor k / R'_A \rfloor$$ -$$w_i = R_B - R'_B \quad \text{(полученные токены)}$$ - -Здесь $w_i$ — *вес* игрока. Это **вычисляемая** величина — количество токенов исхода, созданных кривой, — и она в общем случае **не** равна внесённой сумме $a$: в зависимости от текущих резервов $w_i$ может быть больше или меньше $a$. Вес используется исключительно для распределения пула выигрыша при расчёте (§5.1); он никогда не является требованием, выраженным в валюте. - -**Подразумеваемая вероятность.** Подразумеваемая рынком вероятность каждого исхода: - -$$P(A) = \frac{R_A}{R_A + R_B}, \quad P(B) = \frac{R_B}{R_A + R_B}$$ - -Заметим, что $P(A) + P(B) = 1$ по построению, что устраняет необходимость во внешнем арбитражном механизме для обеспечения согласованности цен. - -### 4.2 Многоисходные рынки: LMSR - -Для рынков с $N > 2$ исходами протокол использует логарифмическое правило рыночного скоринга с ценообразованием softmax. - -**Функция издержек:** - -$$C(\mathbf{q}) = b \cdot \ln\left(\sum_{j=1}^{N} \exp(q_j / b)\right)$$ - -где $\mathbf{q} = (q_1, \ldots, q_N)$ — параметры количеств, $b$ — параметр ликвидности. - -**Функция цены (softmax):** - -$$p_i(\mathbf{q}) = \frac{\exp(q_i / b)}{\sum_{j=1}^{N} \exp(q_j / b)}$$ - -По определению softmax, $\sum_{i=1}^{N} p_i = 1$ тождественно. - -**Издержки сделки.** Издержки покупки $\Delta$ токенов на исход $i$: - -$$\text{cost}(\Delta, i) = C(\mathbf{q} + \Delta \cdot \mathbf{e}_i) - C(\mathbf{q})$$ - -**Параметр ликвидности.** Параметр $b$ финансируется субсидией LP $S$: - -$$b = \frac{S}{\ln N}$$ - -**Численная устойчивость.** Вычисление log-sum-exp использует стандартное тождество: - -$$\ln\left(\sum_j \exp(x_j)\right) = \max(\mathbf{x}) + \ln\left(\sum_j \exp(x_j - \max(\mathbf{x}))\right)$$ - ---- - -## 5. Тотализаторный расчёт и гарантия LP - -### 5.1 Единая модель расчёта - -Оба типа рынков используют идентичный механизм расчёта. При разрешении оракул объявляет выигравший исход $O^* \in \mathbf{O}$. Пусть $\mathcal{W}$ — множество ставок на $O^*$, а $\mathcal{L}$ — все остальные ставки. - -$$S_{\text{lose}} = \sum_{b_i \in \mathcal{L}} a_i$$ - -где $a_i$ — сумма ставки. Пул выигрыша $W$ вычисляется как в §3.3. - -Каждый выигравший игрок $b_i \in \mathcal{W}$ получает: - -$$\pi_i = a_i + \frac{w_i}{\sum_{b_j \in \mathcal{W}} w_j} \cdot W - \tau_i$$ - -где $w_i$ — вес игрока (токены из CPMM или LMSR), а $\tau_i$ — временной штраф на прибыль (§5.3). - -### 5.2 Теорема: гарантия основного капитала LP - -**Теорема 1.** *При тотализаторном расчёте суммарная распределённая сумма равна суммарной полученной сумме, причём основной капитал LP $L$ возвращается безусловно. Основной капитал LP никогда не подвергается риску из-за исходов ставок.* - -*Доказательство.* Рассмотрим полный денежный поток через рынок: - -$$\text{Money}_{\text{IN}} = L + \sum_{b_i \in \mathcal{W}} a_i + \sum_{b_i \in \mathcal{L}} a_i = L + \sum_{\text{all bets}} a_i$$ - -где первая сумма $\sum_{b_i \in \mathcal{W}} a_i$ — совокупный основной капитал, поставленный *выигравшими* (возвращается им полностью при расчёте), а $S_{\text{lose}} = \sum_{b_i \in \mathcal{L}} a_i$ — совокупная ставка, утраченная *проигравшими* (единственный источник пула выигрыша и всех комиссий). При разрешении исходящие потоки таковы: - -$$\text{Money}_{\text{OUT}} = L + \sum_{b_i \in \mathcal{W}} a_i + W + f_{\text{oracle}} + f_{\text{creator}} + f_{\text{liq}}$$ - -Подставляя $W = S_{\text{lose}} - f_{\text{oracle}} - f_{\text{creator}} - f_{\text{liq}}$: - -$$\text{Money}_{\text{OUT}} = L + \sum_{b_i \in \mathcal{W}} a_i + S_{\text{lose}}$$ -$$= L + \sum_{b_i \in \mathcal{W}} a_i + \sum_{b_i \in \mathcal{L}} a_i = L + \sum_{\text{all bets}} a_i = \text{Money}_{\text{IN}} \qquad \blacksquare$$ - -**Следствие 1.** *Гарантия основного капитала LP выполняется независимо от механизма назначения весов, числа игроков, распределения ставок по исходам или параметров комиссий. Это свойство архитектуры расчёта, а не кривой ценообразования.* - -**Следствие 2.** *Максимальная суммарная выплата выигравшим равна $S_{\text{lose}}$ (весь пул проигравших), независимо от весов, назначенных AMM или LMSR. Следовательно, выплаты выигравшим никогда не могут черпаться из капитала LP.* - -**Замечание (целочисленная арифметика и платёжеспособность при округлении).** Теорема 1 сформулирована над рациональными числами, но протокол оперирует целыми числами (милли-VIZ), и каждое деление применяет оператор пола $\lfloor \cdot \rfloor$. Покажем, что гарантия переживает дискретизацию. Каждая комиссия и каждая доля выигравшего округляются *вниз*: - -$$f_\bullet = \lfloor S_{\text{lose}} \cdot \theta_\bullet / 10000 \rfloor, \qquad \hat{\pi}_i^{\text{profit}} = \left\lfloor \frac{w_i}{\sum_{j} w_j} \cdot W \right\rfloor$$ - -Поскольку $\lfloor x \rfloor \leq x$, фактическое распределение выигравшим удовлетворяет $\sum_{i \in \mathcal{W}} \hat{\pi}_i^{\text{profit}} \leq W$, и аналогично $\sum_\bullet f_\bullet \leq S_{\text{lose}} \cdot (\theta_{\text{oracle}}+\theta_{\text{creator}}+\theta_{\text{liq}})/10000 \leq S_{\text{lose}}$. Поэтому фактический отток подчиняется - -$$\text{Money}_{\text{OUT}}^{\text{realized}} = L + \sum_{i \in \mathcal{W}} a_i + \sum_{i \in \mathcal{W}} \hat{\pi}_i^{\text{profit}} + \sum_\bullet f_\bullet \;\leq\; L + \sum_{\text{all bets}} a_i = \text{Money}_{\text{IN}}$$ - -так что система никогда не становится неплатёжеспособной. Неотрицательный остаток - -$$\epsilon = S_{\text{lose}} - \sum_{i \in \mathcal{W}} \hat{\pi}_i^{\text{profit}} - \sum_\bullet f_\bullet \;\geq\; 0$$ - -ограничен $\epsilon < |\mathcal{W}| + 3$ милли-VIZ (одна субъединица на каждую округлённую величину) и сметается в фонд DAO как «пыль». Таким образом, округление может лишь *недо*-распределять, но никогда не пере-распределять: основной капитал LP $L$ сохраняется точно, а неравенство $\text{Money}_{\text{OUT}} \leq \text{Money}_{\text{IN}}$ выполняется на гранулярности реестра. $\blacksquare$ - -### 5.3 Временной штраф за поздние ставки - -Чтобы смягчить информационную асимметрию ставок вблизи истечения (когда неопределённость исхода снижена), протокол вводит настраиваемый временной штраф, применяемый *только к прибыли*, никогда — к основному капиталу. - -Пусть $\Delta t = t_{\text{bet}} - t_{\text{now}}$ — время до истечения, а $\omega$ — окно штрафа. Если $\Delta t < \omega$: - -$$r = 1 - \frac{\Delta t}{\omega}$$ - -$$\tau_{\text{rate}} = \begin{cases} r & \text{линейный} \\ r^2 & \text{квадратичный (по умолчанию)} \end{cases}$$ - -$$\tau_i = \lfloor \pi_{\text{profit},i} \cdot \tau_{\text{rate}} \cdot \tau_{\max} / 10^6 \rfloor$$ - -где $\pi_{\text{profit},i}$ — доля прибыли игрока (без основного капитала), а $\tau_{\max}$ — максимальный штраф в микроединицах. - -**Инвариант:** $\pi_i \geq a_i$ для всех выигравших игроков, поскольку штраф применяется исключительно к компоненте прибыли. - -### 5.4 Взвешенное по времени распределение комиссий LP - -Доли комиссий LP распределяются пропорционально произведению суммы вклада на остаточное время до истечения: - -$$\omega_j = \text{amount}_j \cdot \max(1, t_{\text{bet}} - t_{\text{deposit},j})$$ - -$$f_{\text{share},j} = \left\lfloor \frac{f_{\text{pool}} \cdot \omega_j}{\sum_m \omega_m} \right\rfloor$$ - -Это создаёт сильный стимул к раннему предоставлению ликвидности. На рынке длительностью $T$ LP, вносящий депозит в момент $t = 0$, зарабатывает в $T$ раз больше на единицу капитала, чем вносящий в $t = T - 1$. - ---- - -## 6. «Ленивый» пул ликвидности - -### 6.1 Мотивация - -Индивидуальное предоставление ликвидности требует активной оценки и выбора рынков. Для розничных участников это создаёт непрактичный барьер знаний и усилий. «Ленивый» пул (так названный потому, что не требует от вкладчиков активного выбора рынков) решает это, принимая депозиты и автоматически размещая капитал в активируемые рынки. - -### 6.2 Механика пула - -«Ленивый» пул $\mathcal{L}_P$ — это синглтон-контракт, поддерживающий: - -- $B_{\text{free}}$: нераспределённый баланс -- $B_{\text{alloc}}$: капитал, размещённый в активных рынках -- $B_{\text{earned}}$: реализованная прибыль -- $S_{\text{total}}$: общее число выпущенных долей -- $\rho$: накопитель совокупного вознаграждения на долю - -**Депозит.** Когда пользователь $u$ вносит сумму $a$: - -$$\text{shares}_u = \begin{cases} a & \text{if } S_{\text{total}} = 0 \\ a \cdot S_{\text{total}} / B_{\text{free}} & \text{otherwise} \end{cases}$$ - -Депозит блокируется на заданный управлением период $t_{\text{lock}}$. - -**Авто-размещение.** При активации рынка ($q_0 \rightarrow q_1$): - -$$a_{\text{alloc}} = B_{\text{free}} \cdot \alpha / 100$$ - -при условиях $a_{\text{alloc}} \geq a_{\min}$ и $B_{\text{alloc}} + a_{\text{alloc}} \leq B_{\text{total}} \cdot \alpha_{\max} / 100$, где $\alpha$ — процент размещения на рынок, а $\alpha_{\max}$ — потолок суммарного размещения. - -Формула размещения включает поправки на качество оракула: - -$$a_{\text{alloc}} = B_{\text{free}} \cdot \frac{\alpha}{100} \cdot (1 - \beta)^{n_o} \cdot (1 - \gamma)^{f_o}$$ - -где $n_o$ — число активных рынков оракула, $f_o$ — число активных штрафных меток оракула, а $\beta, \gamma$ — заданные управлением коэффициенты затухания. - -### 6.3 Распределение вознаграждений («ленивый» учёт) - -Вознаграждения распределяются с помощью единственного глобального накопителя $\rho$ (вознаграждение на долю) по паттерну MasterChef (SushiSwap, 2020; Leshner & Hayes, 2019): - -Когда рынок разрешается и LP-позиция пула приносит прибыль $\pi_{\text{pool}}$: - -$$\rho \leftarrow \rho + \frac{\pi_{\text{pool}} \cdot \text{PRECISION}}{S_{\text{total}}}$$ - -Накопленное вознаграждение любого пользователя $u$ вычислимо за $O(1)$: - -$$\text{reward}_u = \text{pending}_u + \frac{\text{shares}_u \cdot (\rho - \rho_{\text{snapshot},u})}{\text{PRECISION}}$$ - -Записи пользователя обновляются только при его действии (депозит или вывод), что даёт амортизированную стоимость $O(1)$ независимо от числа участников. - -### 6.4 Защита от издержек упущенных возможностей - -Механизм авто-размещения создаёт вектор атаки: злонамеренный оракул может создавать долгие рынки с нулевым объёмом, чтобы заблокировать капитал пула. Этому противодействуют три градуированных защитных механизма: - -**Градуированный отзыв.** Длительность рынка делится на $n_{\text{steps}}$ интервалов. На каждой контрольной точке $k$, если накопленный объём $V_k$ удовлетворяет: - -$$V_k < a_{\text{alloc}} \cdot \theta_{\text{recall}} / 100$$ - -то доля $\delta_{\text{recall}}$ текущего размещения отзывается в пул. После $n$ подряд идущих низкообъёмных контрольных точек удержанное размещение равно: - -$$a_{\text{retained}} = a_{\text{alloc}} \cdot (1 - \delta_{\text{recall}})^n$$ - -Для параметров по умолчанию ($\delta_{\text{recall}} = 10\%$, $n_{\text{steps}} = 10$) полностью простаивающий рынок удерживает $(0{.}9)^{10} \approx 35\%$ исходного размещения. - -**Штраф за активные рынки.** Каждый активный рынок оракула $o$ мультипликативно снижает новые размещения на множитель $(1 - \beta)$. При $\beta = 5\%$ и 10 активных рынках новые размещения составляют $(0{.}95)^{10} \approx 60\%$ базовой ставки. - -**Штрафные метки.** Плохие исходы (no-contest, пропуск дедлайнов, проигранные споры, разрешения с нулевым объёмом) порождают штрафные метки на оракуле, дополнительно снижающие размещения на множитель $(1 - \gamma)^{f_o}$. Метки автоматически истекают после заданного управлением окна чистой работы. - -### 6.5 Экстренный вывод - -Пользователи могут выйти из пула до истечения блокировки, при штрафе *только на заблокированную прибыль*: - -$$\text{penalty} = \max(0, V_{\text{total}} - P_{\text{deposited}}) \cdot \frac{S_{\text{locked}}}{S_{\text{total}}} \cdot \theta_{\text{emergency}} / 100$$ - -где $V_{\text{total}}$ — общая стоимость пользователя (доли + накопленные вознаграждения), $P_{\text{deposited}}$ — совокупно внесённый основной капитал, а $S_{\text{locked}} / S_{\text{total}}$ — доля ещё заблокированных долей. Штраф перераспределяется оставшимся участникам пула через $\rho$. - -### 6.6 Опциональное кредитное плечо, финансируемое из пула - -«Ленивый» пул играет вторую, опциональную роль из того же $B_{\text{free}}$: он финансирует подсистему кредитного плеча. Эта подсистема **опциональна, выключена по умолчанию и управляется медианным kill-switch**. Мы рассматриваем её отдельно от остальной статьи по важной причине. **Это единственное место, где капитал пула принимает на себя кредитный риск, поэтому оно *не* покрывается безусловной гарантией Теоремы 1.** Теорема 1 касается пула в роли *поставщика рыночной ликвидности*, где основной капитал структурно изолирован от исходов ставок. Кредитное плечо же ставит пул в роль *кредитора*, а кредитование несёт риск, который мы ограничиваем, а не устраняем. - -**Открытие позиции.** Игрок вносит залог $m$. Пул ссужает маржу $\lambda m$ при коэффициенте плеча $\lambda$ из $B_{\text{free}}$, при потолках на размер позиции, долю фонда пула и минимальную ликвидность рынка. Ни один токен не создаётся: суммарная ставка $(1+\lambda)m$ входит в рынок точно так же, как обычная ставка. Вес кривой позиции, её залог и ссуженная сумма записываются в выделенный объект позиции. Обязательство пула по позиции: - -$$\Omega = \lambda m \,(1 + r),$$ - -где $r$ — накопленный процент. Ссуда плюс процент — это то, что пул стремится вернуть. - -**Ликвидация против записанного снимка резервов.** Трудный случай на бинарных рынках с плечом — *скачковый риск* (jump risk): разрывное движение цены может оставить наивную ликвидацию «продать по текущей цене» неспособной покрыть ссуду. Onix не ликвидирует по текущей цене. Он закрывает позицию через обратный CPMM против **состояния резервов, записанного для этой позиции**, так что собственное рыночное воздействие позиции разматывается первым, а не оплачивается дважды. Возврат обеспечивают два пути: - -1. **Каскад встречной ставки** (`pm_place_bet`, причина ликвидации 0) — входящая встречная ставка запускает принудительное закрытие позиции с плечом против её записанного снимка. -2. **Принудительное закрытие при расчёте** (`pm_leverage_resolve`) — при разрешении позиция закрывается, и ссуда погашается из её веса прежде, чем заёмщику начислится прибыль. - -На этих двух путях проектная цель — полный возврат, $\text{recovered} = \min(V_{\text{close}}, \Omega) \ge \lambda m$, с возвратом фактической ссуды и процента в пул. Мы формулируем это как **проектное свойство, а не теорему**: математика расчёта по плечу реализована и проверяется в `consensus_sim`, но замкнутое доказательство возврата при всех траекториях резервов оставлено на будущую работу (§12.2). Остаточная экспозиция сосредоточена на одном пути: - -- **Добровольная отмена той же стороны** (`pm_cancel_bet`, причина 1) — если заёмщик закрывается на той же стороне при неблагоприятном движении, пул может реализовать недостачу. Эта недостача **ограничена залогом заёмщика $m$**, который удерживает протокол; она не может превысить $m$. - -**Kill-switch отвязан от защиты.** Kill-switch отключает только *новые* открытия. Пути ликвидации намеренно **не** ограничены им, поэтому отключение плеча никогда не лишает защиты уже открытые позиции. - -**Изоляция риска и вознаграждение.** Кредитование с плечом ограничено управляемой долей $B_{\text{free}}$ (`leverage_fund_percent`), так что экспозиция пула в худшем случае ограничена на уровне подсистемы и не может достичь основного капитала рыночного LP, защищённого Теоремой 1. Заработанный процент начисляется в пул через тот же накопитель $\rho$ (§6.3). Поэтому вкладчики пула зарабатывают из двух источников — доли комиссий из пула проигравших (без риска, Теорема 1) и процент по плечу (ограниченный кредитный риск, опционально), — и оба учитываются одинаково, но управляются независимо. Честное резюме: *рыночный* основной капитал LP пула никогда не под риском; его *кредитование с плечом* несёт ограниченный, опциональный, выключенный по умолчанию кредитный риск (§12.2). - -### 6.7 Дизайн-решение: только реальная глубина (без виртуальной/фантомной ликвидности) - -Естественное предложение — засеять кривую ценообразования рынка *виртуальной* (фантомной) ликвидностью: оффсетом резерва $\phi$, который уплощает влияние на цену, но не обеспечен реальным капиталом и удаляется при расчёте, опционально настраиваемым управлением. В закрытом цикле «ставка → отмена → резолюция» это сохраняет стоимость (это техника виртуального AMM) и соблазнительно как «стабилизатор» старта для новых тонких рынков. Onix **сознательно этого не внедряет.** Ту же пользу на старте уже даёт авто-аллокация «ленивого» пула (§6.2) — но *реальным* капиталом, который вдобавок зарабатывает комиссии, имеет подотчётного владельца и следует за спросом по каждому рынку. Мы отвергаем фантомную глубину, потому что при неосторожном применении она вредит ровно тому, что протокол призван защищать, — структуре рынка и доверию: - -1. **Подделываемая глубина подрывает доверие.** Глубина значима как сигнал лишь потому, что это дорогой реальный капитал под риском. Бесплатная виртуальная глубина превращает заявление «рынок глубокий и ликвидный» в подделываемое — тонкий или манипулируемый рынок можно нарядить под глубокий. Реальный капитал делает это заявление неподделываемым. -2. **Она тихо искажает агрегирование информации.** Виртуальная глубина уплощает кривую весов, ослабляя награду за раннюю верную информацию и делая отображаемую цену невосприимчивой к новостям («стабильна, потому что её нельзя сдвинуть» = устаревший прогноз). Правильная величина зависит от рынка и объёма; единственная константа управления не может за ней следовать и, заданная слишком высоко, разрушает то самое свойство открытия цены, ради которого существует рынок предсказаний. -3. **Она платёжеспособна, только пока её не выкупают и не используют как залог.** Как только глубина обеспечивает реальный отток — отмены, ранние выводы, ссуды плеча, общие/межрыночные пулы, — виртуальную часть приходится исключать везде, иначе она утекает реальными деньгами (например, ссуда плеча, рассчитанная или возвращаемая против фейковой глубины, превращается в реальный bad debt вкладчикам пула). Каждая ветка «считать против ликвидности / платить LP» становится граблями, требующими «…но не фантомную часть». Реальный капитал устраняет весь этот класс ошибок по построению. -4. **У неё нет владельца, дохода и подотчётности.** Виртуальная глубина не несёт риска и не зарабатывает комиссию никому реальному; это услуга, за которую никто не платит и за которую никто не отвечает. На рынках, которых она касается, она удаляет розничный продукт безрисковой доходности — основную гипотезу протокола (Q3, H1). - -«Ленивый» пул — та же идея, сделанная на реальных числах: авто-аллокация сглаживает запуск новых рынков, но капитал погашаем, безопасен для плеча, зарабатывает комиссии, принадлежит вкладчикам и самокорректируется по каждому рынку через градуированный отзыв (§6.4). Поэтому Onix держит **только реальные числа** — каждая единица глубины есть реальный капитал, который можно вывести, который зарабатывает и который подотчётен. Это осознанный компромисс: мы отказываемся от дешёвого виртуального стабилизатора ради целостности ценового сигнала и платёжеспособности каждого реального денежного пути. - ---- - -## 7. Разрешение споров под управлением DAO - -### 7.1 Модель оракула с залогом - -Каждый оракул $o$ обязан поддерживать страховой залог $I_o \geq I_{\min}$. Залог создаёт подотчётность: у оракулов, которые неверно разрешают рынки, пропускают дедлайны или проигрывают споры, страховка списывается. Доход оракула складывается из: - -1. Фиксированной комиссии за рынок $f_{\text{fixed}}$ (компенсирующей внесение страховки) -2. Процентной комиссии $f_{\text{oracle}}$ из пула проигравших при разрешении - -Эти две комиссии входят в систему в разных точках, и их нельзя смешивать. **Процентная комиссия** $f_{\text{oracle}}$ удерживается из $S_{\text{lose}}$ при разрешении и фигурирует в денежном потоке Теоремы 1 (§5.2). **Фиксированная комиссия** $f_{\text{fixed}}$ — это прямой перевод создатель→оракул, выполняемый при *принятии* рынка, до каких-либо ставок; поэтому она *не* является частью денежного потока ставок/LP — она не добавляется к начальной ликвидности $L$ и не черпается из $S_{\text{lose}}$, а значит не появляется в Теореме 1. (Она опущена в векторе комиссий $\boldsymbol{\theta}$ из §3.3 по той же причине: $\boldsymbol{\theta}$ собирает только процентные комиссии на момент разрешения, финансируемые проигравшими.) Для рынков с самооракулом перевод не происходит и $f_{\text{fixed}} = 0$. - -Процесс принятия оракулом реализует механизм «оферта–котировка»: создатель публикует потолки комиссий, а оракул фиксирует свои фактические условия (ограниченные как потолком создателя, так и потолком управления) при принятии. - -### 7.2 Двухрежимная система споров - -Любой игрок может оспорить разрешение в течение льготного периода $\Delta t_{\text{grace}}$, внеся в эскроу комиссию спора $d$. Во время споров все выплаты заморожены. Протокол поддерживает два режима спора на рынок: - -**Режим комитета ($\delta_{\text{mode}} = 0$).** Весь электорат держателей токена разрешает спор взвешенным по стейку публичным голосованием. Вес каждого голосующего $v$: - -$$w_v = s_v + \text{shares}_{v,\text{pool}} \cdot \text{NAV}_{\text{pool}} / S_{\text{total}}$$ - -где $s_v$ — вестинг-доли голосующего, а второе слагаемое переводит стейк в «ленивом» пуле в эквивалентный вес управления. Голоса публичны и пересматриваемы до закрытия периода голосования — это намеренное проектное решение: споры суть *прозрачные публичные слушания*, а не тайные бюллетени. Новые данные могут изменить голоса. - -Эта прозрачность несёт известную цену. Поскольку текущий подсчёт виден, голосующий может отложить голос на конец окна и обусловить свой бюллетень раскрытыми позициями других (преимущество последнего хода). Протокол принимает этот компромисс намеренно: для DAO аудируемость и легитимность открытого слушания ценнее тайны бюллетеня, а переворот исхода всё равно требует сдвинуть взвешенное по стейку большинство. Манипуляция сдерживается не тайной, а (i) взвешенным по стейку порогом одобрения $\theta_{\text{approve}}$, (ii) многодневным окном голосования, размывающим любое единичное преимущество тайминга, и (iii) согласованием вознаграждения арбитра, анализируемым в §7.4. Бюллетень commit-reveal был явно рассмотрен и **отвергнут** по этой причине — сокрытие голосов разрушило бы свойство публичного слушания, дающее вердикту легитимность. Мы возвращаемся к этому как к честному ограничению в §12.2. - -Спор удовлетворяется, если процент одобрения превышает заданный управлением порог $\theta_{\text{approve}}$: - -$$\frac{\sum_{v: \text{vote}_v = \text{approve}} w_v}{\sum_{v} w_v} \geq \frac{\theta_{\text{approve}}}{10000}$$ - -**Режим аккаунта ($\delta_{\text{mode}} = 1$).** Именованный арбитр спора (рекомендуется: мультиподпись) выносит обязывающий вердикт. - -### 7.3 Исходы спора и совместимость стимулов - -**Оракул неправ (спор удовлетворён):** - -$$\text{reward}_{\text{pool}} = \min(d \cdot \mu, I_o)$$ - -где $\mu$ — множитель вознаграждения. Пул вознаграждения делится так: - -- Заявитель получает $d$ (возврат комиссии) + $\text{reward}_{\text{pool}} / \mu$ -- Арбитр/голосующие получают $\text{reward}_{\text{pool}} - \text{reward}_{\text{pool}} / \mu$ -- Остаток страховки: дополнительный штраф $\rightarrow$ фонд DAO - -**Оракул прав (спор отклонён):** - -$$d \rightarrow \text{50\% арбитру, 50\% оракулу}$$ - -**Оракул не отвечает (авто-закрытие на 14-й день):** - -- Заявитель: комиссия возвращена -- Оракул: $d$ списывается из страховки -- Все ставки и позиции LP: полный возврат -- Списанная сумма распределяется пропорционально всем участникам - -### 7.4 Теоретико-игровой анализ - -**Совместимость стимулов оракула.** Ожидаемый выигрыш оракула от честного разрешения против манипуляции: - -$$\mathbb{E}[\pi_{\text{honest}}] = f_{\text{fixed}} + f_{\text{oracle}} \quad \text{(за рынок)}$$ - -$$\mathbb{E}[\pi_{\text{dishonest}}] = p_{\text{detect}} \cdot (-I_o \cdot \theta_{\text{penalty}} - B_{\text{ban}}) + (1 - p_{\text{detect}}) \cdot g_{\text{manipulation}}$$ - -где $p_{\text{detect}}$ — вероятность спора, $\theta_{\text{penalty}}$ — доля списания страховки, $B_{\text{ban}}$ — приведённая стоимость бана (упущенный будущий доход), а $g_{\text{manipulation}}$ — разовый выигрыш от манипуляции. - -Честное поведение является равновесием Нэша, когда: - -$$f_{\text{fixed}} + f_{\text{oracle}} > (1 - p_{\text{detect}}) \cdot g_{\text{manipulation}} - p_{\text{detect}} \cdot (I_o \cdot \theta_{\text{penalty}} + B_{\text{ban}})$$ - -Протокол обеспечивает это, требуя $I_o \gg g_{\text{manipulation}}$ и поддерживая высокий $p_{\text{detect}}$ через публичную видимость споров и стимулы участия комитета. - -**Стимул заявителя.** Рациональный игрок подаёт спор, когда ожидаемое вознаграждение превышает комиссию спора: - -$$\mathbb{E}[\text{reward}_{\text{dispute}}] = p_{\text{upheld}} \cdot (d + d \cdot (\mu - 1) / \mu) > d$$ - -Это упрощается до $p_{\text{upheld}} > 1/\mu$, задавая естественный порог подачи спора. - -**Стимул голосования комитета.** В режиме комитета голосующие участвуют, потому что их стейк в «ленивом» пуле (и, следовательно, вес управления) напрямую зарабатывает долю пула вознаграждения арбитра. Ожидаемое вознаграждение голосующего $v$: - -$$\mathbb{E}[\pi_v] = \frac{w_v}{\sum w_j} \cdot \text{voter\_reward\_pool} \cdot p_{\text{upheld}}$$ - -### 7.5 Объявление no-contest - -Оракул, неспособный верифицировать исход, может объявить no-contest по сниженной стоимости ($50\%$ штрафа спора из страховки). Это создаёт трёхуровневый градиент стимулов: - -| Действие | Стоимость для оракула | Риск бана | -|--------|------------|----------| -| Добровольный no-contest | $0{.}5 \cdot d$ из страховки | Нет | -| Проигрыш спора | $I_o \cdot \theta_{\text{penalty}} + \text{доп.}$ | Да | -| Пропуск дедлайна | $I_o \cdot \theta_{\text{miss}}$ | Нет | - -Само объявление no-contest можно оспорить, причём арбитр выбирает из трёх исходов (победил A, победил B или подтвердить no-contest), что предотвращает злоупотребление. - ---- - -## 8. Токен VIZ в эксперименте рынка предсказаний - -### 8.1 Полезность токена - -Токен VIZ выполняет четыре различные функции в экосистеме протокола Onix: - -1. **Средство обмена.** Все ставки, депозиты LP, страховка оракулов и комиссии споров номинированы в VIZ. Протокол никогда не эмитирует токены — он строго нулевой по сумме на уровне консенсуса. - -2. **Вес управления.** VIZ, застейканный как вестинг-доли (или внесённый в «ленивый» пул), даёт право голоса в спорах комитета DAO и в управлении параметрами цепи. Это создаёт прямую полезность владения токеном помимо спекуляции. - -3. **Залог оракула.** Страховые залоги оракулов номинированы в VIZ. Залог должен превышать потенциальную прибыль оракула от манипуляции, создавая спрос на накопление токена операторами оракулов. - -4. **Эскроу спора.** Комиссии споров блокируются в эскроу в VIZ, создавая издержку для несерьёзных споров и механизм вознаграждения для обоснованных вызовов. - -### 8.2 Петля обратной связи спроса на токен - -Протокол создаёт структурный цикл спроса: - -$$\text{депозиты LP} \xrightarrow{\text{блокировка}} \text{снижение оборотного предложения}$$ -$$\text{страховка оракулов} \xrightarrow{\text{блокировка}} \text{снижение оборотного предложения}$$ -$$\text{активные ставки} \xrightarrow{\text{блокировка}} \text{снижение оборотного предложения}$$ -$$\text{вес управления} \xrightarrow{\text{полезность}} \text{спрос на стейкинг}$$ - -Суммарное заблокированное предложение в любой момент: - -$$S_{\text{locked}} = S_{\text{pool}} + \sum_o I_o + \sum_{\text{active bets}} a_i + \sum_{\text{disputes}} d_k$$ - -где $S_{\text{pool}}$ — суммарный заблокированный для вывода баланс «ленивого» пула (как свободная, так и размещённая части заблокированы на период блокировки депозита), $I_o$ — страховка оракулов, $a_i$ — ставки активных позиций, а $d_k$ — комиссии споров в эскроу. Это заблокированное предложение снижает доступное оборотное предложение, потенциально создавая повышательное давление на цену по мере роста использования протокола — та же ставка, что делает любой нативный токен протокола, здесь явно привязанная к измеримой полезности. - -### 8.3 Экспериментальная ценность токена - -Токен VIZ в этом эксперименте служит **измерительным инструментом**: - -- **Цена как сигнал.** Движения цены токена в ответ на события протокола (запуски рынков, разрешения с высоким объёмом, исходы споров) дают непрерывную рыночную оценку воспринимаемой ценности протокола. -- **Уровень участия в управлении.** Доля держателей токена, участвующих в голосованиях по спорам, измеряет жизнеспособность арбитража на основе DAO. -- **Скорость накопления «ленивого» пула.** Темп депозитов в пул измеряет розничный спрос на безрисковую доходность LP, напрямую проверяя гипотезу Q3. - ---- - -## 9. Полный цикл рынка: подробный пример - -Проследим полный жизненный цикл бинарного рынка, чтобы проиллюстрировать работу протокола. - -### 9.1 Постановка - -- **Рынок:** «Произойдёт ли событие X к дате Y?» (Бинарный: Да/Нет) -- **Оракул:** $o$ со страховкой $I_o = 5000$ VIZ -- **Начальная ликвидность:** $L = 200$ VIZ от создателя -- **Параметры комиссий:** $\theta_{\text{oracle}} = 50$ bp, $\theta_{\text{creator}} = 50$ bp, $\theta_{\text{liq}} = 100$ bp -- **«Ленивый» пул:** $B_{\text{free}} = 10{,}000$ VIZ, $\alpha = 2\%$, размещает $a_{\text{alloc}} = 200$ VIZ - -> **Единицы.** Суммы показаны в VIZ для читаемости, но протокол хранит и вычисляет их как **целые милли-VIZ (mVIZ)**, где $1\text{ VIZ} = 1000\text{ mVIZ}$, и каждое деление округляется вниз (ср. замечание из §5.2). Строки комиссий ниже показаны в mVIZ, чтобы $\lfloor\cdot\rfloor$ был точен; эквивалент в VIZ приведён рядом. Это устраняет кажущееся несоответствие округления, возникающее при округлении над целыми VIZ (например, $\lfloor 80\times 50/10000\rfloor$ равно $0$ в VIZ, но $400$ mVIZ $=0{.}4$ VIZ в реальных единицах). - -### 9.2 Инициализация - -$$R_A = 100, \quad R_B = 100, \quad k = 10{,}000$$ - -«Ленивый» пул авто-размещает 200 VIZ как дополнительный LP: - -$$R_A = 200, \quad R_B = 200, \quad k = 40{,}000$$ - -### 9.3 Фаза ставок - -- **Алиса** ставит $a_1 = 50$ VIZ на «Да» (сторона A): - $$R'_A = 250, \quad R'_B = \lfloor 40000/250 \rfloor = 160, \quad w_1 = 200 - 160 = 40$$ - -- **Боб** ставит $a_2 = 80$ VIZ на «Нет» (сторона B): - $$R'_B = 240, \quad R'_A = \lfloor 40000/240 \rfloor = 166, \quad w_2 = 250 - 166 = 84$$ - -Подразумеваемая вероятность: $P(\text{Да}) = 166 / 406 \approx 41\%$, $P(\text{Нет}) = 240 / 406 \approx 59\%$. - -### 9.4 Разрешение - -Оракул объявляет победу **«Да»**. Алиса — единственный победитель; Боб теряет 80 VIZ. - -$$S_{\text{lose}} = 80{,}000 \text{ mVIZ} \;(80 \text{ VIZ})$$ -$$f_{\text{oracle}} = \lfloor 80{,}000 \times 50 / 10000 \rfloor = 400 \text{ mVIZ} \;(0{.}4 \text{ VIZ})$$ -$$f_{\text{creator}} = \lfloor 80{,}000 \times 50 / 10000 \rfloor = 400 \text{ mVIZ} \;(0{.}4 \text{ VIZ})$$ -$$f_{\text{liq}} = \lfloor 80{,}000 \times 100 / 10000 \rfloor = 800 \text{ mVIZ} \;(0{.}8 \text{ VIZ})$$ -$$W = 80{,}000 - 400 - 400 - 800 = 78{,}400 \text{ mVIZ} \;(78{.}4 \text{ VIZ})$$ - -**Выплата Алисе** (единственный победитель, $w_1 / w_1 = 1$): - -$$\pi_1 = 50{,}000 + \lfloor 78{,}400 \times 40/40 \rfloor - \tau_1 = 128{,}400 \text{ mVIZ} - \tau_1 \;(128{.}4 \text{ VIZ} - \tau_1)$$ - -**Возврат LP:** - -- LP создателя: $200 \text{ VIZ основного капитала} + \text{взвешенная по времени доля пула комиссий } 0{.}8 \text{ VIZ}$ -- LP «ленивого» пула: $200 \text{ VIZ основного капитала} + \text{взвешенная по времени доля пула комиссий } 0{.}8 \text{ VIZ}$ - -**Проверка** (Теорема 1): - -$$\text{Money}_{\text{IN}} = 200 + 200 + 50 + 80 = 530$$ -$$\text{Money}_{\text{OUT}} = 200 + 200 + 50 + 78{.}4 + 0{.}4 + 0{.}4 + 0{.}8 = 530 \quad \checkmark$$ - -### 9.5 Сценарий спора - -Если Боб оспаривает в течение $\Delta t_{\text{grace}} = 12\text{ч}$, заплатив $d = 10$ VIZ: - -1. Все выплаты замораживаются. -2. Оракул должен ответить в течение 12 часов. -3. В режиме комитета все держатели токена голосуют (взвешенно по стейку, публично, пересматриваемо). -4. Если спор удовлетворён: выплаты пересчитываются с исправленным исходом, страховка оракула списывается. -5. Если спор отклонён: исходные выплаты идут как есть, Боб теряет 10 VIZ (делятся 50/50 между оракулом и арбитром). -6. Если за 14 дней нет разрешения: авто-закрытие с полными возвратами. - ---- - -## 10. Анти-MEV: пакетные и commit-reveal ставки - -### 10.1 MEV на рынках предсказаний - -На рынках предсказаний в непрерывном времени опережающее исполнение (front-running) и сэндвич-атаки извлекают ценность у игроков. Когда крупная ставка транслируется в mempool, атакующий может: - -1. Опередить: разместить ставку на той же стороне перед крупной ставкой, наживаясь на движении цены -2. Сэндвич: разместить ставки на обеих сторонах вокруг крупной ставки - -### 10.2 Пакетный расчёт - -Протокол поддерживает опциональные пакетные ставки (бинарные рынки). Ставки, поданные в пределах эпохи из $E$ блоков, ставятся в очередь и рассчитываются по **единой цене** на границе эпохи. Только чистый остаток (разница совокупного спроса) сдвигает AMM: - -$$\Delta R_A = \sum_{\text{batch}} a_{i,A} - \sum_{\text{batch}} a_{i,B}$$ - -Это устраняет преимущество порядка внутри пакета: все ставки пакета получают идентичное ценообразование независимо от порядка подачи. - -### 10.3 Commit-reveal - -Для более сильной защиты от MEV игроки могут использовать двухфазную схему commit-reveal: - -1. **Commit:** Подать $H(\text{bet} \| \text{nonce})$ с эскроу. Направление и сумма ставки скрыты. -2. **Reveal:** После закрытия эпохи подать $(\text{bet}, \text{nonce})$ для исполнения по пакетной цене. - -Нераскрытые обязательства теряют заданный управлением процент штрафа от эскроу, что предотвращает спам-обязательства. - ---- - -## 11. Реализация - -### 11.1 Операции консенсусного уровня в VIZ DLT - -Протокол реализован как первоклассные операции, валидируемые консенсусом, в распределённом реестре VIZ — не смарт-контракты, не пользовательские полезные нагрузки. VIZ DLT обеспечивает время блока ~3 секунды, консенсус Delegated Proof of Stake и именованные аккаунты (в стиле Graphene) без виртуальной машины общего назначения. - -Каждое финансовое действие (создание рынка, размещение ставки, разрешение оракулом, подача спора, депозит/вывод LP) — это операция `pm_*`, валидируемая каждым узлом-валидатором. Невалидные операции отклоняются до включения в блок. - -| Слой | Примеры | Валидируется консенсусом | -|-------|---------|---------------------| -| Операции протокола | `pm_create_market`, `pm_place_bet`, `pm_resolve_market`, `pm_dispute_create`, `pm_lazy_deposit` | Да — валидирует каждый узел | -| Виртуальные операции | `pm_payout`, `pm_dispute_finalize`, `pm_lazy_recall`, `pm_batch_settle` | Да — детерминированно, на момент блока | -| Метаданные | Описания рынков, доказательства споров | Нет — только на клиенте | - -### 11.2 Состояние в цепи - -Всё состояние протокола хранится в индексированных объектах chainbase. Ключевые объекты: - -- `pm_market_object`: конфигурация рынка, резервы CPMM, параметры комиссий, состояние -- `pm_bet_object`: позиции игроков с весом и временным штрафом -- `pm_liquidity_object`: позиции LP с весом по времени -- `pm_lazy_pool_object`: состояние синглтон-пула (балансы, доли, накопитель вознаграждения) -- `pm_oracle_object`: регистрация оракула, страховка, счётчики репутации -- `pm_dispute_object`: состояние и разрешение спора - -Метрики репутации вычисляются при чтении (не хранятся), что обеспечивает согласованность без дополнительных операций записи. - -### 11.3 Параметры управления - -Все экономические параметры (комиссии, штрафы, требования к страховке, окна споров, настройки «ленивого» пула) выбираются медианным голосованием делегатов. Каждый избранный валидатор публикует предпочтительные значения; сеть вычисляет медиану. Параметры меняются без хардфорков и развёртываний. Kill-switch позволяют управлению отключать подсистемы (плечо, commit-reveal) медианным голосованием. - -### 11.4 Масштабируемость - -Поскольку каждая операция валидируется консенсусом, модель издержек протокола важна при масштабе (тысячи одновременных рынков и ставок). Дизайн удерживает работу на блок и на операцию ограниченной: - -- **Операции пользователя за $O(1)$.** Размещение ставки, отмена и депозит/вывод LP затрагивают фиксированное число индексированных объектов (рынок, позицию игрока и — для пула — синглтон-накопитель). Ни одна не итерирует по всем участникам. Распределение вознаграждений использует накопитель MasterChef $\rho$ (§6.3), так что пул с $n$ вкладчиками рассчитывает вознаграждения за $O(1)$ на участника, а не за $O(n)$ на разрешение. -- **Ограниченная отложенная работа на блок.** Расчёт — единственный шаг с разветвлением (рынок с $m$ выигравшими ставками порождает $m$ виртуальных операций `pm_payout`). Чтобы одно крупное разрешение не раздувало блок, обработка выплат и cron ограничена медианным параметром `pm_processing_cap_per_block`: за блок обрабатывается не более фиксированного числа выплат/cron-элементов, а остаток переносится на последующие блоки. Поэтому работа на блок в худшем случае — $O(\text{cap})$, независимо от того, сколько рынков разрешается в одном интервале. -- **Нет усиления записи от репутации.** 14 метрик оракула — счётчики, обновляемые только при собственных действиях оракула; составной балл надёжности вычисляется **при чтении**, а не пишется каждый блок (§11.2). Метаданные обнаружения рынков строятся неконсенсусным плагином и никогда не входят в валидацию блока. -- **Рост состояния.** Состояние линейно по числу живых объектов (рынки, открытые позиции, активные споры). Разрешённые рынки и оплаченные позиции терминальны и обрезаемы клиентами; консенсус хранит лишь то, что требуют открытые обязательства. - -Связывающее ограничение при очень большом числе рынков — это пространство блока для разветвления расчётов, которое `pm_processing_cap_per_block` превращает из всплеска задержки в ограниченную, амортизированную пропускную способность, а не в стоимость, останавливающую консенсус. Количественная стресс-симуляция по многим одновременным рынкам разного объёма и волатильности входит в экспериментальную программу (§12.3, H4). - ---- - -## 12. Обсуждение - -### 12.1 Сравнение с существующими подходами - -| Измерение | Протокол Onix | Polymarket (CLOB+UMA) | Стандартный LMSR | AMM в стиле Uniswap | -|-----------|---------------|----------------------|---------------|-------------------| -| Риск LP | **Нулевой** (структурно) | Инвентарный риск | До $b \ln N$ | Непостоянные потери | -| Требуемые знания LP | Низкие (внести депозит) | Высокие (управлять заявками) | Средние | Средние-высокие | -| Непрерывность цены | Непрерывная (AMM/LMSR) | Дискретная (книга заявок) | Непрерывная | Непрерывная | -| Извлечение комиссий | Только у проигравших, при разрешении | Спред bid-ask | Спред | Каждая сделка | -| Модель оракула | Залог + спор DAO | Оптимистичный оракул UMA | Оператор | — | -| Согласованность цен | Математический инвариант | Зависит от арбитража | Математический инвариант | Математический инвариант | -| Нужны split/merge CTF | Нет | Да | Нет | Нет | - -### 12.2 Ограничения и честные компромиссы - -1. **Доходность LP зависит от объёма, а не от глубины.** Рынок с высокой субсидией и низким объёмом зарабатывает столько же абсолютных комиссий, сколько рынок с низкой субсидией и равным объёмом. Субсидия даёт глубину (меньше проскальзывания), но не доходность. - -2. **Прибыль LP не гарантирована.** Если рынок разрешается с нулевыми проигравшими ставками, комиссии не образуются. Основной капитал LP возвращается, но доходность может быть нулевой. - -3. **Тотализаторные токены — не инструменты с фиксированной стоимостью.** В отличие от стандартного LMSR, где 1 выигрышный токен = 1 единице валюты, токены Onix — пропорциональные требования на пул проигравших. Если все игроки выбрали победителя, все выходят «в ноль». - -4. **Компромиссы управления DPoS.** Модель параметров, выбираемых делегатами, имеет известные риски концентрации, присущие DPoS (общие с EOS, Hive, Tron). Механизм веса управления «ленивого» пула частично смягчает это, расширяя эффективный электорат. - -5. **Зависимость от ликвидности токена.** Экономические гарантии (страховые залоги, комиссии споров) масштабируются с рыночной стоимостью токена. Протокол предполагает, что полезность движет спросом, — стандартное допущение для нативных токенов протоколов. - -6. **Плечо добавляет ограниченный кредитный риск пулу.** Опциональная подсистема плеча (§6.6) — единственный компонент, *не* покрытый Теоремой 1. Будучи включённой, пул действует как кредитор и может понести недостачу на пути отмены той же стороны, ограниченную залогом заёмщика и изолированную в управляемой доле фонда. Мы заявляем полный возврат ссуды на путях каскада и расчёта как *проектное свойство, проверенное в симуляции*, а не как доказанную теорему; замкнутое доказательство возврата при произвольных траекториях резервов — открытая задача. Подсистема выключена по умолчанию и отключаема медианным голосованием, поэтому безусловная гарантия LP всегда может быть восстановлена. - -7. **Публичное голосование по спорам имеет компромисс тайминга.** Открытые, пересматриваемые бюллетени комитета (§7.2) допускают преимущество последнего хода и выбраны намеренно вместо commit-reveal ради аудируемости. Это ценностное суждение (прозрачность важнее тайны), а не доказательство, что открытое голосование оптимально против манипуляций; развёртывания, ставящие тайну в приоритет, должны вместо этого использовать режим аккаунта. - -### 12.3 Экспериментальные гипотезы - -Развёрнутый протокол спроектирован для проверки следующих гипотез: - -**H1 (Маховик ликвидности).** *Безрисковое предоставление ликвидности привлекает розничный капитал, достаточный для создания рыночной глубины, которая ощутимо снижает проскальзывание относительно сопоставимых платформ.* - -Измеримо: суммарные депозиты «ленивого» пула, средняя глубина рынка (отношения резервов), проскальзывание на единицу размера ставки. - -**H2 (Агрегирование информации).** *Ценообразование AMM/LMSR с тотализаторным расчётом даёт вероятностные оценки точности, сопоставимой с рынками предсказаний на основе CLOB при эквивалентных информационных условиях.* - -Измеримо: баллы Бриера, кривые калибровки, сравнение с внешними бенчмарками (опросы, модели, другие рынки). - -**H3 (Надёжность оракула под управлением DAO).** *Оракулы с залогом при разрешении споров в режиме комитета достигают точности разрешения, сопоставимой с централизованными оракульными сервисами, при уровне споров ниже устойчивого порога.* - -Измеримо: уровень споров оракула, уровень проигрыша споров, среднее время разрешения, распределение баллов надёжности. - -**H4 (Устойчивость «ленивого» пула).** *Механизмы градуированного отзыва и штрафных меток предотвращают эксплуатацию «ленивого» пула оракулами, поддерживая положительную чистую доходность вкладчиков пула по разнообразным портфелям рынков.* - -Измеримо: NAV пула во времени, частота отзыва, распределение штрафных меток, чистая доходность на долю. - -**H5 (Корреляция спроса на токен).** *Использование протокола (объём, число рынков, депозиты LP) положительно коррелирует со спросом на стейкинг токена и уровнем участия в управлении.* - -Измеримо: уровень стейкинга токена, участие в голосованиях по спорам, темп депозитов «ленивого» пула, корреляционный анализ. - -**H6 (Эффективность анти-MEV).** *Пакетный расчёт и механизмы commit-reveal снижают измеримое извлечение MEV по сравнению с непрерывными мгновенными ставками.* - -Измеримо: асимметрия влияния на цену (пакет против мгновенной), частота сэндвич-атак, качество исполнения для игрока. - -**H7 (Платёжеспособность плеча).** *При живых траекториях ликвидация на основе снимка возвращает ссуду на путях каскада и расчёта, ограничивая реализованную недостачу пула путём отмены той же стороны и в пределах залога заёмщика, так что процент по плечу чисто-приращивает доходность пула.* (Это эмпирически проверяет проектное свойство из §6.6 вместо замкнутого доказательства возврата.) - -Измеримо: фактический коэффициент возврата ссуды на событие ликвидации ($\text{recovered}/\lambda m$), частота и величина недостач отмены той же стороны относительно внесённого залога, доля процента по плечу в общей доходности пула, NAV пула с включённой и выключенной подсистемой плеча. - ---- - -## 13. Заключение - -Мы представили протокол Onix — архитектуру рынка предсказаний, которая достигает структурной гарантии основного капитала LP, разделяя функцию ценообразования (CPMM для бинарного, LMSR для многоисходного) и функцию расчёта (тотализатор, проигравшие финансируют выигравших). Мы доказали, что эта гарантия выполняется для обоих типов рынков независимо от назначения весов, параметров комиссий или распределения ставок. - -Механизм «ленивого» пула обеспечивает пассивное участие розничных LP с автоматическим размещением капитала и градуированной защитой от издержек упущенных возможностей. Двухрежимная система споров — сочетающая оракулов с залогом со взвешенным по стейку голосованием комитета DAO — даёт жизнеспособную альтернативу централизованному арбитражу, сохраняя совместимость стимулов оракула. - -Протокол реализован как операции консенсусного уровня в распределённом реестре VIZ и спроектирован как эксперимент, проверяющий семь явных гипотез о запуске ликвидности, точности агрегирования информации, жизнеспособности управления DAO, платёжеспособности плеча и динамике спроса на токен. Опциональная подсистема плеча — единственный компонент, который меняет безусловную гарантию LP на ограниченный, выключенный по умолчанию кредитный риск пула; всё остальное сохраняет структурную гарантию Теоремы 1. - -Математические свойства проверяемы. Экономические гипотезы будут проверены участием рынка. - ---- - -## Литература - -[1] Hanson, R. (2003). Combinatorial Information Market Design. *Information Systems Frontiers*, 5(1), 107–119. - -[2] Adams, H., Zinsmeister, N., & Robinson, D. (2020). *Uniswap v2 Core.* Uniswap Labs. - -[3] Adams, H., Zinsmeister, N., Salem, M., Keefer, R., & Robinson, D. (2021). *Uniswap v3 Core.* Uniswap Labs. - -[4] Berg, J., Forsythe, R., Nelson, F., & Rietz, T. (2008). Results from a Dozen Years of Election Futures Markets Research. In *Handbook of Experimental Economics Results* (Vol. 1, pp. 742–751). Elsevier. - -[5] Cowgill, B., Wolfers, J., & Zitzewitz, E. (2009). Using Prediction Markets to Track Information Flows: Evidence from Google. In *Auctions, Market Mechanisms and Their Applications (AMMA 2009)*, LNICST Vol. 14. Springer. - -[6] Levitt, S. D. (2004). Why Are Gambling Markets Organised So Differently from Financial Markets? *The Economic Journal*, 114(495), 223–246. - -[7] Gnosis. *Conditional Tokens Framework (CTF) Documentation.* - -[8] UMA Protocol. *Optimistic Oracle Documentation.* - -[9] Leshner, R., & Hayes, G. (2019). *Compound: The Money Market Protocol.* Compound Labs. - -[10] SushiSwap. (2020). *MasterChef Contract.* - -[11] Piskunov, A. *VIZ: Distributed Ledger Technical Description, Fair DPoS, and Governance.* VIZ-Blockchain. - -[12] Arrow, K. J., Forsythe, R., Gorham, M., Hahn, R., Hanson, R., Ledyard, J. O., et al. (2008). The Promise of Prediction Markets. *Science*, 320(5878), 877–878. - -[13] Wolfers, J., & Zitzewitz, E. (2004). Prediction Markets in Theory and Practice. *NBER Working Paper* No. 10248 (also *Journal of Economic Perspectives*, 18(2), 107–126). - -[14] Othman, A., Pennock, D. M., Reeves, D. M., & Sandholm, T. (2013). A Practical Liquidity-Sensitive Automated Market Maker. *ACM Transactions on Economics and Computation*, 1(3), Article 14. - -[15] Abernethy, J., Chen, Y., & Vaughan, J. W. (2011). An Optimization-Based Framework for Automated Market-Making. In *Proceedings of the 12th ACM Conference on Electronic Commerce (EC '11)* (pp. 297–306). diff --git a/.qoder/docs/onix-protocol-paper-ru.pdf b/.qoder/docs/onix-protocol-paper-ru.pdf deleted file mode 100644 index 95a7b4a9ae..0000000000 Binary files a/.qoder/docs/onix-protocol-paper-ru.pdf and /dev/null differ diff --git a/.qoder/docs/onix-protocol-paper.md b/.qoder/docs/onix-protocol-paper.md deleted file mode 100644 index 55cdb2f0ef..0000000000 --- a/.qoder/docs/onix-protocol-paper.md +++ /dev/null @@ -1,767 +0,0 @@ ---- -title: "Combining Parimutuel Settlement with Automated Market Makers for Liquidity-Guaranteed Prediction Markets: The Onix Protocol" -author: | - Anatoly Piskunov\ - Independent Researcher — VIZ Distributed Ledger\ - anatoly.piskunov@gmail.com\ - ORCID: [0009-0000-8883-4111](https://orcid.org/0009-0000-8883-4111) -date: June 2026 -header-includes: | - \usepackage{titling} - \pretitle{\begin{center}\LARGE\bfseries} - \posttitle{\par\end{center}\vspace{1.4em}} - \setlength{\droptitle}{-1.5em} - \setlength{\abovedisplayskip}{8pt} - \setlength{\belowdisplayskip}{8pt} -abstract: | - Prediction markets aggregate dispersed information into probabilistic forecasts that consistently outperform polls and expert panels, yet their adoption is constrained by a structural liquidity problem: providers of market depth bear adverse selection risk that deters retail participation. We present the **Onix Protocol**, a hybrid architecture that decouples the *pricing* function from the *settlement* function in prediction markets. By pairing automated market maker pricing — a Constant Product Market Maker (CPMM) for binary outcomes and a Logarithmic Market Scoring Rule (LMSR) for multi-outcome markets — with parimutuel (totalizator) settlement, we achieve a **structural guarantee** that liquidity-provider principal is never at risk from betting outcomes. We formalize the protocol's economic invariants, prove the LP principal guarantee for both market types, describe a "Lazy" liquidity pool enabling passive retail participation, analyze the dispute resolution mechanism under DAO governance, and discuss the experimental hypotheses this system is designed to test. The protocol is implemented as consensus-level operations on the VIZ distributed ledger. - - \vspace{0.9\baselineskip}\noindent - **Keywords:** prediction markets, automated market makers, parimutuel betting, LMSR, liquidity provision, DAO governance, mechanism design ---- - -\clearpage - -\tableofcontents - -\clearpage - -## 1. Introduction - -### 1.1 The Prediction Market Liquidity Problem - -Prediction markets are among the most reliable mechanisms for aggregating dispersed information into actionable probabilistic forecasts. Empirical evidence from political forecasting (Berg, Forsythe, Nelson & Rietz, 2008), sports betting (Levitt, 2004), and internal corporate markets (Cowgill, Wolfers & Zitzewitz, 2009) demonstrates that market prices consistently outperform alternative forecasting methods. - -Despite this track record, prediction market deployment remains limited. The fundamental constraint is not demand for forecasts but *supply of liquidity*. Every existing prediction market architecture requires liquidity providers (LPs) to accept one of three risk profiles: - -1. **Impermanent loss** in AMM-based markets (Adams, Zinsmeister & Robinson, 2020; Adams et al., 2021), where LPs systematically lose to informed traders; -2. **Adverse selection** in limit-order-book markets, where market makers bear inventory risk against better-informed counterparties; -3. **Bounded subsidy loss** in LMSR markets (Hanson, 2003), where the market maker risks up to $b \ln N$ against correctly-informed crowds. - -These risk profiles restrict liquidity provision to sophisticated actors with active management capabilities, creating thin markets with high slippage that deter bettor participation — a self-reinforcing cycle: - -$$\text{thin markets} \rightarrow \text{high slippage} \rightarrow \text{poor UX} \rightarrow \text{few bettors} \rightarrow \text{low fees} \rightarrow \text{no LP incentive} \rightarrow \text{thin markets}$$ - -### 1.2 Research Questions - -This work poses and seeks to answer the following questions through a deployed experimental system: - -**Q1.** *Can the pricing and settlement functions of a prediction market be architecturally separated such that LP principal is structurally insulated from betting outcomes?* - -**Q2.** *Does a parimutuel settlement mechanism, where losers exclusively fund winners, eliminate the adverse selection problem inherent in AMM-based and LMSR-based prediction markets?* - -**Q3.** *Can automated liquidity deployment via a pooled mechanism ("Lazy Pool") reduce the barrier to LP entry sufficiently to bootstrap market depth without active market selection by individual LPs?* - -**Q4.** *Is a DAO-governed dispute resolution mechanism, with stake-weighted public voting and bonded oracles, a viable alternative to centralized arbitration or optimistic oracle schemes?* - -**Q5.** *What governance-token utility emerges from a prediction market protocol where LP positions carry voting weight, and does this create a sustainable token-demand feedback loop?* - -### 1.3 Contributions - -Our contributions are: - -1. **A formal proof** that combining AMM pricing with parimutuel settlement yields a structural LP principal guarantee for both binary (CPMM) and multi-outcome (LMSR) prediction markets. -2. **The "Lazy Pool" mechanism** — an automated capital deployment system with graduated recall and opportunity-cost protection that enables passive retail LP participation, plus an opt-in, default-off pool-funded leverage subsystem whose pool exposure is *bounded* (worst-case shortfall capped by borrower collateral) and isolated from the Theorem 1 LP guarantee. -3. **A two-mode dispute resolution framework** combining bonded oracles with DAO committee arbitration, including a full game-theoretic analysis of oracle incentive compatibility. -4. **An experimental protocol** implemented as consensus-level operations on a production distributed ledger, with explicit hypotheses and measurable outcomes. - ---- - -## 2. Related Work - -### 2.1 Market Scoring Rules - -Hanson (2003) introduced the Logarithmic Market Scoring Rule (LMSR), in which a market maker quotes prices via a softmax function over quantity parameters. The LMSR guarantees a bounded loss to the market maker of $b \ln N$ and ensures prices sum to unity. However, this bound is also the LP's maximum risk — if the crowd correctly predicts the outcome, the market maker loses the entire subsidy. This has limited LMSR adoption to corporate environments (Microsoft, Inkling) where the operator absorbs losses. - -### 2.2 Constant Product Market Makers - -The constant product formula $x \cdot y = k$, popularized by Uniswap (Adams, Zinsmeister & Robinson, 2020), provides continuous pricing for two-asset pairs. When applied to binary prediction markets, the reserves represent opposing outcomes and the invariant provides natural probability pricing. However, standard AMM LPs suffer impermanent loss — empirical analysis shows over 50% of Uniswap v3 LPs underperform buy-and-hold (Adams et al., 2021). - -### 2.3 Conditional Token Frameworks - -Polymarket employs the Gnosis Conditional Tokens Framework (CTF), where positions are ERC-1155 tokens with split/merge operations to enforce price coherence ($\sum p_i = 1$). This adds composability but introduces complexity and requires an external arbitrage mechanism. UMA's Optimistic Oracle provides dispute resolution through a challenge-response game with economic bonds. - -### 2.4 Parimutuel Betting - -The parimutuel (totalizator) model, originating from Pierre Oller's totalisator (1865), pools all bets and distributes the losers' stakes proportionally among winners. This guarantees the house never pays from its own capital. However, traditional parimutuel systems lack real-time pricing — odds are only final at pool close — making them unsuitable for continuous information aggregation. - -### 2.5 Our Approach - -The Onix Protocol combines the real-time pricing capability of AMMs/LMSR with the zero-LP-risk property of parimutuel settlement. The AMM or LMSR serves exclusively as a *pricing engine* — determining implied probabilities and assigning weights — while settlement follows the parimutuel model where losers fund winners and LP principal is architecturally separated from the payout flow. - -This positions Onix differently from two other hybrid directions in the literature. First, *liquidity-sensitive* market makers (Othman, Pennock, Reeves & Sandholm, 2013; Abernethy, Chen & Vaughan, 2011) keep the operator as counterparty but bound or shape its loss via the cost-function geometry; the operator can still lose. Onix instead removes the counterparty role from the LP altogether — the curve only *prices*, it never *pays*. Second, *CLOB+AMM* hybrids (e.g. order books backstopped by an AMM, as explored in several DEX designs and in Polymarket's CLOB with AMM-style fallbacks) blend two pricing venues but inherit inventory/adverse-selection risk from the market-making side. Onix keeps a single continuous pricing venue and moves the risk-bearing entirely out of pricing and into the parimutuel pool. To our knowledge, pairing a continuous AMM/LMSR pricer with parimutuel (losers-fund-winners) settlement to obtain an unconditional LP-principal guarantee has not previously been formalized. - ---- - -## 3. Protocol Model - -### 3.1 Participants and Roles - -We define the following participant set $\mathcal{P}$: - -| Role | Symbol | Function | -|------|--------|----------| -| Market Creator | $c \in \mathcal{P}$ | Defines market parameters, provides seed liquidity | -| Oracle | $o \in \mathcal{P}$ | Registers insurance bond $I_o$, accepts markets, resolves outcomes | -| Bettor | $b_i \in \mathcal{P}$ | Places bets on outcomes, receives outcome tokens | -| Liquidity Provider | $\ell_j \in \mathcal{P}$ | Deposits capital into market pools or the Lazy Pool | -| Dispute Resolver | $\mathcal{D}$ | Committee (stake-weighted vote) or named account | - -### 3.2 Market Definition - -A market $\mathcal{M}$ is a tuple: - -$$\mathcal{M} = (o, \tau, \mathbf{O}, t_{\text{bet}}, t_{\text{res}}, \boldsymbol{\theta}, L)$$ - -where $o$ is the designated oracle, $\tau \in \{0, 1\}$ is the market type (binary or multi), $\mathbf{O} = \{O_1, \ldots, O_N\}$ is the outcome set ($N = 2$ for binary, $3 \leq N \leq 10$ for multi), $t_{\text{bet}}$ and $t_{\text{res}}$ are betting and resolution expiration times, $\boldsymbol{\theta}$ is the fee parameter vector, and $L$ is the initial liquidity. - -### 3.3 Fee Structure - -All fees are expressed in basis points (bp), where $10000 \text{ bp} = 100\%$. The fee vector is: - -$$\boldsymbol{\theta} = (\theta_{\text{oracle}}, \theta_{\text{creator}}, \theta_{\text{liq}})$$ - -Fees are computed exclusively at resolution from the losing side's aggregate stake $S_{\text{lose}}$ and are never deducted at bet placement time: - -$$f_{\text{oracle}} = \lfloor S_{\text{lose}} \cdot \theta_{\text{oracle}} / 10000 \rfloor$$ -$$f_{\text{creator}} = \lfloor S_{\text{lose}} \cdot \theta_{\text{creator}} / 10000 \rfloor$$ -$$f_{\text{liq}} = \lfloor S_{\text{lose}} \cdot \theta_{\text{liq}} / 10000 \rfloor$$ -$$W = S_{\text{lose}} - f_{\text{oracle}} - f_{\text{creator}} - f_{\text{liq}}$$ - -where $W$ is the **winners' pool** available for distribution to winning bettors. - -### 3.4 Market State Machine - -The lifecycle of $\mathcal{M}$ follows a finite state machine: - -$$q_0 \xrightarrow{\text{oracle accepts}} q_1 \xrightarrow{t \geq t_{\text{bet}}} q_2 \xrightarrow{\text{oracle resolves}} q_3 \xrightarrow{\text{grace period}} \text{paid}$$ - -with a rejection path $q_0 \xrightarrow{\text{oracle rejects}} q_{-1}$ (deleted, liquidity returned). - ---- - -## 4. Pricing Mechanisms - -### 4.1 Binary Markets: Constant Product Market Maker - -For binary markets ($N = 2$), the protocol maintains two reserves $R_A$ and $R_B$ subject to the invariant: - -$$k = R_A \cdot R_B$$ - -**Initialization.** Given seed liquidity $L$: - -$$R_A = \lfloor L / 2 \rfloor, \quad R_B = L - R_A, \quad k = R_A \cdot R_B$$ - -**Bet placement.** When bettor $b_i$ wagers amount $a$ on outcome $A$: - -$$R'_A = R_A + a$$ -$$R'_B = \lfloor k / R'_A \rfloor$$ -$$w_i = R_B - R'_B \quad \text{(tokens received)}$$ - -Here $w_i$ is the bettor's *weight*. It is a **computed** quantity — the number of outcome tokens minted by the curve — and is in general **not** equal to the staked amount $a$: depending on the current reserves, $w_i$ may be larger or smaller than $a$. The weight is used solely to apportion the winners' pool at settlement (§5.1); it is never a currency-denominated claim. - -**Implied probability.** The market-implied probability for each outcome is: - -$$P(A) = \frac{R_A}{R_A + R_B}, \quad P(B) = \frac{R_B}{R_A + R_B}$$ - -Note that $P(A) + P(B) = 1$ by construction, eliminating the need for any external arbitrage mechanism to enforce price coherence. - -### 4.2 Multi-Outcome Markets: LMSR - -For markets with $N > 2$ outcomes, the protocol uses the Logarithmic Market Scoring Rule with softmax pricing. - -**Cost function:** - -$$C(\mathbf{q}) = b \cdot \ln\left(\sum_{j=1}^{N} \exp(q_j / b)\right)$$ - -where $\mathbf{q} = (q_1, \ldots, q_N)$ are quantity parameters and $b$ is the liquidity parameter. - -**Price function (softmax):** - -$$p_i(\mathbf{q}) = \frac{\exp(q_i / b)}{\sum_{j=1}^{N} \exp(q_j / b)}$$ - -By the definition of softmax, $\sum_{i=1}^{N} p_i = 1$ identically. - -**Cost of a trade.** The cost to purchase $\Delta$ tokens on outcome $i$: - -$$\text{cost}(\Delta, i) = C(\mathbf{q} + \Delta \cdot \mathbf{e}_i) - C(\mathbf{q})$$ - -**Liquidity parameter.** The parameter $b$ is funded by the LP subsidy $S$: - -$$b = \frac{S}{\ln N}$$ - -**Numerical stability.** The log-sum-exp computation uses the standard identity: - -$$\ln\left(\sum_j \exp(x_j)\right) = \max(\mathbf{x}) + \ln\left(\sum_j \exp(x_j - \max(\mathbf{x}))\right)$$ - ---- - -## 5. Parimutuel Settlement and LP Guarantee - -### 5.1 Unified Settlement Model - -Both market types share an identical settlement mechanism. At resolution, the oracle declares winning outcome $O^* \in \mathbf{O}$. Let $\mathcal{W}$ be the set of bets on $O^*$ and $\mathcal{L}$ be all other bets. - -$$S_{\text{lose}} = \sum_{b_i \in \mathcal{L}} a_i$$ - -where $a_i$ is the bet amount. The winners' pool $W$ is computed as in §3.3. - -Each winning bettor $b_i \in \mathcal{W}$ receives: - -$$\pi_i = a_i + \frac{w_i}{\sum_{b_j \in \mathcal{W}} w_j} \cdot W - \tau_i$$ - -where $w_i$ is the bettor's weight (tokens from CPMM or LMSR) and $\tau_i$ is the time penalty on profit (§5.3). - -### 5.2 Theorem: LP Principal Guarantee - -**Theorem 1.** *Under parimutuel settlement, the total money distributed equals the total money received, with LP principal $L$ returned unconditionally. The LP principal is never at risk from betting outcomes.* - -*Proof.* Consider the full money flow through a market: - -$$\text{Money}_{\text{IN}} = L + \sum_{b_i \in \mathcal{W}} a_i + \sum_{b_i \in \mathcal{L}} a_i = L + \sum_{\text{all bets}} a_i$$ - -where the first sum $\sum_{b_i \in \mathcal{W}} a_i$ is the aggregate principal staked by *winners* (returned to them in full at settlement) and $S_{\text{lose}} = \sum_{b_i \in \mathcal{L}} a_i$ is the aggregate stake forfeited by *losers* (the sole source of the winners' pool and all fees). At resolution, the outgoing flows are: - -$$\text{Money}_{\text{OUT}} = L + \sum_{b_i \in \mathcal{W}} a_i + W + f_{\text{oracle}} + f_{\text{creator}} + f_{\text{liq}}$$ - -Substituting $W = S_{\text{lose}} - f_{\text{oracle}} - f_{\text{creator}} - f_{\text{liq}}$: - -$$\text{Money}_{\text{OUT}} = L + \sum_{b_i \in \mathcal{W}} a_i + S_{\text{lose}}$$ -$$= L + \sum_{b_i \in \mathcal{W}} a_i + \sum_{b_i \in \mathcal{L}} a_i = L + \sum_{\text{all bets}} a_i = \text{Money}_{\text{IN}} \qquad \blacksquare$$ - -**Corollary 1.** *The LP principal guarantee holds regardless of the weight assignment mechanism, the number of bettors, the distribution of bets across outcomes, or the fee parameters. It is a property of the settlement architecture, not the pricing curve.* - -**Corollary 2.** *The maximum total payout to winners is $S_{\text{lose}}$ (the entire losing pool), regardless of weights assigned by the AMM or LMSR. Therefore, winner payouts can never draw from LP capital.* - -**Remark (integer arithmetic and solvency under rounding).** Theorem 1 is stated over the rationals, but the protocol operates on integers (milli-VIZ), and every division applies the floor operator $\lfloor \cdot \rfloor$. We show the guarantee survives discretization. Each fee and each winner share is rounded *down*: - -$$f_\bullet = \lfloor S_{\text{lose}} \cdot \theta_\bullet / 10000 \rfloor, \qquad \hat{\pi}_i^{\text{profit}} = \left\lfloor \frac{w_i}{\sum_{j} w_j} \cdot W \right\rfloor$$ - -Since $\lfloor x \rfloor \leq x$, the realized winners' distribution satisfies $\sum_{i \in \mathcal{W}} \hat{\pi}_i^{\text{profit}} \leq W$, and likewise $\sum_\bullet f_\bullet \leq S_{\text{lose}} \cdot (\theta_{\text{oracle}}+\theta_{\text{creator}}+\theta_{\text{liq}})/10000 \leq S_{\text{lose}}$. Therefore the realized outflow obeys - -$$\text{Money}_{\text{OUT}}^{\text{realized}} = L + \sum_{i \in \mathcal{W}} a_i + \sum_{i \in \mathcal{W}} \hat{\pi}_i^{\text{profit}} + \sum_\bullet f_\bullet \;\leq\; L + \sum_{\text{all bets}} a_i = \text{Money}_{\text{IN}}$$ - -so the system is never insolvent. The non-negative residual - -$$\epsilon = S_{\text{lose}} - \sum_{i \in \mathcal{W}} \hat{\pi}_i^{\text{profit}} - \sum_\bullet f_\bullet \;\geq\; 0$$ - -is bounded by $\epsilon < |\mathcal{W}| + 3$ milli-VIZ (one sub-unit per floored quantity) and is swept to the DAO fund as dust. Thus rounding can only *under*-distribute, never over-distribute: LP principal $L$ remains exactly conserved and the inequality $\text{Money}_{\text{OUT}} \leq \text{Money}_{\text{IN}}$ holds at the granularity of the ledger. $\blacksquare$ - -### 5.3 Time Penalty on Late Bets - -To mitigate information asymmetry from bets placed near expiration (when outcome uncertainty is reduced), the protocol imposes a configurable time penalty applied *only to profit*, never to principal. - -Let $\Delta t = t_{\text{bet}} - t_{\text{now}}$ be the time to expiration and $\omega$ be the penalty window. If $\Delta t < \omega$: - -$$r = 1 - \frac{\Delta t}{\omega}$$ - -$$\tau_{\text{rate}} = \begin{cases} r & \text{linear} \\ r^2 & \text{quadratic (default)} \end{cases}$$ - -$$\tau_i = \lfloor \pi_{\text{profit},i} \cdot \tau_{\text{rate}} \cdot \tau_{\max} / 10^6 \rfloor$$ - -where $\pi_{\text{profit},i}$ is the bettor's profit share (excluding principal) and $\tau_{\max}$ is the maximum penalty in micro-units. - -**Invariant:** $\pi_i \geq a_i$ for all winning bettors, since the penalty is applied exclusively to the profit component. - -### 5.4 LP Time-Weighted Fee Distribution - -LP fee shares are distributed proportionally to the product of deposit amount and remaining time to expiration: - -$$\omega_j = \text{amount}_j \cdot \max(1, t_{\text{bet}} - t_{\text{deposit},j})$$ - -$$f_{\text{share},j} = \left\lfloor \frac{f_{\text{pool}} \cdot \omega_j}{\sum_m \omega_m} \right\rfloor$$ - -This creates a strong incentive for early liquidity provision. In a market of duration $T$, an LP depositing at time $t = 0$ earns $T$ times more per unit capital than one depositing at $t = T - 1$. - ---- - -## 6. "Lazy" Liquidity Pool - -### 6.1 Motivation - -Individual LP provision requires active market evaluation and selection. For retail participants, this creates an impractical knowledge and effort barrier. The "Lazy" Pool (so named because it requires no active market selection from depositors) solves this by accepting deposits and automatically allocating capital to activated markets. - -### 6.2 Pool Mechanics - -The Lazy Pool $\mathcal{L}_P$ is a singleton contract maintaining: -- $B_{\text{free}}$: unallocated balance -- $B_{\text{alloc}}$: capital deployed to active markets -- $B_{\text{earned}}$: realized profits -- $S_{\text{total}}$: total outstanding shares -- $\rho$: cumulative reward-per-share accumulator - -**Deposit.** When user $u$ deposits amount $a$: - -$$\text{shares}_u = \begin{cases} a & \text{if } S_{\text{total}} = 0 \\ a \cdot S_{\text{total}} / B_{\text{free}} & \text{otherwise} \end{cases}$$ - -The deposit is locked for a governance-defined period $t_{\text{lock}}$. - -**Auto-allocation.** On market activation ($q_0 \rightarrow q_1$): - -$$a_{\text{alloc}} = B_{\text{free}} \cdot \alpha / 100$$ - -subject to $a_{\text{alloc}} \geq a_{\min}$ and $B_{\text{alloc}} + a_{\text{alloc}} \leq B_{\text{total}} \cdot \alpha_{\max} / 100$, where $\alpha$ is the per-market allocation percentage and $\alpha_{\max}$ is the maximum total allocation cap. - -The allocation formula includes oracle quality adjustments: - -$$a_{\text{alloc}} = B_{\text{free}} \cdot \frac{\alpha}{100} \cdot (1 - \beta)^{n_o} \cdot (1 - \gamma)^{f_o}$$ - -where $n_o$ is the oracle's active market count, $f_o$ is the oracle's active fault stamp count, and $\beta, \gamma$ are governance-defined decay factors. - -### 6.3 Reward Distribution (Lazy Accounting) - -Rewards are distributed using a single global accumulator $\rho$ (reward-per-share), following the MasterChef pattern (SushiSwap, 2020; Leshner & Hayes, 2019): - -When a market resolves and the pool's LP position returns profit $\pi_{\text{pool}}$: - -$$\rho \leftarrow \rho + \frac{\pi_{\text{pool}} \cdot \text{PRECISION}}{S_{\text{total}}}$$ - -Any user $u$'s pending reward is computable in $O(1)$: - -$$\text{reward}_u = \text{pending}_u + \frac{\text{shares}_u \cdot (\rho - \rho_{\text{snapshot},u})}{\text{PRECISION}}$$ - -User records are updated only when that user acts (deposit or withdraw), giving $O(1)$ amortized cost regardless of participant count. - -### 6.4 Opportunity-Cost Protection - -The auto-allocation mechanism creates an attack vector: a malicious oracle could create long-duration zero-volume markets to lock pool capital. Three graduated defense mechanisms address this: - -**Graduated Recall.** The market duration is divided into $n_{\text{steps}}$ intervals. At each checkpoint $k$, if cumulative volume $V_k$ satisfies: - -$$V_k < a_{\text{alloc}} \cdot \theta_{\text{recall}} / 100$$ - -then a fraction $\delta_{\text{recall}}$ of the current allocation is recalled to the pool. After $n$ consecutive low-volume checkpoints, the retained allocation is: - -$$a_{\text{retained}} = a_{\text{alloc}} \cdot (1 - \delta_{\text{recall}})^n$$ - -For the default parameters ($\delta_{\text{recall}} = 10\%$, $n_{\text{steps}} = 10$), a completely idle market retains $(0.9)^{10} \approx 35\%$ of its original allocation. - -**Active Market Penalty.** Each active market from oracle $o$ multiplicatively reduces new allocations by factor $(1 - \beta)$. With $\beta = 5\%$ and 10 active markets, new allocations are $(0.95)^{10} \approx 60\%$ of the base rate. - -**Fault Stamps.** Bad outcomes (no-contest, missed deadlines, dispute losses, zero-volume resolutions) generate penalty stamps on the oracle that further reduce allocations by factor $(1 - \gamma)^{f_o}$. Stamps auto-expire after a governance-defined clean-operation window. - -### 6.5 Emergency Withdrawal - -Users may exit the pool before lock expiration, subject to a penalty on *locked profit only*: - -$$\text{penalty} = \max(0, V_{\text{total}} - P_{\text{deposited}}) \cdot \frac{S_{\text{locked}}}{S_{\text{total}}} \cdot \theta_{\text{emergency}} / 100$$ - -where $V_{\text{total}}$ is the user's total value (shares + pending rewards), $P_{\text{deposited}}$ is cumulative principal deposited, and $S_{\text{locked}} / S_{\text{total}}$ is the fraction of shares still locked. The penalty is redistributed to remaining pool participants via $\rho$. - -### 6.6 Opt-In Leverage Funded by the Pool - -The Lazy Pool plays a second, optional role from the same $B_{\text{free}}$: it funds a leverage subsystem. This subsystem is **opt-in, default-off, and governed by a median kill-switch**. We treat it separately from the rest of the paper for an important reason. **It is the one place where pool capital takes on credit risk, so it is *not* covered by the unconditional guarantee of Theorem 1.** Theorem 1 concerns the pool acting as a *market liquidity provider*, where principal is structurally insulated from betting outcomes. Leverage instead has the pool act as a *lender*, and lending carries risk that we bound rather than eliminate. - -**Position opening.** A bettor posts collateral $m$. The pool lends margin $\lambda m$ at leverage factor $\lambda$ from $B_{\text{free}}$, subject to caps on per-position size, pool fund fraction, and minimum market liquidity. No token is minted: the total stake $(1+\lambda)m$ enters the market exactly as an ordinary bet would. The position's curve weight, its collateral, and the lent amount are recorded on a dedicated position object. The pool's obligation on the position is - -$$\Omega = \lambda m \,(1 + r),$$ - -where $r$ is the accrued interest. The loan plus interest is what the pool seeks to recover. - -**Liquidation against the recorded reserve snapshot.** The hard case in leveraged binary markets is *jump risk*: a discontinuous price move can leave a naive "sell at the current price" liquidation unable to cover the loan. Onix does not liquidate at the current price. It closes the position via reverse-CPMM against the **reserve state recorded for that position**, so the position's own market impact is unwound first rather than being paid for twice. Two paths drive recovery: - -1. **Opposing-bet cascade** (`pm_place_bet`, liquidation reason 0) — an incoming opposing bet triggers a forced close of the leveraged position against its recorded snapshot. -2. **Settlement force-close** (`pm_leverage_resolve`) — at resolution the position is closed and the loan repaid from its weight before any profit accrues to the borrower. - -On these two paths the design target is full recovery, $\text{recovered} = \min(V_{\text{close}}, \Omega) \ge \lambda m$, with the realized loan-and-interest returning to the pool. We state this as a **design property, not a theorem**: the leverage settlement math is implemented and exercised in `consensus_sim`, but a closed-form proof of recovery under all reserve trajectories is left to future work (§12.2). The residual exposure is concentrated on one path: - -- **Same-side voluntary cancel** (`pm_cancel_bet`, reason 1) — if the borrower closes on the same side under an adverse move, the pool can realize a shortfall. This shortfall is **bounded by the borrower's collateral $m$**, which the protocol holds; it cannot exceed $m$. - -**Kill-switch decoupled from protection.** The kill-switch disables *new* openings only. The liquidation paths are deliberately **not** gated by it, so disabling leverage never strips protection from already-open positions. - -**Risk isolation and reward.** Leverage lending is confined to a governed fraction of $B_{\text{free}}$ (`leverage_fund_percent`), so worst-case pool exposure is capped at the subsystem level and cannot reach the market-LP principal that Theorem 1 protects. Interest earned accrues to the pool through the same $\rho$ accumulator (§6.3). Pool depositors therefore earn from two sources — losers'-pool fee shares (risk-free, Theorem 1) and leverage interest (bounded credit risk, opt-in) — and the two are accounted identically but governed independently. The honest summary: the pool's *market-LP* principal is never at risk; its *leverage lending* carries bounded, opt-in, default-off credit risk (§12.2). - -### 6.7 Design Decision: Real Depth Only (No Virtual/Phantom Liquidity) - -A natural proposal is to seed a market's pricing curve with *virtual* (phantom) liquidity — a reserve offset $\phi$ added to flatten price impact but backed by no real capital and deleted at settlement, optionally tuned by governance. In the closed bet→cancel→settle loop this is value-conservative (it is the virtual-AMM technique), and it is tempting as a cold-start "stabilizer" for new, thin markets. Onix **deliberately does not implement it.** The same cold-start benefit is already delivered by Lazy-Pool auto-allocation (§6.2) — but with *real* capital, which additionally earns fees, has an accountable owner, and follows demand per market. We reject phantom depth because, applied carelessly, it damages the two things the protocol exists to protect — market structure and trust: - -1. **Forgeable depth erodes trust.** Depth is meaningful as a signal only because it is costly real capital at risk. Free virtual depth makes "this market is deep and liquid" a forgeable claim — a thin or manipulated market can be dressed to look deep. Real capital makes the claim unforgeable. -2. **It silently distorts information aggregation.** Virtual depth flattens the weight curve, weakening the reward for early, correct information and making the displayed price unresponsive to news ("stable because unmovable" = a stale forecast). The right magnitude is per-market and volume-dependent; a single governance constant cannot track it and, set too high, degrades the very price-discovery property a prediction market provides. -3. **It is solvent only while never redeemed or used as collateral.** The moment depth backs a real outflow — cancellations, early withdrawals, leverage loans, shared/cross-market pools — the virtual part must be excluded everywhere or it leaks real money (e.g. a leverage loan sized or recovered against fake depth becomes real bad debt to pool depositors). Every "size against liquidity / pay the LP" branch becomes a footgun requiring "…but not the phantom part." Real capital removes this entire class of excludability bugs by construction. -4. **It has no owner, no yield, no accountability.** Virtual depth bears no risk and earns no fee for anyone real; it is a service nobody is paid for and nobody answers for. For the markets it touches, it deletes the retail safe-yield product that is the protocol's core hypothesis (Q3, H1). - -The Lazy Pool is the same idea done with real numbers: auto-allocation smooths the launch of new markets, but the capital is redeemable, leverage-safe, fee-earning, owned by depositors, and self-correcting per market via graduated recall (§6.4). Onix therefore keeps **only real numbers** — every unit of depth is real capital that can be withdrawn, earns, and is accountable. This is a conscious tradeoff: we forgo a cheap virtual stabilizer to preserve the integrity of the price signal and the solvency of every real-money path. - ---- - -## 7. Dispute Resolution Under DAO Governance - -### 7.1 Bonded Oracle Model - -Each oracle $o$ must maintain an insurance bond $I_o \geq I_{\min}$. The bond creates accountability: oracles who misresolve markets, miss deadlines, or lose disputes have their insurance slashed. Oracle revenue derives from: - -1. A fixed per-market fee $f_{\text{fixed}}$ (compensating insurance staking) -2. A percentage fee $f_{\text{oracle}}$ from the losers' pool at resolution - -These two fees enter the system at different points and must not be conflated. The **percentage fee** $f_{\text{oracle}}$ is deducted from $S_{\text{lose}}$ at resolution and appears in the money flow of Theorem 1 (§5.2). The **fixed fee** $f_{\text{fixed}}$ is a direct creator→oracle transfer executed at market *acceptance*, before any betting; it is therefore *not* part of the betting/LP money flow — it neither adds to the seed liquidity $L$ nor draws from $S_{\text{lose}}$, and so does not appear in Theorem 1. (It is omitted from the fee vector $\boldsymbol{\theta}$ of §3.3 for the same reason: $\boldsymbol{\theta}$ collects only the resolution-time, losers-funded percentage fees.) For self-oracle markets no transfer occurs and $f_{\text{fixed}} = 0$. - -The oracle acceptance flow implements an offer-quote mechanism: the creator publishes fee ceilings, and the oracle freezes its actual terms (bounded by both the creator ceiling and a governance cap) at acceptance. - -### 7.2 Two-Mode Dispute System - -Any bettor may challenge a resolution within a grace period $\Delta t_{\text{grace}}$ by escrowing a dispute fee $d$. During disputes, all payouts are frozen. The protocol supports two dispute modes per market: - -**Committee mode ($\delta_{\text{mode}} = 0$).** The entire token-holder electorate resolves the dispute via stake-weighted public vote. Each voter $v$'s weight is: - -$$w_v = s_v + \text{shares}_{v,\text{pool}} \cdot \text{NAV}_{\text{pool}} / S_{\text{total}}$$ - -where $s_v$ is the voter's vesting shares and the second term converts Lazy Pool stake into equivalent governance weight. Votes are public and revisable until the voting period closes — this is a deliberate design choice: disputes are *transparent public hearings*, not secret ballots. New evidence can change votes. - -This transparency carries a known cost. Because the running tally is visible, a voter can defer until late in the window and condition its ballot on others' revealed positions (a last-mover advantage). The protocol accepts this tradeoff on purpose: for a DAO, the auditability and legitimacy of an open hearing are judged more valuable than ballot secrecy, and a flipped outcome still requires moving a stake-weighted majority. Manipulation is deterred not by secrecy but by (i) the stake-weighted approval threshold $\theta_{\text{approve}}$, (ii) a multi-day voting window that dilutes any single timing edge, and (iii) the resolver-reward alignment analyzed in §7.4. A commit-reveal ballot was explicitly considered and **rejected** for this reason — concealing votes would defeat the public-hearing property that gives the verdict its legitimacy. We revisit this as an honest limitation in §12.2. - -The dispute is upheld if the approval percentage exceeds a governance threshold $\theta_{\text{approve}}$: - -$$\frac{\sum_{v: \text{vote}_v = \text{approve}} w_v}{\sum_{v} w_v} \geq \frac{\theta_{\text{approve}}}{10000}$$ - -**Account mode ($\delta_{\text{mode}} = 1$).** A named dispute resolver (recommended: multisig) issues a binding verdict. - -### 7.3 Dispute Outcomes and Incentive Compatibility - -**Oracle wrong (dispute upheld):** - -$$\text{reward}_{\text{pool}} = \min(d \cdot \mu, I_o)$$ - -where $\mu$ is the reward multiplier. The reward pool is split: - -- Disputer receives $d$ (fee refund) + $\text{reward}_{\text{pool}} / \mu$ -- Resolver/voters receive $\text{reward}_{\text{pool}} - \text{reward}_{\text{pool}} / \mu$ -- Remaining insurance: additional penalty $\rightarrow$ DAO fund - -**Oracle right (dispute rejected):** - -$$d \rightarrow \text{50\% resolver, 50\% oracle}$$ - -**Oracle non-responsive (auto-close at 14 days):** - -- Disputer: fee refunded -- Oracle: $d$ slashed from insurance -- All bets and LP positions: full refund -- Slashed amount distributed proportionally to all participants - -### 7.4 Game-Theoretic Analysis - -**Oracle incentive compatibility.** The oracle's expected payoff from honest resolution vs. manipulation is: - -$$\mathbb{E}[\pi_{\text{honest}}] = f_{\text{fixed}} + f_{\text{oracle}} \quad \text{(per market)}$$ - -$$\mathbb{E}[\pi_{\text{dishonest}}] = p_{\text{detect}} \cdot (-I_o \cdot \theta_{\text{penalty}} - B_{\text{ban}}) + (1 - p_{\text{detect}}) \cdot g_{\text{manipulation}}$$ - -where $p_{\text{detect}}$ is the probability of dispute, $\theta_{\text{penalty}}$ is the insurance slash fraction, $B_{\text{ban}}$ is the present value of a ban (lost future revenue), and $g_{\text{manipulation}}$ is the one-time manipulation gain. - -Honest behavior is a Nash equilibrium when: - -$$f_{\text{fixed}} + f_{\text{oracle}} > (1 - p_{\text{detect}}) \cdot g_{\text{manipulation}} - p_{\text{detect}} \cdot (I_o \cdot \theta_{\text{penalty}} + B_{\text{ban}})$$ - -The protocol ensures this by requiring $I_o \gg g_{\text{manipulation}}$ and maintaining high $p_{\text{detect}}$ through public dispute visibility and committee participation incentives. - -**Disputer incentive.** A rational bettor files a dispute when the expected reward exceeds the dispute fee: - -$$\mathbb{E}[\text{reward}_{\text{dispute}}] = p_{\text{upheld}} \cdot (d + d \cdot (\mu - 1) / \mu) > d$$ - -This simplifies to $p_{\text{upheld}} > 1/\mu$, establishing a natural threshold for dispute filing. - -**Committee voting incentive.** In committee mode, voters participate because their Lazy Pool stake (and thus governance weight) directly earns a share of the resolver reward pool. The expected reward for voter $v$ is: - -$$\mathbb{E}[\pi_v] = \frac{w_v}{\sum w_j} \cdot \text{voter\_reward\_pool} \cdot p_{\text{upheld}}$$ - -### 7.5 No-Contest Declaration - -An oracle unable to verify an outcome may declare no-contest at reduced cost ($50\%$ of the dispute penalty from insurance). This creates a three-tier incentive gradient: - -| Action | Oracle Cost | Ban Risk | -|--------|------------|----------| -| Voluntary no-contest | $0.5 \cdot d$ from insurance | None | -| Dispute loss | $I_o \cdot \theta_{\text{penalty}} + \text{extra}$ | Yes | -| Missed deadline | $I_o \cdot \theta_{\text{miss}}$ | None | - -A no-contest declaration is itself disputable, with the resolver choosing from three outcomes (A wins, B wins, or confirm no-contest), preventing abuse. - ---- - -## 8. The VIZ Token in the Prediction Market Experiment - -### 8.1 Token Utility - -The VIZ token serves four distinct functions within the Onix Protocol ecosystem: - -1. **Medium of exchange.** All bets, LP deposits, oracle insurance, and dispute fees are denominated in VIZ. The protocol never mints tokens — it is strictly zero-sum at the consensus level. - -2. **Governance weight.** VIZ staked as vesting shares (or deposited in the Lazy Pool) confers voting power in DAO committee disputes and chain parameter governance. This creates direct utility for token holding beyond speculation. - -3. **Oracle collateral.** Oracle insurance bonds are denominated in VIZ. The bond must exceed the oracle's potential manipulation profit, creating a demand for token accumulation by oracle operators. - -4. **Dispute escrow.** Dispute fees are escrowed in VIZ, creating a cost for frivolous disputes and a reward mechanism for valid challenges. - -### 8.2 Token Demand Feedback Loop - -The protocol creates a structural demand cycle: - -$$\text{LP deposits} \xrightarrow{\text{lock}} \text{reduced circulating supply}$$ -$$\text{Oracle insurance} \xrightarrow{\text{lock}} \text{reduced circulating supply}$$ -$$\text{Active bets} \xrightarrow{\text{lock}} \text{reduced circulating supply}$$ -$$\text{Governance weight} \xrightarrow{\text{utility}} \text{demand for staking}$$ - -The total locked supply at any time is: - -$$S_{\text{locked}} = S_{\text{pool}} + \sum_o I_o + \sum_{\text{active bets}} a_i + \sum_{\text{disputes}} d_k$$ - -where $S_{\text{pool}}$ is the total withdrawal-locked Lazy-Pool balance (free *and* market-allocated portions are both locked for the deposit lock period), $I_o$ is oracle insurance, $a_i$ are active bet stakes, and $d_k$ are escrowed dispute fees. This locked supply reduces available circulating supply, potentially creating upward price pressure as protocol usage grows — the same bet every protocol-native token makes, here explicitly tied to measurable utility. - -### 8.3 Experimental Value of the Token - -The VIZ token in this experiment serves as a **measurement instrument**: - -- **Price as signal.** Token price movements in response to protocol events (market launches, high-volume resolutions, dispute outcomes) provide a continuous market-based assessment of the protocol's perceived value. -- **Governance participation rate.** The fraction of token holders participating in dispute votes measures the viability of DAO-based arbitration. -- **Lazy Pool accumulation rate.** The rate of pool deposits measures retail demand for risk-free LP yield, directly testing hypothesis Q3. - ---- - -## 9. Full Market Cycle: A Worked Example - -We trace a complete binary market lifecycle to illustrate the protocol's operation. - -### 9.1 Setup - -- **Market:** "Will event X occur by date Y?" (Binary: Yes/No) -- **Oracle:** $o$ with insurance $I_o = 5000$ VIZ -- **Seed liquidity:** $L = 200$ VIZ from creator -- **Fee parameters:** $\theta_{\text{oracle}} = 50$ bp, $\theta_{\text{creator}} = 50$ bp, $\theta_{\text{liq}} = 100$ bp -- **Lazy Pool:** $B_{\text{free}} = 10{,}000$ VIZ, $\alpha = 2\%$, allocates $a_{\text{alloc}} = 200$ VIZ - -> **Units.** Amounts are displayed in VIZ for readability, but the protocol stores and computes them as **integer milli-VIZ (mVIZ)**, with $1\text{ VIZ} = 1000\text{ mVIZ}$, and every division is floored (cf. the §5.2 remark). The fee lines below are shown in mVIZ so that $\lfloor\cdot\rfloor$ is exact; the VIZ equivalent is given alongside. This resolves the apparent rounding mismatch that arises if one floors over whole-VIZ quantities (e.g. $\lfloor 80\times 50/10000\rfloor$ is $0$ in VIZ but $400$ mVIZ $=0.4$ VIZ in the real units). - -### 9.2 Initialization - -$$R_A = 100, \quad R_B = 100, \quad k = 10{,}000$$ - -Lazy Pool auto-allocates 200 VIZ as additional LP: - -$$R_A = 200, \quad R_B = 200, \quad k = 40{,}000$$ - -### 9.3 Betting Phase - -- **Alice** bets $a_1 = 50$ VIZ on Yes (side A): - $$R'_A = 250, \quad R'_B = \lfloor 40000/250 \rfloor = 160, \quad w_1 = 200 - 160 = 40$$ - -- **Bob** bets $a_2 = 80$ VIZ on No (side B): - $$R'_B = 240, \quad R'_A = \lfloor 40000/240 \rfloor = 166, \quad w_2 = 250 - 166 = 84$$ - -Implied probability: $P(\text{Yes}) = 166 / 406 \approx 41\%$, $P(\text{No}) = 240 / 406 \approx 59\%$. - -### 9.4 Resolution - -Oracle declares **Yes** wins. Alice is the sole winner; Bob forfeits 80 VIZ. - -$$S_{\text{lose}} = 80{,}000 \text{ mVIZ} \;(80 \text{ VIZ})$$ -$$f_{\text{oracle}} = \lfloor 80{,}000 \times 50 / 10000 \rfloor = 400 \text{ mVIZ} \;(0.4 \text{ VIZ})$$ -$$f_{\text{creator}} = \lfloor 80{,}000 \times 50 / 10000 \rfloor = 400 \text{ mVIZ} \;(0.4 \text{ VIZ})$$ -$$f_{\text{liq}} = \lfloor 80{,}000 \times 100 / 10000 \rfloor = 800 \text{ mVIZ} \;(0.8 \text{ VIZ})$$ -$$W = 80{,}000 - 400 - 400 - 800 = 78{,}400 \text{ mVIZ} \;(78.4 \text{ VIZ})$$ - -**Alice's payout** (sole winner, $w_1 / w_1 = 1$): - -$$\pi_1 = 50{,}000 + \lfloor 78{,}400 \times 40/40 \rfloor - \tau_1 = 128{,}400 \text{ mVIZ} - \tau_1 \;(128.4 \text{ VIZ} - \tau_1)$$ - -**LP return:** - -- Creator LP: $200 \text{ VIZ principal} + \text{time-weighted share of } 0.8 \text{ VIZ fee pool}$ -- Lazy Pool LP: $200 \text{ VIZ principal} + \text{time-weighted share of } 0.8 \text{ VIZ fee pool}$ - -**Verification** (Theorem 1): - -$$\text{Money}_{\text{IN}} = 200 + 200 + 50 + 80 = 530$$ -$$\text{Money}_{\text{OUT}} = 200 + 200 + 50 + 78.4 + 0.4 + 0.4 + 0.8 = 530 \quad \checkmark$$ - -### 9.5 Dispute Scenario - -If Bob disputes within $\Delta t_{\text{grace}} = 12\text{h}$, paying $d = 10$ VIZ: - -1. All payouts freeze. -2. Oracle must respond within 12 hours. -3. In committee mode, all token holders vote (stake-weighted, public, revisable). -4. If dispute upheld: payouts recalculated with corrected outcome, oracle insurance slashed. -5. If dispute rejected: original payouts proceed, Bob loses 10 VIZ (split 50/50 to oracle and resolver). -6. If no resolution within 14 days: auto-close with full refunds. - ---- - -## 10. Anti-MEV: Batch and Commit-Reveal Betting - -### 10.1 MEV in Prediction Markets - -In continuous-time prediction markets, front-running and sandwich attacks extract value from bettors. When a large bet is broadcast to the mempool, an attacker can: - -1. Front-run: place a bet on the same side before the large bet, profiting from the price movement -2. Sandwich: place bets on both sides around the large bet - -### 10.2 Batch Settlement - -The protocol supports opt-in batch betting (binary markets). Bets submitted within an epoch of $E$ blocks are queued and settled at a **uniform price** at the epoch boundary. Only the net residual (aggregate demand difference) moves the AMM: - -$$\Delta R_A = \sum_{\text{batch}} a_{i,A} - \sum_{\text{batch}} a_{i,B}$$ - -This eliminates intra-batch ordering advantage: all bets in a batch receive identical pricing regardless of submission order. - -### 10.3 Commit-Reveal - -For stronger MEV protection, bettors may use a two-phase commit-reveal scheme: - -1. **Commit:** Submit $H(\text{bet} \| \text{nonce})$ with escrow. The bet's direction and amount are hidden. -2. **Reveal:** After the epoch closes, submit $(\text{bet}, \text{nonce})$ to execute at the batch price. - -Unrevealed commitments forfeit a governance-defined penalty percentage of the escrow, preventing spam commitments. - ---- - -## 11. Implementation - -### 11.1 Consensus-Level Operations on VIZ DLT - -The protocol is implemented as first-class consensus-validated operations on the VIZ distributed ledger — not smart contracts, not custom payloads. VIZ DLT provides ~3-second block times, Delegated Proof of Stake consensus, and named accounts (Graphene-style) with no general-purpose virtual machine. - -Every financial action (market creation, bet placement, oracle resolution, dispute filing, LP deposit/withdrawal) is a `pm_*` operation validated by every validator node. Invalid operations are rejected before block inclusion. - -| Layer | Examples | Consensus-Validated | -|-------|---------|---------------------| -| Protocol operations | `pm_create_market`, `pm_place_bet`, `pm_resolve_market`, `pm_dispute_create`, `pm_lazy_deposit` | Yes — every node validates | -| Virtual operations | `pm_payout`, `pm_dispute_finalize`, `pm_lazy_recall`, `pm_batch_settle` | Yes — deterministic, block-time | -| Metadata | Market descriptions, dispute evidence | No — client-side only | - -### 11.2 On-Chain State - -All protocol state resides in chainbase indexed objects. Key objects include: - -- `pm_market_object`: market configuration, CPMM reserves, fee parameters, state -- `pm_bet_object`: bettor positions with weight and time penalty -- `pm_liquidity_object`: LP positions with time-weight -- `pm_lazy_pool_object`: singleton pool state (balances, shares, reward accumulator) -- `pm_oracle_object`: oracle registration, insurance, reputation counters -- `pm_dispute_object`: dispute state and resolution - -Reputation metrics are computed on read (not stored), ensuring consistency without additional write operations. - -### 11.3 Governance Parameters - -All economic parameters (fees, penalties, insurance requirements, dispute windows, lazy pool settings) are delegate median-voted. Each elected validator publishes preferred values; the network computes the median. Parameters change without hard forks or deployments. Kill-switches allow governance to disable subsystems (leverage, commit-reveal) by median vote. - -### 11.4 Scalability - -Because every operation is consensus-validated, the protocol's cost model matters at scale (thousands of concurrent markets and bets). The design keeps per-block and per-operation work bounded: - -- **$O(1)$ user-facing operations.** Bet placement, cancellation, and LP deposit/withdraw touch a fixed number of indexed objects (the market, the bettor's position, and — for the pool — the singleton accumulator). None iterates over all participants. Reward distribution uses the MasterChef accumulator $\rho$ (§6.3), so a pool with $n$ depositors settles rewards in $O(1)$ per actor rather than $O(n)$ per resolution. -- **Bounded deferred work per block.** Settlement is the only fan-out step (a market with $m$ winning bets generates $m$ `pm_payout` virtual operations). To prevent a single large resolution from bloating a block, payout and cron processing are rate-limited by the median parameter `pm_processing_cap_per_block`: at most a fixed number of payouts/cron items are processed per block, and the remainder carry to subsequent blocks. Worst-case per-block work is therefore $O(\text{cap})$, independent of how many markets resolve in the same interval. -- **No write amplification from reputation.** The 14 oracle metrics are counters updated only on the oracle's own actions; the composite reliability score is computed **on read**, not written each block (§11.2). Market discovery metadata is built by a non-consensus plugin and never enters block validation. -- **State growth.** State is linear in live objects (markets, open positions, active disputes). Resolved markets and paid positions are terminal and prunable by clients; consensus retains only what open obligations require. - -The binding constraint at very high market counts is thus block space for settlement fan-out, which `pm_processing_cap_per_block` converts from a latency spike into bounded, amortized throughput rather than a consensus-halting cost. A quantitative stress simulation across many concurrent markets of varying volume and volatility is part of the experimental program (§12.3, H4). - ---- - -## 12. Discussion - -### 12.1 Comparison with Existing Approaches - -| Dimension | Onix Protocol | Polymarket (CLOB+UMA) | Standard LMSR | Uniswap-style AMM | -|-----------|---------------|----------------------|---------------|-------------------| -| LP risk | **Zero** (structural) | Inventory risk | Up to $b \ln N$ | Impermanent loss | -| LP knowledge required | Low (deposit) | High (manage orders) | Medium | Medium-High | -| Pricing continuity | Continuous (AMM/LMSR) | Discrete (order book) | Continuous | Continuous | -| Fee extraction | Losers only, at resolution | Bid-ask spread | Spread | Every trade | -| Oracle model | Bonded + DAO dispute | UMA Optimistic Oracle | Operator | N/A | -| Price coherence | Mathematical invariant | Arbitrage-dependent | Mathematical invariant | Mathematical invariant | -| CTF split/merge needed | No | Yes | No | No | - -### 12.2 Limitations and Honest Tradeoffs - -1. **LP yield is volume-dependent, not depth-dependent.** A market with high subsidy and low volume earns the same absolute fees as one with low subsidy and equal volume. The subsidy provides depth (lower slippage) but not yield. - -2. **LP profit is not guaranteed.** If a market resolves with zero losing bets, no fees are generated. LP principal is returned, but yield may be zero. - -3. **Parimutuel tokens are not fixed-value instruments.** Unlike standard LMSR where 1 winning token = 1 currency unit, Onix tokens are proportional claims on the losers' pool. If all bettors pick the winner, everyone breaks even. - -4. **DPoS governance tradeoffs.** The delegate-voted parameter model has known concentration risks inherent to DPoS (shared with EOS, Hive, Tron). The Lazy Pool governance-weight mechanism partially mitigates this by broadening the effective electorate. - -5. **Token liquidity dependency.** Economic guarantees (insurance bonds, dispute fees) scale with token market value. The protocol assumes utility drives demand — the standard assumption for protocol-native tokens. - -6. **Leverage adds bounded credit risk to the pool.** The opt-in leverage subsystem (§6.6) is the one component *not* covered by Theorem 1. When enabled, the pool acts as a lender and can incur a shortfall on the same-side-cancel path, bounded by borrower collateral and isolated to a governed fund fraction. We claim full loan recovery on the cascade and settlement paths as a *design property verified in simulation*, not as a proven theorem; a closed-form recovery proof under arbitrary reserve trajectories is open work. The subsystem is default-off and disableable by median vote, so the unconditional LP guarantee can always be restored. - -7. **Public dispute voting has a timing tradeoff.** Open, revisable committee ballots (§7.2) admit a last-mover advantage and were chosen deliberately over commit-reveal for auditability. This is a value judgment (transparency over secrecy), not a proof that open voting is manipulation-optimal; deployments that prioritize secrecy would need account-mode resolution instead. - -### 12.3 Experimental Hypotheses - -The deployed protocol is designed to test the following hypotheses: - -**H1 (Liquidity flywheel).** *Zero-risk LP provision attracts retail capital sufficient to produce market depth that meaningfully reduces slippage relative to comparable platforms.* - -Measurable: Lazy Pool total deposits, average market depth (reserve ratios), slippage per unit bet size. - -**H2 (Information aggregation).** *AMM/LMSR pricing with parimutuel settlement produces probability estimates of comparable accuracy to CLOB-based prediction markets for equivalent information conditions.* - -Measurable: Brier scores, calibration curves, comparison with external benchmarks (polls, models, other markets). - -**H3 (Oracle reliability under DAO governance).** *Bonded oracles with committee-mode dispute resolution achieve resolution accuracy comparable to centralized oracle services, with dispute rates below a sustainable threshold.* - -Measurable: Oracle dispute rate, dispute loss rate, average resolution time, reliability score distribution. - -**H4 (Lazy Pool sustainability).** *The graduated recall and fault stamp mechanisms prevent oracle exploitation of the Lazy Pool, maintaining positive net yield for pool depositors across diverse market portfolios.* - -Measurable: Pool NAV over time, recall frequency, fault stamp distribution, net yield per share. - -**H5 (Token demand correlation).** *Protocol usage (volume, market count, LP deposits) positively correlates with token staking demand and governance participation rate.* - -Measurable: Token staking rate, dispute vote participation, Lazy Pool deposit rate, correlation analysis. - -**H6 (Anti-MEV effectiveness).** *Batch settlement and commit-reveal mechanisms reduce measurable MEV extraction compared to continuous instant betting.* - -Measurable: Price impact asymmetry (batch vs. instant), sandwich attack frequency, bettor execution quality. - -**H7 (Leverage solvency).** *Under live trajectories, snapshot-based liquidation recovers the loan on the cascade and settlement paths, confining realized pool shortfall to the same-side-cancel path and within borrower collateral, so that leverage interest is net-accretive to pool yield.* (This tests the design property of §6.6 empirically, in lieu of a closed-form recovery proof.) - -Measurable: Realized loan-recovery ratio per liquidation event ($\text{recovered}/\lambda m$), frequency and magnitude of same-side-cancel shortfalls relative to posted collateral, leverage interest as a share of total pool yield, pool NAV with vs. without the leverage subsystem enabled. - ---- - -## 13. Conclusion - -We have presented the Onix Protocol, a prediction market architecture that achieves a structural LP principal guarantee by decoupling the pricing function (CPMM for binary, LMSR for multi-outcome) from the settlement function (parimutuel, losers-fund-winners). We proved that this guarantee holds for both market types regardless of weight assignment, fee parameters, or betting distribution. - -The "Lazy" Pool mechanism enables passive retail LP participation with automated capital deployment and graduated opportunity-cost protection. The two-mode dispute system — combining bonded oracles with stake-weighted DAO committee voting — provides a viable alternative to centralized arbitration while maintaining oracle incentive compatibility. - -The protocol is implemented as consensus-level operations on the VIZ distributed ledger and is designed as an experiment testing seven explicit hypotheses about liquidity bootstrapping, information aggregation accuracy, DAO governance viability, leverage solvency, and token demand dynamics. The opt-in leverage subsystem is the single component that trades the unconditional LP guarantee for bounded, default-off pool credit risk; everything else preserves the structural guarantee of Theorem 1. - -The mathematical properties are verifiable. The economic hypotheses will be tested by market participation. - ---- - -## References - -[1] Hanson, R. (2003). Combinatorial Information Market Design. *Information Systems Frontiers*, 5(1), 107–119. - -[2] Adams, H., Zinsmeister, N., & Robinson, D. (2020). *Uniswap v2 Core.* Uniswap Labs. - -[3] Adams, H., Zinsmeister, N., Salem, M., Keefer, R., & Robinson, D. (2021). *Uniswap v3 Core.* Uniswap Labs. - -[4] Berg, J., Forsythe, R., Nelson, F., & Rietz, T. (2008). Results from a Dozen Years of Election Futures Markets Research. In *Handbook of Experimental Economics Results* (Vol. 1, pp. 742–751). Elsevier. - -[5] Cowgill, B., Wolfers, J., & Zitzewitz, E. (2009). Using Prediction Markets to Track Information Flows: Evidence from Google. In *Auctions, Market Mechanisms and Their Applications (AMMA 2009)*, LNICST Vol. 14. Springer. - -[6] Levitt, S. D. (2004). Why Are Gambling Markets Organised So Differently from Financial Markets? *The Economic Journal*, 114(495), 223–246. - -[7] Gnosis. *Conditional Tokens Framework (CTF) Documentation.* - -[8] UMA Protocol. *Optimistic Oracle Documentation.* - -[9] Leshner, R., & Hayes, G. (2019). *Compound: The Money Market Protocol.* Compound Labs. - -[10] SushiSwap. (2020). *MasterChef Contract.* - -[11] Piskunov, A. *VIZ: Distributed Ledger Technical Description, Fair DPoS, and Governance.* VIZ-Blockchain. - -[12] Arrow, K. J., Forsythe, R., Gorham, M., Hahn, R., Hanson, R., Ledyard, J. O., et al. (2008). The Promise of Prediction Markets. *Science*, 320(5878), 877–878. - -[13] Wolfers, J., & Zitzewitz, E. (2004). Prediction Markets in Theory and Practice. *NBER Working Paper* No. 10248 (also *Journal of Economic Perspectives*, 18(2), 107–126). - -[14] Othman, A., Pennock, D. M., Reeves, D. M., & Sandholm, T. (2013). A Practical Liquidity-Sensitive Automated Market Maker. *ACM Transactions on Economics and Computation*, 1(3), Article 14. - -[15] Abernethy, J., Chen, Y., & Vaughan, J. W. (2011). An Optimization-Based Framework for Automated Market-Making. In *Proceedings of the 12th ACM Conference on Electronic Commerce (EC '11)* (pp. 297–306). diff --git a/.qoder/docs/onix-protocol-paper.pdf b/.qoder/docs/onix-protocol-paper.pdf deleted file mode 100644 index e146fd777f..0000000000 Binary files a/.qoder/docs/onix-protocol-paper.pdf and /dev/null differ diff --git a/.qoder/docs/op-account-market.md b/.qoder/docs/op-account-market.md deleted file mode 100644 index b084bae2c2..0000000000 --- a/.qoder/docs/op-account-market.md +++ /dev/null @@ -1,236 +0,0 @@ -# VIZ Blockchain — Account Market Operations - -Spec for implementing account sale operations in PHP/Node.js libraries. - -Account market operations allow accounts to be bought and sold. An account owner sets their account for sale, and a buyer can purchase it. - ---- - -## `set_account_price_operation` - -**Type ID:** `54` -**Required authority:** `master` of `account` - -Sets an account for sale or updates its sale parameters. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account being listed for sale | -| `account_seller` | `account_name_type` | yes | Account to receive payment | -| `account_offer_price` | `asset` (VIZ) | yes | Asking price | -| `account_on_sale` | `bool` | yes | `true` to list, `false` to delist | - -### JSON Example - -```json -[54, { - "account": "alice", - "account_seller": "alice", - "account_offer_price": "1000.000 VIZ", - "account_on_sale": true -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'set_account_price_operation', - 'value' => [ - 'account' => 'alice', - 'account_seller' => 'alice', - 'account_offer_price' => '1000.000 VIZ', - 'account_on_sale' => true, - ], -]; -``` - -### Node.js Example - -```js -const op = ['set_account_price', { - account: 'alice', - account_seller: 'alice', - account_offer_price: '1000.000 VIZ', - account_on_sale: true, -}]; -``` - -### Checklist -- [ ] `account_offer_price.symbol` must be `VIZ` -- [ ] `account_offer_price.amount` > 0 -- [ ] `account_on_sale: false` delists the account -- [ ] Fee (`account_on_sale_fee`) is charged when listing -- [ ] `account_seller` can differ from `account` (payment redirected) -- [ ] Sign with `account`'s master key - ---- - -## `set_subaccount_price_operation` - -**Type ID:** `55` -**Required authority:** `master` of `account` - -Lists the right to create subaccounts of `account` for sale. A "subaccount" of `alice` would be `alice.bob`. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Parent account | -| `subaccount_seller` | `account_name_type` | yes | Account to receive payment | -| `subaccount_offer_price` | `asset` (VIZ) | yes | Price per subaccount creation | -| `subaccount_on_sale` | `bool` | yes | `true` to list, `false` to delist | - -### JSON Example - -```json -[55, { - "account": "alice", - "subaccount_seller": "alice", - "subaccount_offer_price": "50.000 VIZ", - "subaccount_on_sale": true -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'set_subaccount_price_operation', - 'value' => [ - 'account' => 'alice', - 'subaccount_seller' => 'alice', - 'subaccount_offer_price' => '50.000 VIZ', - 'subaccount_on_sale' => true, - ], -]; -``` - -### Checklist -- [ ] `subaccount_offer_price.symbol` must be `VIZ` -- [ ] `subaccount_on_sale: false` delists -- [ ] Fee (`subaccount_on_sale_fee`) is charged when listing -- [ ] Sign with `account`'s master key - ---- - -## `buy_account_operation` - -**Type ID:** `56` -**Required authority:** `active` of `buyer` - -Purchases an account that is listed for sale. All authorities are transferred to the buyer. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `buyer` | `account_name_type` | yes | Purchasing account | -| `account` | `account_name_type` | yes | Account being purchased | -| `account_offer_price` | `asset` (VIZ) | yes | Purchase price (must match listing) | -| `account_authorities_key` | `public_key_type` | yes | New key set as all authorities of purchased account | -| `tokens_to_shares` | `asset` (VIZ) | yes | Additional VIZ to convert to SHARES for bought account | - -### JSON Example - -```json -[56, { - "buyer": "bob", - "account": "alice", - "account_offer_price": "1000.000 VIZ", - "account_authorities_key": "VIZ5newowner...", - "tokens_to_shares": "0.000 VIZ" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'buy_account_operation', - 'value' => [ - 'buyer' => 'bob', - 'account' => 'alice', - 'account_offer_price' => '1000.000 VIZ', - 'account_authorities_key' => 'VIZ5newowner...', - 'tokens_to_shares' => '0.000 VIZ', - ], -]; -``` - -### Node.js Example - -```js -const op = ['buy_account', { - buyer: 'bob', - account: 'alice', - account_offer_price: '1000.000 VIZ', - account_authorities_key: 'VIZ5newowner...', - tokens_to_shares: '0.000 VIZ', -}]; -``` - -### Checklist -- [ ] `account` must be currently listed for sale (`account_on_sale: true`) -- [ ] `account_offer_price` must exactly match the listed price -- [ ] `account_authorities_key` is set as master, active, regular, and memo key -- [ ] `tokens_to_shares.symbol` must be `VIZ` -- [ ] `tokens_to_shares.amount` >= 0 (can be 0) -- [ ] Payment goes to `account_seller` as specified in the listing -- [ ] Virtual `account_sale_operation` fires on successful purchase -- [ ] Sign with `buyer`'s active key - ---- - -## `target_account_sale_operation` - -**Type ID:** `61` -**Required authority:** `master` of `account` - -Lists an account for sale to a specific buyer only (private/targeted sale). Added in HF11. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account being listed | -| `account_seller` | `account_name_type` | yes | Account to receive payment | -| `target_buyer` | `account_name_type` | yes | Only this account can buy | -| `account_offer_price` | `asset` (VIZ) | yes | Asking price | -| `account_on_sale` | `bool` | yes | `true` to list, `false` to delist | - -### JSON Example - -```json -[61, { - "account": "alice", - "account_seller": "alice", - "target_buyer": "charlie", - "account_offer_price": "500.000 VIZ", - "account_on_sale": true -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'target_account_sale_operation', - 'value' => [ - 'account' => 'alice', - 'account_seller' => 'alice', - 'target_buyer' => 'charlie', - 'account_offer_price' => '500.000 VIZ', - 'account_on_sale' => true, - ], -]; -``` - -### Checklist -- [ ] Only `target_buyer` can purchase this account via `buy_account_operation` -- [ ] `account_offer_price.symbol` must be `VIZ` -- [ ] `account_on_sale: false` delists the targeted sale -- [ ] Sign with `account`'s master key diff --git a/.qoder/docs/op-account.md b/.qoder/docs/op-account.md deleted file mode 100644 index 397d8288a0..0000000000 --- a/.qoder/docs/op-account.md +++ /dev/null @@ -1,199 +0,0 @@ -# VIZ Blockchain — Account Operations - -Spec for implementing account-related operations in PHP/Node.js libraries. - ---- - -## `account_create_operation` - -**Type ID:** `20` -**Required authority:** `active` of `creator` - -Creates a new blockchain account. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `fee` | `asset` (VIZ) | yes | Creation fee (converted to SHARES for new account) | -| `delegation` | `asset` (SHARES) | yes | Initial SHARES delegation to new account | -| `creator` | `account_name_type` | yes | Account paying the fee and creating | -| `new_account_name` | `account_name_type` | yes | Name for the new account | -| `master` | `authority` | yes | Master authority for new account | -| `active` | `authority` | yes | Active authority for new account | -| `regular` | `authority` | yes | Regular authority for new account | -| `memo_key` | `public_key_type` | yes | Memo public key | -| `json_metadata` | `string` | yes | JSON metadata (may be empty `""`) | -| `referrer` | `account_name_type` | yes | Referrer account name (may be empty `""`) | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### JSON Example - -```json -[20, { - "fee": "1.000 VIZ", - "delegation": "10.000000 SHARES", - "creator": "alice", - "new_account_name": "bob", - "master": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9", 1]] - }, - "active": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9", 1]] - }, - "regular": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9", 1]] - }, - "memo_key": "VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9", - "json_metadata": "", - "referrer": "", - "extensions": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'account_create_operation', - 'value' => [ - 'fee' => '1.000 VIZ', - 'delegation' => '10.000000 SHARES', - 'creator' => 'alice', - 'new_account_name' => 'bob', - 'master' => ['weight_threshold' => 1, 'account_auths' => [], 'key_auths' => [['VIZ5hq...', 1]]], - 'active' => ['weight_threshold' => 1, 'account_auths' => [], 'key_auths' => [['VIZ5hq...', 1]]], - 'regular' => ['weight_threshold' => 1, 'account_auths' => [], 'key_auths' => [['VIZ5hq...', 1]]], - 'memo_key' => 'VIZ5hq...', - 'json_metadata' => '', - 'referrer' => '', - 'extensions' => [], - ], -]; -``` - -### Node.js Example - -```js -const op = ['account_create', { - fee: '1.000 VIZ', - delegation: '10.000000 SHARES', - creator: 'alice', - new_account_name: 'bob', - master: { weight_threshold: 1, account_auths: [], key_auths: [['VIZ5hq...', 1]] }, - active: { weight_threshold: 1, account_auths: [], key_auths: [['VIZ5hq...', 1]] }, - regular: { weight_threshold: 1, account_auths: [], key_auths: [['VIZ5hq...', 1]] }, - memo_key: 'VIZ5hq...', - json_metadata: '', - referrer: '', - extensions: [], -}]; -``` - -### Checklist -- [ ] `fee.symbol` must be `VIZ` -- [ ] `delegation.symbol` must be `SHARES` -- [ ] `new_account_name` must pass `is_valid_create_account_name` validation -- [ ] All three authorities must be provided (even if identical) -- [ ] `memo_key` must be a valid VIZ public key -- [ ] `fee` >= chain `account_creation_fee` property -- [ ] Sign with `creator`'s active key - ---- - -## `account_update_operation` - -**Type ID:** `5` -**Required authority:** `master` of `account` (if changing master key), else `active` - -Updates account keys and metadata. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account to update | -| `master` | `optional` | no | New master authority (triggers master-auth requirement) | -| `active` | `optional` | no | New active authority | -| `regular` | `optional` | no | New regular authority | -| `memo_key` | `public_key_type` | yes | New memo key | -| `json_metadata` | `string` | yes | New JSON metadata | - -### JSON Example - -```json -[5, { - "account": "alice", - "active": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5new...", 1]] - }, - "memo_key": "VIZ5new...", - "json_metadata": "{\"profile\":\"updated\"}" -}] -``` - -### Checklist -- [ ] `master` field is **optional** — omit (or set to `null`) if not changing master key -- [ ] If `master` is present → sign with current **master** key -- [ ] If `master` is absent → sign with current **active** key -- [ ] `memo_key` is always required (even if unchanged) - ---- - -## `account_metadata_operation` - -**Type ID:** `21` -**Required authority:** `regular` of `account` - -Updates only the account's JSON metadata. Cheaper in bandwidth than full `account_update`. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account to update | -| `json_metadata` | `string` | yes | New JSON metadata string | - -### JSON Example - -```json -[21, { - "account": "alice", - "json_metadata": "{\"name\":\"Alice\",\"about\":\"Hello!\"}" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'account_metadata_operation', - 'value' => [ - 'account' => 'alice', - 'json_metadata' => json_encode(['name' => 'Alice', 'about' => 'Hello!']), - ], -]; -``` - -### Node.js Example - -```js -const op = ['account_metadata', { - account: 'alice', - json_metadata: JSON.stringify({ name: 'Alice', about: 'Hello!' }), -}]; -``` - -### Checklist -- [ ] `json_metadata` must be valid UTF-8 -- [ ] Max metadata size limited by bandwidth -- [ ] Sign with `account`'s regular key -- [ ] Faster/cheaper than `account_update` for metadata-only changes diff --git a/.qoder/docs/op-award.md b/.qoder/docs/op-award.md deleted file mode 100644 index 4a914c605f..0000000000 --- a/.qoder/docs/op-award.md +++ /dev/null @@ -1,162 +0,0 @@ -# VIZ Blockchain — Award Operations - -Spec for implementing award operations in PHP/Node.js libraries. - -Awards are the primary social reward mechanism in VIZ. An account spends "energy" to award SHARES directly to another account (and optionally to beneficiaries). - ---- - -## `award_operation` - -**Type ID:** `47` -**Required authority:** `regular` of `initiator` - -Awards SHARES to `receiver` from the reward pool, proportional to the initiator's energy expenditure and SHARES stake. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `initiator` | `account_name_type` | yes | Account giving the award | -| `receiver` | `account_name_type` | yes | Account receiving the award | -| `energy` | `uint16_t` | yes | Energy to spend (basis points: 1–10000) | -| `custom_sequence` | `uint64_t` | yes | Application-defined sequence number | -| `memo` | `string` | yes | Optional message/reason | -| `beneficiaries` | `vector` | yes | Optional beneficiaries receiving a share | - -### JSON Example - -```json -[47, { - "initiator": "alice", - "receiver": "bob", - "energy": 1000, - "custom_sequence": 0, - "memo": "great article!", - "beneficiaries": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'award_operation', - 'value' => [ - 'initiator' => 'alice', - 'receiver' => 'bob', - 'energy' => 1000, - 'custom_sequence' => 0, - 'memo' => 'great article!', - 'beneficiaries' => [], - ], -]; -``` - -### Node.js Example - -```js -const op = ['award', { - initiator: 'alice', - receiver: 'bob', - energy: 1000, - custom_sequence: 0, - memo: 'great article!', - beneficiaries: [], -}]; -``` - -### Checklist -- [ ] `energy` range: 1–10000 (1 = 0.01%, 10000 = 100%) -- [ ] Energy regenerates at 100%/day (CHAIN_ENERGY_REGENERATION_SECONDS) -- [ ] Beneficiary weights sum must be <= 10000 (100%) -- [ ] Beneficiaries list must be sorted by account name ascending -- [ ] If beneficiaries are present, `receiver` gets `(1 - sum_beneficiary_weights/10000)` share -- [ ] `custom_sequence` is app-defined, can be 0 -- [ ] Sign with `initiator`'s regular key -- [ ] Virtual `receive_award_operation` fires for `receiver` -- [ ] Virtual `benefactor_award_operation` fires for each beneficiary - ---- - -## `fixed_award_operation` - -**Type ID:** `60` -**Required authority:** `regular` of `initiator` - -Awards a **fixed amount** of SHARES to `receiver`, spending energy proportionally based on the desired amount. Added in HF11. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `initiator` | `account_name_type` | yes | Account giving the award | -| `receiver` | `account_name_type` | yes | Account receiving the award | -| `reward_amount` | `asset` (SHARES) | yes | Fixed amount of SHARES to award | -| `max_energy` | `uint16_t` | yes | Maximum energy to spend (0 = no limit) | -| `custom_sequence` | `uint64_t` | yes | Application-defined sequence number | -| `memo` | `string` | yes | Optional message/reason | -| `beneficiaries` | `vector` | yes | Optional beneficiaries | - -### JSON Example - -```json -[60, { - "initiator": "alice", - "receiver": "bob", - "reward_amount": "10.000000 SHARES", - "max_energy": 5000, - "custom_sequence": 1, - "memo": "fixed reward", - "beneficiaries": [ - {"account": "charlie", "weight": 1000} - ] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'fixed_award_operation', - 'value' => [ - 'initiator' => 'alice', - 'receiver' => 'bob', - 'reward_amount' => '10.000000 SHARES', - 'max_energy' => 5000, - 'custom_sequence' => 1, - 'memo' => 'fixed reward', - 'beneficiaries' => [ - ['account' => 'charlie', 'weight' => 1000], - ], - ], -]; -``` - -### Node.js Example - -```js -const op = ['fixed_award', { - initiator: 'alice', - receiver: 'bob', - reward_amount: '10.000000 SHARES', - max_energy: 5000, - custom_sequence: 1, - memo: 'fixed reward', - beneficiaries: [ - { account: 'charlie', weight: 1000 }, - ], -}]; -``` - -### Checklist -- [ ] `reward_amount.symbol` must be `SHARES` -- [ ] `reward_amount.amount` must be > 0 -- [ ] `max_energy` = 0 means no energy cap (spend as much as needed) -- [ ] `max_energy` = 1–10000 limits maximum energy expenditure -- [ ] Actual energy spent depends on initiator's stake and current reward pool -- [ ] Beneficiary weights sum must be <= 10000 (100%) -- [ ] Beneficiaries list must be sorted by account name ascending -- [ ] Sign with `initiator`'s regular key -- [ ] Virtual `receive_award_operation` fires for `receiver` -- [ ] Virtual `benefactor_award_operation` fires for each beneficiary diff --git a/.qoder/docs/op-committee.md b/.qoder/docs/op-committee.md deleted file mode 100644 index 2f4c18191c..0000000000 --- a/.qoder/docs/op-committee.md +++ /dev/null @@ -1,193 +0,0 @@ -# VIZ Blockchain — Committee Operations - -Spec for implementing committee (worker proposal) operations in PHP/Node.js libraries. - -The committee mechanism allows community governance: anyone can create worker requests for funding, and VIZ SHARES holders vote to approve or reject them. - ---- - -## `committee_worker_create_request_operation` - -**Type ID:** `35` -**Required authority:** `regular` of `creator` - -Creates a new committee worker request (funding proposal). - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `creator` | `account_name_type` | yes | Account creating the request | -| `url` | `string` | yes | URL describing the work/proposal | -| `worker` | `account_name_type` | yes | Account that will receive the payout | -| `required_amount_min` | `asset` (VIZ) | yes | Minimum acceptable payout | -| `required_amount_max` | `asset` (VIZ) | yes | Maximum acceptable payout | -| `duration` | `uint32_t` | yes | Request duration in seconds | - -### Constraints - -| Parameter | Value | Description | -|---|---|---| -| `COMMITTEE_MIN_DURATION` | 5 days | Minimum duration | -| `COMMITTEE_MAX_DURATION` | 30 days | Maximum duration | -| `COMMITTEE_MAX_REQUIRED_AMOUNT` | chain configured | Max tokens per request | - -### JSON Example - -```json -[35, { - "creator": "alice", - "url": "https://alice.example.com/proposal", - "worker": "alice", - "required_amount_min": "100.000 VIZ", - "required_amount_max": "500.000 VIZ", - "duration": 604800 -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'committee_worker_create_request_operation', - 'value' => [ - 'creator' => 'alice', - 'url' => 'https://alice.example.com/proposal', - 'worker' => 'alice', - 'required_amount_min' => '100.000 VIZ', - 'required_amount_max' => '500.000 VIZ', - 'duration' => 604800, - ], -]; -``` - -### Node.js Example - -```js -const op = ['committee_worker_create_request', { - creator: 'alice', - url: 'https://alice.example.com/proposal', - worker: 'alice', - required_amount_min: '100.000 VIZ', - required_amount_max: '500.000 VIZ', - duration: 604800, -}]; -``` - -### Checklist -- [ ] `url.size()` must be > 0 and < 256 characters -- [ ] `required_amount_min.symbol` must be `VIZ` -- [ ] `required_amount_max.symbol` must be `VIZ` -- [ ] `required_amount_min.amount` >= 0 -- [ ] `required_amount_max.amount` > `required_amount_min.amount` -- [ ] `duration` in range `[COMMITTEE_MIN_DURATION, COMMITTEE_MAX_DURATION]` -- [ ] Fee (`committee_create_request_fee`) is charged to creator -- [ ] Sign with `creator`'s regular key - ---- - -## `committee_worker_cancel_request_operation` - -**Type ID:** `36` -**Required authority:** `regular` of `creator` - -Cancels an existing committee worker request before it expires. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `creator` | `account_name_type` | yes | Creator of the request | -| `request_id` | `uint32_t` | yes | ID of the request to cancel | - -### JSON Example - -```json -[36, { - "creator": "alice", - "request_id": 42 -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'committee_worker_cancel_request_operation', - 'value' => [ - 'creator' => 'alice', - 'request_id' => 42, - ], -]; -``` - -### Node.js Example - -```js -const op = ['committee_worker_cancel_request', { - creator: 'alice', - request_id: 42, -}]; -``` - -### Checklist -- [ ] Only the `creator` of the request can cancel it -- [ ] `request_id` must refer to an existing active request -- [ ] Sign with `creator`'s regular key - ---- - -## `committee_vote_request_operation` - -**Type ID:** `37` -**Required authority:** `regular` of `voter` - -Votes on a committee worker request. Positive = support, negative = oppose. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `voter` | `account_name_type` | yes | Account casting the vote | -| `request_id` | `uint32_t` | yes | ID of the request to vote on | -| `vote_percent` | `int16_t` | yes | Vote weight (-10000 to 10000) | - -### JSON Example - -```json -[37, { - "voter": "bob", - "request_id": 42, - "vote_percent": 10000 -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'committee_vote_request_operation', - 'value' => [ - 'voter' => 'bob', - 'request_id' => 42, - 'vote_percent' => 10000, - ], -]; -``` - -### Node.js Example - -```js -const op = ['committee_vote_request', { - voter: 'bob', - request_id: 42, - vote_percent: 10000, -}]; -``` - -### Checklist -- [ ] `vote_percent` range: -10000 (strong oppose) to 10000 (strong support) -- [ ] `vote_percent == 0` removes vote -- [ ] Voting power weighted by voter's SHARES -- [ ] Request is approved when net vote percent >= `committee_request_approve_min_percent` -- [ ] Sign with `voter`'s regular key diff --git a/.qoder/docs/op-content.md b/.qoder/docs/op-content.md deleted file mode 100644 index 8229f47611..0000000000 --- a/.qoder/docs/op-content.md +++ /dev/null @@ -1,157 +0,0 @@ -# VIZ Blockchain — Content & Custom Operations - -Spec for implementing content and custom operations in PHP/Node.js libraries. - -> **Note:** `vote_operation` (ID 0), `content_operation` (ID 1), and `delete_content_operation` (ID 9) are **deprecated**. They remain in the operation variant for historical compatibility but should not be used in new code. Shown here for completeness. - ---- - -## `content_operation` *(deprecated)* - -**Type ID:** `1` -**Required authority:** `regular` of `author` - -Creates or updates content (post or comment). - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `parent_author` | `account_name_type` | yes | Author of parent content (empty `""` for root post) | -| `parent_permlink` | `string` | yes | Permlink of parent (category/tag for root posts) | -| `author` | `account_name_type` | yes | Content author | -| `permlink` | `string` | yes | Unique identifier per author | -| `title` | `string` | yes | Post title | -| `body` | `string` | yes | Post body (Markdown) | -| `curation_percent` | `int16_t` | yes | Curation reward share in basis points (0–10000) | -| `json_metadata` | `string` | yes | JSON metadata | -| `extensions` | `content_extensions_type` | yes | Optional beneficiaries | - -### Beneficiaries Extension - -To add beneficiaries, add to `extensions`: -```json -[ - [0, { - "beneficiaries": [ - {"account": "bob", "weight": 2500} - ] - }] -] -``` - -### Checklist -- [ ] `permlink` must be unique per author -- [ ] `parent_author == ""` → root post; otherwise comment -- [ ] `curation_percent` range 0–10000 (must be within chain min/max) -- [ ] Beneficiary weights sum must be <= 10000 (100%) -- [ ] Beneficiaries list must be sorted by account name ascending -- [ ] **Deprecated** — avoid creating new content with this operation - ---- - -## `vote_operation` *(deprecated)* - -**Type ID:** `0` -**Required authority:** `regular` of `voter` - -Casts a vote on content. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `voter` | `account_name_type` | yes | Voting account | -| `author` | `account_name_type` | yes | Content author | -| `permlink` | `string` | yes | Content permlink | -| `weight` | `int16_t` | yes | Vote weight (-10000 to 10000) | - -### Checklist -- [ ] `weight` > 0 → upvote; `weight` < 0 → flag/downvote -- [ ] `weight == 0` → remove vote -- [ ] Flag votes may cost extra energy (see `flag_energy_additional_cost` chain property) -- [ ] **Deprecated** — avoid in new code - ---- - -## `delete_content_operation` *(deprecated)* - -**Type ID:** `9` -**Required authority:** `regular` of `author` - -Deletes a piece of content. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `author` | `account_name_type` | yes | Content author | -| `permlink` | `string` | yes | Content permlink to delete | - -### Checklist -- [ ] Content must have no pending payout to be deletable -- [ ] **Deprecated** — avoid in new code - ---- - -## `custom_operation` - -**Type ID:** `10` -**Required authority:** `active` or `regular` of signers - -Posts arbitrary JSON data to the blockchain. Used by applications for custom logic/protocols. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `required_active_auths` | `flat_set` | yes | Accounts requiring active auth | -| `required_regular_auths` | `flat_set` | yes | Accounts requiring regular auth | -| `id` | `string` | yes | Application-defined ID (max 32 characters) | -| `json` | `string` | yes | Valid UTF-8 JSON string | - -### JSON Example - -```json -[10, { - "required_active_auths": [], - "required_regular_auths": ["alice"], - "id": "my_app", - "json": "{\"action\":\"follow\",\"target\":\"bob\"}" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'custom_operation', - 'value' => [ - 'required_active_auths' => [], - 'required_regular_auths' => ['alice'], - 'id' => 'my_app', - 'json' => json_encode(['action' => 'follow', 'target' => 'bob']), - ], -]; -``` - -### Node.js Example - -```js -const op = ['custom', { - required_active_auths: [], - required_regular_auths: ['alice'], - id: 'my_app', - json: JSON.stringify({ action: 'follow', target: 'bob' }), -}]; -``` - -### Checklist -- [ ] `id` must be <= 32 characters -- [ ] `json` must be valid UTF-8 JSON -- [ ] At least one of `required_active_auths` or `required_regular_auths` must be non-empty -- [ ] `required_active_auths` entries → sign with those accounts' active keys -- [ ] `required_regular_auths` entries → sign with those accounts' regular keys -- [ ] Both `required_active_auths` and `required_regular_auths` may be populated simultaneously -- [ ] Data operations may cost additional bandwidth (see `data_operations_cost_additional_bandwidth`) -- [ ] `json` field is considered a "data operation" for bandwidth purposes diff --git a/.qoder/docs/op-escrow.md b/.qoder/docs/op-escrow.md deleted file mode 100644 index 26e99f18df..0000000000 --- a/.qoder/docs/op-escrow.md +++ /dev/null @@ -1,224 +0,0 @@ -# VIZ Blockchain — Escrow Operations - -Spec for implementing escrow-related operations in PHP/Node.js libraries. - -Escrow allows conditional transfers: funds are held in escrow until approved by both parties, or resolved by an agent in case of dispute. - ---- - -## Escrow Flow - -``` -escrow_transfer → escrow_approve (by agent & to) - → [escrow_dispute] → escrow_release (by agent) - → escrow_release (by from or to) - (expire) → expire_escrow_ratification_operation [virtual] -``` - ---- - -## `escrow_transfer_operation` - -**Type ID:** `15` -**Required authority:** `active` of `from` - -Creates an escrow transfer proposal. Funds leave `from` into escrow balance. Both `agent` and `to` must approve before funds can be released. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from` | `account_name_type` | yes | Sender | -| `to` | `account_name_type` | yes | Intended recipient | -| `agent` | `account_name_type` | yes | Escrow agent (arbitrator) | -| `escrow_id` | `uint32_t` | yes | Unique ID (chosen by sender), default 30 | -| `token_amount` | `asset` (VIZ) | yes | Amount held in escrow | -| `fee` | `asset` (VIZ) | yes | Agent fee (paid on approval) | -| `ratification_deadline` | `time_point_sec` | yes | Deadline for agent & to to approve | -| `escrow_expiration` | `time_point_sec` | yes | When escrow expires if not released | -| `json_metadata` | `string` | yes | Optional metadata / terms | - -### JSON Example - -```json -[15, { - "from": "alice", - "to": "bob", - "agent": "charlie", - "escrow_id": 1001, - "token_amount": "100.000 VIZ", - "fee": "1.000 VIZ", - "ratification_deadline": "2024-06-01T00:00:00", - "escrow_expiration": "2024-07-01T00:00:00", - "json_metadata": "{\"description\":\"payment for work\"}" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'escrow_transfer_operation', - 'value' => [ - 'from' => 'alice', - 'to' => 'bob', - 'agent' => 'charlie', - 'escrow_id' => 1001, - 'token_amount' => '100.000 VIZ', - 'fee' => '1.000 VIZ', - 'ratification_deadline' => '2024-06-01T00:00:00', - 'escrow_expiration' => '2024-07-01T00:00:00', - 'json_metadata' => json_encode(['description' => 'payment for work']), - ], -]; -``` - -### Node.js Example - -```js -const op = ['escrow_transfer', { - from: 'alice', - to: 'bob', - agent: 'charlie', - escrow_id: 1001, - token_amount: '100.000 VIZ', - fee: '1.000 VIZ', - ratification_deadline: '2024-06-01T00:00:00', - escrow_expiration: '2024-07-01T00:00:00', - json_metadata: JSON.stringify({ description: 'payment for work' }), -}]; -``` - -### Checklist -- [ ] `token_amount.symbol` must be `VIZ` -- [ ] `fee.symbol` must be `VIZ` -- [ ] `token_amount.amount` must be > 0 -- [ ] `ratification_deadline` must be before `escrow_expiration` -- [ ] Both deadlines must be in the future at time of broadcast -- [ ] `escrow_id` must be unique for the `from` account -- [ ] If not approved before `ratification_deadline`, virtual `expire_escrow_ratification_operation` fires and funds return -- [ ] Sign with `from`'s active key - ---- - -## `escrow_approve_operation` - -**Type ID:** `18` -**Required authority:** `active` of `who` - -Approves (or rejects) an escrow transfer. Both `to` and `agent` must approve. Once approved, approval cannot be revoked. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from` | `account_name_type` | yes | Original escrow sender | -| `to` | `account_name_type` | yes | Original escrow recipient | -| `agent` | `account_name_type` | yes | Escrow agent | -| `who` | `account_name_type` | yes | Who is approving (`to` or `agent`) | -| `escrow_id` | `uint32_t` | yes | Escrow ID | -| `approve` | `bool` | yes | `true` to approve, `false` to reject | - -### JSON Example - -```json -[18, { - "from": "alice", - "to": "bob", - "agent": "charlie", - "who": "bob", - "escrow_id": 1001, - "approve": true -}] -``` - -### Checklist -- [ ] `who` must be either `to` or `agent` -- [ ] Once approved, cannot be undone -- [ ] If `approve: false` → escrow is cancelled, funds returned to `from` -- [ ] Escrow is only active once both `to` and `agent` have approved -- [ ] Must be done before `ratification_deadline` -- [ ] Sign with `who`'s active key - ---- - -## `escrow_dispute_operation` - -**Type ID:** `16` -**Required authority:** `active` of `who` - -Raises a dispute on an approved escrow. Once disputed, only the `agent` can release funds. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from` | `account_name_type` | yes | Original escrow sender | -| `to` | `account_name_type` | yes | Original escrow recipient | -| `agent` | `account_name_type` | yes | Escrow agent | -| `who` | `account_name_type` | yes | Who is raising the dispute (`from` or `to`) | -| `escrow_id` | `uint32_t` | yes | Escrow ID | - -### JSON Example - -```json -[16, { - "from": "alice", - "to": "bob", - "agent": "charlie", - "who": "alice", - "escrow_id": 1001 -}] -``` - -### Checklist -- [ ] Dispute can only be raised on an **approved** escrow (both parties approved) -- [ ] Dispute can be raised before or on the `escrow_expiration` deadline -- [ ] `who` must be `from` or `to` -- [ ] Once disputed, only `agent` can release via `escrow_release_operation` -- [ ] Sign with `who`'s active key - ---- - -## `escrow_release_operation` - -**Type ID:** `17` -**Required authority:** `active` of `who` - -Releases escrow funds to `receiver`. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from` | `account_name_type` | yes | Original escrow sender | -| `to` | `account_name_type` | yes | Original escrow recipient | -| `agent` | `account_name_type` | yes | Escrow agent | -| `who` | `account_name_type` | yes | Account releasing funds | -| `receiver` | `account_name_type` | yes | Account that receives the funds | -| `escrow_id` | `uint32_t` | yes | Escrow ID | -| `token_amount` | `asset` (VIZ) | yes | Amount to release | - -### JSON Example - -```json -[17, { - "from": "alice", - "to": "bob", - "agent": "charlie", - "who": "alice", - "receiver": "bob", - "escrow_id": 1001, - "token_amount": "100.000 VIZ" -}] -``` - -### Checklist -- [ ] `token_amount.symbol` must be `VIZ` -- [ ] Release permission rules: - - No dispute, before expiration: `from` can release to `to`; `to` can release to `from` - - No dispute, after expiration: either party can release to either party - - Disputed: only `agent` can release to either party -- [ ] `receiver` must be `from` or `to` -- [ ] `token_amount` can be partial — remaining stays in escrow -- [ ] Sign with `who`'s active key diff --git a/.qoder/docs/op-invite.md b/.qoder/docs/op-invite.md deleted file mode 100644 index f73d3b93f9..0000000000 --- a/.qoder/docs/op-invite.md +++ /dev/null @@ -1,219 +0,0 @@ -# VIZ Blockchain — Invite Operations - -Spec for implementing invite-related operations in PHP/Node.js libraries. - -Invites allow existing VIZ users to onboard new users without requiring them to have an existing account. An invite is a one-time-use key with a VIZ balance. - ---- - -## `create_invite_operation` - -**Type ID:** `43` -**Required authority:** `active` of `creator` - -Creates an invite link by generating a key and funding it with VIZ tokens. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `creator` | `account_name_type` | yes | Account creating the invite | -| `balance` | `asset` (VIZ) | yes | Amount of VIZ to lock in the invite | -| `invite_key` | `public_key_type` | yes | Public key of the invite (secret = private key) | - -### JSON Example - -```json -[43, { - "creator": "alice", - "balance": "5.000 VIZ", - "invite_key": "VIZ5invite..." -}] -``` - -### PHP Example - -```php -// Generate invite key pair first: -// $privateKey = '...' // keep secret, share as invite link -// $publicKey = derived from privateKey - -$op = [ - 'type' => 'create_invite_operation', - 'value' => [ - 'creator' => 'alice', - 'balance' => '5.000 VIZ', - 'invite_key' => 'VIZ5invite...', - ], -]; -``` - -### Node.js Example - -```js -const op = ['create_invite', { - creator: 'alice', - balance: '5.000 VIZ', - invite_key: 'VIZ5invite...', -}]; -``` - -### Checklist -- [ ] `balance.symbol` must be `VIZ` -- [ ] `balance.amount` >= chain `create_invite_min_balance` property -- [ ] Generate a random secp256k1 key pair for the invite -- [ ] Private key = invite secret (share in invite link) -- [ ] Public key = `invite_key` field -- [ ] Sign with `creator`'s active key - ---- - -## `claim_invite_balance_operation` - -**Type ID:** `44` -**Required authority:** `active` of `initiator` - -Claims the VIZ balance from an invite, transferring it to `receiver`. The invite is consumed. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `initiator` | `account_name_type` | yes | Account claiming the invite | -| `receiver` | `account_name_type` | yes | Account to receive the VIZ balance | -| `invite_secret` | `string` | yes | WIF private key of the invite | - -### JSON Example - -```json -[44, { - "initiator": "bob", - "receiver": "bob", - "invite_secret": "5Ky1MXn..." -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'claim_invite_balance_operation', - 'value' => [ - 'initiator' => 'bob', - 'receiver' => 'bob', - 'invite_secret' => '5Ky1MXn...', - ], -]; -``` - -### Node.js Example - -```js -const op = ['claim_invite_balance', { - initiator: 'bob', - receiver: 'bob', - invite_secret: '5Ky1MXn...', -}]; -``` - -### Checklist -- [ ] `invite_secret` is the WIF (Wallet Import Format) private key of the invite -- [ ] `initiator` must be an existing account -- [ ] `receiver` may differ from `initiator` (can redirect balance to another account) -- [ ] Invite is consumed after claiming — cannot be reused -- [ ] Sign with `initiator`'s active key - ---- - -## `invite_registration_operation` - -**Type ID:** `45` -**Required authority:** `active` of `initiator` - -Uses an invite to create a new account. The invite balance is used to fund the new account. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `initiator` | `account_name_type` | yes | Existing account triggering registration | -| `new_account_name` | `account_name_type` | yes | Name for the new account | -| `invite_secret` | `string` | yes | WIF private key of the invite | -| `new_account_key` | `public_key_type` | yes | Master/active/regular/memo key for new account | - -### JSON Example - -```json -[45, { - "initiator": "bob", - "new_account_name": "carol", - "invite_secret": "5Ky1MXn...", - "new_account_key": "VIZ5newacct..." -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'invite_registration_operation', - 'value' => [ - 'initiator' => 'bob', - 'new_account_name' => 'carol', - 'invite_secret' => '5Ky1MXn...', - 'new_account_key' => 'VIZ5newacct...', - ], -]; -``` - -### Node.js Example - -```js -const op = ['invite_registration', { - initiator: 'bob', - new_account_name: 'carol', - invite_secret: '5Ky1MXn...', - new_account_key: 'VIZ5newacct...', -}]; -``` - -### Checklist -- [ ] `invite_secret` is the WIF private key of the invite -- [ ] `new_account_name` must pass account name validation -- [ ] `new_account_key` is set as all four keys (master, active, regular, memo) for the new account -- [ ] Invite balance is converted to SHARES for the new account -- [ ] Invite is consumed after use -- [ ] Sign with `initiator`'s active key - ---- - -## `use_invite_balance_operation` - -**Type ID:** `58` -**Required authority:** `active` of `initiator` - -Alternative to `claim_invite_balance_operation` — transfers invite balance to receiver. The difference from `claim_invite_balance_operation` is that this one may convert balance to SHARES. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `initiator` | `account_name_type` | yes | Account using the invite | -| `receiver` | `account_name_type` | yes | Account receiving the balance | -| `invite_secret` | `string` | yes | WIF private key of the invite | - -### JSON Example - -```json -[58, { - "initiator": "bob", - "receiver": "bob", - "invite_secret": "5Ky1MXn..." -}] -``` - -### Checklist -- [ ] `invite_secret` is the WIF private key -- [ ] `receiver` must be an existing account -- [ ] Invite is consumed after use -- [ ] Sign with `initiator`'s active key diff --git a/.qoder/docs/op-proposal.md b/.qoder/docs/op-proposal.md deleted file mode 100644 index c43d340e3c..0000000000 --- a/.qoder/docs/op-proposal.md +++ /dev/null @@ -1,229 +0,0 @@ -# VIZ Blockchain — Proposal Operations - -Spec for implementing multi-signature proposal operations in PHP/Node.js libraries. - -Proposals allow a group of accounts to jointly approve and execute a set of operations. Any account can create a proposal; signatories approve via `proposal_update_operation`. - ---- - -## `proposal_create_operation` - -**Type ID:** `22` -**Required authority:** `active` of `author` - -Creates a transaction proposal containing one or more operations that require multi-sig approval. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `author` | `account_name_type` | yes | Account creating the proposal | -| `title` | `string` | yes | Unique title per author (used as proposal ID) | -| `memo` | `string` | yes | Description of the proposal | -| `expiration_time` | `time_point_sec` | yes | Proposal expiration time | -| `proposed_operations` | `vector` | yes | Operations in the proposal | -| `review_period_time` | `optional` | no | Optional review period | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### `operation_wrapper` - -Each entry in `proposed_operations` is an `operation_wrapper`: -```json -{"op": [type_id, operation_object]} -``` - -### JSON Example - -```json -[22, { - "author": "alice", - "title": "transfer-proposal-001", - "memo": "Joint transfer to shared fund", - "expiration_time": "2024-12-31T23:59:59", - "proposed_operations": [ - { - "op": [2, { - "from": "multisig-wallet", - "to": "fund", - "amount": "1000.000 VIZ", - "memo": "" - }] - } - ], - "review_period_time": null, - "extensions": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'proposal_create_operation', - 'value' => [ - 'author' => 'alice', - 'title' => 'transfer-proposal-001', - 'memo' => 'Joint transfer to shared fund', - 'expiration_time' => '2024-12-31T23:59:59', - 'proposed_operations' => [ - ['op' => [2, [ - 'from' => 'multisig-wallet', - 'to' => 'fund', - 'amount' => '1000.000 VIZ', - 'memo' => '', - ]]], - ], - 'review_period_time' => null, - 'extensions' => [], - ], -]; -``` - -### Node.js Example - -```js -const op = ['proposal_create', { - author: 'alice', - title: 'transfer-proposal-001', - memo: 'Joint transfer to shared fund', - expiration_time: '2024-12-31T23:59:59', - proposed_operations: [ - { op: ['transfer', { - from: 'multisig-wallet', - to: 'fund', - amount: '1000.000 VIZ', - memo: '', - }] }, - ], - review_period_time: null, - extensions: [], -}]; -``` - -### Checklist -- [ ] `title` must be unique per `author` (together they form the proposal ID) -- [ ] `expiration_time` must be in the future -- [ ] `review_period_time` if set, must be before `expiration_time` -- [ ] `proposed_operations` may contain multiple operations -- [ ] Operations inside a proposal follow the same rules as normal operations -- [ ] Sign with `author`'s active key - ---- - -## `proposal_update_operation` - -**Type ID:** `23` -**Required authority:** Depends on which approval sets are modified - -Adds or removes approvals from a proposal. Proposal executes automatically when enough approvals are collected. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `author` | `account_name_type` | yes | Author of the proposal | -| `title` | `string` | yes | Title of the proposal | -| `active_approvals_to_add` | `flat_set` | yes | Accounts adding active approval | -| `active_approvals_to_remove` | `flat_set` | yes | Accounts removing active approval | -| `master_approvals_to_add` | `flat_set` | yes | Accounts adding master approval | -| `master_approvals_to_remove` | `flat_set` | yes | Accounts removing master approval | -| `regular_approvals_to_add` | `flat_set` | yes | Accounts adding regular approval | -| `regular_approvals_to_remove` | `flat_set` | yes | Accounts removing regular approval | -| `key_approvals_to_add` | `flat_set` | yes | Keys adding approval | -| `key_approvals_to_remove` | `flat_set` | yes | Keys removing approval | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### JSON Example - -```json -[23, { - "author": "alice", - "title": "transfer-proposal-001", - "active_approvals_to_add": ["bob"], - "active_approvals_to_remove": [], - "master_approvals_to_add": [], - "master_approvals_to_remove": [], - "regular_approvals_to_add": [], - "regular_approvals_to_remove": [], - "key_approvals_to_add": [], - "key_approvals_to_remove": [], - "extensions": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'proposal_update_operation', - 'value' => [ - 'author' => 'alice', - 'title' => 'transfer-proposal-001', - 'active_approvals_to_add' => ['bob'], - 'active_approvals_to_remove'=> [], - 'master_approvals_to_add' => [], - 'master_approvals_to_remove'=> [], - 'regular_approvals_to_add' => [], - 'regular_approvals_to_remove'=> [], - 'key_approvals_to_add' => [], - 'key_approvals_to_remove' => [], - 'extensions' => [], - ], -]; -``` - -### Checklist -- [ ] Transaction must be signed by keys satisfying the authorities being added/removed -- [ ] If proposal requires only active authority, do NOT add master authority -- [ ] If both master and active are required, only master can approve -- [ ] Proposal executes automatically when approval threshold is reached -- [ ] After successful execution, proposal is resolved and further updates are rejected -- [ ] All `*_to_add` and `*_to_remove` arrays default to `[]` if not needed - ---- - -## `proposal_delete_operation` - -**Type ID:** `24` -**Required authority:** `active` of `requester` - -Vetoes and permanently deletes a proposal. Can be done by any required authority. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `author` | `account_name_type` | yes | Author of the proposal | -| `title` | `string` | yes | Title of the proposal | -| `requester` | `account_name_type` | yes | Account requesting deletion | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### JSON Example - -```json -[24, { - "author": "alice", - "title": "transfer-proposal-001", - "requester": "bob", - "extensions": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'proposal_delete_operation', - 'value' => [ - 'author' => 'alice', - 'title' => 'transfer-proposal-001', - 'requester' => 'bob', - 'extensions' => [], - ], -]; -``` - -### Checklist -- [ ] `requester` must be a required authority on the proposal -- [ ] Permanently removes the proposal — cannot be undone -- [ ] Sign with `requester`'s active key diff --git a/.qoder/docs/op-recovery.md b/.qoder/docs/op-recovery.md deleted file mode 100644 index 8c9c279161..0000000000 --- a/.qoder/docs/op-recovery.md +++ /dev/null @@ -1,188 +0,0 @@ -# VIZ Blockchain — Account Recovery Operations - -Spec for implementing account recovery operations in PHP/Node.js libraries. - -The recovery mechanism allows a trusted recovery account to help restore access to a compromised account using a previous valid master authority. - ---- - -## Recovery Flow - -``` -request_account_recovery → recover_account (within 24 hours) -change_recovery_account (30-day delay) -``` - ---- - -## `request_account_recovery_operation` - -**Type ID:** `12` -**Required authority:** `active` of `recovery_account` - -Initiates an account recovery request. The recovery account proposes a new master authority for the compromised account. The account holder has 24 hours to confirm. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `recovery_account` | `account_name_type` | yes | The trusted recovery account | -| `account_to_recover` | `account_name_type` | yes | Compromised account to recover | -| `new_master_authority` | `authority` | yes | The new master authority to assign | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### JSON Example - -```json -[12, { - "recovery_account": "recover-service", - "account_to_recover": "alice", - "new_master_authority": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5newkey...", 1]] - }, - "extensions": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'request_account_recovery_operation', - 'value' => [ - 'recovery_account' => 'recover-service', - 'account_to_recover' => 'alice', - 'new_master_authority' => [ - 'weight_threshold' => 1, - 'account_auths' => [], - 'key_auths' => [['VIZ5newkey...', 1]], - ], - 'extensions' => [], - ], -]; -``` - -### Node.js Example - -```js -const op = ['request_account_recovery', { - recovery_account: 'recover-service', - account_to_recover: 'alice', - new_master_authority: { - weight_threshold: 1, - account_auths: [], - key_auths: [['VIZ5newkey...', 1]], - }, - extensions: [], -}]; -``` - -### Checklist -- [ ] Only the listed recovery account of `account_to_recover` can send this -- [ ] Only one active recovery request per account at any time -- [ ] Sending again updates the request to a new authority and resets the 24h timer -- [ ] To cancel: set `new_master_authority.weight_threshold` to `0` -- [ ] Sign with `recovery_account`'s active key - ---- - -## `recover_account_operation` - -**Type ID:** `13` -**Required authority:** Both `new_master_authority` AND `recent_master_authority` signatures - -Confirms account recovery. The account holder proves past ownership via a recent master authority, and takes the new master authority. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account_to_recover` | `account_name_type` | yes | Account being recovered | -| `new_master_authority` | `authority` | yes | New master authority (must match recovery request) | -| `recent_master_authority` | `authority` | yes | A previous valid master authority (within last 30 days) | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### JSON Example - -```json -[13, { - "account_to_recover": "alice", - "new_master_authority": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5newkey...", 1]] - }, - "recent_master_authority": { - "weight_threshold": 1, - "account_auths": [], - "key_auths": [["VIZ5oldkey...", 1]] - }, - "extensions": [] -}] -``` - -### Checklist -- [ ] Must be broadcast within 24 hours of the recovery request -- [ ] `new_master_authority` must exactly match the one in the recovery request -- [ ] `recent_master_authority` must have been valid in the past 30 days -- [ ] Transaction must be signed by keys satisfying **both** authorities -- [ ] After recovery, the old master key is invalidated - ---- - -## `change_recovery_account_operation` - -**Type ID:** `14` -**Required authority:** `master` of `account_to_recover` - -Changes the recovery account for an account. Takes effect after a 30-day delay. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account_to_recover` | `account_name_type` | yes | Account changing its recovery account | -| `new_recovery_account` | `account_name_type` | yes | New recovery account name | -| `extensions` | `extensions_type` | yes | Always `[]` | - -### JSON Example - -```json -[14, { - "account_to_recover": "alice", - "new_recovery_account": "new-recovery-service", - "extensions": [] -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'change_recovery_account_operation', - 'value' => [ - 'account_to_recover' => 'alice', - 'new_recovery_account' => 'new-recovery-service', - 'extensions' => [], - ], -]; -``` - -### Node.js Example - -```js -const op = ['change_recovery_account', { - account_to_recover: 'alice', - new_recovery_account: 'new-recovery-service', - extensions: [], -}]; -``` - -### Checklist -- [ ] 30-day delay between submitting the change and it taking effect -- [ ] This prevents attackers from changing the recovery account during an active attack -- [ ] `new_recovery_account` must be an existing account -- [ ] If `new_recovery_account == ""`, top-voted validator becomes recovery account -- [ ] Sign with `account_to_recover`'s master key diff --git a/.qoder/docs/op-subscription.md b/.qoder/docs/op-subscription.md deleted file mode 100644 index e274ac9ab4..0000000000 --- a/.qoder/docs/op-subscription.md +++ /dev/null @@ -1,146 +0,0 @@ -# VIZ Blockchain — Paid Subscription Operations - -Spec for implementing paid subscription operations in PHP/Node.js libraries. - -Paid subscriptions allow accounts to offer tiered subscription services payable in VIZ tokens, with optional auto-renewal. - ---- - -## `set_paid_subscription_operation` - -**Type ID:** `50` -**Required authority:** `active` of `account` - -Creates or updates a paid subscription offering for an account. Subscribers can then subscribe via `paid_subscribe_operation`. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account offering the subscription | -| `url` | `string` | yes | URL with subscription details | -| `levels` | `uint16_t` | yes | Number of subscription tiers (1–N) | -| `amount` | `asset` (VIZ) | yes | Price per period per unit level | -| `period` | `uint16_t` | yes | Subscription period in days | - -### JSON Example - -```json -[50, { - "account": "alice", - "url": "https://alice.example.com/subscribe", - "levels": 3, - "amount": "10.000 VIZ", - "period": 30 -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'set_paid_subscription_operation', - 'value' => [ - 'account' => 'alice', - 'url' => 'https://alice.example.com/subscribe', - 'levels' => 3, - 'amount' => '10.000 VIZ', - 'period' => 30, - ], -]; -``` - -### Node.js Example - -```js -const op = ['set_paid_subscription', { - account: 'alice', - url: 'https://alice.example.com/subscribe', - levels: 3, - amount: '10.000 VIZ', - period: 30, -}]; -``` - -### Checklist -- [ ] `amount.symbol` must be `VIZ` -- [ ] `amount.amount` must be > 0 -- [ ] `levels` must be >= 1 -- [ ] `period` must be >= 1 (days) -- [ ] Fee (`create_paid_subscription_fee`) charged on first creation -- [ ] Actual subscription cost = `amount * level` per period -- [ ] Sign with `account`'s active key - ---- - -## `paid_subscribe_operation` - -**Type ID:** `51` -**Required authority:** `active` of `subscriber` - -Subscribes to or renews a paid subscription. Tokens are transferred from `subscriber` to `account`. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `subscriber` | `account_name_type` | yes | Subscribing account | -| `account` | `account_name_type` | yes | Account offering the subscription | -| `level` | `uint16_t` | yes | Subscription tier (1–max_levels) | -| `amount` | `asset` (VIZ) | yes | Payment amount | -| `period` | `uint16_t` | yes | Number of periods to subscribe | -| `auto_renewal` | `bool` | yes | Whether to auto-renew | - -### JSON Example - -```json -[51, { - "subscriber": "bob", - "account": "alice", - "level": 2, - "amount": "20.000 VIZ", - "period": 1, - "auto_renewal": true -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'paid_subscribe_operation', - 'value' => [ - 'subscriber' => 'bob', - 'account' => 'alice', - 'level' => 2, - 'amount' => '20.000 VIZ', - 'period' => 1, - 'auto_renewal' => true, - ], -]; -``` - -### Node.js Example - -```js -const op = ['paid_subscribe', { - subscriber: 'bob', - account: 'alice', - level: 2, - amount: '20.000 VIZ', - period: 1, - auto_renewal: true, -}]; -``` - -### Checklist -- [ ] `amount.symbol` must be `VIZ` -- [ ] `amount` must match `subscription_amount * level * period` -- [ ] `level` must be in range [1, subscription.levels] -- [ ] `period` >= 1 -- [ ] `auto_renewal: true` → tokens deducted automatically each period -- [ ] `auto_renewal: false` → one-time subscription -- [ ] If already subscribed: upgrading level requires matching payment for remaining time difference -- [ ] Virtual `paid_subscription_action_operation` fires on payment -- [ ] Virtual `cancel_paid_subscription_operation` fires on cancellation/expiry -- [ ] Sign with `subscriber`'s active key diff --git a/.qoder/docs/op-transfer-vesting.md b/.qoder/docs/op-transfer-vesting.md deleted file mode 100644 index fcee224f73..0000000000 --- a/.qoder/docs/op-transfer-vesting.md +++ /dev/null @@ -1,224 +0,0 @@ -# VIZ Blockchain — Transfer & Vesting Operations - -Spec for implementing transfer and vesting-related operations in PHP/Node.js libraries. - ---- - -## `transfer_operation` - -**Type ID:** `2` -**Required authority:** `active` of `from` (for VIZ tokens), `master` of `from` (for SHARES) - -Transfers tokens (VIZ or SHARES) from one account to another. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from` | `account_name_type` | yes | Sending account | -| `to` | `account_name_type` | yes | Receiving account | -| `amount` | `asset` | yes | Amount to transfer (VIZ or SHARES) | -| `memo` | `string` | yes | Optional memo (plain text or encrypted) | - -### JSON Example - -```json -[2, { - "from": "alice", - "to": "bob", - "amount": "10.000 VIZ", - "memo": "payment for services" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'transfer_operation', - 'value' => [ - 'from' => 'alice', - 'to' => 'bob', - 'amount' => '10.000 VIZ', - 'memo' => 'payment for services', - ], -]; -``` - -### Node.js Example - -```js -const op = ['transfer', { - from: 'alice', - to: 'bob', - amount: '10.000 VIZ', - memo: 'payment for services', -}]; -``` - -### Checklist -- [ ] `amount.symbol` must be `VIZ` or `SHARES` -- [ ] If `amount.symbol == SHARES` → sign with **master** key -- [ ] If `amount.symbol == VIZ` → sign with **active** key -- [ ] `amount.amount` must be > 0 -- [ ] `memo` may be empty string `""` -- [ ] Encrypted memo format starts with `#` followed by base58 ciphertext - ---- - -## `transfer_to_vesting_operation` - -**Type ID:** `3` -**Required authority:** `active` of `from` - -Converts liquid VIZ tokens into SHARES (staking/vesting). Can vest into another account's balance. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from` | `account_name_type` | yes | Account providing VIZ | -| `to` | `account_name_type` | yes | Account receiving SHARES (same as `from` if empty) | -| `amount` | `asset` (VIZ) | yes | Amount of VIZ to convert to SHARES | - -### JSON Example - -```json -[3, { - "from": "alice", - "to": "alice", - "amount": "100.000 VIZ" -}] -``` - -### Checklist -- [ ] `amount.symbol` must be `VIZ` -- [ ] `amount.amount` must be > 0 -- [ ] `to` can equal `from` (self-vesting) or be a different account -- [ ] Sign with `from`'s active key - ---- - -## `withdraw_vesting_operation` - -**Type ID:** `4` -**Required authority:** `active` of `account` - -Initiates a vesting withdrawal — schedules gradual conversion of SHARES back to VIZ over multiple intervals. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account initiating withdrawal | -| `vesting_shares` | `asset` (SHARES) | yes | Total SHARES to withdraw (0 to cancel) | - -### JSON Example - -```json -[4, { - "account": "alice", - "vesting_shares": "1000.000000 SHARES" -}] -``` - -### Checklist -- [ ] `vesting_shares.symbol` must be `SHARES` -- [ ] Set `vesting_shares` to `"0.000000 SHARES"` to **cancel** an active withdrawal -- [ ] Withdrawal is spread over `withdraw_intervals` intervals (default 28 days) -- [ ] Each interval: `vesting_shares / withdraw_intervals` SHARES are withdrawn -- [ ] Sign with `account`'s active key - ---- - -## `set_withdraw_vesting_route_operation` - -**Type ID:** `11` -**Required authority:** `active` of `from_account` - -Routes a percentage of vesting withdrawals to another account (optionally re-vesting immediately). - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `from_account` | `account_name_type` | yes | Account whose withdrawals are being routed | -| `to_account` | `account_name_type` | yes | Account receiving routed funds | -| `percent` | `uint16_t` | yes | Percentage to route (0–10000, basis points) | -| `auto_vest` | `bool` | yes | If true, immediately re-vest the routed tokens | - -### JSON Example - -```json -[11, { - "from_account": "alice", - "to_account": "bob", - "percent": 5000, - "auto_vest": false -}] -``` - -### Checklist -- [ ] `percent` range: 0 (remove route) to 10000 (100%) -- [ ] Multiple routes allowed, but total percent across all routes must be <= 10000 -- [ ] `auto_vest: true` means Bob receives SHARES, not VIZ -- [ ] Set `percent: 0` to delete the route to `to_account` -- [ ] Sign with `from_account`'s active key - ---- - -## `delegate_vesting_shares_operation` - -**Type ID:** `19` -**Required authority:** `active` of `delegator` - -Delegates SHARES from one account to another. The delegator retains ownership but the delegatee gains bandwidth and voting power. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `delegator` | `account_name_type` | yes | Account delegating SHARES | -| `delegatee` | `account_name_type` | yes | Account receiving delegation | -| `vesting_shares` | `asset` (SHARES) | yes | Amount to delegate (0 removes delegation) | - -### JSON Example - -```json -[19, { - "delegator": "alice", - "delegatee": "bob", - "vesting_shares": "500.000000 SHARES" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'delegate_vesting_shares_operation', - 'value' => [ - 'delegator' => 'alice', - 'delegatee' => 'bob', - 'vesting_shares' => '500.000000 SHARES', - ], -]; -``` - -### Node.js Example - -```js -const op = ['delegate_vesting_shares', { - delegator: 'alice', - delegatee: 'bob', - vesting_shares: '500.000000 SHARES', -}]; -``` - -### Checklist -- [ ] `vesting_shares.symbol` must be `SHARES` -- [ ] Set `vesting_shares` to `"0.000000 SHARES"` to **remove** delegation -- [ ] `vesting_shares` must be >= chain `min_delegation` property (unless 0) -- [ ] When delegation is removed, SHARES enter a 1-week limbo period before returning -- [ ] Virtual operation `return_vesting_delegation_operation` fires when limbo period ends -- [ ] Sign with `delegator`'s active key diff --git a/.qoder/docs/op-validator.md b/.qoder/docs/op-validator.md deleted file mode 100644 index 8849d2ed59..0000000000 --- a/.qoder/docs/op-validator.md +++ /dev/null @@ -1,252 +0,0 @@ -# VIZ Blockchain — validator Operations - -Spec for implementing validator-related operations in PHP/Node.js libraries. - ---- - -## `witness_update_operation` - -**Type ID:** `6` -**Required authority:** `active` of `owner` - -Registers or updates a validator. Setting `block_signing_key` to the null key removes the validator from block production contention. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `owner` | `account_name_type` | yes | validator account name | -| `url` | `string` | yes | validator website or info URL | -| `block_signing_key` | `public_key_type` | yes | Key used to sign blocks (set to null key to deactivate) | - -**Null key** (deactivate validator): `"VIZ1111111111111111111111111111111114T1Anm"` - -### JSON Example - -```json -[6, { - "owner": "alice", - "url": "https://alice.example.com", - "block_signing_key": "VIZ5hqSa4NkEZGAMUpoH5EaEr64mBJuMcPpGjvk8qb7hcPFTbXSQ9" -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'witness_update_operation', - 'value' => [ - 'owner' => 'alice', - 'url' => 'https://alice.example.com', - 'block_signing_key' => 'VIZ5hq...', - ], -]; -``` - -### Node.js Example - -```js -const op = ['witness_update', { - owner: 'alice', - url: 'https://alice.example.com', - block_signing_key: 'VIZ5hq...', -}]; -``` - -### Checklist -- [ ] `block_signing_key` must be a valid VIZ public key or the null key -- [ ] Null key = `VIZ1111111111111111111111111111111114T1Anm` (deactivates validator) -- [ ] `url` must be non-empty and < `CHAIN_MAX_URL_LENGTH` (256) bytes -- [ ] Requires `witness_declaration_fee` paid to committee (see chain properties) -- [ ] Sign with `owner`'s active key - ---- - -## `chain_properties_update_operation` - -**Type ID:** `25` -**Required authority:** `active` of `owner` - -validator votes on base chain properties (`chain_properties_init` format only). Use `versioned_chain_properties_update_operation` for extended properties. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `owner` | `account_name_type` | yes | validator account voting | -| `props` | `chain_properties_init` | yes | Proposed chain properties | - -### JSON Example - -```json -[25, { - "owner": "alice", - "props": { - "account_creation_fee": "1.000 VIZ", - "maximum_block_size": 65536, - "create_account_delegation_ratio": 10, - "create_account_delegation_time": 2592000, - "min_delegation": "1.000 VIZ", - "min_curation_percent": 0, - "max_curation_percent": 10000, - "bandwidth_reserve_percent": 1000, - "bandwidth_reserve_below": "1.000000 SHARES", - "flag_energy_additional_cost": 1000, - "vote_accounting_min_rshares": 0, - "committee_request_approve_min_percent": 1000 - } -}] -``` - -### Checklist -- [ ] `account_creation_fee.symbol` must be `VIZ` -- [ ] `min_delegation.symbol` must be `VIZ` -- [ ] `bandwidth_reserve_below.symbol` must be `SHARES` -- [ ] `min_curation_percent` <= `max_curation_percent` -- [ ] All percent fields in basis points (0–10000) -- [ ] Median of all active validator values is used as actual chain property - ---- - -## `versioned_chain_properties_update_operation` - -**Type ID:** `46` -**Required authority:** `active` of `owner` - -validator votes on versioned chain properties (supports all hardfork extensions). - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `owner` | `account_name_type` | yes | validator account voting | -| `props` | `versioned_chain_properties` | yes | Versioned props variant | - -### JSON Example (using hf9 = index 3) - -```json -[46, { - "owner": "alice", - "props": [3, { - "account_creation_fee": "1.000 VIZ", - "maximum_block_size": 65536, - "create_account_delegation_ratio": 10, - "create_account_delegation_time": 2592000, - "min_delegation": "1.000 VIZ", - "min_curation_percent": 0, - "max_curation_percent": 10000, - "bandwidth_reserve_percent": 1000, - "bandwidth_reserve_below": "1.000000 SHARES", - "flag_energy_additional_cost": 1000, - "vote_accounting_min_rshares": 0, - "committee_request_approve_min_percent": 1000, - "inflation_witness_percent": 2000, - "inflation_ratio_committee_vs_reward_fund": 1000, - "inflation_recalc_period": 28800, - "data_operations_cost_additional_bandwidth": 0, - "witness_miss_penalty_percent": 100, - "witness_miss_penalty_duration": 86400, - "create_invite_min_balance": "1.000 VIZ", - "committee_create_request_fee": "1.000 VIZ", - "create_paid_subscription_fee": "1.000 VIZ", - "account_on_sale_fee": "10.000 VIZ", - "subaccount_on_sale_fee": "1.000 VIZ", - "witness_declaration_fee": "1.000 VIZ", - "withdraw_intervals": 28 - }] -}] -``` - -### Checklist -- [ ] `props` is serialized as a static_variant: `[index, object]` -- [ ] Use index `3` for `chain_properties_hf9` (current latest) -- [ ] See `data-types.md` for full field list per version - ---- - -## `account_witness_vote_operation` - -**Type ID:** `7` -**Required authority:** `active` of `account` - -Votes for or against a validator to be included in block production. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Voting account | -| `validator` | `account_name_type` | yes | validator to vote for/against | -| `approve` | `bool` | yes | `true` to add vote, `false` to remove vote | - -### JSON Example - -```json -[7, { - "account": "alice", - "validator": "bob", - "approve": true -}] -``` - -### PHP Example - -```php -$op = [ - 'type' => 'account_witness_vote_operation', - 'value' => [ - 'account' => 'alice', - 'validator' => 'bob', - 'approve' => true, - ], -]; -``` - -### Node.js Example - -```js -const op = ['account_witness_vote', { - account: 'alice', - validator: 'bob', - approve: true, -}]; -``` - -### Checklist -- [ ] Account must have SHARES to have meaningful voting weight -- [ ] `approve: false` removes a previously cast vote -- [ ] Top 21 validators by vote weight produce blocks -- [ ] Sign with `account`'s active key - ---- - -## `account_witness_proxy_operation` - -**Type ID:** `8` -**Required authority:** `active` of `account` - -Assigns a proxy account for validator voting. All existing votes are removed when a proxy is set. - -### Fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `account` | `account_name_type` | yes | Account setting the proxy | -| `proxy` | `account_name_type` | yes | Proxy account (empty string `""` to remove proxy) | - -### JSON Example - -```json -[8, { - "account": "alice", - "proxy": "bob" -}] -``` - -### Checklist -- [ ] Setting `proxy` to `""` (empty string) removes the proxy -- [ ] Cannot set proxy to self -- [ ] Proxy chains are resolved (A→B→C); max depth is limited -- [ ] Setting a proxy removes all direct validator votes -- [ ] Sign with `account`'s active key diff --git a/.qoder/docs/p2p-messages.md b/.qoder/docs/p2p-messages.md deleted file mode 100644 index 334d9ee45f..0000000000 --- a/.qoder/docs/p2p-messages.md +++ /dev/null @@ -1,837 +0,0 @@ -# VIZ P2P Message Protocol Reference - -Complete reference for all P2P network message types, their structures, flow diagrams, and the transaction exchange pipeline through JSON-RPC, chain, and network broadcast plugins. - ---- - -## Overview - -The VIZ P2P network uses a binary protocol over TCP with ECDH key exchange. Messages are serialized using FC reflection. Each message carries a type tag (`core_message_type_enum`) used for dispatch. - -Messages fall into three categories: - -| Category | Range | Purpose | -|----------|-------|---------| -| Item messages | 1000-1099 | Block and transaction data payloads | -| Core protocol | 5000-5099 | Sync, handshake, peer discovery, diagnostics | -| Extension | 6000+ | Block post-validation | - -**Key files:** -- [core_messages.hpp](file:///d:/Work/viz-cpp-node/libraries/network/include/graphene/network/core_messages.hpp) — message type enum, all message structs, FC_REFLECT serialization -- [core_messages.cpp](file:///d:/Work/viz-cpp-node/libraries/network/core_messages.cpp) — static type constant definitions -- [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) — message dispatch, sync state machine, peer management -- [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) — bridge between P2P network and blockchain - ---- - -## Message Type Enum - -```cpp -// core_messages.hpp:72-95 -enum core_message_type_enum { - trx_message_type = 1000, - block_message_type = 1001, - - core_message_type_first = 5000, - item_ids_inventory_message_type = 5001, - blockchain_item_ids_inventory_message_type = 5002, - fetch_blockchain_item_ids_message_type = 5003, - fetch_items_message_type = 5004, - item_not_available_message_type = 5005, - hello_message_type = 5006, - connection_accepted_message_type = 5007, - connection_rejected_message_type = 5008, - address_request_message_type = 5009, - address_message_type = 5010, - closing_connection_message_type = 5011, - current_time_request_message_type = 5012, - current_time_reply_message_type = 5013, - check_firewall_message_type = 5014, - check_firewall_reply_message_type = 5015, - get_current_connections_request_message_type = 5016, - get_current_connections_reply_message_type = 5017, - chain_status_announcement_message_type = 5018, - core_message_type_last = 5018, - - block_post_validation_message_type = 6009, -}; -``` - ---- - -## Detailed Message Reference - -### 1000 — trx_message - -**Purpose:** Carries a signed transaction between peers. - -**Structure:** -```cpp -struct trx_message { - static const core_message_type_enum type; // = trx_message_type (1000) - signed_transaction trx; -}; -``` - -**FC_REFLECT:** `(trx)` - -**Dispatch path:** -1. [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `process_ordinary_message()` (line 5099) — checks `items_requested_from_peer` set -2. If `msg_type == trx_message_type`: calls `_delegate->handle_transaction(trx_msg)` -3. [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `handle_transaction()` (line 287) — calls `chain.accept_transaction(trx_msg.trx)` -4. On success: the message is broadcast to all other connected peers via `broadcast()` (node.cpp line 5146) - -**Usage sites:** -| Caller | File | Line | -|--------|------|------| -| JSON-RPC `broadcast_transaction` | [network_broadcast_api.cpp](file:///d:/Work/viz-cpp-node/plugins/network_broadcast_api/network_broadcast_api.cpp) | 50 | -| validator block production (contained txs) | [validator.cpp](file:///d:/Work/viz-cpp-node/plugins/validator/validator.cpp) | — | -| P2P relay (incoming tx → other peers) | [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) | 5146 | - ---- - -### 1001 — block_message - -**Purpose:** Carries a full signed block between peers. - -**Structure:** -```cpp -struct block_message { - static const core_message_type_enum type; // = block_message_type (1001) - signed_block block; - block_id_type block_id; // cached block.id() for quick ID lookup -}; -``` - -**FC_REFLECT:** `(block)(block_id)` - -**Dispatch path:** -1. [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `process_block_message()` (line 4773) — routes to sync or normal handler -2. Sync path: `process_block_during_sync()` → `send_sync_block_to_node_delegate()` → `_delegate->handle_block(blk_msg, sync_mode=true, ...)` -3. Normal path: `process_block_during_normal_operation()` → `_delegate->handle_block(blk_msg, sync_mode=false, ...)` -4. [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `handle_block()` (line 145) — calls `chain.accept_block(blk_msg.block, ...)` -5. On success: broadcast to other peers - -**DLT emergency near-caught-up logic** (p2p_plugin.cpp:219): When `sync_mode && gap <= 2 && dlt_mode && block_age < 30s`, treats the sync block as a normal block to avoid triggering "Syncing Blockchain started" and disrupting validator production. - -**Usage sites:** -| Caller | File | Line | -|--------|------|------| -| JSON-RPC `broadcast_block` | [network_broadcast_api.cpp](file:///d:/Work/viz-cpp-node/plugins/network_broadcast_api/network_broadcast_api.cpp) | 88 | -| validator block production | [validator.cpp](file:///d:/Work/viz-cpp-node/plugins/validator/validator.cpp) | — | -| P2P relay | [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) | 5146 | - ---- - -### 5001 — item_ids_inventory_message - -**Purpose:** Gossip advertisement. Tells a peer "I have these items available" so the peer can request them. - -This is the **push-based broadcast** mechanism — when a node receives a new block or transaction, it advertises it to all other connected peers (except the one it came from). - -**Structure:** -```cpp -struct item_ids_inventory_message { - static const core_message_type_enum type; // = 5001 - uint32_t item_type; // trx_message_type or block_message_type - std::vector item_hashes_available; -}; -``` - -**Handler:** [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `on_item_ids_inventory_message()` (line 3339) - -**Gate conditions** (message is skipped if): -- `we_need_sync_items_from_peer == true` (we're syncing from this peer) -- Any peer has `we_need_sync_items_from_peer == true` (global sync in progress) -- Head block time is >30 seconds behind real time - -**Inventory Gate Deadlock Breaker** (node.cpp:3480-3513): If head is >30s behind AND no sync is in progress with ANY peer, triggers `start_synchronizing_with_peer()` to break the stalemate. - ---- - -### 5002 — blockchain_item_ids_inventory_message - -**Purpose:** Response to a `fetch_blockchain_item_ids_message`. Contains a sequential list of block IDs after the common ancestor point identified by the synopsis. Used in **pull-based sync mode**. - -**Structure:** -```cpp -struct blockchain_item_ids_inventory_message { - static const core_message_type_enum type; // = 5002 - uint32_t total_remaining_item_count; // how many more block IDs the peer has beyond this batch - uint32_t item_type; // always block_message_type - std::vector item_hashes_available; // sequential block IDs -}; -``` - -**Handler:** [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `on_blockchain_item_ids_inventory_message()` (line 2787) - -**Validation:** Checks that IDs are sequential block numbers, links to the synopsis, and the first ID is in our fork history or synopsis. On failure → disconnects with "invalid response". - -**Done condition:** When `total_remaining_item_count == 0` and all items are already known, sets `we_need_sync_items_from_peer = false` (transitions to broadcast mode). - -**Server-side generation:** [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `get_block_ids()` (line 337) - ---- - -### 5003 — fetch_blockchain_item_ids_message - -**Purpose:** Request from a syncing node to a peer. Sends a "blockchain synopsis" (logarithmically-spaced block IDs from our chain) so the peer can find the most recent common block and respond with `blockchain_item_ids_inventory_message`. - -**Structure:** -```cpp -struct fetch_blockchain_item_ids_message { - static const core_message_type_enum type; // = 5003 - uint32_t item_type; // always block_message_type - std::vector blockchain_synopsis; -}; -``` - -**Handler:** [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `on_fetch_blockchain_item_ids_message()` (line 2748) - -**Synopsis generation:** [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `get_blockchain_synopsis()` (line 568) - -**Synopsis format (logarithmic fall-off):** -- First entry: highest non-undoable block (LIB) -- Second: ~1/2 way through undoable segment -- Third: ~3/4 way through -- Fourth: ~7/8 way through -- Last: head block (guaranteed by post-loop guard) - ---- - -### 5004 — fetch_items_message - -**Purpose:** Request actual block or transaction data from a peer. Contains a list of item hashes to fetch. The peer responds with `block_message` or `trx_message` (one per item) or `item_not_available_message`. - -**Structure:** -```cpp -struct fetch_items_message { - static const core_message_type_enum type; // = 5004 - uint32_t item_type; - std::vector items_to_fetch; -}; -``` - -**Handler:** [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `on_fetch_items_message()` - -**Server-side serving:** [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `get_item()` (line 528) — fetches block or transaction from chain database. - ---- - -### 5005 — item_not_available_message - -**Purpose:** Response when a peer requests an item we don't have. The requesting peer may soft-ban or apply a strike counter. - -**Structure:** -```cpp -struct item_not_available_message { - static const core_message_type_enum type; // = 5005 - item_id requested_item; // { item_type, item_hash } -}; -``` - ---- - -### 5006 — hello_message - -**Purpose:** Initial handshake message sent immediately after TCP connection. Exchanges node identity, protocol version, chain state, and capabilities. - -**Structure:** -```cpp -struct hello_message { - static const core_message_type_enum type; // = 5006 - std::string user_agent; - uint32_t core_protocol_version; - fc::ip::address inbound_address; - uint16_t inbound_port; - uint16_t outbound_port; - node_id_t node_public_key; - fc::ecc::compact_signature signed_shared_secret; - fc::variant_object user_data; // extensible key-value metadata -}; -``` - -**FC_REFLECT:** `(user_agent)(core_protocol_version)(inbound_address)(inbound_port)(outbound_port)(node_public_key)(signed_shared_secret)(user_data)` - -**Handler:** [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `on_hello_message()` (line 2299) - -**user_data fields** (generated by `generate_hello_user_data()`, node.cpp:2231): - -| Key | Type | Description | -|-----|------|-------------| -| `fc_git_revision_sha` | string | FC library git revision | -| `fc_git_revision_unix_timestamp` | uint32 | FC library build timestamp | -| `platform` | string | `"osx"`, `"linux"`, `"win32"`, or `"other"` | -| `bitness` | uint32 | `32` or `64` | -| `node_id` | node_id_t | Node public key as hex | -| `last_known_block_hash` | block_id_type | Head block hash | -| `last_known_block_number` | uint32 | Head block number | -| `last_known_block_time` | time_point_sec | Head block timestamp | -| `last_known_fork_block_number` | uint32 | Latest hardfork block known to this node | -| `chain_id` | chain_id_type | Blockchain chain ID | -| `dlt_mode` | bool | Node is in DLT (rolling block log) mode | -| `dlt_earliest_block` | uint32 | Earliest available block in DLT window (only present if dlt_mode=true) | -| `emergency_consensus_active` | bool | Emergency consensus is active on this node | -| `has_emergency_key` | bool | Node holds the emergency committee private key (block_producer heuristic) | - -**Validation checks performed on hello:** -1. ECDH signature validation (line 2338) -2. Hardfork compatibility check (line 2353) -3. Chain ID match (line 2386) -4. Duplicate connection check (line 2407) - -**Rejection reasons** (`rejection_reason_code` enum): -- `unspecified` — generic -- `different_chain` — chain ID mismatch -- `already_connected` — duplicate node_id -- `connected_to_self` — connected to own node_id -- `not_accepting_connections` — node is full -- `blocked` — in allowed_peers list but blocked -- `invalid_hello_message` — signature validation failed -- `client_too_old` — hardfork version too old - ---- - -### 5007 — connection_accepted_message - -**Purpose:** Empty message sent by the receiving node to confirm it has accepted the hello handshake and the connection is fully established. - -**Structure:** -```cpp -struct connection_accepted_message { - static const core_message_type_enum type; // = 5007 - // empty -}; -``` - -**Handler:** [node.cpp](file:///d:/Work/viz-cpp-node/libraries/network/node.cpp) `on_connection_accepted_message()` (line 2502) - -Takes the peer from `peer_connection::their_connection_state::connection_accepted` to `connection_established`. Calls `new_peer_just_added()` which starts sync. - ---- - -### 5008 — connection_rejected_message - -**Purpose:** Sent when a node rejects the hello handshake. The connection is closed after this message. - -**Structure:** -```cpp -struct connection_rejected_message { - static const core_message_type_enum type; // = 5008 - std::string user_agent; - uint32_t core_protocol_version; - fc::ip::endpoint remote_endpoint; - std::string reason_string; - fc::enum_type reason_code; -}; -``` - ---- - -### 5009/5010 — address_request_message / address_message - -**Purpose:** Peer discovery. A node requests known peer addresses from a connected peer, and receives a list of `address_info` records. - -**address_info structure:** -```cpp -struct address_info { - fc::ip::endpoint remote_endpoint; - fc::time_point_sec last_seen_time; - fc::microseconds latency; - node_id_t node_id; - fc::enum_type direction; // unknown, inbound, outbound - fc::enum_type firewalled; // unknown, firewalled, not_firewalled -}; -``` - ---- - -### 5011 — closing_connection_message - -**Purpose:** Graceful disconnect. Sent before a peer closes the connection, with an optional reason. - -**Structure:** -```cpp -struct closing_connection_message { - static const core_message_type_enum type; // = 5011 - std::string reason_for_closing; - bool closing_due_to_error; - fc::oexception error; -}; -``` - ---- - -### 5012/5013 — current_time_request_message / current_time_reply_message - -**Purpose:** NTP-style clock synchronization. A node requests the peer's current time for clock offset calculation. - -**current_time_reply_message structure:** -```cpp -struct current_time_reply_message { - static const core_message_type_enum type; // = 5013 - fc::time_point request_sent_time; // our timestamp when we sent the request - fc::time_point request_received_time; // peer's timestamp when they received our request - fc::time_point reply_transmitted_time; // peer's timestamp when they sent the reply -}; -``` - -Clock offset is computed as: `((T2 - T1) + (T3 - T4)) / 2` (standard NTP formula). - -Sent on every new connection in `new_peer_just_added()` (node.cpp:5186). - ---- - -### 5014/5015 — check_firewall_message / check_firewall_reply_message - -**Purpose:** NAT/firewall detection. A node asks a peer to try connecting to a specified endpoint to test if the requesting node is reachable from the internet. - ---- - -### 5016/5017 — get_current_connections_request_message / get_current_connections_reply_message - -**Purpose:** Network diagnostics. Request a peer's list of current connections, upload/download rates. - ---- - -### 6009 — block_post_validation_message - -**Purpose:** Block signing validator verification (Hardfork 11+). Sent after a block is applied to confirm the signing validator identity. The receiving node validates the signature against the validator's signing key on-chain and triggers `apply_block_post_validation()`. - -**Structure:** -```cpp -struct block_post_validation_message { - static const core_message_type_enum type; // = 6009 - block_id_type block_id; - std::string witness_account; - signature_type witness_signature; -}; -``` - -**Handler:** [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `handle_message()` (line 304) - -**Validation:** Recovers the public key from the signature, compares against the validator's on-chain `signing_key`. If matched, calls `apply_block_post_validation()`. - -**Broadcast:** Sent by validators after producing a block. Also emitted via [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `broadcast_block_post_validation()` (line 1470). - ---- - -### 5018 — chain_status_announcement_message - -**Purpose:** Announces a node's chain state — head block, irreversible block, DLT mode window, and emergency consensus status — to all connected peers. Sent automatically when a new peer joins (via `connection_count_changed`) and can also be broadcast manually via `p2p_plugin::broadcast_chain_status()`. - -This message complements the hello handshake (5006): the hello provides a **snapshot** of chain state at connection time, while chain_status_announcement provides **live updates** when DLT window shifts or emergency consensus activates/deactivates during an ongoing connection. - -**Structure:** -```cpp -struct chain_status_announcement_message { - static const core_message_type_enum type; // = 5018 - - block_id_type head_block_id; // hash of our head block - uint32_t head_block_num; // height of our head block - uint32_t last_irreversible_block_num; // LIB height - bool dlt_mode; // true if rolling block log mode - uint32_t dlt_earliest_block; // lowest block number we can serve - bool emergency_consensus_active; // true if emergency consensus is on - bool has_emergency_key; // true if we hold committee private key -}; -``` - -**Handler:** [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `handle_message()` (line 326) — logs the received chain state at debug level. The peer's prior hello `user_data` already stores this info; this message serves as a refresh. - -**Broadcast trigger:** [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp) `connection_count_changed()` (line 793) — when the connection count increases (new peer joined), a `chain_status_announcement_message` is built from the chain database and broadcast to all peers. - -**Manual call site:** `p2p_plugin::broadcast_chain_status()` — can be called from any plugin (e.g., validator or snapshot) when chain state materially changes. - ---- - -## Message Flow Diagrams - -### Handshake Flow - -``` -Client (Initiator) Server (Acceptor) - | | - | TCP connect | - |----------------------------------------->| - | | - | hello_message (5006) | - | [user_agent, protocol_version, | - | node_public_key, signed_shared_secret, | - | user_data{...chain state...}] | - |----------------------------------------->| - | | validate signature - | | check chain ID - | | check hardfork version - | | check duplicate connection - | | - | connection_accepted_message (5007) | - |<-----------------------------------------| - | | - | hello_message (5006) | - | [server's chain state] | - |<-----------------------------------------| - | validate | - | | - | connection_accepted_message (5007) | - |----------------------------------------->| - | | - | current_time_request_message (5012) | - |<-----------------------------------------| - | current_time_reply_message (5013) | - |----------------------------------------->| - -Both sides now call new_peer_just_added() → start_synchronizing_with_peer() -``` - ---- - -### Sync Mode Flow (Pull-Based) - -``` -Our Node (needs blocks) Peer (has blocks) - | | - | fetch_blockchain_item_ids_msg (5003) | - | [blockchain_synopsis: LIB,...head] | - |--------------------------------------->| - | | Server finds common ancestor - | | via get_block_ids() - | | - | blockchain_item_ids_inventory (5002) | - | [remaining=N, item_hashes=[...]] | - |<---------------------------------------| - | | - | fetch_items_message (5004) | - | [items_to_fetch=[id1, id2, ...]] | - |--------------------------------------->| - | | Server calls get_item() - | block_message (1001) | - | [signed_block data] | - |<---------------------------------------| - | push_block() | - | ... repeat for each block ... | - | | - | fetch_blockchain_item_ids_msg (5003) | - | (next batch if remaining > 0) | - |--------------------------------------->| - | ... repeat until remaining == 0 ... | - -When remaining == 0 and all items known: - we_need_sync_items_from_peer = false (transition to broadcast mode) -``` - ---- - -### Broadcast/Inventory Mode Flow (Push-Based) - -``` -validator Node Peer A Peer B (us) Peer C - | | | | - | 1. generate_block | - | 2. broadcast_block| | | - | [block_message] | | | - |------------------>| | | - | | | | - | | 3. item_ids_inventory| | - | | [block #N hash] | | - | |--------------------->| | - | | | | - | | 4. fetch_items_msg | | - | | [request block] | | - | |<---------------------| | - | | | | - | | 5. block_message | | - | | [block data] | | - | |--------------------->| | - | | | | - | | | 6. push_block() | - | | | 7. item_ids_inv | - | | | [block #N hash] | - | | |------------------->| - | | | | - | | | 8. fetch + block | - | | |<------------------>| -``` - -**Broadcast suppression during sync:** When `we_need_sync_items_from_peer == true` for ANY peer, incoming inventory from ALL peers is skipped (node.cpp:3358). This prevents inventory-request timeouts from killing sync connections. - ---- - -### Block Post-Validation Flow - -``` -validator Node Other Peers - | | - | 1. produce_block() + broadcast | - | 2. broadcast_block_post_validation() | - | [block_id, witness_account, sig] | - |--------------------------------------->| - | | 3. handle_message() in p2p_plugin - | | - recover public key from signature - | | - compare with validator on-chain signing_key - | | - if match: apply_block_post_validation() -``` - ---- - -## Transaction Exchange Flow - -### JSON-RPC → Chain → P2P Broadcast - -This is how a transaction submitted via the JSON-RPC API reaches the P2P network: - -``` -External Client - | - | HTTP/WS POST {"jsonrpc":"2.0", "method":"call", - | "params":["network_broadcast_api","broadcast_transaction", - | [signed_transaction]]} - v -[webserver plugin] → [json_rpc plugin] → dispatch to registered API - | -[network_broadcast_api_plugin::broadcast_transaction] - | - | 1. pimpl->_chain.accept_transaction(trx) - | → chain_plugin::accept_transaction() - | → database::push_transaction() - | → validates, pushes to _pending_tx - | - | 2. pimpl->_p2p.broadcast_transaction(trx) - | → p2p_plugin::broadcast_transaction() - | → node->broadcast(trx_message(trx)) - | → sends trx_message (1000) to all connected peers - | - | 3. Peers receive → process_ordinary_message() - | → _delegate->handle_transaction(trx_msg) - | → chain.accept_transaction() - | → broadcast() to their OTHER peers (relay) - | - v - Transaction propagates through the network via gossip - (each peer that accepts it broadcasts to its other peers) - -Eventually included in a block by a validator. -``` - -### Callback-Based Variant - -`broadcast_transaction_synchronous` (network_broadcast_api.cpp:55): -- Registers a callback keyed by `transaction_id` -- Accepts and broadcasts the transaction -- When a block containing the transaction is applied (`on_applied_block`, line 159), fires the callback with `(txid, block_num, trx_num, expired=false)` -- When the transaction expires before inclusion, fires callback with `expired=true` - -### P2P Receive → Chain → Relay - -``` -Remote Peer - | - | trx_message (1000) arrives - v -[node.cpp] process_ordinary_message() - | - | Check: items_requested_from_peer contains this item? - | No → "received a message I didn't ask for" → disconnect - | Yes → erase from items_requested_from_peer - | - | msg_type == trx_message_type? - | Yes → _delegate->handle_transaction(trx_msg) - | No → _delegate->handle_message(msg) [for block_post_validation, etc.] - | - v -[p2p_plugin.cpp] handle_transaction() - | - | chain.accept_transaction(trx_msg.trx) - | → database::push_transaction() - | → validates, pushes to _pending_tx - | - | On success: node.cpp broadcast() to all OTHER peers - v - Transaction relayed to rest of network -``` - ---- - -## Dispatch Decision Tree (node.cpp) - -``` -message received - | - +-- connection_accepted (5007) → on_connection_accepted_message() - | - +-- connection_rejected (5008) → disconnect - | - +-- address_request (5009) → send address_message - +-- address_message (5010) → add to potential_peer_db - +-- closing_connection (5011) → disconnect - | - +-- current_time_request (5012)→ send current_time_reply - +-- current_time_reply (5013) → update clock_offset - | - +-- check_firewall (5014) → try connecting, send result - +-- check_firewall_reply (5015)→ update firewalled state - | - +-- get_current_connections_req (5016) → send reply - +-- get_current_connections_reply (5017) → diagnostics - | - +-- chain_status_announcement (5018) → handle_message() - | → log DLT/emergency state from peer - | - +-- hello_message (5006) → on_hello_message() - | → validate, accept or reject - | - +-- fetch_blockchain_item_ids (5003) - | → on_fetch_blockchain_item_ids() - | → build synopsis, find common ancestor - | → send blockchain_item_ids_inventory - | - +-- blockchain_item_ids_inventory (5002) - | → on_blockchain_item_ids_inventory() - | → validate sequential IDs - | → add unknown IDs to ids_of_items_to_get - | - +-- fetch_items (5004) → on_fetch_items() - | → get_item() for each requested hash - | → send block_message or trx_message - | - +-- item_not_available (5005) → soft-ban / strike - | - +-- item_ids_inventory (5001) - | → on_item_ids_inventory() - | → if not syncing: request unknown blocks - | - +-- block_message (1001) OR trx_message (1000) - → process_ordinary_message() - → was this item requested? (items_requested_from_peer) - → delegate to handle_block() or handle_transaction() - → on success: broadcast to other peers -``` - ---- - -## DLT Mode Considerations for P2P Messages - -### Synopsis Handling - -When a DLT node (rolling window of blocks) receives a `fetch_blockchain_item_ids_message`: - -1. **Synopsis below DLT range**: If ALL synopsis entries are below `earliest_available_block_num()`, the peer is NOT on a fork — it simply has older blocks than we store. Use the highest synopsis entry as anchor and serve from our earliest block. (Implemented in [p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp#L412-L442)) - -2. **Synopsis above head**: If ALL synopsis entries are above our head, the peer is ahead of us. Return empty — nothing to serve. - -3. **Synopsis matched below range**: When the matched anchor is below `earliest_available_block_num()`, include it in the response as a known anchor, then continue from earliest. Prevents "invalid response" disconnections. - -### Block Serving - -`get_item()` (p2p_plugin.cpp:528) in DLT mode: when a block is not found, logs the full context (available range, DLT range) and throws `key_not_found_exception` instead of the generic "Couldn't find block" error. This gives visibility into whether the block is genuinely missing or just outside the DLT window. - -### Near-Caught-Up Sync Blocks - -[p2p_plugin.cpp](file:///d:/Work/viz-cpp-node/plugins/p2p/p2p_plugin.cpp#L202-L226): When a sync block arrives with `gap <= 2 && dlt_mode && block_age < 30s`, it's treated as a normal (non-sync) block. This prevents "Syncing Blockchain started" from firing when the node is only 1-2 blocks behind, which would set `currently_syncing=true` and disrupt validator block production. - ---- - -## How `chain_status_announcement_message` (5018) Makes DLT Mode Safer - -### Problem: The Hello Handshake Alone Is Not Enough - -The hello message (5006) sends chain state **once** at connection time. In DLT mode with emergency consensus, the chain state can change dramatically *during* an active connection: - -- **DLT window slides forward** every block (the rolling window of ~350 blocks advances by 1 each time). -- **Emergency consensus can activate or deactivate** at any time (1-hour timeout triggers; exit after 21 normal blocks). -- **The emergency master can change** (blank key after 5 missed rounds). - -Without live updates, peers make decisions based on **stale connection-time data**, leading to: - -1. **False `peer_is_on_an_unreachable_fork`**: A peer sends a synopsis with entries from block numbers we *used* to have but which have now aged out of our DLT window. Without knowing our DLT `earliest_available_block_num()`, the peer can't adjust its synopsis to use entries inside our window. Result: we throw the fork exception, disconnect the peer, and create a sync oscillation. - -2. **Sync ping-pong in emergency mode**: During emergency consensus, competing forks at the same height cause both nodes to restart sync. Without knowing the peer is also in emergency mode, a node treats the peer's blocks as "normal" fork blocks and triggers full sync restarts instead of recognizing the emergency situation and treating it as a committee-led recovery. - -3. **Inventory flooding during DLT window mismatch**: A peer in broadcast mode may advertise block IDs that are below our DLT window. We can't serve them, but we don't know the peer has stale information. This generates spurious `fetch_items` → `item_not_available` cycles that increment the peer's strike counter and lead to soft-bans. - -### Solution: The Chain Status Announcement - -The `chain_status_announcement_message` (5018) solves these problems by providing: - -#### 1. Live DLT Window Information (`dlt_mode`, `dlt_earliest_block`) - -When a peer receives a `chain_status_announcement` showing the sender is in DLT mode with `dlt_earliest_block = 79632101`, it knows: - -- **Don't use block numbers below 79632101 in synopses** sent to this peer — those blocks are no longer in their rolling window. -- **Don't request blocks below 79632101** — they'll get `item_not_available` responses and risk soft-ban strikes. -- **Expected result**: Eliminates the entire class of `peer_is_on_an_unreachable_fork` errors caused by below-DLT-range synopses. - -#### 2. Live Emergency Consensus Status (`emergency_consensus_active`, `has_emergency_key`) - -When a peer receives a `chain_status_announcement` with `emergency_consensus_active = true`: - -- **Don't treat competing blocks at the same height as forks** — they may be emergency committee blocks from a legitimate committee recovery. -- **If `has_emergency_key = true`**: This peer is the emergency master — prioritize syncing from it. -- **If `has_emergency_key = false`**: This peer is an emergency follower — don't soft-ban it for producing blocks we disagree with (the committee decides). -- **Expected result**: Eliminates sync ping-pong loops and false fork detections during emergency consensus recovery. - -#### 3. Continuous Refresh on New Connections - -The message is automatically broadcast via `connection_count_changed()` whenever a new peer joins. This means: - -- **Every new peer immediately learns** our current DLT window and emergency status, even if we connected hours ago. -- **No polling needed** — push-based, not pull-based. -- **Zero protocol breakage** — old peers simply ignore message type 5018 (it goes to `handle_message()` which, pre-5018, would throw `Invalid Message Type`, but since old peers never send this message to begin with, the throw never fires). - -### Error Classes Eliminated - -| Error Class | Root Cause | Fixed By | -|---|---|---| -| `peer_is_on_an_unreachable_fork` (below-range) | Peer synopsis entries below our DLT `earliest_available_block_num()` | Peer sees `dlt_earliest_block` and adjusts synopsis | -| Sync restart oscillation | Both nodes think the other is on a fork in emergency mode | Both see `emergency_consensus_active=true` and relax fork detection | -| `item_not_available` soft-bans | Peer requests blocks we can't serve (below window) | Peer skips below-DLT block requests | -| `unlinkable_block_exception` spamming | Dead-fork sync blocks from before our head | Peer sees `dlt_mode` and avoids sending ancient blocks | -| Emergency follower disconnection | Master treats follower blocks as invalid fork blocks | `has_emergency_key` flag identifies master vs follower roles | - -### Integration with Existing Protections - -The `chain_status_announcement` works **alongside** (not instead of) the existing server-side protections: - -| Protection | Side | When It Helps | -|---|---|---| -| `chain_status_announcement` info | Client (peer) | **Before** sending — avoids problematic requests entirely | -| `get_block_ids()` below-range check | Server | **During** synopsis processing — catches what the client missed | -| `get_block_ids()` above-head check | Server | **During** synopsis processing — handles ahead-of-us peers | -| DLT near-caught-up logic | Server | **During** block receiving — prevents sync mode disruption | -| Soft-ban strike counters | Server | **After** repeated errors — last-resort penalty | - -**Key insight**: The server-side protections handle errors reactively (strikes, disconnection), while `chain_status_announcement` prevents errors proactively (peers know what not to do before they do it). Both layers together create defense-in-depth. - -### Backward Compatibility - -| Peer Combination | Behavior | -|---|---| -| Old ↔ Old | No change. Works as before. | -| New ↔ New | Both exchange DLT/emergency info via hello user_data AND chain_status_announcement (5018). Full benefits. | -| New → Old | New peer sends hello with DLT fields (old peer ignores unknown keys). New peer may send 5018 (old peer's handle_message throws `Invalid Message Type`). This is a one-time benign throw since old peers don't understand the type. The new peer's code handles this gracefully. | -| Old → New | Old peer sends hello without DLT fields. New peer defaults `peer_dlt_mode=false`, `peer_emergency_active=false` — assumes the old peer operates in normal (non-DLT, non-emergency) mode. The new peer does NOT send 5018 to the old peer because `connection_count_changed` broadcasts to ALL peers, which includes the old one. This is a known limitation — old peers will see one `Invalid Message Type` throw per new-peer connection. To mitigate, future work could add per-peer capability tracking based on hello user_data fields. | - ---- - -## Key Type Definitions - -| Type | Underlying | Size | Defined In | -|------|-----------|------|------------| -| `node_id_t` | `fc::ecc::public_key_data` | 33 bytes | core_messages.hpp:51 | -| `item_hash_t` | `fc::ripemd160` | 20 bytes | core_messages.hpp:52 | -| `item_id` | `{ uint32_t item_type; item_hash_t item_hash; }` | 24 bytes | core_messages.hpp:54 | -| `block_id_type` | `fc::ripemd160` | 20 bytes | protocol/types.hpp | -| `transaction_id_type` | `fc::ripemd160` | 20 bytes | protocol/types.hpp | - ---- - -## Key Configuration Constants - -| Constant | File | Default | Description | -|----------|------|---------|-------------| -| `GRAPHENE_NET_PROTOCOL_VERSION` | config.hpp | — | Version sent in hello_message | -| `GRAPHENE_NET_DEFAULT_PEER_CONNECTION_RETRY_TIME` | config.hpp | 30s | Base retry interval | -| `GRAPHENE_NET_MAX_FAILED_CONNECTION_ATTEMPTS` | config.hpp | 5 | Cap on failure counter (max backoff = 180s) | -| `GRAPHENE_NET_DEFAULT_DESIRED_CONNECTIONS` | config.hpp | 20 | Target active connections | -| `GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH` | config.hpp | 10000 | Min IDs to collect before block fetching during concurrent sync | -| `DISCONNECT_RECONNECT_COOLDOWN_SEC` | node.cpp | 30s | Per-IP cooldown after disconnect | diff --git a/.qoder/docs/p2p-sync-workflow.md b/.qoder/docs/p2p-sync-workflow.md deleted file mode 100644 index a6293bb044..0000000000 --- a/.qoder/docs/p2p-sync-workflow.md +++ /dev/null @@ -1,699 +0,0 @@ -# P2P Synchronization & Block Push Workflow - -## Overview - -The VIZ P2P network uses two distinct modes for block propagation between nodes: - -1. **Sync Mode** — Active pull-based synchronization for catching up with the network -2. **Broadcast/Inventory Mode** — Passive push-based delivery of new blocks in real-time - -A node transitions from sync mode to broadcast mode once it catches up to the network head. Both modes operate per-peer — a node can be syncing from one peer while receiving broadcasts from another. - ---- - -## Architecture - -### Key Components - -| Component | File | Role | -|-----------|------|------| -| `node_impl` | `libraries/network/node.cpp` | Core P2P engine: sync state machine, message handlers, peer management | -| `peer_connection` | `libraries/network/include/graphene/network/peer_connection.hpp` | Per-peer state: sync flags, item queues, soft-ban timers | -| `p2p_plugin_impl` | `plugins/p2p/p2p_plugin.cpp` | Bridge between P2P network and blockchain: block handling, sync initiation | -| `chain::database` | `libraries/chain/database.cpp` | Blockchain state: push_block, fork_db, block validation | - -### Per-Peer Sync State Flags - -Each `peer_connection` carries two critical boolean flags: - -``` -peer_needs_sync_items_from_us — Does the peer need blocks from us? (controls our OUTBOUND sync) -we_need_sync_items_from_peer — Do we need blocks from them? (controls our INBOUND sync) -``` - -These flags determine which protocol mode is active for each peer. - ---- - -## Mode 1: Sync Mode (Pull-Based) - -### When Active - -`we_need_sync_items_from_peer = true` - -Active when: -- A new peer connection is established (`new_peer_just_added()` → `start_synchronizing_with_peer()`) -- After minority fork recovery (`resync()` resets all peers) -- When a peer is detected to be ahead of us - -### Flow - -``` -Our Node Peer - | | - | 1. fetch_blockchain_item_ids | - | (our blockchain synopsis) | - |----------------------------------->| - | | - | 2. blockchain_item_ids_inventory | - | (list of block IDs + remaining) | - |<-----------------------------------| - | | - | 3. fetch_items_message | - | (request actual block data) | - |----------------------------------->| - | | - | 4. block_message | - | (signed block data) | - |<-----------------------------------| - | | - | [repeat 3-4 for each block] | - | | - | 5. fetch_blockchain_item_ids | - | (request next batch of IDs) | - |----------------------------------->| - | | - | [repeat until remaining=0] | -``` - -### Detailed Steps - -#### Step 1: Build and Send Synopsis -**Function:** `fetch_next_batch_of_item_ids_from_peer()` (node.cpp L2748) - -The node builds a "blockchain synopsis" — a logarithmically-spaced list of block IDs from its chain — and sends it to the peer via `fetch_blockchain_item_ids_message`. The synopsis allows the peer to find the most recent common block efficiently. - -#### Step 2: Receive Block ID Inventory -**Handler:** `on_blockchain_item_ids_inventory_message()` (node.cpp L2787) - -The peer responds with: -- `item_hashes_available` — sequential list of block IDs the peer has after the common point -- `total_remaining_item_count` — how many more block IDs the peer can provide beyond this batch - -The node validates the response (sequential block numbers, valid link to synopsis) and adds unknown block IDs to `peer->ids_of_items_to_get`. - -#### Step 3-4: Fetch Actual Blocks -**Function:** `fetch_sync_items_loop()` (node.cpp ~L1100) - -Runs periodically. For each peer with `we_need_sync_items_from_peer = true` and `inhibit_fetching_sync_blocks = false`: -- Picks blocks from `ids_of_items_to_get` that aren't already requested from other peers -- Sends `fetch_items_message` to request the block data -- Tracks requests in `_active_sync_requests` to avoid duplicate fetches - -#### Step 5: Batch Continuation -If `total_remaining_item_count > 0`, the node sends another `fetch_blockchain_item_ids_message` to get the next batch of block IDs. This continues until the peer reports `remaining = 0`. - -### Sync Completion - -When the peer responds with `remaining = 0` and all offered items are already known: - -```cpp -// node.cpp L2967 or L3181 -originating_peer->we_need_sync_items_from_peer = false; -``` - -The node transitions this peer to **broadcast mode**. - ---- - -## Mode 2: Broadcast/Inventory Mode (Push-Based) - -### When Active - -`we_need_sync_items_from_peer = false` - -Active after sync completes — the node is caught up and receives new blocks in real-time. - -### Flow: Block Production and Propagation - -``` -validator Node Peer A Peer B (us) - | | | - | 1. generate_block() | | - | 2. broadcast_block() | | - |--------------------------->| | - | | | - | | 3. item_ids_inventory_msg | - | | ("I have block #N") | - | |--------------------------->| - | | | - | | 4. fetch_items_message | - | | ("send me block #N") | - | |<---------------------------| - | | | - | | 5. block_message | - | | (signed block data) | - | |--------------------------->| - | | | - | | 6. push_block() - | | 7. broadcast to - | | other peers -``` - -### Detailed Steps - -#### Step 1-2: Block Production -A validator node produces a block and calls `p2p_plugin::broadcast_block()`, which sends the block to all connected peers via `node::broadcast()`. - -#### Step 3: Inventory Advertisement -When a peer receives a new block (either via broadcast or sync), it advertises it to all its OTHER connected peers via `item_ids_inventory_message`. This is the gossip protocol — blocks propagate through the network hop by hop. - -#### Step 4-5: Block Request and Delivery -**Handler:** `on_item_ids_inventory_message()` (node.cpp L3339) - -When we receive an inventory message: -1. Check we're NOT in sync mode (skip if `we_need_sync_items_from_peer = true`) -2. Check no global sync in progress (skip if ANY peer has sync flag) -3. Check our head block is recent (skip if >30 seconds behind) -4. For each advertised item we don't have, request it via `fetch_items_message` -5. Peer responds with `block_message` containing the actual block - -#### Step 6-7: Block Application -**Handler:** `p2p_plugin_impl::handle_block()` (p2p_plugin.cpp L145) - -The block is pushed to the chain via `chain.accept_block()` → `database::push_block()`. If accepted, the node broadcasts it to its other peers, continuing propagation. - -### Broadcast Gate (Critical) - -At L3358 of node.cpp: -```cpp -if (originating_peer->we_need_sync_items_from_peer) { - // skip broadcast inventory — we're syncing from this peer - return; -} -``` - -**Broadcast inventory is completely suppressed during sync mode.** This prevents the 1-second inactivity timeout from killing sync connections (requesting tip-of-chain items during sync would time out before they arrive, disconnecting the peer). - ---- - -## Peer Blocking Mechanisms - -### Soft-Ban (`fork_rejected_until`) - -A time-based ban that silently discards incoming sync requests from the peer: - -```cpp -// node.cpp L2462 -if (originating_peer->fork_rejected_until > fc::time_point::now()) { - // silently discard sync request - return; -} -``` - -**Triggered by:** -- 50 competing-fork sync spam strikes → 300 second ban (L2600, L2637) -- 20 unlinkable block strikes → dynamic duration ban (L3721, L3837) -- Peer on a dead/old fork → dynamic duration ban (L3823) -- Item not available from peer → 30 second ban (L3311, L3325) - -### Sync Inhibition (`inhibit_fetching_sync_blocks`) - -Prevents fetching sync blocks from a specific peer without fully banning them: - -```cpp -// node.cpp L1158 -if (!peer->inhibit_fetching_sync_blocks) { - // fetch sync blocks from this peer -} -``` - -**Triggered by:** -- Peer can't advance our sync (returns only known blocks) — L3152 -- Item not available from peer — L3310, L3324 -- During soft-ban (always set alongside `fork_rejected_until`) - -### 30-Second Stuck Flag Auto-Clear - -**Location:** `terminate_inactive_connections_loop()` (node.cpp L1547-1556) - -Runs every 1 second. If `peer_needs_sync_items_from_us = true` but the peer hasn't sent a sync request in 30+ seconds, auto-clears the flag to `false`. This prevents inventory starvation when a race condition leaves the flag stuck. - -**Important:** A matching auto-clear for `we_need_sync_items_from_peer` was added as a safety net — see "Stuck `we_need_sync_items_from_peer` Auto-Clear" below. - -### Stuck `we_need_sync_items_from_peer` Auto-Clear - -**Location:** `terminate_inactive_connections_loop()` (node.cpp L1624-1638) - -If a peer has `we_need_sync_items_from_peer = true` but ALL sync-related lists are empty: -- `ids_of_items_to_get` empty -- `ids_of_items_being_processed` empty -- `sync_items_requested_from_peer` empty -- `number_of_unfetched_item_ids == 0` -- No pending `item_ids_requested_from_peer` - -AND this state has persisted for **30+ seconds** (measured via `last_sync_item_received_time`), the flag is auto-cleared to `false`. This is a safety net for edge cases where sync completes but the flag isn't properly reset (e.g., fork-switch race conditions — see "Gap/Fork Block Sync Stall Recovery" below). - -**Important:** This does NOT touch `sync_items_requested_from_peer` — clearing in-flight requests would cause arriving responses to be treated as "unsolicited block" and disconnect the peer. - ---- - -## Minority Fork Recovery - -### Detection - -In `witness_plugin::impl::maybe_produce_block()` (validator.cpp), if the last 21 blocks in `fork_db` were ALL produced by the node's own configured validators, the node is likely on a minority fork (isolated from the network). - -### Recovery Flow - -``` -1. MINORITY FORK DETECTED - ↓ -2. resync_from_lib() (p2p_plugin.cpp) - ├── Pop all reversible blocks back to LIB - ├── Reset fork_db, seed with LIB block - ├── node->sync_from(LIB block ID) - ├── node->resync() ← full peer state reset - └── Reconnect seed nodes - ↓ -3. _production_enabled = false - ↓ -4. Production loop returns not_synced every 250ms - (waiting for get_slot_time(1) >= now) - ↓ -5. P2P sync delivers blocks from peers - (head advances toward real time) - ↓ -6. Once head catches up: get_slot_time(1) >= now - → _production_enabled = true - ↓ -7. Block production resumes -``` - -### Full Peer State Reset - -The peer state reset logic lives in `node_impl::reset_active_peer_states()` (node.cpp) and is shared by two callers: - -1. **`resync()`** — called during minority fork recovery via `resync_from_lib()`. Resets all peer state, clears `_active_sync_requests`, then calls `start_synchronizing()`. -2. **`reconnect_seeds()`** — called by the Validator Plugin when producing a block with <2 peers. Resets all peer state, then force-reconnects seed nodes. - -The `resync()` function calls `reset_active_peer_states()` which clears per-peer state: - -``` -For each active peer: - - fork_rejected_until = epoch (lift soft-ban) - - unlinkable_block_strikes = 0 (clear strike counter) - - sync_spam_strikes = 0 (clear spam counter) - - inhibit_fetching_sync_blocks = false - - peer_needs_sync_items_from_us = true - - we_need_sync_items_from_peer = true - - Clear: ids_of_items_to_get, ids_of_items_being_processed, - sync_items_requested_from_peer - - Reset: last_block_delegate_has_seen -``` - -Then `resync()` also clears global sync state: - -``` - - _active_sync_requests.clear() (stale in-flight request tracking) - - _received_sync_items.clear() (accumulated blocks that failed to link) - - _new_received_sync_items.clear() (recently arrived blocks not yet tried) - - _most_recent_blocks_accepted → reset to [current head block ID] - (prevents "already seen" skip of gap blocks) -``` - -Without clearing `_received_sync_items` and `_new_received_sync_items`, `have_already_received_sync_item()` would skip re-requesting blocks that arrived but failed to link (e.g., unlinkable blocks during a gap), leaving permanent gaps — especially critical in DLT emergency mode. Without resetting `_most_recent_blocks_accepted`, `process_backlog_of_sync_blocks()` would skip blocks that were accepted before the gap but need re-evaluation after resync. - -> **Note:** The DLT P2P node (`dlt_p2p_node.cpp`) has an analogous mechanism called `emergency_peer_reset()` (P53 fix) that handles peer isolation — when all peers are disconnected/banned for 60+ seconds, it clears soft bans, resets backoffs, and forces immediate reconnection. See [DLT Forward Mode — Peer Isolation Recovery](./dlt-forward-mode.md#peer-isolation-recovery) for details. - ---- - -## Connection Retry & Seed Reconnection - -### Connection Loop - -`p2p_network_connect_loop()` (node.cpp) runs continuously, every **10 seconds**. It: - -1. Processes `_add_once_node_list` — priority peers (seeds added via `add_node()`) that bypass connection limits -2. Checks `is_wanting_new_connections()` — true if `active_connections < desired_connections` (default 20) -3. Iterates `_potential_peer_db` with exponential backoff: `(failed_attempts + 1) * 30s` -4. Skips peers in disconnect cooldown (30 seconds after disconnect) - -### Backoff Cap - -`number_of_failed_connection_attempts` is capped at `GRAPHENE_NET_MAX_FAILED_CONNECTION_ATTEMPTS` (5) in `config.hpp`. This limits the maximum retry delay to `(5+1) * 30 = 180 seconds = 3 minutes`. - -When a peer reaches the maximum failure count and still fails to connect, an info-level log is emitted: -``` -P2P seed node not responding (5 consecutive failures), check config and remove if not needed -``` - -### Low-Peer Seed Reconnection - -The Validator Plugin checks the connection count after each successfully produced block. If fewer than 2 peers are connected: - -``` -1. validator produces block, broadcasts it -2. Check: get_connections_count() < 2? -3. YES → p2p_plugin::reconnect_seeds() - ├── node->reset_active_peer_states() (clear all blocking state) - └── For each seed: add_node() + connect_to_endpoint() - (bypasses exponential backoff via timer reset) -``` - -`add_node()` resets the `last_connection_attempt_time` to allow immediate retry, and adds the peer to `_add_once_node_list` for priority processing in the next connect loop iteration. This means the node retries seeds every block interval (~3 seconds) when isolated, rather than waiting for backoff. - ---- - -## State Diagram - -``` - ┌─────────────────────────┐ - │ Connection Established │ - └────────────┬────────────┘ - │ - ▼ - ┌─────────────────────────┐ - │ start_synchronizing_ │ - │ with_peer() │ - │ we_need_sync = true │ - └────────────┬────────────┘ - │ - ▼ - ┌─────────────────────────┐ - │ SYNC MODE │ - │ Pull block IDs + data │◄──── resync() on - │ from peer │ minority fork - └────────────┬────────────┘ recovery - │ - │ remaining=0 && - │ all items known - ▼ - ┌─────────────────────────┐ - │ we_need_sync = false │ - └────────────┬────────────┘ - │ - ▼ - ┌─────────────────────────┐ - │ BROADCAST MODE │ - │ Receive inventory ads │ - │ Request unknown blocks │ - └─────────────────────────┘ -``` - ---- - -## DLT Mode Considerations - -In DLT mode (node loaded from snapshot), several sync behaviors are adjusted: - -1. **Block serving is clamped** to the available range (dlt_block_log + fork_db). The node won't advertise blocks it can't serve. -2. **Synopsis matching** tolerates gaps between the anchor block and continuation blocks (a DLT node may not have all historical blocks). -3. **Peer ahead detection**: If all peer synopsis entries are above our head, return empty (peer is ahead, not on a fork). -4. **Broadcast inventory** is suppressed when head block is >30 seconds behind real time, even if no peer has `we_need_sync_items_from_peer = true`. -5. **Sync oscillation prevention**: When sync blocks arrive ahead of head (gap between head and arriving blocks), a progressive cooldown (5s→10s max) prevents thundering-herd restarts, and `resync()` clears all stale sync state so missing gap blocks can be re-fetched. See "DLT Emergency Sync Oscillation Prevention" below. -6. **Synopsis head-block guarantee**: `get_blockchain_synopsis()` ensures the reference point (`high_block_num`) is always included in the returned synopsis. When `true_high_block_num >> high_block_num` (which happens when IDs have already been queued), the logarithmic step can skip over `high_block_num` in a single jump; a post-loop guard appends it if missing. Without this, peer responses starting at our head fail validation ("invalid response"). See "Synopsis High-Block Guarantee" below. -7. **Concurrent ID + block fetching**: Block fetching is allowed while block IDs are still being collected from a peer, once at least `GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH` (10000) IDs are available. This prevents the "NOT IDLE (ids_req=true)" stall where a peer is perpetually busy fetching IDs and blocks are never requested. See "Concurrent ID and Block Fetching" below. -8. **Duplicate sync-start guards**: `start_synchronizing_with_peer()` and `new_peer_just_added()` both guard against duplicate sync initiation for the same peer, preventing duplicate synopsis requests that cause "invalid response" disconnections. See "Duplicate Sync-Start Guards" below. - ---- - -## Stale Sync Detection - -A background safety mechanism that detects when the node has stopped receiving blocks from the network and automatically triggers recovery. - -### Configuration - -| Option | Default | Description | -|--------|---------|-------------| -| `p2p-stale-sync-detection` | `true` | Enable/disable the feature | -| `p2p-stale-sync-timeout-seconds` | `120` | Seconds without any block before triggering recovery | - -### How It Works - -A scheduled task runs every **30 seconds** (`stale_sync_check_task()` in p2p_plugin.cpp L935): - -1. Computes `elapsed = now - _last_block_received_time` -2. If `elapsed > timeout` (default 120s = 2 minutes): - -``` -Stale sync detected! - │ - ├── 1. Get LIB number from database - ├── 2. node->sync_from(LIB block ID) ← reset sync start point - ├── 3. node->resync() ← full peer state reset + start_synchronizing() - ├── 4. For each seed node: - │ add_node() + connect_to_endpoint() ← force reconnect - └── 5. Reset _last_block_received_time ← prevent immediate retry -``` - -3. Reschedules itself for another check in 30 seconds - -### Timer Reset Points - -The `_last_block_received_time` is reset to `now` in these situations: - -| Location | When | -|----------|------| -| `handle_block()` (L148) | Every time a block is received from any peer | -| `stale_sync_check_task()` (L984) | After recovery triggers (prevents immediate re-trigger) | -| `resync_from_lib()` (L1361) | After minority fork recovery | -| `trigger_resync()` (L1422) | After snapshot hot-reload | -| Plugin startup (L1236) | Initial value when node starts | - -### Interaction with Other Recovery Mechanisms - -The stale sync detector acts as a **last-resort safety net**. It complements: - -- **Minority fork detection** (Validator Plugin) — triggers faster (after 21 own-validator blocks), but only if the node is actively producing. Stale sync covers the case where the node is NOT a validator or production is already disabled. -- **Low-peer seed reconnection** (Validator Plugin) — triggers per-block when <2 peers, but only while producing. Stale sync covers periods when production is halted. -- **Connection loop backoff** (node.cpp) — handles normal reconnection with exponential backoff. Stale sync overrides this by calling `resync()` which does a full peer state reset + `add_node()` on seeds. -- **Snapshot stalled sync detection** (snapshot plugin) — a separate, heavier mechanism described below. - ---- - -## Snapshot Stalled Sync Detection - -A separate stalled sync detector lives in the **snapshot plugin** (`plugins/snapshot/plugin.cpp`). Unlike the P2P-level detector (which resets sync and reconnects seeds), this one downloads a **newer snapshot** from trusted peers — a much heavier recovery action designed for DLT mode nodes that are hopelessly behind. - -### Configuration - -| Option | Default | Description | -|--------|---------|-------------| -| `enable-stalled-sync-detection` | `false` | Enable/disable snapshot-level stall detection | -| `stalled-sync-timeout-minutes` | `5` | Minutes without any block before triggering snapshot re-download | -| `trusted-snapshot-peer` | (none) | Trusted peer endpoints for snapshot download (can specify multiple) | - -Requires `trusted-snapshot-peer` to be configured — without trusted peers, the detection won't start even if enabled. - -### How It Works - -A background thread runs `check_stalled_sync_loop()` (plugin.cpp L1682), checking every **30 seconds**: - -1. Computes `elapsed = now - last_block_received_time` -2. If `elapsed > stalled_sync_timeout_minutes` (default 5 min), uses a two-stage escalation: - -``` -Stalled sync detected! - │ - ├── First trigger (P2P recovery): - │ ├── p2p_plugin->trigger_resync() ← resync + reconnect seeds - │ ├── Set _p2p_recovery_attempted = true - │ └── Delay timer by 1 minute (give P2P recovery time to work) - │ - └── Second trigger (snapshot download): - ├── 1. Query trusted peers for a newer snapshot - │ download_snapshot_from_peers() - │ - ├── 2a. Newer snapshot found: - │ ├── load_snapshot() ← replace chain state - │ ├── set_dlt_mode(true) - │ ├── initialize_hardforks() - │ ├── Replay dlt_block_log ← apply local blocks beyond snapshot - │ ├── p2p_plugin->trigger_resync() ← resync + reconnect seeds - │ └── Reset timer + guard, restart loop - │ - └── 2b. No newer snapshot available: - └── Reset timer, continue with P2P sync -``` - -The `_p2p_recovery_attempted` guard resets to `false` whenever a block is received (`on_applied_block`), so each new stall starts fresh with P2P recovery. - -### Difference from P2P Stale Sync Detection - -| | P2P Stale Sync (p2p_plugin) | Snapshot Stalled Sync (snapshot plugin) | -|---|---|---| -| **Default** | Enabled (`true`) | Disabled (`false`) | -| **Timeout** | 120 seconds (2 min) | 5 minutes | -| **Recovery action** | Reset sync to LIB + reconnect seeds | Download entire snapshot from trusted peer | -| **Requires** | Nothing (works with any peers) | `trusted-snapshot-peer` configured | -| **Severity** | Lightweight (P2P-level reset) | Heavy (full chain state replacement) | -| **Use case** | Temporary network issues, soft-bans | Node hopelessly behind, DLT mode bootstrap | - -The P2P detector fires first (2 min) and attempts a soft recovery. If that doesn't work and blocks still don't arrive, the snapshot detector fires later (5 min) and does a hard recovery by re-downloading state. - ---- - -## Gap/Fork Block Sync Stall Recovery - -A three-layer defense against a sync stall that occurs when a broadcast block with a missing parent triggers sync, and the fork switch during sync applies both the gap-filling block AND the deferred broadcast block to the chain. - -### Problem Scenario - -``` -1. Block #N+2 arrives via broadcast, deferred to fork_db (parent #N+1 missing). - Added to _most_recent_blocks_accepted. -2. Sync starts with peer. ids_of_items_to_get = [#N+1, #N+2]. -3. Block #N+1 arrives via sync, fork switch applies both #N+1 and #N+2. -4. send_sync_block_to_node_delegate for #N+1 cleans its own - ids_of_items_being_processed, but NOT #N+2's entry in ids_of_items_to_get. -5. fetch_sync_items_loop re-requests #N+2 from peer. Peer becomes NOT IDLE. -6. When #N+2 arrives again, process_backlog_of_sync_blocks finds it in - _most_recent_blocks_accepted — originally just logged and broke WITHOUT - cleaning ids_of_items_being_processed. -7. Orphaned ids_of_items_being_processed entry prevents sync completion. - we_need_sync_items_from_peer stays permanently true. -8. All broadcast inventory from the peer is silently suppressed. -9. Peer eventually disconnects on inactivity timeout. -``` - -### Layer 1: Stale `ids_of_items_to_get` Cleanup (Defensive) - -**Location:** `fetch_sync_items_loop()` (node.cpp L1154-1196) - -At the start of each loop iteration, before requesting blocks from peers, scan each syncing peer's `ids_of_items_to_get` and remove blocks that are already on the chain (`_delegate->has_item()` returns true AND block number <= head). This catches blocks applied via fork switch that the sync layer never "received". - -Peers whose lists become fully empty (all four sync lists empty) are collected. After the `ASSERT_TASK_NOT_PREEMPTED` block, `fetch_next_batch_of_item_ids_from_peer()` is called for each to confirm sync completion (L1251-1257). This call yields, so it must be outside the non-preemptable section. - -**Does NOT** clean `sync_items_requested_from_peer` — those are in-flight requests. Task 2 handles them when the response arrives. - -### Layer 2: Proper `_most_recent_blocks_accepted` Handling (Primary Fix) - -**Location:** `process_backlog_of_sync_blocks()` (node.cpp L4173-4201) - -When a sync block is found in `_most_recent_blocks_accepted` (already applied via broadcast + fork switch), the block has already been moved from `ids_of_items_to_get` into `ids_of_items_being_processed` (at L4130-4136). The fix properly cleans up: - -1. Erases the block from `_received_sync_items` (fixes memory leak) -2. Decrements `_total_number_of_unfetched_items` -3. For each peer: erases the block from `ids_of_items_being_processed`, updates `last_block_delegate_has_seen` -4. If peer's sync lists are now all empty, adds to `peers_with_newly_empty_item_lists` -5. Sets `block_processed_this_iteration = true` so the do-while loop continues - -After the do-while loop (L4228-4234), `fetch_next_batch_of_item_ids_from_peer()` is called for peers in `peers_with_newly_empty_item_lists`. The response handler at `on_blockchain_item_ids_inventory` (L2953) clears `we_need_sync_items_from_peer` when the peer reports `remaining = 0` with all items known. - -### Layer 3: Stuck Flag Safety Net - -**Location:** `terminate_inactive_connections_loop()` (node.cpp L1624-1638) - -Described above in "Stuck `we_need_sync_items_from_peer` Auto-Clear". Acts as a 30-second last-resort timeout if Layers 1 and 2 both miss the cleanup. - -### Other Related Fixes - -#### Gap Block Sync Trigger - -**Location:** `process_block_during_normal_operation()` (node.cpp L4316-4331) - -When a broadcast block's number is ahead of head+1 (parent missing) and `handle_block()` returns false, the block is stored in fork_db's unlinked index. If no sync is in progress (`!we_need_sync_items_from_peer`), sync is restarted with the originating peer to fetch the missing parent blocks. Without this, the node would stall permanently after receiving a gap block in broadcast mode. - -#### Inventory Gate Deadlock Breaker - -**Location:** `on_item_ids_inventory_message()` (node.cpp L3480-3513) - -When head is >30 seconds behind real time, broadcast inventory is normally suppressed. But if NO sync is in progress with ANY peer, the node is stuck — it ignores inventory AND doesn't sync. The fix detects this state and triggers `start_synchronizing_with_peer()` with the first peer that advertises blocks, breaking the deadlock. - -#### DLT Emergency Sync Oscillation Prevention - -**Location:** `send_sync_block_to_node_delegate()` (node.cpp L4141-4171) - -When a DLT emergency follower node loses sync with its master and reconnects, sync blocks ahead of the current head arrive but fail with `unlinkable_block_exception` (parent missing from `fork_db`). This triggers a sync restart, but the old implementation had two critical flaws: - -1. **Thundering herd**: Multiple concurrent async `send_sync_block_to_node_delegate` tasks fail simultaneously. Each slept 2s then called `start_synchronizing_with_peer()` for all peers — causing N concurrent full sync restarts at the same time. -2. **Incomplete state reset**: `start_synchronizing_with_peer()` cleared `ids_of_items_to_get` but NOT `sync_items_requested_from_peer`, `ids_of_items_being_processed`, `_active_sync_requests`, `_received_sync_items`, or `_most_recent_blocks_accepted`. Old failed blocks remained, causing `have_already_received_sync_item()` to skip re-requesting the missing gap blocks. - -The fix replaces the per-peer `start_synchronizing_with_peer()` loop with a single `resync()` call (which does a complete state reset — see "Full Peer State Reset" above) protected by a **progressive cooldown**: - -``` -First restart: 5s cooldown -Second restart: 7s cooldown -Third restart: 9s cooldown -Fourth+: 10s cooldown (max) - -Cooldown resets to 5s on any successful block acceptance. -``` - -The cooldown is tracked by `_last_deferred_resize_time` and `_consecutive_deferred_resize_count`. When a duplicate restart is attempted within the cooldown window, it is skipped entirely (early `return`), preventing the thundering herd. - -#### Synopsis High-Block Guarantee - -**Location:** `get_blockchain_synopsis()` (p2p_plugin.cpp, after the do-while loop) - -When the node has already collected block IDs (`ids_of_items_to_get` non-empty), subsequent synopsis requests pass `number_of_blocks_after_reference_point > 0`. Inside `get_blockchain_synopsis`, `true_high_block_num = high_block_num + number_of_blocks_after_reference_point`. The do-while loop steps logarithmically using `true_high_block_num` but exits when `low_block_num > high_block_num`. When `true_high_block_num >> high_block_num`, a single step can jump past `high_block_num`, producing a synopsis that omits the node's own head block entirely. - -When the peer responds with blocks starting near our head, the validation in `on_blockchain_item_ids_inventory` checks if the first block ID is in the synopsis. Since our head block is missing, validation fails and the peer is disconnected with "invalid response". - -The fix adds a guard after the do-while loop that appends `high_block_num` if the synopsis is empty or its last entry isn't `high_block_num`. The entry is looked up from the main chain (if within `non_fork_high_block_num`) or from `fork_history` (if on a fork). - -#### Duplicate Sync-Start Guards - -**Locations:** `start_synchronizing_with_peer()` (node.cpp) and `new_peer_just_added()` (node.cpp) - -When a peer connects, two code paths both call `start_synchronizing_with_peer` for the same peer: -- `on_fetch_blockchain_item_ids` handler — fires when processing the peer's initial synopsis request -- `new_peer_just_added` — fires when the peer finishes handshaking - -The second call clears the state set by the first (including `ids_of_items_to_get`) and sends a duplicate synopsis. The peer responds to both, and the second response triggers "invalid response" disconnection (exacerbated by the missing `high_block_num` from the synopsis bug). - -Two guards prevent this: -1. **`start_synchronizing_with_peer`**: Skips if `item_ids_requested_from_peer` is already set (a pending ID request already exists for this peer). Logs the skip at debug level. -2. **`new_peer_just_added`**: Checks both `we_need_sync_items_from_peer` and `item_ids_requested_from_peer` before calling `start_synchronizing_with_peer`. If either is set, sync was already started by the `on_fetch_blockchain_item_ids` handler and is skipped. - -#### Concurrent ID and Block Fetching - -**Location:** `fetch_sync_items_loop()` (node.cpp ~L1244) - -Previously, `fetch_sync_items_loop` required `peer->idle()` to schedule block requests. The `idle()` method returns false when `item_ids_requested_from_peer` is set — meaning blocks could never be fetched while IDs were being collected. During ID fetching, `fetch_next_batch_of_item_ids_from_peer` is called immediately after each batch, keeping `item_ids_requested_from_peer` permanently set. The loop logged "NOT IDLE (ids_req=true)" and skipped block fetching entirely. - -The fix replaces `peer->idle()` with a condition that only blocks on block-level requests: - -```cpp -(!peer->item_ids_requested_from_peer || // not fetching IDs, OR - peer->ids_of_items_to_get.size() >= GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH) // have enough IDs -&& peer->items_requested_from_peer.empty() // no pending block requests -&& peer->sync_items_requested_from_peer.empty() // no pending sync block requests -``` - -This allows block fetching once at least 10,000 IDs are available, even while more IDs are being fetched. The peer can serve both block data and ID responses simultaneously. - -The "NOT IDLE" diagnostic log was also updated to distinguish between "busy with blocks" (genuinely busy, can't fetch more) vs "skipped-other" (blocked by non-block state like ID requests with insufficient IDs). - -#### Emergency Consensus Head-Advancement Checks - -Both stale sync detectors skip recovery during emergency consensus if head is still advancing: - -- **P2P stale sync** (`p2p_plugin.cpp` L1021-1038): Compares `current_head > _last_stale_check_head`. If advancing, resets timer and skips recovery. -- **Snapshot stalled sync** (`plugin.cpp` L1772-1786): Same logic with `_last_stalled_check_head`. If advancing, resets timer and continues loop. - -This prevents false recovery triggers when the node is receiving emergency blocks from a master but not through the normal P2P sync path. - -### Fork DB State After Stale Sync Recovery - -Stale sync recovery (`resync()`) resets peer state and sync queues but does **not** reset `fork_db`. This means `fork_db._head` can point to a stale higher block accumulated from previous failed sync cycles. - -Without mitigation, this causes a permanent sync stall: - -1. `fork_db._head` points to block #N+5 from a previous cycle -2. Database head is at block #N (the actual applied chain tip) -3. Block #N+1 arrives from peer — its `previous` (#N) may not be in `fork_db` -4. If #N is missing from `fork_db`: `unlinkable_block_exception` → block silently rejected -5. If #N+1 is a duplicate in `fork_db`: returns stale `_head` (#N+5) → fork switch logic rejects -6. Head never advances → node re-enters sync → peer says "up-to-date" → repeat - -**Mitigation in `_push_block()`:** -- **Fork DB head-seeding:** Before pushing to `fork_db`, if the incoming block extends `head_block_id()` and the head is not in `fork_db`, the head block is seeded via `fork_db.start_block()`. This ensures the block can link. -- **Direct-extension bypass:** After pushing to `fork_db`, if `new_block.previous == head_block_id()`, the fork switch logic is bypassed entirely and the block is applied directly. This handles the stale `_head` pointing to a higher block. - -See [block-processing.md](block-processing.md) for details. - ---- - -## Key Configuration Constants - -| Constant | File | Default | Description | -|----------|------|---------|-------------| -| `GRAPHENE_NET_DEFAULT_PEER_CONNECTION_RETRY_TIME` | `config.hpp` | 30s | Base retry interval per failed attempt | -| `GRAPHENE_NET_MAX_FAILED_CONNECTION_ATTEMPTS` | `config.hpp` | 5 | Cap on failure counter (max backoff = 180s) | -| `GRAPHENE_NET_DEFAULT_DESIRED_CONNECTIONS` | `config.hpp` | 20 | Target number of active connections | -| `GRAPHENE_NET_DEFAULT_MAX_CONNECTIONS` | `config.hpp` | 200 | Maximum allowed connections | -| `DISCONNECT_RECONNECT_COOLDOWN_SEC` | `node.cpp` | 30s | Per-IP cooldown after disconnect | -| `GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH` | `config.hpp` | 10000 | Minimum IDs to collect before requesting blocks during concurrent ID+block fetch | -| `GRAPHENE_PEER_DATABASE_RETRY_DELAY` | `config.hpp` | 15s | (unused, replaced by 10s sleep) | diff --git a/.qoder/docs/plugins.md b/.qoder/docs/plugins.md deleted file mode 100644 index 9ec2a3d2a1..0000000000 --- a/.qoder/docs/plugins.md +++ /dev/null @@ -1,1079 +0,0 @@ -# VIZ Blockchain — Plugins Reference - -Complete specification of all VIZ node plugins: what they do, dependencies, status (active/deprecated), and JSON-RPC API methods. - ---- - -## Plugin Architecture Overview - -VIZ uses a modular plugin architecture based on Appbase. Plugins can: -- Provide JSON-RPC API methods -- Store additional data in the chainbase database -- React to blockchain events (applied blocks, operations, etc.) -- Depend on other plugins - -### Plugin Categories - -| Category | Description | -|---|---| -| **Core** | Essential for node operation | -| **API** | Expose JSON-RPC endpoints | -| **Index** | Index blockchain data for queries | -| **Infrastructure** | Networking, web server | -| **External** | Integration with external systems | -| **Debug/Test** | Development and testing only | - ---- - -## Core Plugins - -### `chain` -**Status:** Active (Required) -**Category:** Core -**Dependencies:** `json_rpc` - -The fundamental plugin that manages the blockchain database, block validation, and transaction processing. - -**Purpose:** -- Maintains the blockchain state database (chainbase) -- Validates and applies blocks and transactions -- Provides database access to other plugins -- Emits signals on block application - -**JSON-RPC:** None (internal only) - -**CLI options:** -| Option | Type | Description | -|--------|------|-------------| -| `--replay-blockchain` | `bool` | Clear chain database and replay all blocks | -| `--replay-if-corrupted` | `bool` (default: `true`) | Replay all blocks if shared memory is corrupted | -| `--force-replay-blockchain` | `bool` | Force clear chain database and replay all blocks | -| `--replay-from-snapshot` | `bool` | Crash recovery: import snapshot and replay dlt_block_log | -| `--auto-recover-from-snapshot` | `bool` (default: `true`) | Automatic runtime recovery from shared memory corruption via snapshot | -| `--resync-blockchain` | `bool` | Clear chain database and block log | - -**Config options:** -```ini -shared-file-size = 2G -shared-file-dir = /path/to/blockchain -flush-state-interval = 0 -``` - -| Option | Default | Description | -|--------|---------|-------------| -| `shared-file-size` | `2G` | Start size of the shared memory file | -| `shared-file-dir` | `state` | Location of the shared memory files | -| `inc-shared-file-size` | `2G` | Size increment when shared memory runs low | -| `min-free-shared-file-size` | `500M` | Minimum free space before auto-grow | -| `block-num-check-free-size` | `1000` | Check free space every N blocks | -| `flush-state-interval` | `10000` | Flush shared memory to disk every N blocks | -| `single-write-thread` | `false` | Push blocks/transactions from one thread | -| `skip-virtual-ops` | `false` | Skip virtual operations (saves memory) | -| `enable-plugins-on-push-transaction` | `false` | Notify plugins on push_transaction | -| `dlt-block-log-max-blocks` | `100000` | Blocks to keep in the DLT rolling block_log | - ---- - -### `json_rpc` -**Status:** Active (Required) -**Category:** Core -**Dependencies:** None - -Provides the JSON-RPC 2.0 framework for API method registration and dispatching. - -**Purpose:** -- Registers API methods from all plugins -- Parses JSON-RPC requests -- Dispatches to appropriate handlers -- Returns formatted responses - -**JSON-RPC:** None (framework only) - ---- - -### `webserver` -**Status:** Active (Required for API access) -**Category:** Infrastructure -**Dependencies:** `json_rpc` - -HTTP/WebSocket server that accepts JSON-RPC requests with built-in response caching. - -**Purpose:** -- Serves HTTP and WebSocket connections -- Routes requests to `json_rpc` plugin -- Handles CORS, timeouts, connection limits -- Caches read-only JSON-RPC responses with id-independent cache keys -- Patches response IDs to match request IDs per JSON-RPC 2.0 spec -- Clears cache on each new block to maintain consistency -- Filters out mutating APIs (`network_broadcast_api.*`, `debug_node.*`) from cache - -**JSON-RPC:** None (transport only) - -**Config options:** -```ini -webserver-http-endpoint = 0.0.0.0:8090 -webserver-ws-endpoint = 0.0.0.0:8091 -webserver-thread-pool-size = 32 -webserver-cache-enabled = true -webserver-cache-size = 10000 -``` - -**Cache behavior:** -- Cache keys are derived from `method` + `params` only (excluding `id`), preventing bypass via ID rotation spam -- Uses `fc::json::from_string` for robust JSON parsing — invalid JSON bypasses cache -- Mutating APIs are detected in both direct (`"method":"network_broadcast_api.xxx"`) and call-style (`"method":"call","params":["network_broadcast_api",...]`) formats -- Cached responses have their `id` field patched before sending to match the client's request ID - ---- - -### `p2p` -**Status:** Active (Required for network sync) -**Category:** Infrastructure -**Dependencies:** `chain` - -Peer-to-peer networking for block and transaction propagation. - -**Purpose:** -- Discovers and connects to peers -- Syncs blockchain from the network -- Broadcasts blocks and transactions -- Maintains peer database -- Minority fork auto-recovery (`resync_from_lib()`) - -**JSON-RPC:** None (internal only) - -**Config options:** -```ini -p2p-endpoint = 0.0.0.0:2001 -p2p-seed-node = seed1.viz.world:2001 -p2p-max-connections = 200 -p2p-stats-enabled = true -p2p-stats-interval = 300 -p2p-stale-sync-detection = false -p2p-stale-sync-timeout-seconds = 120 -``` - -**P2P stats task:** When `p2p-stats-enabled = true`, every `p2p-stats-interval` seconds the plugin logs: -- Per-peer stats (IP, port, latency, bytes received, blocked status) -- Failed/rejected peers from the peer database -- **Block storage diagnostics:** head, LIB, earliest available block, DLT block log range, regular block log end, fork_db linked/unlinked counts and ranges, DLT mode flag, and total `resize()` count -- In DLT mode, also runs `dlt_block_log::verify_mapping()` to detect and self-heal stale memory-mapped file state - -**Minority fork auto-recovery:** The P2P plugin exposes `resync_from_lib()` which is called by the Validator Plugin when a minority fork is detected (last 21 blocks all from our own validators). It pops all reversible blocks back to LIB, resets fork_db, re-initiates P2P sync, and reconnects seed nodes. This replicates the effect of a manual node restart. See [fork-collision-hardfork-proposal.md](fork-collision-hardfork-proposal.md) for details. - -**Post-snapshot `trigger_resync()`:** The P2P plugin exposes `trigger_resync()` which is called by the snapshot plugin after a hot-reload (snapshot import while the node is running). It re-initiates P2P sync from the new head block so the P2P layer picks up the chain state change. Without this, the P2P layer would continue advertising stale block IDs. - -**Sync deadlock prevention:** Two mechanisms prevent the P2P sync from stalling permanently: - -1. **Early inventory-mode transition (`remaining == 0`):** When the master responds to a peer's `fetch_blockchain_item_ids` request, it checks `total_remaining_item_count`. If `remaining == 0` (all blocks sent in this reply), the master sets `peer_needs_sync_items_from_us = false` immediately, enabling inventory advertisements. This prevents an infinite chase loop where the peer is almost caught up but the master keeps producing blocks faster than the sync round-trips can converge — especially on live chains with `deferred_resize_exception` slowing the peer. - -2. **Auto-clear safety net (30-second timeout):** In `terminate_inactive_connections_loop`, if `peer_needs_sync_items_from_us` has been `true` for >30 seconds without the peer sending any `fetch_blockchain_item_ids` request, the flag is force-cleared. This handles edge cases where the sync state becomes inconsistent (e.g., `deferred_resize_exception` prevents the seed from sending the final synopsis that would normally clear the flag). The `last_peer_sync_request_time` field on `peer_connection` tracks the last request time. - -**DEFERRED_RESIZE diagnostic logging:** When `deferred_resize_exception` interrupts sync block processing, diagnostic messages (`DEFERRED_RESIZE:`) are logged via the sync logger, including the deferred block number and the subsequent sync restart. This helps diagnose stalls caused by shared memory resizes during catch-up. - ---- - -### `validator` -**Status:** Active -**Category:** Producer -**Dependencies:** `chain`, `p2p` - -Block production and validator scheduling. - -**Purpose:** -- Produces blocks when scheduled -- Manages validator signing keys -- Detects fork collisions and defers production -- Detects minority fork (last 21 blocks all from own validators) and triggers auto-recovery -- Supports emergency consensus block production - -**JSON-RPC:** None (internal only) - -**Config options:** -```ini -validator = "mywitness" -private-key = 5K... -enable-stale-production = false -required-participation = 3300 -fork-collision-timeout-blocks = 21 -``` - -| Option | Default | Description | -|---|---|---| -| `validator` | (none) | validator account name(s) to produce blocks for | -| `private-key` | (none) | WIF private key(s) for block signing | -| `emergency-private-key` | (none) | WIF key for emergency consensus production | -| `enable-stale-production` | `false` | Allow production even if chain is stale or on a minority fork | -| `required-participation` | `3300` (33%) | Minimum validator participation rate (basis points) | -| `fork-collision-timeout-blocks` | `21` | Deferrals before forcing production past a fork collision | - -**Minority fork detection:** Before producing a block, the Validator Plugin walks the last `CHAIN_MAX_WITNESSES` (21) blocks in fork_db. If ALL were produced by the node's own configured validators, the node is stuck on a minority fork. With `enable-stale-production=false` (default), the plugin calls `p2p.resync_from_lib()` to pop back to LIB and resync. With `enable-stale-production=true`, production continues (for bootstrap/testnet scenarios). Detection is skipped during emergency consensus mode. - -**Emergency consensus:** When `emergency-private-key` is configured, the committee account is added to the validator set. During emergency consensus mode (`dgp.emergency_consensus_active`), the node produces blocks using the committee account's schedule. - -See [block-processing.md](block-processing.md) for production timing details and [fork-collision-hardfork-proposal.md](fork-collision-hardfork-proposal.md) for fork handling. - ---- - -### `snapshot` -**Status:** Active -**Category:** Infrastructure -**Dependencies:** `chain` - -Snapshot creation, loading, and P2P sync for fast node bootstrap and crash recovery in DLT mode. - -**Purpose:** -- Create JSON snapshots of blockchain state -- Load state from snapshots (near-instant startup) -- Serve snapshots to other nodes over TCP -- Download snapshots from trusted peers -- Crash recovery via snapshot + dlt_block_log replay -- Detect stale snapshots at startup (snapshot block < DLT start block) and create urgent fresh snapshots - -**JSON-RPC:** None - -**Non-blocking snapshot creation:** Snapshot creation runs asynchronously on a dedicated background thread. Only the database read phase (serialization) holds a read lock (~1 second); compression and file I/O run without any lock. This eliminates read-lock timeouts and `unlinkable_block_exception` errors that occurred when snapshots ran synchronously inside the write-lock scope. - -**CLI options:** -| Option | Type | Description | -|--------|------|-------------| -| `--snapshot ` | `string` | Load state from a snapshot file (DLT mode) | -| `--snapshot-auto-latest` | `bool` | Auto-discover latest snapshot in `snapshot-dir` | -| `--replay-from-snapshot` | `bool` | Crash recovery: import snapshot + replay dlt_block_log | -| `--auto-recover-from-snapshot` | `bool` (default: `true`) | Automatic runtime recovery from shared memory corruption | -| `--create-snapshot ` | `string` | Create a snapshot and exit | -| `--sync-snapshot-from-trusted-peer` | `bool` | Download snapshot from trusted peers on empty state | - -**Config options:** -```ini -snapshot-dir = /data/snapshots -snapshot-every-n-blocks = 28800 -snapshot-max-age-days = 90 -allow-snapshot-serving = false -trusted-snapshot-peer = seed1.viz.world:8092 -dlt-block-log-max-blocks = 100000 -``` - -See [snapshot-plugin.md](snapshot-plugin.md) for full documentation. - ---- - -## API Plugins - -### `database_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Primary read API for blockchain state queries. - -**Purpose:** -- Query blocks, transactions, accounts -- Query chain properties, hardfork status -- Validate transactions and signatures -- Query escrows, delegations, proposals - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `database_api.get_block_header` | Get block header by height | -| `database_api.get_block` | Get full signed block | -| `database_api.get_irreversible_block_header` | Get irreversible block header | -| `database_api.get_irreversible_block` | Get irreversible block | -| `database_api.set_block_applied_callback` | Subscribe to new blocks | -| `database_api.get_config` | Get compile-time chain constants | -| `database_api.get_dynamic_global_properties` | Get current chain state | -| `database_api.get_chain_properties` | Get median validator properties | -| `database_api.get_hardfork_version` | Get current hardfork version | -| `database_api.get_next_scheduled_hardfork` | Get next hardfork info | -| `database_api.get_accounts` | Get accounts by names | -| `database_api.lookup_account_names` | Lookup accounts (nullable) | -| `database_api.lookup_accounts` | List accounts by prefix | -| `database_api.get_account_count` | Get total account count | -| `database_api.get_master_history` | Get account master key history | -| `database_api.get_recovery_request` | Get pending recovery request | -| `database_api.get_escrow` | Get escrow by ID | -| `database_api.get_withdraw_routes` | Get vesting withdraw routes | -| `database_api.get_vesting_delegations` | Get active delegations | -| `database_api.get_expiring_vesting_delegations` | Get expiring delegations | -| `database_api.get_transaction_hex` | Get transaction as hex | -| `database_api.get_required_signatures` | Get required signatures | -| `database_api.get_potential_signatures` | Get all potential signers | -| `database_api.verify_authority` | Verify transaction authority | -| `database_api.verify_account_authority` | Verify account authority | -| `database_api.get_database_info` | Get database statistics | -| `database_api.get_proposed_transactions` | Get proposals for account | -| `database_api.get_accounts_on_sale` | List accounts for sale | -| `database_api.get_accounts_on_auction` | List accounts on auction | -| `database_api.get_subaccounts_on_sale` | List subaccounts for sale | - ---- - -### `network_broadcast_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain`, `p2p` - -Broadcasts transactions and blocks to the network. - -**Purpose:** -- Broadcast signed transactions -- Broadcast signed blocks (for validators) -- Synchronous transaction confirmation - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `network_broadcast_api.broadcast_transaction` | Broadcast transaction (async) | -| `network_broadcast_api.broadcast_transaction_synchronous` | Broadcast and wait for inclusion | -| `network_broadcast_api.broadcast_transaction_with_callback` | Broadcast with callback | -| `network_broadcast_api.broadcast_block` | Broadcast a signed block | - ---- - -### `witness_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Query validator information. - -**Purpose:** -- List active/scheduled validators -- Query validator by account or vote rank -- Get validator schedule - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `witness_api.get_active_witnesses` | Get current active validator set | -| `witness_api.get_witness_schedule` | Get validator schedule object | -| `witness_api.get_witnesses` | Get validators by IDs | -| `witness_api.get_witness_by_account` | Get validator by account name | -| `witness_api.get_witnesses_by_vote` | Get validators ranked by votes | -| `witness_api.get_witnesses_by_counted_vote` | Get validators by counted votes | -| `witness_api.get_witness_count` | Get total validator count | -| `witness_api.lookup_witness_accounts` | List validator accounts by prefix | - ---- - -### `account_by_key` -**Status:** Active -**Category:** Index/API -**Dependencies:** `json_rpc`, `chain` - -Indexes accounts by their public keys for reverse lookup. - -**Purpose:** -- Find accounts that use a given public key -- Useful for wallet applications - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `account_by_key.get_key_references` | Get accounts using given public keys | - ---- - -### `account_history` -**Status:** Active -**Category:** Index/API -**Dependencies:** `json_rpc`, `chain`, `operation_history` - -Indexes operation history per account. - -**Purpose:** -- Query operation history for a specific account -- Paginated access to account activity - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `account_history.get_account_history` | Get operations for an account | - -#### `get_account_history` - -**Parameters:** - -| # | Name | Type | Description | -|---|---|---|---| -| 1 | `account` | string | Account name to query | -| 2 | `from` | uint32 | Starting sequence number, or `-1` for newest | -| 3 | `limit` | uint32 | Max entries to return (1-1000) | - -**Behavior:** -- `from = -1` (or `4294967295`): Start from the most recent operation -- Returns entries in descending order (newest first) -- If `limit` exceeds available entries, returns all available without error -- Example: Account has 5 entries, request `from=-1, limit=10` → returns all 5 entries - -**Request:** -```json -{ - "jsonrpc": "2.0", - "method": "account_history.get_account_history", - "params": ["on1x", -1, 10], - "id": 1 -} -``` - -**Response:** -```json -{ - "jsonrpc": "2.0", - "result": { - "4": {"trx_id": "abc...", "block": 1234, "op": [...]}, - "3": {"trx_id": "def...", "block": 1233, "op": [...]}, - "2": {"trx_id": "ghi...", "block": 1232, "op": [...]} - }, - "id": 1 -} -``` - -**Config options:** -```ini -track-account-range = ["", "zzzzzzzzzzzzzzzz"] -history-count-blocks = 4294967295 -``` - -**Memory Management:** -- Old history entries are automatically purged based on `history-count-blocks` -- Coordinates with `operation_history` plugin to avoid dangling references -- Uses the more aggressive purge threshold between both plugins -- Signal handlers are properly disconnected on shutdown to prevent memory leaks - ---- - -### `operation_history` -**Status:** Active -**Category:** Index/API -**Dependencies:** `json_rpc`, `chain` - -Indexes all operations in blocks. - -**Purpose:** -- Query operations within a block -- Lookup transactions by ID -- Provides base operation storage for `account_history` plugin - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `operation_history.get_ops_in_block` | Get operations in a block | -| `operation_history.get_transaction` | Get transaction by ID | - -**Config options:** -```ini -history-whitelist-ops = [] # Only store these operations (exclusive with blacklist) -history-blacklist-ops = [] # Don't store these operations -history-start-block = 0 # Start recording from this block -history-count-blocks = 4294967295 # How many blocks of history to keep -``` - -**Memory Management:** -- Old operations are automatically purged based on `history-count-blocks` -- `account_history` plugin coordinates purging with this plugin -- Signal handlers are properly disconnected on shutdown to prevent memory leaks -- Exposes `get_min_keep_block()` for dependent plugins to coordinate purging - ---- - -### `committee_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Query committee worker requests. - -**Purpose:** -- Get committee request details -- List all committee requests -- Get votes on requests - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `committee_api.get_committee_request` | Get request by ID | -| `committee_api.get_committee_request_votes` | Get votes on a request | -| `committee_api.get_committee_requests_list` | List all request IDs | - ---- - -### `invite_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Query invite codes. - -**Purpose:** -- List active invites -- Lookup invite by ID or public key - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `invite_api.get_invites_list` | List all invite IDs | -| `invite_api.get_invite_by_id` | Get invite by database ID | -| `invite_api.get_invite_by_key` | Get invite by public key | - ---- - -### `paid_subscription_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Query paid subscriptions. - -**Purpose:** -- List subscription offerings -- Check subscription status between accounts - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `paid_subscription_api.get_paid_subscriptions` | List all subscription offerings | -| `paid_subscription_api.get_paid_subscription_options` | Get subscription config for account | -| `paid_subscription_api.get_paid_subscription_status` | Get subscription status subscriber→account | -| `paid_subscription_api.get_active_paid_subscriptions` | List active subscriptions for subscriber | -| `paid_subscription_api.get_inactive_paid_subscriptions` | List expired subscriptions | - ---- - -### `follow` -**Status:** Deprecated -**Category:** Index/API -**Dependencies:** `json_rpc`, `chain` - -Indexes follow relationships and content feeds. - -**Purpose:** -- Track followers/following -- Build personalized feeds -- Track reblogs - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `follow.get_followers` | Get followers of an account | -| `follow.get_following` | Get accounts followed by an account | -| `follow.get_follow_count` | Get follower/following counts | -| `follow.get_feed_entries` | Get feed entries (references only) | -| `follow.get_feed` | Get feed with full content | -| `follow.get_blog_entries` | Get blog entries (references) | -| `follow.get_blog` | Get blog with full content | -| `follow.get_reblogged_by` | Get accounts that reblogged content | -| `follow.get_blog_authors` | Get authors reblogged on a blog | - ---- - -### `tags` -**Status:** Deprecated -**Category:** Index/API -**Dependencies:** `json_rpc`, `chain`, `follow` - -Indexes content by tags and provides content discovery. - -**Purpose:** -- Query trending/hot/new content by tag -- Track tag statistics -- Content discovery APIs - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `tags.get_trending_tags` | Get tags sorted by activity | -| `tags.get_tags_used_by_author` | Get tags used by an author | -| `tags.get_discussions_by_trending` | Get trending discussions | -| `tags.get_discussions_by_created` | Get newest discussions | -| `tags.get_discussions_by_active` | Get recently active discussions | -| `tags.get_discussions_by_cashout` | Get discussions by cashout time | -| `tags.get_discussions_by_payout` | Get discussions by payout | -| `tags.get_discussions_by_votes` | Get discussions by vote count | -| `tags.get_discussions_by_children` | Get discussions by reply count | -| `tags.get_discussions_by_hot` | Get hot discussions | -| `tags.get_discussions_by_feed` | Get discussions from feed | -| `tags.get_discussions_by_blog` | Get discussions from blog | -| `tags.get_discussions_by_contents` | Get discussions by content | -| `tags.get_discussions_by_author_before_date` | Get author's posts before date | -| `tags.get_languages` | Get available content languages | - ---- - -### `social_network` -**Status:** Deprecated -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -High-level content and social queries (combines multiple data sources). - -**Purpose:** -- Query content discussions -- Get votes on content -- Committee and invite queries (convenience) - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `social_network.get_content` | Get discussion by author/permlink | -| `social_network.get_content_replies` | Get direct replies | -| `social_network.get_all_content_replies` | Get all nested replies | -| `social_network.get_account_votes` | Get votes cast by account | -| `social_network.get_active_votes` | Get votes on content | -| `social_network.get_replies_by_last_update` | Get replies sorted by update | -| `social_network.get_committee_request` | Get committee request | -| `social_network.get_committee_request_votes` | Get committee request votes | -| `social_network.get_committee_requests_list` | List committee requests | -| `social_network.get_invites_list` | List invites | -| `social_network.get_invite_by_id` | Get invite by ID | -| `social_network.get_invite_by_key` | Get invite by key | - ---- - -### `private_message` -**Status:** Deprecated -**Category:** Index/API -**Dependencies:** `json_rpc`, `chain` - -Indexes encrypted private messages sent via `custom_operation`. - -**Purpose:** -- Track inbox/outbox messages -- Encrypted message protocol support - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `private_message.get_inbox` | Get received messages | -| `private_message.get_outbox` | Get sent messages | - -**Config options:** -```ini -pm-account-range = ["", "zzzzzzzzzzzzzzzz"] -``` - ---- - -### `custom_protocol_api` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Tracks custom protocol sequences from `custom_operation`. - -**Purpose:** -- Get account info with custom protocol metadata -- Useful for apps using custom_operation - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `custom_protocol_api.get_account` | Get account with custom protocol reference | - ---- - -### `auth_util` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Authority verification utilities. - -**Purpose:** -- Check signatures against account authority - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `auth_util.check_authority_signature` | Verify signature satisfies authority | - ---- - -### `block_info` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Detailed block information queries. - -**Purpose:** -- Get extended block information -- Block statistics - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `block_info.get_block_info` | Get block info for range | -| `block_info.get_blocks_with_info` | Get blocks with extended info | - ---- - -### `raw_block` -**Status:** Active -**Category:** API -**Dependencies:** `json_rpc`, `chain` - -Get raw serialized blocks. - -**Purpose:** -- Export blocks in raw binary format -- Useful for block archival/replication - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `raw_block.get_raw_block` | Get raw block by height | - ---- - -## validator/Producer Plugins - -### `validator` -**Status:** Active -**Category:** Core (for block producers) -**Dependencies:** `chain`, `p2p` - -Block production plugin for validators. - -**Purpose:** -- Sign and produce blocks on schedule -- Manage validator private keys - -**JSON-RPC:** None - -**Config options:** -```ini -validator = "your-validator-account" -private-key = 5K... -enable-stale-production = true # Produce blocks even on a stale chain (default: false) -required-participation = 3300 # Min validator participation in basis points to produce (default: 33% = 3300) -``` - -**Bug Fix: `enable-stale-production` and `required-participation` option parsing** - -Two bugs were fixed in the Validator Plugin option definitions ([validator.cpp](../../plugins/validator/validator.cpp)): - -| Bug | Before | After | -|---|---|---| -| `enable-stale-production` used `implicit_value(false)` | `--enable-stale-production` without a value set production to `false` (same as not using the flag at all) | `implicit_value(true)` — using the flag alone now correctly enables stale production | -| `required-participation` used `implicit_value(33)` then multiplied by `CHAIN_1_PERCENT` | Config file value `required-participation = 50` was interpreted as 50×100=5000 basis points (500%) | Now uses `default_value(33 * CHAIN_1_PERCENT)` and reads the raw value directly — config value is in basis points | - -The `required-participation` value is now always in **basis points** (0–10000 = 0%–100%): -- Default: `3300` = 33% -- Config: `required-participation = 5000` = 50% -- CLI: `--required-participation 5000` = 50% - -**Optimization: Block Production Timing** - -The Validator Plugin's production loop uses a timer + look-ahead mechanism to determine when to produce a block. The timer ticks at regular intervals and the look-ahead shifts `now` forward so the slot boundary is detected earlier. - -Source: [validator.cpp](../../plugins/validator/validator.cpp) — `schedule_production_loop()`, `maybe_produce_block()` - -| Parameter | Value | Meaning | -|---|---|---| -| Timer tick interval | 250ms | How often the production loop wakes up | -| Look-ahead | +250ms | `now = ntp_time + 250ms` — shifts current time forward | -| Lag threshold | 500ms | If `|scheduled_time - now| > 500ms`, block is NOT produced (LAG condition) | - -The look-ahead compensates for OS timer jitter. With 250ms ticks + 250ms look-ahead, the tick at `T_slot - 250ms` aligns `now` exactly to the slot boundary, achieving near-zero-lag production: - -``` -Slot at T=6.000: - Tick at T=5.750 → now=6.000 → slot matched → lag=0ms → PRODUCE -``` - -If the tick fires late (OS jitter), the next tick 250ms later still has comfortable margin: -``` - Tick at T=6.000 → now=6.250 → lag=250ms → PRODUCE (within 500ms threshold) - Tick at T=6.250 → now=6.500 → lag=500ms → borderline LAG -``` - -**Previous behavior** (before optimization): 1000ms tick + 500ms look-ahead → best-case lag was 500ms (exactly at the threshold), and even 50ms of OS jitter caused a LAG condition. - ---- - -## Debug/Test Plugins - -### `debug_node` -**Status:** Active (Development only) -**Category:** Debug -**Dependencies:** `chain` - -Development and testing utilities. **NOT for production use.** - -**Purpose:** -- Generate test blocks -- Push blocks from files -- Set hardforks manually - -**JSON-RPC Methods:** - -| Method | Description | -|---|---| -| `debug_node.debug_generate_blocks` | Generate N test blocks | -| `debug_node.debug_generate_blocks_until` | Generate blocks until time | -| `debug_node.debug_push_blocks` | Push blocks from database | -| `debug_node.debug_push_json_blocks` | Push blocks from JSON file | -| `debug_node.debug_pop_block` | Pop and return last block | -| `debug_node.debug_get_witness_schedule` | Get validator schedule | -| `debug_node.debug_set_hardfork` | Force set hardfork | -| `debug_node.debug_has_hardfork` | Check if hardfork applied | - ---- - -### `test_api` -**Status:** Active (Testing only) -**Category:** Test -**Dependencies:** `json_rpc` - -Test API plugin for connectivity testing. - -**JSON-RPC Methods:** None documented (internal testing) - ---- - -## External Integration Plugins - -### `mongo_db` -**Status:** Active -**Category:** External -**Dependencies:** `chain` - -Exports blockchain data to MongoDB. - -**Purpose:** -- Real-time export of blocks, transactions, operations -- Enable MongoDB-based queries and analytics - -**JSON-RPC:** None - -**Config options:** -```ini -mongodb-uri = mongodb://localhost:27017 -mongodb-db-name = viz -``` - ---- - -## Plugin Status Summary - -| Plugin | Status | Has API | Category | -|---|---|---|---| -| `chain` | Active | No | Core | -| `json_rpc` | Active | No | Core | -| `webserver` | Active | No | Infrastructure | -| `p2p` | Active | No | Infrastructure | -| `snapshot` | Active | No | Infrastructure | -| `database_api` | Active | Yes | API | -| `network_broadcast_api` | Active | Yes | API | -| `witness_api` | Active | Yes | API | -| `account_by_key` | Active | Yes | Index/API | -| `account_history` | Active | Yes | Index/API | -| `operation_history` | Active | Yes | Index/API | -| `committee_api` | Active | Yes | API | -| `invite_api` | Active | Yes | API | -| `paid_subscription_api` | Active | Yes | API | -| `follow` | Deprecated | Yes | Index/API | -| `tags` | Deprecated | Yes | Index/API | -| `social_network` | Deprecated | Yes | API | -| `private_message` | Deprecated | Yes | Index/API | -| `custom_protocol_api` | Active | Yes | API | -| `auth_util` | Active | Yes | API | -| `block_info` | Active | Yes | API | -| `raw_block` | Active | Yes | API | -| `validator` | Active | No | Producer | -| `debug_node` | Dev only | Yes | Debug | -| `test_api` | Test only | Yes | Test | -| `mongo_db` | Active | No | External | - ---- - -## JSON-RPC Quick Reference - -All methods use JSON-RPC 2.0 format: - -```json -{ - "jsonrpc": "2.0", - "method": "api_name.method_name", - "params": {}, - "id": 1 -} -``` - -### Complete API Method Index - -| API | Method | Description | -|---|---|---| -| `database_api` | `get_block_header` | Block header by height | -| `database_api` | `get_block` | Full block by height | -| `database_api` | `get_irreversible_block_header` | Irreversible block header | -| `database_api` | `get_irreversible_block` | Irreversible block | -| `database_api` | `set_block_applied_callback` | Subscribe to blocks | -| `database_api` | `get_config` | Chain constants | -| `database_api` | `get_dynamic_global_properties` | Current chain state | -| `database_api` | `get_chain_properties` | Median validator props | -| `database_api` | `get_hardfork_version` | Current HF version | -| `database_api` | `get_next_scheduled_hardfork` | Next HF info | -| `database_api` | `get_accounts` | Accounts by names | -| `database_api` | `lookup_account_names` | Lookup accounts | -| `database_api` | `lookup_accounts` | List accounts | -| `database_api` | `get_account_count` | Total accounts | -| `database_api` | `get_master_history` | Key history | -| `database_api` | `get_recovery_request` | Recovery request | -| `database_api` | `get_escrow` | Escrow by ID | -| `database_api` | `get_withdraw_routes` | Withdraw routes | -| `database_api` | `get_vesting_delegations` | Delegations | -| `database_api` | `get_expiring_vesting_delegations` | Expiring delegations | -| `database_api` | `get_transaction_hex` | TX as hex | -| `database_api` | `get_required_signatures` | Required sigs | -| `database_api` | `get_potential_signatures` | Potential signers | -| `database_api` | `verify_authority` | Verify TX auth | -| `database_api` | `verify_account_authority` | Verify account auth | -| `database_api` | `get_database_info` | DB stats | -| `database_api` | `get_proposed_transactions` | Proposals | -| `database_api` | `get_accounts_on_sale` | Accounts for sale | -| `database_api` | `get_accounts_on_auction` | Accounts on auction | -| `database_api` | `get_subaccounts_on_sale` | Subaccounts for sale | -| `network_broadcast_api` | `broadcast_transaction` | Broadcast TX | -| `network_broadcast_api` | `broadcast_transaction_synchronous` | Broadcast TX (sync) | -| `network_broadcast_api` | `broadcast_transaction_with_callback` | Broadcast TX (callback) | -| `network_broadcast_api` | `broadcast_block` | Broadcast block | -| `witness_api` | `get_active_witnesses` | Active validators | -| `witness_api` | `get_witness_schedule` | validator schedule | -| `witness_api` | `get_witnesses` | validators by ID | -| `witness_api` | `get_witness_by_account` | validator by account | -| `witness_api` | `get_witnesses_by_vote` | validators by votes | -| `witness_api` | `get_witnesses_by_counted_vote` | validators by counted votes | -| `witness_api` | `get_witness_count` | validator count | -| `witness_api` | `lookup_witness_accounts` | List validators | -| `account_by_key` | `get_key_references` | Accounts by key | -| `account_history` | `get_account_history` | Account operations | -| `operation_history` | `get_ops_in_block` | Block operations | -| `operation_history` | `get_transaction` | TX by ID | -| `committee_api` | `get_committee_request` | Request by ID | -| `committee_api` | `get_committee_request_votes` | Request votes | -| `committee_api` | `get_committee_requests_list` | All requests | -| `invite_api` | `get_invites_list` | All invites | -| `invite_api` | `get_invite_by_id` | Invite by ID | -| `invite_api` | `get_invite_by_key` | Invite by key | -| `paid_subscription_api` | `get_paid_subscriptions` | All subscriptions | -| `paid_subscription_api` | `get_paid_subscription_options` | Subscription config | -| `paid_subscription_api` | `get_paid_subscription_status` | Subscription status | -| `paid_subscription_api` | `get_active_paid_subscriptions` | Active subscriptions | -| `paid_subscription_api` | `get_inactive_paid_subscriptions` | Inactive subscriptions | -| `follow` | `get_followers` | Followers | -| `follow` | `get_following` | Following | -| `follow` | `get_follow_count` | Follow counts | -| `follow` | `get_feed_entries` | Feed entries | -| `follow` | `get_feed` | Feed content | -| `follow` | `get_blog_entries` | Blog entries | -| `follow` | `get_blog` | Blog content | -| `follow` | `get_reblogged_by` | Rebloggers | -| `follow` | `get_blog_authors` | Blog authors | -| `tags` | `get_trending_tags` | Trending tags | -| `tags` | `get_tags_used_by_author` | Author's tags | -| `tags` | `get_discussions_by_trending` | Trending posts | -| `tags` | `get_discussions_by_created` | New posts | -| `tags` | `get_discussions_by_active` | Active posts | -| `tags` | `get_discussions_by_cashout` | Posts by cashout | -| `tags` | `get_discussions_by_payout` | Posts by payout | -| `tags` | `get_discussions_by_votes` | Posts by votes | -| `tags` | `get_discussions_by_children` | Posts by replies | -| `tags` | `get_discussions_by_hot` | Hot posts | -| `tags` | `get_discussions_by_feed` | Feed posts | -| `tags` | `get_discussions_by_blog` | Blog posts | -| `tags` | `get_discussions_by_contents` | Content posts | -| `tags` | `get_discussions_by_author_before_date` | Author posts | -| `tags` | `get_languages` | Languages | -| `social_network` | `get_content` | Discussion | -| `social_network` | `get_content_replies` | Replies | -| `social_network` | `get_all_content_replies` | All replies | -| `social_network` | `get_account_votes` | Account's votes | -| `social_network` | `get_active_votes` | Votes on content | -| `social_network` | `get_replies_by_last_update` | Replies by update | -| `private_message` | `get_inbox` | Inbox | -| `private_message` | `get_outbox` | Outbox | -| `custom_protocol_api` | `get_account` | Account + custom | -| `auth_util` | `check_authority_signature` | Check sig | -| `block_info` | `get_block_info` | Block info | -| `block_info` | `get_blocks_with_info` | Blocks + info | -| `raw_block` | `get_raw_block` | Raw block | -| `debug_node` | `debug_generate_blocks` | Generate blocks | -| `debug_node` | `debug_generate_blocks_until` | Generate until | -| `debug_node` | `debug_push_blocks` | Push blocks | -| `debug_node` | `debug_push_json_blocks` | Push JSON blocks | -| `debug_node` | `debug_pop_block` | Pop block | -| `debug_node` | `debug_get_witness_schedule` | validator schedule | -| `debug_node` | `debug_set_hardfork` | Set hardfork | -| `debug_node` | `debug_has_hardfork` | Check hardfork | - ---- - -## Recommended Plugin Sets - -### Minimal API Node -```ini -plugin = chain -plugin = json_rpc -plugin = webserver -plugin = p2p -plugin = database_api -plugin = network_broadcast_api -``` - -### Full API Node -```ini -plugin = chain -plugin = json_rpc -plugin = webserver -plugin = p2p -plugin = database_api -plugin = network_broadcast_api -plugin = witness_api -plugin = account_by_key -plugin = account_history -plugin = operation_history -plugin = committee_api -plugin = invite_api -plugin = paid_subscription_api -plugin = follow -plugin = tags -plugin = social_network -plugin = private_message -``` - -### validator Node -```ini -plugin = chain -plugin = p2p -plugin = validator -plugin = json_rpc -plugin = webserver -plugin = database_api -plugin = network_broadcast_api -plugin = witness_api -plugin = snapshot - -snapshot-every-n-blocks = 28800 -snapshot-dir = /data/snapshots -dlt-block-log-max-blocks = 100000 -``` diff --git a/.qoder/docs/shared-memory.md b/.qoder/docs/shared-memory.md deleted file mode 100644 index 8d9e50d923..0000000000 --- a/.qoder/docs/shared-memory.md +++ /dev/null @@ -1,499 +0,0 @@ -# Shared Memory Architecture - -The VIZ node stores all blockchain state in a memory-mapped file (`shared_memory.bin`) managed by the **chainbase** library, which wraps Boost.Interprocess `managed_mapped_file`. This is the sole database for chain state — the node **cannot operate without shared memory**. - ---- - -## Overview - -``` -┌─────────────────────────────────────────────────────────────┐ -│ vizd process │ -│ │ -│ ┌──────────────┐ ┌──────────────────────────────────┐ │ -│ │ block_log │ │ shared_memory.bin (mmap) │ │ -│ │ dlt_block_log│ │ │ │ -│ │ (raw blocks) │ │ ┌────────────────────────────┐ │ │ -│ │ │ │ │ chainbase::database │ │ │ -│ └──────┬───────┘ │ │ ┌──────────────────────┐ │ │ │ -│ │ │ │ │ account_index │ │ │ │ -│ │ replay/ │ │ │ witness_index │ │ │ │ -│ │ sync │ │ │ transaction_index │ │ │ │ -│ ▼ │ │ │ ... (all objects) │ │ │ │ -│ ┌──────────────┐ │ │ └──────────────────────┘ │ │ │ -│ │ database │────▶ │ boost::shared_mutex _mutex│ │ │ -│ │ (chainbase) │ │ └────────────────────────────┘ │ │ -│ └──────────────┘ └──────────────────────────────────┘ │ -│ │ -│ ┌──────────────┐ ┌──────────────────────────────────┐ │ -│ │ API threads │────▶│ read_lock (shared, multiple) │ │ -│ │ (webserver │ │ write_lock (exclusive) │ │ -│ │ pool=256) │ └──────────────────────────────────┘ │ -│ └──────────────┘ │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Key Source Files - -| File | Role | -|------|------| -| `thirdparty/chainbase/include/chainbase/chainbase.hpp` | Core chainbase database class, lock wrappers, index types | -| `thirdparty/chainbase/src/chainbase.cpp` | `open()`, `close()`, `resize()`, `flush()`, `wipe()` implementations | -| `libraries/chain/database.cpp` | Chain-level `open()`, `_resize()`, `check_free_memory()`, `push_block()`, `_generate_block()` | -| `libraries/chain/include/graphene/chain/database.hpp` | Chain database class declaration, resize/memory parameters | -| `plugins/chain/plugin.cpp` | Config option definitions, initialization, snapshot loading | -| `plugins/validator/validator.cpp` | Lockless reads in `maybe_produce_block()` and `is_witness_scheduled_soon()`, guarded by `operation_guard` | -| `plugins/p2p/p2p_plugin.cpp` | Lockless reads in block post-validation (`get_witness_key()`), guarded by `operation_guard` | - ---- - -## Memory-Mapped File Internals - -### File Format - -The `shared_memory.bin` file is a Boost.Interprocess `managed_mapped_file`: - -- **On create**: file is allocated at `shared-file-size` bytes, OS maps it into process address space -- **On open (write)**: if file exists and `shared-file-size` > current size, `managed_mapped_file::grow()` extends it -- **On open (read-only)**: mapped read-only, no growth possible -- **File lock**: when opened for writing, a `boost::interprocess::file_lock` ensures **only one process** can write - -### Internal Structure - -All chainbase objects (accounts, validators, transactions, etc.) are stored as C++ objects allocated inside the mapped segment using Boost.Interprocess allocators: - -```cpp -template -using allocator = boost::interprocess::allocator; - -using shared_string = boost::interprocess::basic_string, allocator>; -``` - -Objects are organized into **indices** (Boost.MultiIndex containers) that live inside the mapped segment. Pointers within these structures are **offset-based** (managed by the segment manager), which is why the file can be re-mapped at a different virtual address and still function — provided the remapping is done correctly. - ---- - -## Locking Model - -### Lock Types - -Chainbase uses a single `boost::shared_mutex _mutex` per database instance: - -| Lock Type | Wrapper Method | Concurrency | -|-----------|---------------|-------------| -| **Read lock** (`boost::shared_lock`) | `with_read_lock()`, `with_weak_read_lock()`, `with_strong_read_lock()` | Multiple readers can hold simultaneously | -| **Write lock** (`boost::unique_lock`) | `with_write_lock()`, `with_weak_write_lock()`, `with_strong_write_lock()` | Exclusive — blocks all readers AND other writers | - -### Lock Variants - -| Variant | Wait Time | Retries | Use Case | -|---------|-----------|---------|----------| -| **weak** | `_read_wait_micro` / `_write_wait_micro` (default 500ms) | `_max_read_wait_retries` / `_max_write_wait_retries` (default 3) | Normal API calls | -| **strong** | 1,000,000 μs (1 sec) | 100,000 | Critical operations (block apply, genesis init, replay) | - -### Read Lock Guarantees - -A read lock guarantees that **no write lock is held at the moment of acquisition** and that **no write lock can be acquired while any read lock is held**. However: - -- A pending write lock request will **block** new read lock acquisitions (writer priority prevents starvation) -- The read lock does **not** prevent the segment from being destroyed by `resize()` if the resize happens from the same thread or while no read locks are held - -### Write Lock Behavior - -The write lock is **exclusive**: while held, no other thread can acquire either a read or write lock. All block processing, state modifications, and memory resizing occur under write lock. - ---- - -## Configuration Parameters - -All parameters are defined in `plugins/chain/plugin.cpp` and read from `config.ini`. - -### Size Parameters - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `shared-file-dir` | `state` | Directory for `shared_memory.bin` (relative to data dir or absolute) | -| `shared-file-size` | `2G` | Initial size of the shared memory file. If file exists and this value is larger, the file grows. If smaller, no change. Does **not** require replay. | -| `inc-shared-file-size` | `2G` | Step size for auto-growth. When free space drops below `min-free-shared-file-size`, the file grows by this amount. | -| `min-free-shared-file-size` | `500M` | Free space threshold that triggers auto-growth. | - -### Lock Timeout Parameters - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `read-wait-micro` | `500000` (500ms) | Timeout per read lock acquisition attempt | -| `max-read-wait-retries` | `3` | Maximum read lock retry attempts before throwing `"Unable to acquire READ lock"` | -| `write-wait-micro` | `500000` (500ms) | Timeout per write lock acquisition attempt | -| `max-write-wait-retries` | `3` | Maximum write lock retry attempts before throwing `"Unable to acquire WRITE lock"` | - -### Operational Parameters - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `single-write-thread` | `false` | Serialize all block/transaction pushes through one thread. Reduces write lock contention but limits throughput. | -| `block-num-check-free-size` | `1000` | Check free space in shared memory every N blocks. Lower values = more frequent checks = earlier resize detection but more overhead. | -| `flush-state-interval` | (unset) | Flush shared memory changes to disk every N blocks | -| `clear-votes-before-block` | `0` | Remove votes older than this block number (0 = keep all). Reduces memory usage. | -| `skip-virtual-ops` | `false` | Skip virtual operation plugin notifications. Saves memory and processing. | -| `enable-plugins-on-push-transaction` | `false` | Enable plugin notifications for pushed transactions (not applied blocks). Safe to disable for performance. | - -### Recommended Configurations - -**validator node (production):** -```ini -shared-file-size = 4G -inc-shared-file-size = 2G -min-free-shared-file-size = 500M -block-num-check-free-size = 1000 -single-write-thread = true -``` - -**API node (high read throughput):** -```ini -shared-file-size = 8G -inc-shared-file-size = 2G -min-free-shared-file-size = 500M -block-num-check-free-size = 1000 -single-write-thread = true -read-wait-micro = 1000000 -max-read-wait-retries = 10 -webserver-thread-pool-size = 256 -``` - -**Replay/sync:** -```ini -shared-file-size = 8G -inc-shared-file-size = 4G -min-free-shared-file-size = 500M -block-num-check-free-size = 10 -clear-votes-before-block = 0 -skip-virtual-ops = true -``` - ---- - -## Auto-Resize Workflow - -### Trigger Conditions - -Resize is triggered in two places: - -1. **Periodic check** — `check_free_memory()` called after each `_push_block()` in `push_block()`: - ``` - if (current_block_num % block_num_check_free_size == 0 - && free_memory < min_free_shared_file_size) - → _resize() - ``` - -2. **bad_alloc fallback** — if `_push_block()` throws `boost::interprocess::bad_alloc`: - ``` - catch bad_alloc → set_reserved_memory(free_memory()) → _resize() → retry _push_block() - ``` - -### Resize Sequence - -The resize is implemented in `chainbase::database::resize()` (`thirdparty/chainbase/src/chainbase.cpp`): - -``` -1. Assert no undo sessions active (_undo_session_count == 0) -2. _segment.reset() ← DESTROY the memory mapping -3. open(_data_dir, read_write, new_size) ← Re-open with larger size - ├── managed_mapped_file::grow() ← Extend file on disk - └── new managed_mapped_file(...) ← Re-map into address space -4. _index_list.clear() ← Clear cached index pointers -5. _index_map.clear() -6. for each index_type: - index_type->add_index(*this) ← Rebuild index pointers from new mapping -``` - -### Resize Barrier - -Because `_segment.reset()` invalidates **all** pointers/references into shared memory, the resize must ensure that **no thread** — whether holding a lock or reading locklessly — has any live reference into the mapped segment. A simple write lock is insufficient because several code paths read chainbase indices without holding any lock ("lockless reads"). - -The **resize barrier** (`chainbase::database`) solves this with an atomic operation counter, a flag, and a condition variable: - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ Resize Barrier Protocol │ -│ │ -│ Normal operation: │ -│ enter_operation() ── wait while _resize_in_progress │ -│ ── increment _active_operations │ -│ ... access shared memory ... │ -│ exit_operation() ── decrement _active_operations │ -│ ── notify resize thread if last op │ -│ │ -│ Resize: │ -│ begin_resize_barrier() ── set _resize_in_progress = true │ -│ ── wait until _active_operations == 0 │ -│ ... _segment.reset() + open() + rebuild indices ... │ -│ end_resize_barrier() ── set _resize_in_progress = false │ -│ ── notify all waiting threads │ -└──────────────────────────────────────────────────────────────────┘ -``` - -**Participation points:** - -| Code Path | How It Participates | -|-----------|--------------------| -| `with_read_lock()` / `with_write_lock()` | `operation_guard` acquired automatically inside the lock wrapper before the `boost::shared_mutex` lock | -| `_generate_block()` lockless reads (pre-write-lock) | Explicit scoped `operation_guard` around `get_slot_at_time()`, `get_scheduled_witness()`, `get_witness()`, `find_account()` | -| `_generate_block()` lockless reads (post-write-lock) | Second `operation_guard` (`op_guard2`) around `get_dynamic_global_properties()`, `head_block_id()`, `get_witness()`, `get_hardfork_property_object()`; released via `release()` before `push_block()` | -| Validator Plugin `maybe_produce_block()` | Explicit `operation_guard` around `get_slot_at_time()`, `get_scheduled_witness()`, index lookups; released via `release()` before `generate_block()` | -| Validator Plugin `is_witness_scheduled_soon()` | Explicit `operation_guard` around `get_slot_at_time()`, `get_scheduled_witness()`, index lookups; released via `release()` before return | -| P2P plugin block post-validation | Explicit `operation_guard` around `get_witness_key()` calls; released via `release()` before `apply_block_post_validation()` | - -**Key classes:** - -- `chainbase::database::operation_guard` — RAII guard that calls `enter_operation()` on construction and `exit_operation()` on destruction. Supports early release via `release()`. Move-only (non-copyable). -- `chainbase::database::make_operation_guard()` — Factory method returning an `operation_guard`. -- `chainbase::database::begin_resize_barrier()` / `end_resize_barrier()` — Called by `apply_pending_resize()` to establish exclusive access for the resize. - -**Immediate resize (reindex) does NOT use the barrier.** During reindex, the caller already holds an exclusive write lock and no API threads are running. Using the barrier would deadlock because the write lock itself holds an operation guard. - -### Historical Context: Pre-Barrier Race Condition - -Before the resize barrier was added, the resize used `with_strong_write_lock()` which only blocked threads holding or waiting for a `boost::shared_mutex` lock. This left lockless reads unprotected: - -- `boost::shared_mutex` does **not** protect against segment destruction — a read lock only prevents concurrent writes, but the resize **is** the write -- Threads performing lockless reads (Validator Plugin, `_generate_block()`) could hold stale pointers into the old mapping after `_segment.reset()` - -This was the root cause of shared memory corruption symptoms like `CRITICAL: validator X account object MISSING from database!`. - -### Corruption Symptoms - -Typical corruption indicators (should not occur with the resize barrier in place, but listed for historical reference and diagnostics): - -- `CRITICAL: validator X account object MISSING from database!` — account index entry not found despite `account_index_size` showing entries exist -- `Could not modify object, most likely a uniqueness constraint was violated` — internal index pointers corrupted, uniqueness check fails -- Node crashes and restarts in an infinite loop — each restart opens the corrupted file, fails to produce/apply blocks, crashes again - -### Why Misconfigured Thresholds Make It Worse - -If `min-free-shared-file-size > inc-shared-file-size`: -``` -shared-file-size = 500M -inc-shared-file-size = 500M -min-free-shared-file-size = 1000M ← THRESHOLD > INCREMENT! -``` - -After one resize (500M → 1000M), free space is still below 1000M, triggering **another resize on the next check**, causing cascading resizes. While the resize barrier prevents corruption, frequent resizes still cause latency spikes as all operations are paused during each resize. - ---- - -## Concurrency Architecture - -### Thread Model - -``` -th_0 ─── Main thread: block production loop, block application -th_180+ ─── Webserver thread pool (default 256 threads): JSON-RPC API calls - Each API call acquires read_lock (weak) or write_lock (for broadcast) -th_? ─── P2P thread: block/transaction reception -``` - -### Read Path (API Calls) - -Most `database_api` methods use `with_weak_read_lock()`: - -```cpp -// Example: get_accounts -auto result = with_weak_read_lock([&]() { - // Read from chainbase indices — operation_guard acquired automatically - return accounts; -}); -``` - -If the read lock cannot be acquired within `read-wait-micro × max-read-wait-retries`, the API returns error: `"Unable to acquire READ lock"`. If a resize barrier is active, the `operation_guard` inside `with_weak_read_lock()` will block until the resize completes before attempting the lock. - -### Write Path (Block Application) - -All state modifications go through `with_strong_write_lock()`: - -```cpp -// push_block -apply_pending_resize(); // uses resize barrier (begin/end) -with_strong_write_lock([&]() { // operation_guard acquired inside - _push_block(new_block, skip); - check_free_memory(false, new_block.block_num()); -}); -``` - -### Lockless Reads (Validator Plugin, Block Generation, P2P) - -Some code paths read from chainbase indices **without** holding a `boost::shared_mutex` lock. These must explicitly use an `operation_guard` to participate in the resize barrier: - -```cpp -// validator.cpp — maybe_produce_block() -auto op_guard = db.make_operation_guard(); -uint32_t slot = db.get_slot_at_time(now); // lockless read -string scheduled = db.get_scheduled_witness(slot); // lockless read -// ... more lockless reads ... -op_guard.release(); // release before generate_block() which has its own guard -``` - -```cpp -// validator.cpp — is_witness_scheduled_soon() -auto op_guard = db.make_operation_guard(); -uint32_t slot = db.get_slot_at_time(now); -string scheduled = db.get_scheduled_witness(s); -// ... -op_guard.release(); // release before returning true -``` - -```cpp -// database.cpp — _generate_block() (pre-write-lock reads) -{ - auto op_guard = make_operation_guard(); - uint32_t slot_num = get_slot_at_time(when); - string scheduled_witness = get_scheduled_witness(slot_num); - const auto& witness_obj = get_witness(witness_owner); - const auto* witness_acct = find_account(witness_owner); -} // op_guard released before with_strong_write_lock -``` - -```cpp -// database.cpp — _generate_block() (post-write-lock reads) -auto op_guard2 = make_operation_guard(); -auto maximum_block_size = get_dynamic_global_properties().maximum_block_size; -// ... with_strong_write_lock, then post-lock reads ... -pending_block.previous = head_block_id(); -const auto& validator = get_witness(witness_owner); -const auto& hfp = get_hardfork_property_object(); -const auto& dgp_block = get_dynamic_global_properties(); -op_guard2.release(); // release before push_block() -``` - -```cpp -// p2p_plugin.cpp — block post-validation -auto op_guard = chain.db().make_operation_guard(); -fc::ecc::public_key w_signing_key = chain.db().get_witness_key(account); -op_guard.release(); // release before apply_block_post_validation() -``` - -### single-write-thread Mode - -When `single-write-thread = true`, all `push_block()` and `push_transaction()` calls are serialized through a single `fc::async()` queue. This: - -- Prevents concurrent write lock contention -- Reduces the chance of read lock timeouts for API threads -- Is **recommended for all production nodes** - -When `single-write-thread = false`, blocks and transactions can be pushed from multiple threads simultaneously, causing frequent write lock contention and `"Unable to acquire READ lock"` errors for API clients. - ---- - -## Startup and Recovery - -### Normal Startup - -``` -1. chainbase::database::open(shared_mem_dir, read_write, shared_file_size) - ├── If file exists and shared_file_size > file_size → grow() - ├── Map file into process address space - └── Acquire file lock (exclusive write access) -2. Initialize indices -3. If no dynamic_global_property_object → init_genesis() -4. Open block_log / dlt_block_log -5. undo_all() → rewind to last irreversible block -6. Verify head_block matches block_log -``` - -### Snapshot Import Startup - -``` -1. chainbase::database::wipe(shared_mem_dir) ← Delete old shared memory -2. chainbase::database::open(shared_mem_dir, read_write, shared_file_size) ← Create fresh -3. init_genesis() ← Write genesis state -4. Load snapshot data via callback -5. Replay dlt_block_log blocks on top (if recovery mode) -``` - -### Replay - -``` -1. Open existing shared memory -2. with_strong_write_lock(): - ├── set_reserved_memory(1GB) ← Protect from fragmentation - ├── For each block in block_log: - │ ├── apply_block() - │ └── check_free_memory() ← May trigger _resize() - └── set_reserved_memory(0) -``` - -### Crash Recovery - -If the node crashes due to shared memory corruption: - -1. **Automatic restart** (Docker/systemd) will try to open the corrupted file -2. If the file is corrupted, the node will likely crash again (infinite loop) -3. **Manual recovery options:** - - `--replay-blockchain` — Delete shared memory, replay from block_log - - `--resync-blockchain` — Delete shared memory AND block_log, sync from network - - `--snapshot ` — Load from snapshot file, then replay dlt_block_log - ---- - -## File Layout - -``` -/ -├── shared_memory.bin ← Main memory-mapped file (all chain state) -``` - -The `shared_memory.bin` file size grows in steps of `inc-shared-file-size`. The OS may not actually allocate physical memory until pages are touched (sparse file / lazy allocation), but the virtual address space reservation equals the file size. - -### Size Planning - -Approximate memory usage for a VIZ mainnet node at ~79M blocks: - -| Component | Estimated Size | -|-----------|---------------| -| Account index (~14K accounts) | ~50 MB | -| validator index | ~5 MB | -| Transaction history (operation_history plugin) | ~200–500 MB | -| Account history (account_history plugin) | ~100–300 MB | -| Follow/social indexes | ~50–100 MB | -| Other indexes | ~100–200 MB | -| **Total recommended starting size** | **4–8 GB** | - ---- - -## Diagnostic Commands - -### Check shared memory file size -```bash -ls -lh /var/lib/vizd/blockchain/shared_memory.bin -``` - -### Monitor free memory (from logs) -``` -Free memory is now XmM -Memory is almost full on block N, increasing to XmM -``` - -### Detect corruption -``` -CRITICAL: validator X account object MISSING from database! -Could not modify object, most likely a uniqueness constraint was violated -``` - -### Lock timeout monitoring -``` -Read lock timeout -No more retries for read lock -Write lock timeout -FATAL write lock timeout!!! -``` - ---- - -## Safety Rules - -1. **`min-free-shared-file-size` must be less than `inc-shared-file-size`** — otherwise cascading resizes occur, causing frequent operation pauses -2. **Pre-allocate generously** — set `shared-file-size` large enough that resize is rare. Each resize pauses all operations while the segment is remapped. -3. **Use `single-write-thread = true`** in production — prevents write lock contention -4. **Avoid resize during validator production** — a validator node should have enough pre-allocated memory that resize never triggers during block generation. The resize barrier guarantees safety but introduces latency. -5. **After corruption, always replay** — there is no safe way to repair a corrupted `shared_memory.bin`. Use `--replay-blockchain` or `--snapshot` to rebuild state from block_log/dlt_block_log. -6. **Backup before config changes** — changing `shared-file-size` to a larger value triggers `grow()` on next startup, which is safe. Reducing it has no effect (file doesn't shrink). -7. **Any new lockless read path must use `operation_guard`** — if you add code that reads from chainbase indices without `with_read_lock()`/`with_write_lock()`, wrap it with `make_operation_guard()` to participate in the resize barrier. Failing to do so can cause stale pointer access during resize. diff --git a/.qoder/docs/snapshot-pause-workflow.md b/.qoder/docs/snapshot-pause-workflow.md deleted file mode 100644 index 76688f89b5..0000000000 --- a/.qoder/docs/snapshot-pause-workflow.md +++ /dev/null @@ -1,369 +0,0 @@ -# Snapshot Pause Block Workflow - -## Overview - -When the snapshot plugin creates a snapshot, it **pauses P2P block processing** to -prevent concurrent database modifications. During this pause, incoming blocks from -peers are **buffered in a queue** instead of being dropped. After the pause ends, -the P2P layer drains the queue (applying all buffered blocks), then checks whether -peers are still ahead. The Validator Plugin defers block production until all queued -blocks are applied and any remaining gap is filled. - -## Sequence Diagram: Snapshot Pause Lifecycle - -```mermaid -sequenceDiagram - participant SP as Snapshot Plugin - participant P2P as P2P Layer - participant W as Validator Plugin - participant Peer as Remote Peer - - Note over SP: Block N applied - SP->>SP: on_applied_block() triggers snapshot - SP->>P2P: pause_block_processing() - Note over P2P: _block_processing_paused = true - Note over P2P: is_catching_up_after_pause() = true - - Peer->>P2P: Block N+1 (from delegate) - P2P->>P2P: QUEUED in _paused_block_queue - Peer->>P2P: Block N+2 (from delegate) - P2P->>P2P: QUEUED in _paused_block_queue - - Note over W: Production loop fires (250ms) - W->>P2P: is_catching_up_after_pause()? - P2P-->>W: true (_block_processing_paused) - W-->>W: return not_synced (deferred) - Note over W: generate_block() NOT called
No write lock attempt - - SP->>SP: create_snapshot() DB read completes - SP->>P2P: resume_block_processing() - Note over P2P: _block_processing_paused = false - Note over P2P: _catchup_after_pause = true - Note over P2P: Posts drain to P2P thread - - Note over P2P: P2P thread: drain_paused_block_queue() - P2P->>P2P: Sort queue by block_num - P2P->>P2P: accept_block(N+1) → applied - P2P->>P2P: accept_block(N+2) → applied - P2P->>P2P: Check peers: no one ahead - Note over P2P: _catchup_after_pause = false - - Note over W: Production loop fires - W->>P2P: is_catching_up_after_pause()? - P2P-->>W: false - W->>W: Normal production resumes -``` - -## Incoming Block Workflow (During Pause) - -```mermaid -flowchart TD - A["Block message received from peer"] --> B{"_block_processing_paused?"} - B -->|Yes| C{"Message type?"} - C -->|hello/hello_reply/fork_status| D["Process normally
(keeps peer_head_num updated)"] - C -->|block_reply, range_reply,
gap_fill_reply| E["Deserialize and push
to _paused_block_queue"] - C -->|other: transaction,
peer exchange, etc.| F["Drop (return true)"] - B -->|No| G{"Queue has pending blocks?
(leftover from recent pause)"} - G -->|Yes| H["drain_paused_block_queue()\nfirst"] - G -->|No| I["Normal processing:
accept_block() → push_block()"] - H --> I - I --> J{"Block accepted?"} - J -->|ACCEPTED| K["on_block_applied()
retransmit to fork peers"] - J -->|FORK_DB_ONLY| L["Store in fork_db
(competing fork)"] - J -->|DEAD_FORK| M["Soft-ban peer"] - J -->|REJECTED| N["Log + track rejections"] -``` - -## validator Production Workflow (With Catchup Gate) - -```mermaid -flowchart TD - Start["maybe_produce_block()"] --> Sync{"DLT mode +
chain.is_syncing()?"} - Sync -->|Yes, not emergency master| Ret1["return not_synced"] - Sync -->|No / emergency master| Gate{"p2p.is_catching_up_after_pause()?"} - Gate -->|Yes| Ret2["return not_synced
(defer production)"] - Gate -->|No| HF12{"Hardfork 12 checks
(prate, emergency)"} - HF12 -->|prate < 33% AND no stale-production override| RetLP["return low_participation
⚠ partition guard"] - HF12 -->|prate >= 33% or override| MinFork{"Minority fork check:
last 21 fork_db blocks
all from our validators?"} - MinFork -->|Yes — isolated| RetMF["resync_from_lib()
return minority_fork"] - MinFork -->|No| Slot{"get_slot_at_time()"} - Slot -->|slot == 0| Ret3["return not_time_yet"] - Slot -->|slot > 0| validator{"Our validator scheduled?"} - validator -->|No| Ret4["return not_my_turn"] - validator -->|Yes| Stale{"scheduled_time <=
head_block_time?"} - Stale -->|Yes| Ret5["return not_time_yet
(slot already filled)"] - Stale -->|No| Fork{"Competing block in fork_db?"} - Fork -->|Yes, weaker fork| Produce - Fork -->|Yes, stronger fork| Ret6["return fork_collision"] - Fork -->|No| Produce["generate_block()"] - Produce --> Broadcast["p2p.broadcast_block()"] - Broadcast --> Done["return produced"] -``` - -### Note: two complementary partition guards - -`low_participation` and `minority_fork` are **not interchangeable** — they protect against -different failure modes and must both be active: - -| Guard | Trigger | Scenario it stops | -|-------|---------|-------------------| -| `low_participation` | `prate < 33%` (< 7 of 21 validators active) | Node in a small isolated segment — stops it from building a chain alone | -| `minority_fork` | Last 21 fork_db blocks are ALL from our validators | Node is producing in isolation despite appearing to have enough validators locally | - -**Why `low_participation` must not be removed:** -If a network partitions into two datacenters and one segment holds fewer than 7 of the -21 scheduled validators, it sees participation drop below 33% within ~85 missed slots -(~4 minutes). Without this guard that segment would keep building a competing chain -that neither side recognises as a minority fork, because `minority_fork` only fires when -**all** recent fork_db blocks are from our validators — possible only once the other -segment's blocks are completely absent from our fork_db. - -The operator escape hatch for legitimate outages (many validators offline but network -not partitioned) is `enable-stale-production = true`, which bypasses the `low_participation` -check explicitly. See `consensus-emergency-params.md` for the full workflow. - -## Post-Pause Catchup State Machine - -```mermaid -stateDiagram-v2 - [*] --> Normal: Node running - Normal --> Paused: pause_block_processing() - Paused --> Queuing: Block arrives - Queuing --> Queuing: Next block queued - Queuing --> Draining: resume_block_processing() - Note right of Draining: _catchup_after_pause = true - Draining --> DrainComplete: Queue empty - DrainComplete --> Normal: No peers ahead → flag cleared - DrainComplete --> GapFill: Peers still ahead - GapFill --> Normal: transition_to_forward()
_catchup_after_pause=false -``` - -## Key Files - -| File | Role | -|------|------| -| `libraries/network/dlt_p2p_node.cpp` | P2P block reception, pause/resume, catchup flag | -| `libraries/network/include/graphene/network/dlt_p2p_node.hpp` | `_catchup_after_pause` flag and getter | -| `plugins/p2p/p2p_plugin.cpp` | Exposes `is_catching_up_after_pause()` to other plugins | -| `plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp` | Public API declaration | -| `plugins/validator/validator.cpp` | Production gate that checks catchup flag | -| `plugins/snapshot/plugin.cpp` | Calls `pause/resume_block_processing()` | - -## The Bug (Before Fix) - -Two interrelated bugs: - -**Bug 1 — Write lock deadlock**: The emergency master's validator production loop -(250ms tick) bypasses all sync checks. During snapshot creation, the snapshot thread -holds a strong DB read lock for 30–120s. The production loop called `generate_block()` -→ `push_block()` → write lock → **deadlocked** behind the read lock, producing -11+ second write lock timeouts (readers=0, waiter spinning). - -**Bug 2 — Fork on stale head**: Without the block queue, blocks arriving during the -pause were silently dropped. After resume, the emergency master produced a block on a -stale head before gap-fill could deliver the real blocks from delegates, creating a -fork that other nodes had to resolve. - -Sequence: -1. Snapshot starts → P2P paused -2. Other delegates produce blocks N+1, N+2 → **dropped** by P2P -3. Emergency master production loop → `generate_block()` → **write lock timeout** (11s+) -4. Snapshot finishes → `resume_block_processing()` requests gap fill → returns immediately -5. Emergency master produces block N+1 with emergency key → **fork conflict** -6. Other nodes see two competing blocks → fork switch chaos - -## The Fix - -**Production gate during pause**: `is_catching_up_after_pause()` returns true when -either `_block_processing_paused` OR `_catchup_after_pause` is set. This prevents the -Validator Plugin from calling `generate_block()` while the snapshot holds the DB read -lock (avoids write-lock deadlock) AND during post-pause catchup (avoids stale-head fork). - -**Block queue**: During the pause, block-carrying messages (block_reply, block_range_reply, -gap_fill_reply) are deserialized and pushed into `_paused_block_queue`. Hello and fork_status -messages are still processed normally to keep `peer_head_num` up to date. - -**Queue drain**: When `resume_block_processing()` is called, it posts `drain_paused_block_queue()` -to the P2P thread. The drain sorts queued blocks by block_num, applies each via -`accept_block()`, and then checks whether peers are still ahead. - -**Catchup flag lifecycle**: `_catchup_after_pause` is set when `resume_block_processing()` -runs and cleared when: -- The drain completes and no peer is ahead (immediate path), or -- `transition_to_forward()` runs after a SYNC gap fill (delayed path), or -- `periodic_task()` confirms no gap exists (5s fallback) - -The Validator Plugin checks `is_catching_up_after_pause()` in `maybe_produce_block()` and -defers production while the flag is set. - ---- - -## Bug 3 — currently_syncing not cleared on SYNC→FORWARD (p72, 570 s silence) - -### Observed symptom - -After a scheduled snapshot the node produced no blocks for **570 seconds** despite -`_catchup_after_pause` being cleared at 10:54:40. WATCHDOG fired at 11:04:01 and -production resumed immediately after it called `chain.clear_syncing()`. - -### Root cause - -`currently_syncing` in the chain plugin (`plugin_impl::currently_syncing`) is set to -`true` by every `accept_block(sync_mode=true)` call — i.e. every block fetched during -SYNC mode. It self-clears **only** when the next `accept_block(sync_mode=false)` runs -(the first FORWARD-mode block). - -The chain of events in p72: - -``` -10:54:38 pause_block_processing() _block_processing_paused=true -10:54:40 resume_block_processing() _catchup_after_pause=true -10:54:40 drain_paused_block_queue() sync_mode=false → currently_syncing=false ✓ -10:54:40 drain: no peers ahead _catchup_after_pause=false ✓ - ← at this point both flags are clear, production should resume - -~10:54:41 periodic_task(): peers still ahead by 1-2 blocks - check_forward_behind() → transition_to_sync() - Fetch missing blocks: call_accept_block(sync_mode=true) - → currently_syncing.store(true) - Sync completes → transition_to_forward() - → currently_syncing NOT cleared ← BUG - -10:54:41–11:04:01 validator loop: - is_syncing()=true → return not_synced (rate-limited, silent) - not_my_turn_streak stays at 0–2 (resets on not_synced) - -11:04:01 WATCHDOG fires → chain.clear_syncing() - → currently_syncing=false → production resumes -``` - -The circular deadlock: `currently_syncing=true` blocks our validators; our validators are -the only remaining producers; no FORWARD block arrives to self-clear the flag. - -### Why the WATCHDOG evidence confirms this - -- `slot_result=2` (`not_my_turn`) at watchdog time — just switched away from `not_synced` -- `not_my_turn_streak=2` — very short; had been returning `not_synced` (resets the streak) - for almost all of the 570 s -- `prod=true`, `minority_recovering=false` — `_production_enabled` was fine; the block on - `_catchup_after_pause` was gone; only `is_syncing()` was blocking production - -### Fix - -`clear_syncing()` added to `dlt_p2p_delegate` and called from `transition_to_forward()` -**before** the early-return guard, so it fires on every SYNC→FORWARD transition -(and is a no-op when `currently_syncing` is already false): - -```cpp -// dlt_p2p_node.cpp — transition_to_forward() -if (_delegate) _delegate->clear_syncing(); // ← added -if (_node_status == DLT_NODE_STATUS_FORWARD) return; -``` - -`dlt_delegate::clear_syncing()` in `p2p_plugin.cpp` delegates to `chain.clear_syncing()`. - -### Files changed - -| File | Change | -|------|--------| -| `libraries/network/include/graphene/network/dlt_p2p_node.hpp` | `clear_syncing()` pure virtual in `dlt_p2p_delegate` | -| `plugins/p2p/p2p_plugin.cpp` | `dlt_delegate::clear_syncing()` → `chain.clear_syncing()` | -| `libraries/network/dlt_p2p_node.cpp` | `transition_to_forward()` calls `_delegate->clear_syncing()` | - ---- - -## Bug 4 — resume_block_processing() deadlock via async().wait() under read lock - -### Observed symptom - -After a snapshot the P2P layer would occasionally stall: the paused block queue never -drained and production never resumed, requiring a node restart. - -### Root cause - -`resume_block_processing()` was implemented by posting a task to the P2P thread via -`async().wait()` — i.e. it **blocked the calling thread** until the P2P thread completed -the task. - -The call chain: - -``` -snapshot completes - → on_applied_block signal - → flush_pending_block_notifications() - → with_weak_read_lock() - → resume_block_processing() - → async(P2P thread).wait() ← blocks here -``` - -A second P2P fiber that had already passed the `_block_processing_paused` check called -`push_block()`, which needed a **write lock** on the database. That write lock was -blocked by our read lock. The OS thread was frozen waiting for the write lock; the -posted async task could never run (it was on the same thread); therefore `wait()` never -returned. Deadlock. - -### Fix - -`resume_block_processing()` split into two phases: - -**Phase 1 (immediate, any thread):** `dlt_p2p_node::set_resume_flags()` atomically sets -`_catchup_after_pause = true` and clears `_block_processing_paused`. Both are -`std::atomic` — no P2P-thread dispatch needed. - -**Critical ordering:** `_catchup_after_pause` is set **before** `snapshot_in_progress` is -cleared. If reversed, the Validator Plugin could observe the snapshot complete while the -catchup flag is still false, misclassify the head as a stale fork, and refuse to produce. - -**Phase 2 (async, P2P thread, no wait):** An async post (fire-and-forget) to the P2P -thread calls `run_resume_on_p2p_thread()` which logs the resume and drains -`_paused_block_queue`. The caller does **not** `.wait()` — it returns immediately after -Phase 1. - -```cpp -void p2p_plugin::resume_block_processing() { - // Phase 1: set atomic flags immediately (thread-safe, no dispatch needed) - if (my && my->node) - my->node->set_resume_flags(); // _catchup_after_pause=true, _block_processing_paused=false - - // Phase 2: post drain to P2P thread (fire-and-forget) - my->p2p_thread.async([this]() { - if (my->node) my->node->run_resume_on_p2p_thread(); - }); - // No .wait() here — caller can proceed and release snapshot_in_progress immediately -} -``` - -### Files changed - -| File | Change | -|------|--------| -| `libraries/network/dlt_p2p_node.cpp` | Block-processing flags changed to `std::atomic`; `set_resume_flags()` / `run_resume_on_p2p_thread()` added | -| `plugins/p2p/p2p_plugin.cpp` | `resume_block_processing()` two-phase refactor; `pause_block_processing()` documented as P2P-thread-only direct call | - ---- - -## Bug 5 — snapshot async task does not reset P2P flags on failure path - -### Observed symptom - -If the snapshot async task encountered an error or was cancelled, `_block_processing_paused` -and `_catchup_after_pause` could remain in their paused state indefinitely, permanently -blocking validator production. - -### Root cause - -The flag reset / `resume_block_processing()` call was only on the happy path of the async -task. Error and early-exit branches fell through without resetting. - -### Fix - -`plugin.cpp` snapshot async task now calls `resume_block_processing()` (via the RAII -guard pattern) unconditionally — even when the task exits via exception or early return. -The snapshot path was also passed correctly into the load callback (it was empty before, -causing the load to fail silently). - -### Files changed - -| File | Change | -|------|--------| -| `plugins/snapshot/plugin.cpp` | Snapshot path passed into async load callback; flag reset moved to a scope guard ensuring it fires on all exit paths | diff --git a/.qoder/docs/snapshot-plugin.md b/.qoder/docs/snapshot-plugin.md deleted file mode 100644 index b9075419dc..0000000000 --- a/.qoder/docs/snapshot-plugin.md +++ /dev/null @@ -1,1209 +0,0 @@ -# Snapshot Plugin (DLT Mode) - -The snapshot plugin enables near-instant node startup by serializing and restoring the full blockchain state as a JSON snapshot file. Instead of replaying millions of blocks from the block log, a node can load a pre-built snapshot and begin syncing from the snapshot's block height via P2P. - -## Plugin Name - -`snapshot` - -## Dependencies - -- `chain` plugin (required, auto-loaded) - -## Config & CLI Options - -### CLI-only options (command line arguments) - -| Option | Type | Description | -|--------|------|-------------| -| `--snapshot ` | `string` | Load state from a snapshot file instead of replaying blockchain. The node opens in DLT mode (no block log). Safe for restarts — skips import if shared_memory already exists, and renames the file to `.used` after successful import. | -| `--snapshot-auto-latest` | `bool` (default: `false`) | Auto-discover the latest snapshot file in `snapshot-dir` by parsing block numbers from filenames (`snapshot-block-NNNNN.vizjson` or `.json`). Typically used with `--replay-from-snapshot` to avoid specifying the file path manually. Ignored if `--snapshot` is already specified. | -| `--replay-from-snapshot` | `bool` (default: `false`) | Crash recovery mode: import a snapshot and then replay blocks from `dlt_block_log` to bring the node up to the latest available state. Unlike `--snapshot`, this always wipes shared memory (assumes corruption), does NOT rename the snapshot to `.used`, and replays subsequent blocks from the DLT rolling block log. Requires `--snapshot ` or `--snapshot-auto-latest`. | -| `--auto-recover-from-snapshot` | `bool` (default: `true`) | Automatic runtime recovery from shared memory corruption. When corruption is detected during block processing or generation (e.g., missing validator account), the node immediately closes the database, finds the latest snapshot, wipes shared memory, imports the snapshot, replays `dlt_block_log`, and resumes P2P sync — all without a restart. Requires `plugin = snapshot` and snapshots in `snapshot-dir`. | -| `--create-snapshot ` | `string` | Create a snapshot file at the given path using the current database state, then exit. | -| `--sync-snapshot-from-trusted-peer` | `bool` (default: `false`) | Download and load snapshot from trusted peers when state is empty (`head_block_num == 0`). Requires `trusted-snapshot-peer` to be configured. Defaults to `false` (opt-in) — must be explicitly enabled to prevent accidental state wipe via `chainbase::wipe()`. | - -### Config file options (snapshot plugin) - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `snapshot-at-block` | `uint32_t` | `0` | Create snapshot when the specified block number is reached (while the node is running). | -| `snapshot-every-n-blocks` | `uint32_t` | `0` | Automatically create a snapshot every N blocks (0 = disabled). | -| `snapshot-dir` | `string` | `""` | Directory for auto-generated snapshot files. Used by `snapshot-at-block` and `snapshot-every-n-blocks`. | -| `snapshot-max-age-days` | `uint32_t` | `90` | Delete snapshots older than N days after creating a new one (0 = disabled). Built-in rotation replaces external cron jobs. | -| `allow-snapshot-serving` | `bool` | `false` | Enable serving snapshots over TCP to other nodes. | -| `allow-snapshot-serving-only-trusted` | `bool` | `false` | Restrict snapshot serving to trusted peers only (from `trusted-snapshot-peer` list). | -| `snapshot-serve-endpoint` | `string` | `0.0.0.0:8092` | TCP endpoint for the snapshot serving listener. | -| `trusted-snapshot-peer` | `string` (multi) | — | Trusted peer endpoint for snapshot sync (`IP:port`). Can be specified multiple times. | - -### Config file options (chain plugin) - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `dlt-block-log-max-blocks` | `uint32_t` | `100000` | Number of recent blocks to keep in the DLT rolling block log (0 = disabled). Only active in DLT mode (after snapshot import). | - -## Enabling the Plugin - -The snapshot plugin is registered by default in `vizd`. To enable it, add it to your `config.ini`: - -```ini -plugin = snapshot -``` - -Or pass it on the command line: - -```bash -vizd --plugin snapshot -``` - -## Creating a Snapshot - -### Method 1: One-shot snapshot (stop node, create, exit) - -Stop the node, then restart it with the `--create-snapshot` flag. The node will open the existing database (block log + shared memory), replay if needed to bring the state up to date, create the snapshot, and exit — **before** P2P or validator plugins activate: - -```bash -vizd --create-snapshot /data/snapshots/viz-snapshot.json --plugin snapshot -``` - -### What happens during `--create-snapshot` - -1. All plugins call `plugin_initialize()`. The **snapshot plugin** registers a `snapshot_create_callback` on the **chain plugin**. -2. The **chain plugin** `plugin_startup()` opens the database normally — block log, shared memory, and replays from block log if the chainbase revision doesn't match the head block. -3. After the database is fully loaded, the chain plugin calls `snapshot_create_callback()` — the **snapshot plugin** serializes all 32 tracked object types as JSON arrays with a SHA-256 checksum, writes the file, and calls `app().quit()`. -4. The chain plugin **never calls `on_sync()`** — P2P and validator plugins never activate. - -All snapshot creation happens **inside** `chain::plugin_startup()`. The database is fully consistent (post-replay) and no new blocks arrive during serialization. - -### Method 2: Snapshot at a specific block (no downtime) - -The node creates a snapshot automatically when the specified block number is applied. Add to `config.ini`: - -```ini -plugin = snapshot -snapshot-at-block = 5000000 -snapshot-dir = /data/snapshots -``` - -Start the node normally: - -```bash -vizd -``` - -When block 5,000,000 is applied, the snapshot is created at `/data/snapshots/snapshot-block-5000000.json` without stopping the node. Snapshot creation runs asynchronously on a dedicated background thread — only the database read phase (serialization) holds a read lock, so block processing is only briefly paused and API/P2P reads are never blocked. - -### Method 3: Periodic automatic snapshots (no downtime, recommended) - -The node creates snapshots automatically every N blocks. This is the recommended approach for production nodes that need regular backups. - -Add to `config.ini`: - -```ini -plugin = snapshot - -# Create a snapshot every 100,000 blocks (~3.5 days at 3s/block) -snapshot-every-n-blocks = 100000 - -# Directory where snapshot files are saved -snapshot-dir = /data/snapshots -``` - -Start the node normally: - -```bash -vizd -``` - -Snapshot files are named automatically: `snapshot-block-.json` - -Example output in the snapshots directory: - -``` -/data/snapshots/ - snapshot-block-100000.json - snapshot-block-200000.json - snapshot-block-300000.json - ... -``` - -**Recommended intervals:** - -| Interval | Blocks | Approximate time (at 3s/block) | -|----------|--------|-------------------------------| -| Frequent | 10,000 | ~8.3 hours | -| Daily | 28,800 | ~24 hours | -| Weekly | 100,000 | ~3.5 days | -| Rare | 1,000,000 | ~34.7 days | - -**Notes on periodic snapshots:** -- **Only triggers on live blocks**: Periodic snapshots are skipped while the node is syncing from P2P (block time >60s behind wall clock). This prevents wasteful snapshot creation during initial sync from genesis or from an older snapshot. Snapshots begin only after the node catches up to the live chain head. -- **Auto-creates directory**: The snapshot directory (`snapshot-dir`) is automatically created if it doesn't exist. No need to `mkdir` before starting the node. -- **Non-blocking snapshot creation**: Snapshot creation runs asynchronously on a dedicated `fc::thread`. The `on_applied_block` callback returns immediately after scheduling the snapshot — the write lock is released and block processing resumes. The snapshot itself is split into two phases: - - **Phase 1 (read lock, ~1 second)**: Serializes all database state into local variables. During this phase, block processing waits (read lock conflicts with write lock), but API/P2P reads proceed concurrently. - - **Phase 2 (no lock, ~2 seconds)**: Compression, checksum computation, and file I/O. Block processing and API reads run normally during this phase. -- This prevents the read-lock timeouts and `unlinkable_block_exception` errors that previously occurred when snapshot creation ran synchronously inside the write-lock scope (which blocked all database access for 3+ seconds). -- For large chains, consider using a longer interval (e.g., `100000` blocks) to minimize the impact on block processing. -- **Built-in rotation**: Old snapshot files are automatically deleted if older than `snapshot-max-age-days` (default 90 days, 0 = disabled). Rotation runs after each new snapshot is created. -- If snapshot creation fails (e.g., disk full), the error is logged but the node continues running normally. - -### Method 4: Combining at-block with periodic - -You can use both `snapshot-at-block` and `snapshot-every-n-blocks` together: - -```ini -plugin = snapshot -snapshot-at-block = 5000000 -snapshot-every-n-blocks = 100000 -snapshot-dir = /data/snapshots -``` - -This creates a snapshot at block 5,000,000 AND every 100,000 blocks. - -### Managing snapshot disk space - -Snapshot rotation is built-in. By default, snapshots older than 90 days are automatically deleted after each new snapshot is created. - -To configure or disable rotation, set `snapshot-max-age-days` in `config.ini`: - -```ini -# Delete snapshots older than 30 days (default: 90) -snapshot-max-age-days = 30 - -# Or disable rotation entirely -# snapshot-max-age-days = 0 -``` - -Alternatively, you can use an external cron job for finer control: - -```bash -# Keep only the 5 most recent snapshots -ls -t /data/snapshots/snapshot-block-*.json | tail -n +6 | xargs rm -f -``` - -## Loading from a Snapshot (DLT Mode) - -To start a node from a snapshot file: - -```bash -vizd --snapshot /path/to/snapshot.json --plugin snapshot -``` - -### What happens during snapshot loading - -1. All plugins call `plugin_initialize()`. The **snapshot plugin** registers a `snapshot_load_callback` on the **chain plugin**. -2. The **chain plugin** `plugin_startup()` detects the `--snapshot` option and checks three conditions in order: - - **shared_memory.bin already exists** → skips import, falls through to normal startup (prevents re-importing on container restart). - - **Snapshot file not found** (e.g., already renamed to `.used`) → skips import, falls through to normal startup. - - **Both checks pass** → proceeds with snapshot import. -3. The chain plugin opens the database using `open_from_snapshot()` — this wipes shared memory, initializes chainbase, indexes, and evaluators. -4. The chain plugin calls `snapshot_load_callback()` — the **snapshot plugin** reads the JSON file, validates the header (format version, chain ID, SHA-256 checksum), imports all 32 tracked object types into the database under a strong write lock, and calls `initialize_hardforks()` to populate the hardfork schedule. -5. **The snapshot file is renamed to `.used`** (e.g., `snapshot.json` → `snapshot.json.used`) to prevent re-import on restart. -6. LIB (Last Irreversible Block) is promoted to `head_block_num` so P2P's blockchain synopsis starts from the snapshot's head — peers will only offer blocks after the snapshot point. -7. The fork database is seeded with the head block from the snapshot. -8. The chain plugin emits `on_sync` so other plugins (webserver, APIs, etc.) know the node is ready. -9. **P2P plugin** starts — sees the snapshot's head block and begins syncing from **LIB + 1** via the P2P network. - -All snapshot loading happens **inside** `chain::plugin_startup()`, before any other plugin starts. P2P and validator never see incomplete/genesis state. - -### Restart safety - -The node is safe to restart with `--snapshot` still on the command line (e.g., via `VIZD_EXTRA_OPTS` in Docker). Three layers of protection prevent accidental re-import: - -| Restart scenario | What happens | -|---|---| -| **1st start** (no shared_memory, file exists) | Imports snapshot, renames file to `.used` | -| **Restart** (shared_memory exists) | Skips import: "Shared memory already exists" | -| **Restart** (shared_memory wiped, file already `.used`) | Skips import: "Snapshot file not found" | -| **Force re-import** | `--resync-blockchain` wipes shared_memory + provide a fresh snapshot file | - -### Important notes - -- **No block log (DLT mode)**: When loaded from a snapshot, the node runs in DLT mode — the main `block_log` remains empty. A separate **DLT rolling block log** stores recent blocks (see below). -- **Automatic DLT mode detection on restart**: After the initial snapshot load, the node detects DLT mode automatically (block_log empty, chainbase has state), skips block_log validation, and continues syncing from P2P. -- **Chain ID validation**: The snapshot's chain ID must match the node's compiled chain ID. Mismatches are rejected. -- **Checksum verification**: The payload checksum is verified before any objects are imported. -- **Restart with `--snapshot` is safe**: See "Restart safety" above — no need to remove the flag after initial import. - -## Snapshot File Format - -The snapshot is a single JSON file with this structure: - -```json -{ - "header": { - "version": 1, - "chain_id": "...", - "snapshot_block_num": 12345678, - "snapshot_block_id": "...", - "snapshot_block_time": "2025-01-01T00:00:00", - "last_irreversible_block_num": 12345660, - "last_irreversible_block_id": "...", - "snapshot_creation_time": "2025-01-01T00:05:00", - "payload_checksum": "sha256...", - "object_counts": { - "account": 50000, - "validator": 100, - "content": 200000, - ... - } - }, - "state": { - "dynamic_global_property": [ ... ], - "witness_schedule": [ ... ], - "hardfork_property": [ ... ], - "account": [ ... ], - "account_authority": [ ... ], - "validator": [ ... ], - ... - "fork_db_head_block": { ... } - } -} -``` - -### Included Object Types (32 total) - -**Critical (11)** — consensus-essential, always required: - -- `dynamic_global_property` — global chain state (singleton) -- `witness_schedule` — current validator schedule (singleton) -- `hardfork_property` — hardfork tracking state (singleton) -- `account` — all accounts -- `account_authority` — master/active/regular authorities -- `validator` — validator registrations -- `witness_vote` — validator votes -- `block_summary` — block ID summaries (65536 entries) -- `content` — content/posts -- `content_vote` — content votes -- `block_post_validation` — block post-validation records - -**Important (15)** — needed for full operation: - -- `transaction` — pending transactions -- `vesting_delegation` — vesting delegations -- `vesting_delegation_expiration` — expiring delegations -- `fix_vesting_delegation` — delegation fix records -- `withdraw_vesting_route` — vesting withdrawal routes -- `escrow` — escrow transfers -- `proposal` — governance proposals -- `required_approval` — proposal approval requirements -- `committee_request` — committee funding requests -- `committee_vote` — committee votes -- `invite` — account invites -- `award_shares_expire` — expiring award shares -- `paid_subscription` — paid subscription offers -- `paid_subscribe` — active subscriptions -- `witness_penalty_expire` — validator penalty expirations - -**Optional (5)** — metadata and recovery: - -- `content_type` — content title/body/metadata -- `account_metadata` — account JSON metadata -- `master_authority_history` — authority change history -- `account_recovery_request` — pending recovery requests -- `change_recovery_account_request` — recovery account change requests - -## Example: Full DLT Node Setup - -### Step 1: Create a snapshot on a synced node - -```bash -# Option A: One-shot (creates and exits) -vizd --create-snapshot /data/snapshots/viz-snapshot-20250101.json --plugin snapshot - -# Option B: Already running with periodic snapshots in config.ini — just grab the latest file -ls -t /data/snapshots/snapshot-block-*.json | head -1 -``` - -### Step 2: Transfer the snapshot file to the new node - -```bash -scp /data/snapshots/viz-snapshot-20250101.json newnode:/data/snapshots/ -``` - -### Step 3: Start the new node from the snapshot - -```bash -vizd \ - --snapshot /data/snapshots/viz-snapshot-20250101.json \ - --plugin snapshot \ - --plugin p2p \ - --p2p-seed-node seed1.viz.world:2001 \ - --shared-file-size 4G -``` - -The node will load the snapshot state in seconds and begin syncing new blocks from the network. - -### Step 4: Subsequent restarts - -The node is safe to restart even with `--snapshot` still on the command line. It detects existing shared_memory and skips re-import automatically: - -```bash -# Just restart — no need to remove --snapshot flag -vizd --plugin p2p --p2p-seed-node seed1.viz.world:2001 -``` - -## DLT Rolling Block Log - -When a node runs in DLT mode (loaded from snapshot), the main `block_log` is empty. However, a separate **DLT rolling block log** (`dlt_block_log`) stores the most recent irreversible blocks. This enables: - -- **P2P block serving**: Peers can request recent blocks from this node (for fork resolution and initial sync catch-up). -- **Local block queries**: API calls like `get_block` work for recent blocks. - -### Configuration - -```ini -# Keep the last 100,000 blocks in the DLT block log (default) -dlt-block-log-max-blocks = 100000 - -# Or disable the DLT block log entirely -# dlt-block-log-max-blocks = 0 -``` - -### How it works - -- The DLT block log is stored in two files: `dlt_block_log.log` and `dlt_block_log.index` in the blockchain data directory. -- The index uses an **offset-aware format**: an 8-byte header stores the start block number, followed by 8-byte position entries for each block. -- When the log exceeds `dlt-block-log-max-blocks`, old blocks are truncated from the front (rolling window). -- On restart, the DLT block log is preserved — blocks are only re-written from where they left off. -- When the DLT block log is empty (fresh snapshot import), the node skips ahead to the last irreversible block number, since snapshot state is already trusted. -- If a block is not found in the fork database during DLT block log writes (normal after restart), the gap is logged via `wlog` and fills naturally as LIB advances past the post-restart head. - -### Mapped file integrity - -The DLT block log uses `boost::iostreams::mapped_file` for both data and index files. Each `append()` calls `resize()` which internally does close→truncate→remap. After thousands of resize cycles during long-running block production, `mapped_file.size()` can return **stale values** (reflecting an older, smaller size), causing `get_block_pos()` to reject valid block numbers and break P2P sync (the node claims to have blocks but fails to look them up). - -**Fix:** The implementation tracks **logical file sizes** (`_logical_block_size`, `_logical_index_size`) independently of `mapped_file.size()`. These are updated after every `resize()` in `append()` and re-synced from the actual mapping on `open()`. All range checks in `get_block_pos()` and `read_block()` use the tracked logical sizes. - -**Self-healing:** A `verify_mapping()` method compares `mapped_file.size()` against the tracked logical size. If a discrepancy is detected, the mapping is closed and reopened (healing the stale state). This is called automatically every 5 minutes by the P2P stats task. - -**Diagnostics:** The P2P stats task (every 5 minutes) logs a `Block storage` line showing: -- `dlt_log: [start..end]` — current DLT block log range -- `dlt_resizes` — total `resize()` calls since open (useful for correlating with staleness) -- `fork_db` — linked/unlinked block counts and ranges - -### P2P block serving path - -When a peer requests a block: -1. `p2p_plugin::get_item()` → `database::fetch_block_by_id()` -2. First checks main `_block_log` (empty in DLT mode) -3. Falls back to `_dlt_block_log` for recent blocks -4. If not found in either → block unavailable - -**Note on `is_known_block()`:** In DLT mode, the `block_summary` table (TAPOS buffer, 65536 entries) survives snapshot import but may reference blocks not actually available on disk. The DLT mode implementation checks `block_summary` as a hint, then verifies the block is on the **preferred chain** via `find_block_id_for_num()`. This two-step check: -- Returns `true` for blocks on our chain (enabling P2P's `has_item()` to work correctly during sync negotiation) -- Returns `false` for blocks not on our chain or blocks where `block_summary` has stale fork entries (preventing the node from lying to P2P peers about being able to serve the block data) -- Falls through to `fetch_block_by_id()` for blocks outside the `block_summary` range - -### P2P sync reliability (DLT mode) - -After snapshot import, the node must sync all subsequent blocks from P2P. Several fixes ensure this works: - -**LIB promotion:** After snapshot import, LIB is set to `head_block_num` so P2P's blockchain synopsis starts from the snapshot's head. Peers will only offer blocks after the snapshot point, which can link correctly in the fork database. - -**Fork database seeding:** -- Fresh snapshot import: `fork_db` is seeded with the head block via `start_block()`. The head block is also appended to `dlt_block_log` so that restart can reconstruct `fork_db` from it. -- DLT mode restart: `fork_db` is in-memory and lost on restart. The node seeds it from `dlt_block_log` if it covers the head block (guaranteed after the fix above). If `dlt_block_log` somehow does not cover the head, the early rejection logic in `_push_block` handles the empty `fork_db` case by always allowing blocks whose `previous == head_block_id()` - -**Block ID advertisement clamping (`get_block_ids`):** After snapshot import, the `block_summary` (TAPOS buffer, 65536 entries) contains block IDs for blocks the node *knows about* but cannot serve (the actual block data only exists in `dlt_block_log` which starts at the snapshot head). Without clamping, `get_block_ids()` would advertise these un-serveable blocks to peers, causing them to request the blocks, receive `item_not_available`, and disconnect with "You are missing a sync item you claim to have, your database is probably corrupted." - -Fix: `database::earliest_available_block_num()` returns the lowest block the node can actually serve (from `dlt_block_log`, `block_log`, or `fork_db`). In DLT mode after snapshot import, this is typically the snapshot head block. `get_block_ids()` in `p2p_plugin.cpp` clamps its start to `earliest_available_block_num()`, ensuring the node only advertises blocks it can deliver. - -**Graceful `item_not_available` handling:** When a peer sends `item_not_available` for a sync block, the node no longer disconnects with a "corrupted database" message. Instead, it sets `inhibit_fetching_sync_blocks = true` on that peer and tries other peers. This allows DLT nodes with limited block history to participate in the network without being aggressively disconnected. - -**Broadcast inventory suppression during sync:** During initial sync (catching up from snapshot), peers send both sync data (block IDs for catch-up) and broadcast items (recent transactions/blocks at chain tip). If the node tries to fetch these broadcast items, the 1-second `active_ignored_request_timeout` in `terminate_inactive_connections()` fires before the items arrive, disconnecting peers and killing sync connections. - -The node suppresses broadcast inventory (`on_item_ids_inventory_message`) with a 3-layer defense: -1. **Per-peer sync check:** Skip if the originating peer has `we_need_sync_items_from_peer = true` -2. **Global sync check:** Skip if *any* active peer has `we_need_sync_items_from_peer = true` — prevents inventory from non-syncing peers from polluting `items_requested_from_peer` -3. **Head block time check:** Skip if the node's head block is >30 seconds behind wall clock — catches the brief window after all peers respond "up to date" but the node is still behind (e.g., when the peer was at the same block and set `we_need_sync_items_from_peer = false`) - -Broadcast items are useless during sync — the node will receive them naturally once caught up. - -**Early block rejection in `_push_block`:** When a node is far behind, it receives sync blocks (sequential, must accept) and broadcast blocks (real-time, potentially thousands ahead, must reject silently). Checks prevent sync disruption: -1. Duplicate blocks at/before head with matching ID → skipped silently -2. Blocks at/before head on a different fork with parent not in fork_db → silently rejected (prevents infinite P2P sync restart loop where each failure triggers another sync attempt with the same peer) -3. Far-ahead blocks whose parent is neither `head_block_id()` nor in `fork_db` → returned false silently (no `unlinkable_block_exception` thrown, preventing P2P sync restart) -4. Blocks with `previous == head_block_id()` → always allowed (critical for the first sync block to be accepted) - -**Fork database bug fixes:** Several bugs in `fork_database` were fixed: -- `_unlinked_index.insert()` was dead code (after `throw`) — moved before the throw -- `_push_next()` was never called after inserting a new block — added the call to resolve previously-unlinkable blocks -- Duplicate block check added in `_push_block` -- `_unlinked_index.clear()` added to `reset()` - -## Stale Snapshot Detection (DLT Mode) - -In DLT mode, the `dlt_block_log` is a rolling window that prunes old blocks. If the node's latest snapshot is older than the DLT block log's start block, downloading nodes would face an unsyncable gap (snapshot at block N, DLT log starts at block M > N, blocks N+1..M-1 missing). - -At startup, the snapshot plugin detects this condition: -1. Checks if in DLT mode with snapshot serving or periodic creation enabled -2. Compares latest snapshot block number against `dlt_block_log.start_block_num()` -3. If snapshot block < DLT start block, logs a `STALE SNAPSHOT DETECTED` warning -4. On the first fully-synced block, creates an urgent fresh snapshot (async, with validator-aware deferral) - -This ensures serving nodes always have a snapshot that peers can actually sync from. The check runs automatically — no configuration needed beyond the existing `snapshot-every-n-blocks` or `allow-snapshot-serving` settings. - -## Crash Recovery: `--replay-from-snapshot` - -When `shared_memory.bin` becomes corrupted (e.g., after an unclean shutdown, disk full, or hardware fault), the node cannot start normally. The standard `--replay-blockchain` replays from `block_log`, which is empty in DLT mode. The `--replay-from-snapshot` option provides a recovery path that combines snapshot import with DLT block log replay. - -### The problem - -| Scenario | Why it fails | -|----------|-------------| -| Normal startup | `shared_memory.bin` has corrupted indices → FC_ASSERT crash | -| `--replay-blockchain` | Reads from `block_log`, which is empty in DLT mode | -| `--snapshot ` alone | Imports snapshot state but does not replay subsequent blocks from `dlt_block_log` | - -### The solution - -`--replay-from-snapshot` performs a three-step recovery: - -1. **Wipe & import snapshot** — Always wipes `shared_memory.bin` (assumes corruption), opens the database from genesis via `open_from_snapshot()`, and imports the snapshot state. -2. **Replay dlt_block_log** — If the `dlt_block_log` contains blocks beyond the snapshot's head, they are replayed via `database::reindex_from_dlt()` to bring the node as close to the chain tip as possible. -3. **Resume P2P sync** — The node emits `on_sync` and begins normal P2P sync from the replayed head block onward. - -Unlike `--snapshot`, the recovery mode: -- Always wipes shared memory (no "already exists" check) -- Does **not** rename the snapshot file to `.used` (the snapshot may be needed again) -- Initializes hardforks after snapshot import (before replay) -- Replays blocks from `dlt_block_log` with standard reindex skip flags - -### Usage - -```bash -# Simple: specify the snapshot path explicitly -vizd --replay-from-snapshot --snapshot /data/snapshots/snapshot-block-79273800.vizjson --plugin snapshot - -# Convenient: auto-discover the latest snapshot -vizd --replay-from-snapshot --snapshot-auto-latest --plugin snapshot -``` - -With `--snapshot-auto-latest`, the node scans `snapshot-dir` for files matching `snapshot-block-*.vizjson` or `snapshot-block-*.json`, parses the block number from each filename, and selects the one with the highest block number. This avoids having to manually find and specify the snapshot path. - -### What happens during recovery - -1. All plugins call `plugin_initialize()`. The **snapshot plugin** registers a `snapshot_load_callback` on the **chain plugin**. -2. The **chain plugin** `plugin_startup()` detects `--replay-from-snapshot` and validates that a snapshot path is available (via `--snapshot` or `--snapshot-auto-latest`). -3. The chain plugin opens the database using `open_from_snapshot()` — this wipes shared memory, initializes chainbase, indexes, and evaluators in DLT mode. -4. The chain plugin calls `snapshot_load_callback()` — the **snapshot plugin** reads the JSON file, validates the header, imports all tracked object types, and calls `initialize_hardforks()`. -5. The chain plugin calls `initialize_hardforks()` to populate the hardfork schedule arrays. -6. The chain plugin checks if `dlt_block_log` has blocks beyond the snapshot head. If yes, it calls `database::reindex_from_dlt(snapshot_head + 1)`. -7. `reindex_from_dlt()` replays each block from the DLT rolling block log with reindex skip flags (no signature checks, no merkle checks, etc.), reporting progress to stderr every 10%. -8. After replay, the fork database is seeded with the last block from `dlt_block_log`. -9. The chain plugin emits `on_sync` — P2P starts syncing from the replayed head block. - -### Example recovery scenario - -A DLT-mode node with periodic snapshots every 100,000 blocks and `dlt-block-log-max-blocks = 100000` crashes at block 79,274,318 with corrupted shared memory: - -``` -/data/viz-snapshots/ - snapshot-block-79273800.vizjson ← latest snapshot (518 blocks behind crash point) - -/blockchain/ - dlt_block_log.log ← contains blocks 79174319..79274318 - dlt_block_log.index - shared_memory.bin ← CORRUPTED -``` - -Recovery command: -```bash -vizd --replay-from-snapshot --snapshot-auto-latest --plugin snapshot -``` - -Recovery log output: -``` -RECOVERY MODE: replaying from snapshot + dlt_block_log... -Opening database for snapshot import. Please wait... -Database opened for snapshot import (DLT mode), elapsed time 2.1 sec -Loading state from snapshot: /data/viz-snapshots/snapshot-block-79273800.vizjson -Snapshot loaded successfully at block 79273800, elapsed time 12.3 sec -Snapshot loaded at block 79273800. Initializing hardforks... -Replaying dlt_block_log from block 79273801 to 79274318... - 10% 52 of 518 (block 79273852, 3840M free, elapsed 0.8 sec) - 20% 104 of 518 (block 79273904, 3839M free, elapsed 1.5 sec) - ... - 100% 518 of 518 (block 79274318, 3830M free, elapsed 7.2 sec) -Done replaying from dlt_block_log, head_block=79274318, elapsed time: 7.3 sec -Recovery complete. Started on blockchain with 79274318 blocks -``` - -The node is now at block 79,274,318 and P2P sync fills the gap to the live chain head. - -### Key differences from `--snapshot` - -| Aspect | `--snapshot` | `--replay-from-snapshot` | -|--------|-------------|--------------------------| -| Purpose | Bootstrap a new node | Recover from corruption | -| Shared memory check | Skips if already exists | Always wipes and re-imports | -| Snapshot file rename | Renames to `.used` | Does NOT rename | -| DLT block log replay | No | Yes, from snapshot_head+1 | -| Hardfork initialization | In callback | In callback + explicit call | -| Typical use case | First-time node setup | Crash recovery | - -## Automatic Runtime Recovery: `--auto-recover-from-snapshot` - -While `--replay-from-snapshot` requires a manual restart, `--auto-recover-from-snapshot` (enabled by default) provides **immediate, automatic recovery** when shared memory corruption is detected at runtime — without any restart. - -### How it works - -When the node detects corruption during normal operation (e.g., a validator account object is missing from the database during block processing or block generation), it throws a `shared_memory_corruption_exception`. This exception is caught at the plugin level: - -1. **Block acceptance path** (P2P sync): `plugin::accept_block()` catches the exception and calls `attempt_auto_recovery()`. -2. **Block generation path** (validator): The Validator Plugin's `generate_block` retry loop catches the exception before the generic `fc::exception` handler, and calls `chain().attempt_auto_recovery()`. - -### Recovery flow (`attempt_auto_recovery`) - -1. **Find snapshot** — Scans `snapshot-dir` for the latest `snapshot-block-*.vizjson` or `.json` file (same logic as `--snapshot-auto-latest`). -2. **Close database** — Calls `db.close(false)` (no rewind — state is corrupted anyway). Errors during close are ignored. -3. **Wipe & import** — Calls `do_snapshot_load(data_dir, true)`, which is the exact same code path as `--replay-from-snapshot`: wipes `shared_memory.bin`, opens from genesis via `open_from_snapshot()`, imports snapshot state via callback, replays `dlt_block_log` blocks. -4. **Resume** — Calls `on_sync()` to resume P2P sync from the recovered head block. The node continues running. - -If recovery fails at any step (no snapshots found, snapshot plugin not configured, import error), the node logs an error and calls `appbase::app().quit()`. - -### Prerequisites - -- `plugin = snapshot` must be enabled -- Snapshots must exist in `snapshot-dir` (default: `/snapshots/`). Use `--snapshot-every-n-blocks` to create them automatically. -- The `dlt_block_log` should cover blocks beyond the snapshot for minimal data loss. - -### Key differences from `--replay-from-snapshot` - -| Aspect | `--replay-from-snapshot` | `--auto-recover-from-snapshot` | -|--------|--------------------------|-------------------------------| -| Trigger | Manual (restart with CLI flag) | Automatic (runtime exception) | -| Requires restart | Yes | No | -| Snapshot selection | `--snapshot` or `--snapshot-auto-latest` | Always auto-discovers latest | -| Default | `false` (opt-in) | `true` (enabled by default) | -| Database state before recovery | Failed to open | Open but corrupted → closed | -| Startup corruption | Yes (catch blocks in `plugin_startup`) | Yes (catch blocks in `plugin_startup`) | -| Runtime corruption | No | Yes (`accept_block`, `generate_block`) | - -### Disabling - -To disable automatic recovery (e.g., for debugging), pass `--no-auto-recover-from-snapshot` on the command line. - -## P2P Snapshot Sync - -Nodes can download snapshots directly from trusted peers over a custom TCP protocol, enabling fully automated bootstrap without manual file transfers. - -### Server (snapshot provider) - -Add to `config.ini`: - -```ini -plugin = snapshot - -# Enable TCP snapshot serving -allow-snapshot-serving = true - -# TCP endpoint for the snapshot server -snapshot-serve-endpoint = 0.0.0.0:8092 - -# Optional: restrict to trusted peers only -# allow-snapshot-serving-only-trusted = true -# trusted-snapshot-peer = 1.2.3.4:8092 - -# Must have snapshots to serve -snapshot-every-n-blocks = 28800 -snapshot-dir = /data/viz-snapshots -``` - -### Client (new node bootstrap) - -Start a new node that automatically downloads and loads a snapshot from trusted peers: - -```bash -vizd \ - --plugin snapshot \ - --plugin p2p -``` - -With `config.ini`: - -```ini -trusted-snapshot-peer = seed1.viz.world:8092 -trusted-snapshot-peer = seed2.viz.world:8092 -trusted-snapshot-peer = seed3.viz.world:8092 -``` - -Since `sync-snapshot-from-trusted-peer` defaults to `false`, you must explicitly enable it in `config.ini` along with `trusted-snapshot-peer`: - -```ini -trusted-snapshot-peer = seed1.viz.world:8092 -trusted-snapshot-peer = seed2.viz.world:8092 -trusted-snapshot-peer = seed3.viz.world:8092 -sync-snapshot-from-trusted-peer = true -``` - -When the node starts with 0 blocks and sync is enabled, it automatically downloads the snapshot from the best available peer. To disable auto-sync (the default): - -```ini -sync-snapshot-from-trusted-peer = false -``` - -If the node has 0 blocks and no `trusted-snapshot-peer` is configured, a console warning is shown advising the user to configure one. - -### How P2P sync works - -1. **Query phase**: The node connects to each trusted peer (5-second timeout per operation: connect, send, read), sends a `snapshot_info_request`, and collects metadata (block number, checksum, compressed size). Progress is logged to console. -2. **Selection**: Picks the peer with the highest block number. -3. **Download phase**: Downloads the snapshot in 1 MB chunks, writing to a temp file. Download progress is printed to console every 5% (size in MB and percentage). -4. **Verification**: Streams the downloaded file through SHA-256 to verify checksum (without loading into memory). -5. **Import**: Clears database state, loads the verified snapshot, initializes hardforks. Each stage (decompress, parse, validate, import) is logged to console with timing. - -All operations happen during `chain::plugin_startup()`, **before** P2P and validator plugins activate. The node is fully blocked during download and import — no blocks are processed until the snapshot is loaded. - -### Security features - -- **Max snapshot size**: Downloads exceeding 2 GB are rejected. -- **Streaming checksum**: SHA-256 verification uses streaming (1 MB chunks) to avoid loading the entire file into memory. -- **Trusted peer list**: Connections are only accepted from/to configured trusted peers. -- **Anti-spam**: Rate limiting (max connections per hour per IP), max 5 concurrent connections (each in a separate fiber with mutex-protected session tracking), 60-second enforced connection deadline (checked before each I/O operation, not just post-hoc). -- **Payload limits**: Control messages limited to 64 KB, only data replies allow up to 64 MB. -- **Dedicated server thread**: The TCP server runs all fibers (accept loop, watchdog, connection handlers) on a dedicated `fc::thread`, ensuring they are not blocked by the main thread's `io_serv->run()` loop. - -## Recommended Production Config - -A full `config.ini` snippet for a production node with automatic periodic snapshots: - -```ini -# Enable the snapshot plugin -plugin = snapshot - -# Create a snapshot every ~24 hours (28800 blocks * 3 seconds = 86400 sec) -snapshot-every-n-blocks = 28800 - -# Store snapshots in a dedicated directory -snapshot-dir = /data/viz-snapshots - -# Auto-delete snapshots older than 90 days (default) -snapshot-max-age-days = 90 - -# DLT rolling block log: keep last 100k blocks (default) -dlt-block-log-max-blocks = 100000 - -# Standard chain settings -shared-file-size = 4G -plugin = p2p -p2p-seed-node = seed1.viz.world:2001 -``` - -This configuration ensures your node always has a recent snapshot available for quick recovery or for bootstrapping new nodes. - -## Docker Usage - -The standard Docker launch command: - -```bash -docker run \ - -p 8083:2001 \ - -p 9991:8090 \ - -v ~/vizconfig:/etc/vizd \ - -v ~/vizhome:/var/lib/vizd \ - --name vizd -d vizblockchain/vizd:latest -``` - -Volumes: -- `~/vizconfig` → `/etc/vizd` — config files (`config.ini`, `seednodes`) -- `~/vizhome` → `/var/lib/vizd` — blockchain data, shared memory, snapshots - -The entry point script (`vizd.sh`) supports `VIZD_EXTRA_OPTS` environment variable for passing additional CLI arguments. - -### Creating a snapshot (periodic, no downtime) - -Add snapshot options to your config file `~/vizconfig/config.ini`: - -```ini -plugin = snapshot - -# Create a snapshot every ~24 hours (28800 blocks * 3s = ~86400s) -snapshot-every-n-blocks = 28800 - -# Store snapshots inside the data volume -snapshot-dir = /var/lib/vizd/snapshots -``` - -Start (or restart) the container: - -```bash -docker stop vizd && docker rm vizd - -docker run \ - -p 8083:2001 \ - -p 9991:8090 \ - -v ~/vizconfig:/etc/vizd \ - -v ~/vizhome:/var/lib/vizd \ - --name vizd -d vizblockchain/vizd:latest -``` - -Snapshots will appear on the host at `~/vizhome/snapshots/snapshot-block-*.json`. - -### Creating a one-shot snapshot (stop & export) - -Stop the running container, then run a temporary one with `--create-snapshot`: - -```bash -# Stop the running node -docker stop vizd - -# Create a snapshot using VIZD_EXTRA_OPTS -docker run --rm \ - -v ~/vizconfig:/etc/vizd \ - -v ~/vizhome:/var/lib/vizd \ - -e VIZD_EXTRA_OPTS="--create-snapshot /var/lib/vizd/snapshots/viz-snapshot.json --plugin snapshot" \ - vizblockchain/vizd:latest - -# The snapshot is now at ~/vizhome/snapshots/viz-snapshot.json on the host - -# Restart the node normally -docker start vizd -``` - -### Creating a snapshot at a specific block (no downtime) - -Add to `~/vizconfig/config.ini`: - -```ini -plugin = snapshot -snapshot-at-block = 5000000 -snapshot-dir = /var/lib/vizd/snapshots -``` - -Restart the container. When block 5,000,000 is applied, the snapshot file will be created at `/var/lib/vizd/snapshots/snapshot-block-5000000.json` (accessible on host at `~/vizhome/snapshots/`). - -### Loading from a snapshot (new node bootstrap) - -Place the snapshot file on the host: - -```bash -# Copy snapshot to the new server -scp viz-snapshot.json newserver:~/vizhome/snapshots/ -``` - -Start the container with `--snapshot` via `VIZD_EXTRA_OPTS`: - -```bash -docker run \ - -p 8083:2001 \ - -p 9991:8090 \ - -v ~/vizconfig:/etc/vizd \ - -v ~/vizhome:/var/lib/vizd \ - -e VIZD_EXTRA_OPTS="--snapshot /var/lib/vizd/snapshots/viz-snapshot.json --plugin snapshot" \ - --name vizd -d vizblockchain/vizd:latest -``` - -The node loads the snapshot state in seconds and begins syncing new blocks from P2P. - -**Restart safety:** The node is safe to restart with `VIZD_EXTRA_OPTS` still set. On restart: -1. If shared_memory already exists → skips import, uses existing state. -2. If the snapshot file was renamed to `.used` → skips import. -3. No need to remove `VIZD_EXTRA_OPTS` after the first run. - -### Managing snapshot disk space (Docker) - -Snapshot rotation is built-in (default: delete files older than 90 days). Add `snapshot-max-age-days` to `~/vizconfig/config.ini` to customize: - -```ini -# Delete snapshots older than 30 days -snapshot-max-age-days = 30 -``` - -Alternatively, add a cron job on the **host** machine: - -```bash -# Keep only the 5 most recent snapshots -0 0 * * * ls -t ~/vizhome/snapshots/snapshot-block-*.json | tail -n +6 | xargs rm -f -``` - -### Quick reference - -| Task | Command | -|------|---------| -| Start with periodic snapshots | Add `snapshot-every-n-blocks` to `~/vizconfig/config.ini`, restart container | -| One-shot snapshot | `docker run --rm -e VIZD_EXTRA_OPTS="--create-snapshot /var/lib/vizd/snapshots/snap.json --plugin snapshot" ...` | -| Load from snapshot | `docker run -e VIZD_EXTRA_OPTS="--snapshot /var/lib/vizd/snapshots/snap.json --plugin snapshot" ...` | -| Crash recovery | `docker run -e VIZD_EXTRA_OPTS="--replay-from-snapshot --snapshot-auto-latest --plugin snapshot" ...` | -| Auto-recovery (default) | Enabled by default with `--auto-recover-from-snapshot`. Ensure `plugin = snapshot` and `snapshot-every-n-blocks` are set in config. | -| P2P auto-bootstrap | Add `trusted-snapshot-peer = :` to config, start container with `--plugin snapshot` | -| Find snapshots on host | `ls -lt ~/vizhome/snapshots/` | -| Check snapshot creation logs | `docker logs vizd \| grep -i snapshot` | -| Force re-import | `docker run -e VIZD_EXTRA_OPTS="--resync-blockchain --snapshot /path/snap.json --plugin snapshot" ...` | - -## P2P Sync Flow (DLT Mode) - -This section documents the complete P2P sync protocol for a DLT node that loaded from a snapshot. The example uses a snapshot with head block #79504801 — the node has exactly **1 block** in its state, **1 entry** in fork_db, and its blockchain synopsis contains that single block ID. - -### Node state after snapshot import - -| Property | Value | -|----------|-------| -| `head_block_num` | 79504801 | -| `last_irreversible_block_num` (LIB) | 79504801 (promoted to head after snapshot import) | -| `block_log` | Empty | -| `dlt_block_log` | Contains block #79504801 only | -| `fork_db` | Contains 1 item: block #79504801 | -| `block_summary` (TAPOS buffer) | 65536 entries surviving from snapshot (blocks ~79439265..79504801) | -| `earliest_available_block_num()` | 79504801 | -| `_dlt_mode` | `true` | - -### Flow 1: Outbound sync — our node fetches blocks FROM a peer - -This is the primary sync flow. Our DLT node connects to peers and downloads blocks to catch up. - -#### Step 1: Connection & handshake - -``` -connect_to_peer(seed_node) - → TCP connect - → send_hello_message() [our_state = just_connected] - → peer responds: connection_accepted_message - → on_connection_accepted_message() [our_state = connection_accepted] - → send address_request_message - → peer responds: address_message - → on_address_message() - → both our_state and their_state == connection_accepted - → move_peer_to_active_list(peer) ← LOG: "New peer is connected (X.X.X.X:2001), now N active peers" - → new_peer_just_added(peer) -``` - -**Code path:** `node.cpp` lines 4793→4802 (outbound connect), 2287 (connection accepted), 2373→2397 (address message completes handshake). - -#### Step 2: Sync initiation — `new_peer_just_added()` - -Called immediately after handshake completes: - -```cpp -void new_peer_just_added(peer) { - send current_time_request_message; - start_synchronizing_with_peer(peer); // ← triggers sync -} -``` - -#### Step 3: `start_synchronizing_with_peer(peer)` - -Resets peer sync state and begins fetching: - -```cpp -peer->ids_of_items_to_get.clear(); -peer->number_of_unfetched_item_ids = 0; -peer->we_need_sync_items_from_peer = true; // ← marks peer as sync source -peer->last_block_delegate_has_seen = item_hash_t(); // empty — no reference point yet -peer->inhibit_fetching_sync_blocks = false; -fetch_next_batch_of_item_ids_from_peer(peer); -``` - -#### Step 4: `fetch_next_batch_of_item_ids_from_peer(peer)` - -Builds our blockchain synopsis and sends it to the peer: - -``` -create_blockchain_synopsis_for_peer(peer) - → reference_point = peer->last_block_delegate_has_seen = empty (first call) - → ids_of_items_to_get is empty (just cleared) - → calls _delegate->get_blockchain_synopsis(item_hash_t(), 0) -``` - -#### Step 5: `get_blockchain_synopsis()` — what our DLT node sends - -In `p2p_plugin.cpp`, with empty reference point (= "summarize whole chain"): - -``` -high_block_num = head_block_num() = 79504801 -low_block_num = last_non_undoable_block_num() = 79504801 (LIB == head) - -Loop iteration: - push get_block_id_for_num(79504801) → block_id_79504801 - low_block_num += (79504801 - 79504801 + 2) / 2 = 1 - low_block_num = 79504802 > 79504801 → stop - -Result: synopsis = [block_id_79504801] (1 entry) -``` - -LOG: `"DLT mode: get_blockchain_synopsis() returning 1 entries, low=79504801, high=79504801, head=79504801, LIB=79504801, earliest_available=79504801"` - -The node sends `fetch_blockchain_item_ids_message { type=block, synopsis=[block_id_79504801] }` to the peer. - -Also stores: `peer->item_ids_requested_from_peer = (synopsis, timestamp)` — records that we're waiting for a response. - -#### Step 6: Peer processes our synopsis - -The **peer** (a normal full node, e.g. at head #80000000) receives our `fetch_blockchain_item_ids_message` and processes it in their `on_fetch_blockchain_item_ids_message()`: - -``` -peer's get_block_ids(synopsis=[block_id_79504801], remaining): - → iterates synopsis in reverse - → is_known_block(block_id_79504801)? - → block_summary: slot 79504801 & 0xFFFF was overwritten by block ~79570337 → no match - → fetch_block_by_id: read block #79504801 from block_log, check id matches → YES - → is_included_block(block_id_79504801)? - → get_block_id_for_num(79504801): reads from block_log → same id → YES - → found! last_known_block_id = block_id_79504801, start_num = 79504801 - - → Loop from 79504801 to min(peer_head, 79504801 + limit): - result = [block_id_79504801, block_id_79504802, block_id_79504803, ..., block_id_X] - → remaining_item_count = peer_head - X -``` - -Peer sends back `blockchain_item_ids_inventory_message`: -- `item_hashes_available`: [block_id_79504801, block_id_79504802, ..., block_id_X] (up to `limit`, typically 2000) -- `total_remaining_item_count`: N (many thousands more blocks) - -#### Step 7: Our node receives peer's block ID list - -`on_blockchain_item_ids_inventory_message()` processes the response: - -``` -1. Diagnostic log: peer, count, block range, remaining - -2. Check: item_ids_requested_from_peer is set? YES (set in step 4) - -3. Validate: item_hashes_available is sequential? YES - -4. Validate: first item (block_id_79504801) is in our synopsis? YES - -5. Reset: item_ids_requested_from_peer = empty - -6. Check "up to date" condition: - total_remaining_item_count == 0? NO (N > 0) - → NOT up to date, proceed to receive blocks - -7. Dedup: pop blocks we already have from front of list: - - block_id_79504801: has_item() → is_known_block() → YES → pop - - block_id_79504802: has_item() → is_known_block() → NO → stop - -8. Remaining: item_hashes_received = [block_id_79504802, ..., block_id_X] - -9. Append to: peer->ids_of_items_to_get - -10. Since total_remaining_item_count > 0: - if ids_of_items_to_get.size() > GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH: - trigger_fetch_sync_items_loop() ← starts downloading actual blocks - else: - fetch_next_batch_of_item_ids_from_peer() ← get more IDs first -``` - -#### Step 8: Block fetching and application - -`fetch_sync_items_loop()` requests actual block data from peers: - -``` -For each block_id in ids_of_items_to_get: - → send fetch_items_message to peer - → peer responds with block_message containing full signed_block - → process_block_message(): - → _delegate->handle_block() - → chain.accept_block() → database::push_block() → fork_db → apply - → head advances: 79504802, 79504803, ... -``` - -As blocks are applied: -- `dlt_block_log` receives new irreversible blocks -- `block_summary` is updated with new block IDs -- `fork_db` grows with new blocks -- LIB advances as validators produce super-majority - -#### Step 9: Subsequent synopsis rounds - -After all IDs from the first batch are fetched, or when more IDs are needed: - -``` -fetch_next_batch_of_item_ids_from_peer(peer): - → peer->last_block_delegate_has_seen is now set to the last deduped block - → create_blockchain_synopsis_for_peer: - → reference_point = last block peer told us about that we already had - → synopsis includes blocks from our chain + ids_of_items_to_get - → sends next fetch_blockchain_item_ids_message - → peer responds with the next batch of block IDs - → cycle continues until peer says total_remaining_item_count == 0 -``` - -#### Step 10: Sync complete - -When peer responds with `total_remaining_item_count == 0` and all blocks have been fetched: - -``` -on_blockchain_item_ids_inventory_message: - → "up to date" condition is true - → peer->we_need_sync_items_from_peer = false - → LOG: "Sync: peer X says we're up-to-date" - → node transitions to normal operation (receiving broadcast blocks) -``` - -### Flow 2: Inbound sync — peer fetches blocks FROM our DLT node - -This flow describes what happens when another node connects to our DLT node and asks for blocks. - -#### Step 1: Peer connects to us - -The peer initiates a TCP connection. We receive their hello message in `on_hello_message()`: - -``` -Validate: signature, protocol version, chain ID - → all pass → their_state = connection_accepted - → send connection_accepted_message - → exchange address messages - → move_peer_to_active_list() - → new_peer_just_added() ← we also start syncing FROM them (Flow 1) -``` - -Meanwhile, the peer also starts syncing from us (they send their synopsis). - -#### Step 2: Peer sends us their synopsis - -The peer sends `fetch_blockchain_item_ids_message` with their synopsis, e.g. `[..., block_id_79501245]` (their most recent known block). Our `on_fetch_blockchain_item_ids_message()` processes it: - -``` -_delegate->get_block_ids(synopsis=[..., block_id_79501245], remaining): - → iterate synopsis in reverse looking for a known block - → find block_id_79501245 (if it's in our block_summary and on our chain) - → start_num = 79501245 - - DLT mode clamping: - → earliest_available = earliest_available_block_num() = 79504801 - → 79501245 < 79504801 → clamp start_num to 79504801 - - → Loop from 79504801 to head (79504801): - result = [block_id_79504801] - → remaining_item_count = 0 - - LOG: "DLT mode: get_block_ids() clamping start from 79501245 to 79504801" - LOG: "DLT mode: get_block_ids() returning 1 block IDs (start=79504801, head=79504801)" -``` - -We respond with `blockchain_item_ids_inventory_message`: -- `item_hashes_available`: [block_id_79504801] -- `total_remaining_item_count`: 0 - -#### Step 3: Peer processes our response - -On the peer's side, they receive our 1-block response: -- `item_hashes_available.size() == 1` -- `has_item(block_id_79504801)` → YES (peer already has this block) -- `total_remaining_item_count == 0` -- Conclusion: **we're up to date** — the peer marks `we_need_sync_items_from_peer = false` for us - -This is correct — our DLT node only has 1 block, so the peer can't get anything useful from us yet. As our node syncs and accumulates blocks in `dlt_block_log`, future peers will get more blocks from us. - -#### Step 4: Peer determines sync direction - -The peer also checks `peer_needs_sync_items_from_us`: -- Our synopsis had `[block_id_79504801]` -- Peer's `get_block_ids(our_synopsis)` → finds block #79504801, returns blocks after it -- `peer_needs_sync_items_from_us = true` → peer will send us blocks (Flow 1, step 6) - -### Flow 3: Broadcast inventory suppression - -During sync (Flow 1), peers also send real-time broadcast inventory (new transactions/blocks at chain tip). Without suppression, this causes timeout disconnects: - -``` -Peer sends item_ids_inventory_message (broadcast transactions) - → on_item_ids_inventory_message(): - Layer 1: originating_peer->we_need_sync_items_from_peer? → YES → SKIP - (or) - Layer 2: any active peer has we_need_sync_items_from_peer? → YES → SKIP - (or) - Layer 3: head block time > 30s behind wall clock? → YES → SKIP - - → broadcast inventory silently dropped during sync - → prevents items_requested_from_peer pollution - → prevents 1-second active_ignored_request_timeout disconnects -``` - -### Key code locations - -| Function | File | Purpose | -|----------|------|---------| -| `new_peer_just_added()` | node.cpp:4356 | Entry point: triggers sync after handshake | -| `start_synchronizing_with_peer()` | node.cpp:4333 | Sets sync flags, begins ID fetch | -| `fetch_next_batch_of_item_ids_from_peer()` | node.cpp:2580 | Builds synopsis, sends to peer | -| `create_blockchain_synopsis_for_peer()` | node.cpp:2527 | Generates synopsis from chain state | -| `get_blockchain_synopsis()` | p2p_plugin.cpp:370 | DLT-aware synopsis generation | -| `on_blockchain_item_ids_inventory_message()` | node.cpp:2618 | Processes peer's block ID response | -| `on_fetch_blockchain_item_ids_message()` | node.cpp:2403 | Responds to peer's synopsis with block IDs | -| `get_block_ids()` | p2p_plugin.cpp:249 | DLT-aware block ID enumeration with clamping | -| `on_item_ids_inventory_message()` | node.cpp:3048 | Broadcast inventory suppression | -| `fetch_sync_items_loop()` | node.cpp | Requests actual block data from peers | -| `send_sync_block_to_node_delegate()` | node.cpp | Pushes sync blocks to chain; handles `deferred_resize_exception` | -| `terminate_inactive_connections_loop()` | node.cpp | Auto-clears stuck `peer_needs_sync_items_from_us` (30s timeout) | -| `trigger_resync()` | p2p_plugin.cpp | Re-initiates P2P sync after snapshot hot-reload | -| `is_known_block()` | database.cpp:726 | DLT-aware block existence check | -| `earliest_available_block_num()` | database.cpp | Lowest block the node can actually serve | - -### Sync deadlock prevention - -On a live chain producing blocks every 3 seconds, a race condition can cause the sync to stall permanently: - -1. The seed finishes processing sync blocks and sends a "final synopsis" to the master -2. The master has already produced new blocks, so the reply has >1 item -3. `peer_needs_sync_items_from_us` stays `true` on the master → inventory advertisements blocked -4. The seed fetches the new blocks, sends another synopsis, but the master has more blocks again -5. This chase loop repeats — especially when `deferred_resize_exception` slows the seed during catch-up - -**Fix 1: Early inventory-mode transition (`remaining == 0`)** - -In `on_fetch_blockchain_item_ids_message()`, when the master's reply has `total_remaining_item_count == 0` (all blocks sent), the master now sets `peer_needs_sync_items_from_us = false` immediately — even if the reply has multiple items. The peer is close enough that inventory mode can deliver any new blocks produced during the remaining sync processing. - -Flag logic (master-side `on_fetch_blockchain_item_ids_message`): - -| Condition | `peer_needs_sync_items_from_us` | Meaning | -|---|---|---| -| Reply empty | `false` | Our chain is empty | -| Reply = 1 item in synopsis | `false` | Peer is fully caught up | -| Reply >1 item, `remaining == 0` | `false` | Peer is nearly caught up — switch to inventory | -| Reply >1 item, `remaining > 0` | `true` | Peer is far behind, keep sync mode | - -**Fix 2: Auto-clear safety net (30-second timeout)** - -In `terminate_inactive_connections_loop()`, if `peer_needs_sync_items_from_us` has been `true` for >30 seconds without the peer sending any `fetch_blockchain_item_ids` request, the flag is force-cleared. This catches edge cases where `deferred_resize_exception` prevents the seed from sending the final synopsis (because `peers_with_newly_empty_item_lists` is never populated when the block isn't applied). - -**Fix 3: Post-snapshot `trigger_resync()`** - -After the snapshot plugin completes a hot-reload (importing a new snapshot while the node is running), it calls `p2p_plugin::trigger_resync()` to re-initiate P2P sync from the new head block. Without this, the P2P layer would continue with stale state and the peer would never receive new blocks. - -### Diagnostic logging - -All sync negotiation messages use `fc_ilog(fc::logger::get("sync"), ...)` to go through the **"sync" logger**, which must be configured in `config.ini`: - -```ini -[logger.sync] -level = info -appenders = stderr -``` - -Key diagnostic messages: -- `"Starting sync with peer ..."` — sync initiation with peer state flags -- `"sync: sending synopsis to peer ..."` — synopsis details (count, last block) -- `"on_blockchain_item_ids_inventory: ..."` — peer's response (block range, remaining, sync flags) -- `"Sync: peer X says we're up-to-date"` — sync complete for this peer -- `"Sync: received N block IDs from peer ..."` — block ID batch received -- `"sync: peer X nearly caught up (sent N items, remaining=0)"` — early inventory-mode transition -- `"sync: peer X is now in sync with us (peer_needs_sync=false)"` — flag cleared (reply=1 known item) -- `"auto-clearing stuck peer_needs_sync_items_from_us for peer X"` — 30-second safety net fired -- `"DEFERRED_RESIZE: sync block #N deferred due to shared memory resize"` — resize interrupted sync -- `"DEFERRED_RESIZE: restarting sync with all peers"` — sync restart after deferred resize -- `"sync: peer X lists now empty — sending final synopsis"` — all sync blocks processed, checking completion - -**Important:** `node.cpp` defines `#define DEFAULT_LOGGER "p2p"` (line 75), so all `ilog()`/`dlog()`/`wlog()` macros in that file go to the "p2p" logger, NOT the default logger. To make messages visible, either configure the "p2p" logger or use `fc_ilog(fc::logger::get("sync"), ...)` explicitly. - -## Modified Components - -The snapshot plugin required changes to several core components: - -| Component | Change | -|-----------|--------| -| `chainbase::generic_index` | Added `set_next_id()` / `next_id()` for ID preservation during import | -| `fork_database.cpp` | Fixed `_unlinked_index.insert()` dead code (moved before `throw`); added `_push_next(item)` call at end of `_push_block` to resolve previously-unlinkable blocks; added duplicate block check in `_push_block`; added `_unlinked_index.clear()` to `reset()` | -| `database.hpp/cpp` | Added `open_from_snapshot()`, `initialize_hardforks()`, `reindex_from_dlt()`, `get_fork_db()`, `_dlt_mode` flag with `set_dlt_mode()` setter; DLT mode skips block_log writes and detects empty block_log on restart; `is_known_block()` in DLT mode checks block_summary then verifies preferred chain via `find_block_id_for_num()` (prevents both lying to peers and breaking sync negotiation); DLT restart seeds fork_db from dlt_block_log when available; early rejection in `_push_block`: duplicate blocks (at/before head, same ID), blocks at/before head on different fork with unknown parent (prevents P2P sync restart loop), far-ahead broadcast blocks (parent unknown and not head_block_id), with immediate successor (`previous == head_block_id()`) always allowed; DLT block log gap logging via `wlog`; DLT block log empty-skip logic for fresh snapshot imports; corruption detection throws `shared_memory_corruption_exception` (instead of `FC_ASSERT`) from all validator-account-missing checks (generate_block, process_funds, HF4 path) | -| `database_exceptions.hpp` | Added `shared_memory_corruption_exception` (code 4140000) for runtime corruption detection | -| `dlt_block_log.hpp/cpp` | New class: offset-aware rolling block log with 8-byte header index, `truncate_before()` for rotation, read/write with mutex locking | -| `chain plugin` | Added `snapshot_load_callback`, `snapshot_create_callback`, `snapshot_p2p_sync_callback`; restart safety (shared_memory check + `.used` rename + file existence check); LIB promotion after snapshot import (LIB = head_block_num for correct P2P synopsis); `dlt-block-log-max-blocks` config option; diagnostic warning when node has 0 blocks and no snapshot sync configured; `--replay-from-snapshot` crash recovery mode (wipes shared_memory, imports snapshot, replays dlt_block_log via `reindex_from_dlt`, does not rename snapshot); `--snapshot-auto-latest` auto-discovery of latest snapshot in `snapshot-dir`; `--auto-recover-from-snapshot` (default: `true`) immediate runtime recovery — `accept_block` and validator `generate_block` catch `shared_memory_corruption_exception`, call `attempt_auto_recovery()` which closes DB, finds latest snapshot, wipes shared memory, imports snapshot, replays dlt_block_log, and resumes P2P sync without restart | -| `snapshot plugin` | Full implementation: create/load/periodic snapshots; P2P TCP sync (serve + download with concurrent fiber handling on a dedicated `fc::thread`); 5-second peer operation timeout (connect, send, read); anti-spam (rate limiting, mutex-protected session tracking, enforced per-operation connection deadline); cached snapshot info; streaming SHA-256 checksum; built-in rotation (`snapshot-max-age-days`); elapsed time logging for all operations; console progress logging for download/import; `sync-snapshot-from-trusted-peer` defaults to `false` (opt-in); auto-creates snapshot directory; periodic snapshots only trigger on live blocks (skipped during P2P sync); memory optimization: compressed data freed immediately after decompression; `--snapshot-auto-latest` auto-discovery of latest snapshot file by block number; `find_latest_snapshot()` helper; **async snapshot creation**: `on_applied_block` schedules snapshot on a dedicated `fc::thread` (not the main thread's fc scheduler, which is blocked by `io_serv->run()`); `create_snapshot` splits into Phase 1 (read lock for DB serialization only) and Phase 2 (compression + file I/O without lock); `snapshot_in_progress` atomic flag prevents overlapping snapshots; `write_snapshot_to_file` accepts pre-captured header+state (no DB access needed); shutdown waits for in-progress snapshot before quitting thread; **DLT restart safety**: `load_snapshot()` persists the snapshot's head block into `dlt_block_log` so `database::open()` can seed `fork_db` on restart; **stale snapshot detection**: at startup, compares latest snapshot block against `dlt_block_log.start_block_num()` — if snapshot is older than DLT start, creates an urgent fresh snapshot on first synced block to prevent serving a broken snapshot with unsyncable gap | -| `content_object.hpp` | Added missing `FC_REFLECT` for `content_object`, `content_type_object`, `content_vote_object` | -| `witness_objects.hpp` | Added missing `FC_REFLECT` for `witness_vote_object` | -| `proposal_object.hpp` | Added missing `FC_REFLECT` for `required_approval_object` | -| `vizd/main.cpp` | Registered `snapshot_plugin`, linked `graphene::snapshot` | -| `p2p_plugin` | Added `_dlt_block_log` fallback in `get_item()` for serving blocks to peers in DLT mode; added `trigger_resync()` for post-snapshot-reload P2P re-initiation; added `last_peer_sync_request_time` to `peer_connection` for stuck-flag detection | -| `node.cpp` (network library) | Added early inventory-mode transition when `remaining == 0`; auto-clear of stuck `peer_needs_sync_items_from_us` (30s timeout); `DEFERRED_RESIZE` diagnostic logging; final synopsis diagnostic logging | diff --git a/.qoder/docs/staking-and-dao-governance.md b/.qoder/docs/staking-and-dao-governance.md deleted file mode 100644 index 0905c6814d..0000000000 --- a/.qoder/docs/staking-and-dao-governance.md +++ /dev/null @@ -1,291 +0,0 @@ -# VIZ Staking (Vesting Shares) and DAO Self-Governance - -## What is Staking in VIZ - -In VIZ, **staking** means converting liquid VIZ tokens into **SHARES** (vesting shares). Staked tokens are locked and cannot be transferred directly, but they grant the holder **governance power** — the ability to vote, award, delegate, and influence every aspect of the blockchain. - -Every VIZ account has three vesting fields: - -| Field | Meaning | -|---|---| -| `vesting_shares` | SHARES owned by the account | -| `delegated_vesting_shares` | SHARES delegated to other accounts (reduces your power) | -| `received_vesting_shares` | SHARES received via delegation from others (increases your power) | - -**Effective vesting shares** — the actual governance power used everywhere: - -``` -effective_vesting_shares = vesting_shares − delegated_vesting_shares + received_vesting_shares -``` - -This is the number that determines your weight in every vote, award, and governance action. - ---- - -## Staking Operations - -### Stake: `transfer_to_vesting_operation` - -Converts liquid VIZ to SHARES. Can stake to yourself or to another account. - -```json -["transfer_to_vesting", { - "from": "alice", - "to": "bob", - "amount": "1000.000 VIZ" -}] -``` - -### Unstake: `withdraw_vesting_operation` - -Initiates gradual withdrawal of SHARES back to liquid VIZ. The withdrawal happens over **28 daily intervals** (28 days total), where each day `vesting_shares / 28` is converted to liquid tokens. - -```json -["withdraw_vesting", { - "account": "alice", - "vesting_shares": "1000.000000 SHARES" -}] -``` - -Setting `vesting_shares` to 0 cancels the withdrawal. - -### Withdrawal Routes: `set_withdraw_vesting_route_operation` - -Directs withdrawn SHARES to another account, optionally re-staking them automatically (`auto_vest = true`). Up to 10 routes per account. - -```json -["set_withdraw_vesting_route", { - "from_account": "alice", - "to_account": "bob", - "percent": 5000, - "auto_vest": true -}] -``` - -### Delegate: `delegate_vesting_shares_operation` - -Transfers voting power (not ownership) to another account. The delegator keeps ownership, the delegatee gets governance power. - -```json -["delegate_vesting_shares", { - "delegator": "alice", - "delegatee": "bob", - "vesting_shares": "500.000000 SHARES" -}] -``` - -When removing delegation, the SHARES enter a 5-day expiration period (matching energy regeneration time) to prevent "double-spend" of voting energy. - ---- - -## Where Staked VIZ (SHARES) Are Used - -SHARES are the universal governance token. Every meaningful action in VIZ is weighted by `effective_vesting_shares`: - -### 1. validator Voting - -validators produce blocks and set chain parameters. Every account can vote for up to 100 validators. - -```json -["account_witness_vote", { - "account": "alice", - "validator": "node1", - "approve": true -}] -``` - -**Fair-DPOS weighting**: vote weight is divided equally across all validators the account votes for: - -``` -fair_weight = (vesting_shares + proxied_votes) / witnesses_voted_for -``` - -This prevents concentration of power — if you vote for 10 validators, each gets 1/10 of your stake weight, not the full amount. - -Accounts can also set a **proxy** (`account_witness_proxy_operation`), delegating their validator voting to another account. - -### 2. Committee DAO Voting - -Any account can create a **worker request** (funding proposal) and all SHARES holders vote to approve or reject it: - -```json -["committee_vote_request", { - "voter": "alice", - "request_id": 42, - "vote_percent": 7500 -}] -``` - -- `vote_percent` ranges from **−10000** (strong oppose) to **+10000** (strong support) -- Weight: `effective_vesting_shares × vote_percent / 10000` -- The payout amount is proportional to net positive consensus -- Requires minimum participation threshold (% of total staked supply) - -See [committee-dao-and-prediction-markets.md](committee-dao-and-prediction-markets.md) for full details. - -### 3. Awards (Social Capital Distribution) - -The **award operation** is VIZ's unique mechanism for distributing rewards from the shared reward fund. Any account can award any other account, spending regenerating **energy**: - -```json -["award", { - "initiator": "alice", - "receiver": "bob", - "energy": 1000, - "custom_sequence": 0, - "memo": "Great work!", - "beneficiaries": [] -}] -``` - -**How energy works:** -- Every account has energy in range 0–10000 (0%–100%) -- Energy regenerates linearly over 5 days (`CHAIN_ENERGY_REGENERATION_SECONDS = 432000`) -- Each award consumes the specified energy amount - -**How SHARES determine reward size:** - -``` -rshares = effective_vesting_shares × used_energy / 10000 -``` - -The `rshares` value determines how much of the global reward fund is claimed. An account with 10× more SHARES creates 10× more reward for the same energy expenditure. Rewards are distributed as SHARES to the receiver (and beneficiaries). - -A `fixed_award_operation` variant lets you specify an exact reward amount — the system calculates the required energy. - -### 4. Chain Parameter Governance - -validators publish their preferred chain parameters, and the **median** value across all active validators becomes the consensus setting. Since validators are elected by stake-weighted voting, the chain parameters are indirectly controlled by all SHARES holders. - -Parameters governed this way include: -- `account_creation_fee` — cost to create new accounts -- `maximum_block_size` — throughput limit -- `min_delegation` — minimum delegation amount -- `committee_request_approve_min_percent` — participation threshold for DAO proposals -- `vote_accounting_min_rshares` — minimum award impact threshold -- `bandwidth_reserve_percent` — network bandwidth allocation -- `min_curation_percent`, `max_curation_percent` — content reward distribution -- `withdraw_intervals` — vesting withdrawal period -- And others - -### 5. Bandwidth Allocation - -Network bandwidth (transaction throughput) is allocated proportionally to `effective_vesting_shares`. Accounts with more stake can send more transactions per unit of time. Accounts below 500 SHARES get an additional 10% bandwidth reserve. - -### 6. Account Creation via Delegation - -New accounts can be created by delegating SHARES to them (at 10× ratio for 30 days), making account creation accessible without spending liquid tokens. - ---- - -## VIZ as a DAO: Every Member Controls Their Share - -### The DAO Analogy - -VIZ is not just a blockchain — it is a **Decentralized Autonomous Organization** where every SHARES holder is a member with governance rights proportional to their stake. Think of it this way: - -| Traditional DAO | VIZ Blockchain | -|---|---| -| DAO treasury | Committee fund + reward fund | -| DAO shares / governance tokens | SHARES (vesting shares) | -| Proposal voting | Committee worker requests | -| Board of directors | Elected validators | -| Director elections | validator voting (Fair-DPOS) | -| Dividend distribution | Award mechanism (reward fund) | -| Bylaws / parameters | Chain properties (median governance) | -| Delegation of voting rights | `delegate_vesting_shares` + validator proxy | - -### How Each Member "Controls Their Share of the DAO" - -#### 1. Direct Financial Governance - -Every SHARES holder can vote on **how the DAO treasury (committee fund) is spent**. Worker proposals request funding for development, marketing, infrastructure, or any community purpose. Your vote weight is your exact share of the total staked supply. - -If you hold 1% of all SHARES, your vote carries exactly 1% weight in every committee decision. You can vote **for** (up to +100%) or **against** (down to −100%) with fine-grained intensity. You don't just approve or reject — you express **how strongly** you feel. - -#### 2. Electing Leadership - -validators are the "board of directors" — they produce blocks, validate transactions, and **set chain parameters** through median voting. Every SHARES holder votes for validators, and Fair-DPOS ensures your voting power is split equally across your chosen validators, preventing vote concentration. - -If you don't want to vote directly, you can **set a proxy** — delegate your validator voting to someone you trust, just like a proxy vote in a shareholder meeting. - -#### 3. Direct Value Distribution - -The **award mechanism** lets every member distribute value from the shared reward fund to any other member. This is unique to VIZ — there's no proposal process needed, no minimum threshold. If you hold SHARES and have energy, you can reward anyone, anytime. - -The reward is proportional to your stake: more SHARES = more reward per energy unit. This is like having a share of the DAO's dividend pool that you can direct to anyone you choose. - -#### 4. Power Delegation - -Through `delegate_vesting_shares`, you can lend your governance power to another account without transferring ownership. This enables: - -- **Empowering new members**: delegate SHARES to newcomers so they can participate in governance -- **Specialization**: delegate to accounts that focus on specific governance tasks -- **Service providers**: delegate to bots or services that vote on your behalf -- **Account creation**: bootstrap new accounts by delegating the minimum required stake - -The delegatee gains your voting power for awards, committee votes, and bandwidth, while you retain full ownership and can revoke at any time (with a 5-day cooldown). - -#### 5. Rule-Setting - -Chain parameters are set by the **median** of validator-published values. Since you elect validators, you indirectly control: -- How expensive it is to create accounts -- How the reward fund is distributed between content creators and curators -- What the minimum participation threshold is for committee proposals -- How long vesting withdrawals take -- Network capacity and transaction limits - -If the current parameters don't serve your interests, you vote for validators who share your vision. The median mechanism ensures no single validator (or voter) can impose extreme values — only the community consensus prevails. - -### Why This Works as Self-Governance - -1. **Proportional representation**: 1 SHARES = 1 unit of influence everywhere. No special privileges, no tiered membership. - -2. **Bipolar voting**: negative votes are first-class citizens. Opposing a bad proposal is just as powerful as supporting a good one. This prevents apathy-driven approval. - -3. **Continuous governance**: there are no "governance seasons" or "voting periods" for validators — you can change your votes at any time. Committee proposals have time-bounded voting, but you can update your vote throughout. - -4. **Skin in the game**: SHARES are locked tokens. To gain governance power, you must commit capital. To exit, you wait 28 days. This ensures voters have long-term alignment with the ecosystem. - -5. **No trusted intermediaries**: all governance rules are enforced by protocol code, not by humans. Committee payouts, validator elections, parameter changes — everything is automatic and verifiable. - -6. **Delegation without custody**: you can amplify others' power without giving them your tokens. Revocation is always possible. This enables trust hierarchies without centralization. - -### The Governance Cycle - -``` -Stake VIZ → Get SHARES → Governance Power - ├── Vote for validators → Control block production & chain parameters - ├── Vote on committee → Control treasury spending - ├── Award other accounts → Distribute value from reward fund - ├── Delegate to others → Amplify allies' governance power - └── Set chain parameters → Shape the rules (via validator election) - ↓ - validators produce blocks → Rewards generated → Reward fund grows - ↓ - Committee fund grows → Proposals funded → Ecosystem develops - ↓ - VIZ value increases → More staking incentive → Cycle continues -``` - -This is a complete, self-sustaining DAO where every participant's influence is exactly proportional to their committed stake, and every aspect of the system — from treasury spending to network parameters to value distribution — is governed by the collective will of SHARES holders. - ---- - -## Constants Reference - -| Constant | Value | Description | -|---|---|---| -| `CHAIN_VESTING_WITHDRAW_INTERVALS` | 28 | Number of daily withdrawal installments | -| `CHAIN_VESTING_WITHDRAW_INTERVAL_SECONDS` | 86400 (1 day) | Time between withdrawal installments | -| `CHAIN_MAX_WITHDRAW_ROUTES` | 10 | Maximum withdrawal routing destinations | -| `CHAIN_ENERGY_REGENERATION_SECONDS` | 432000 (5 days) | Full energy regeneration period | -| `CHAIN_100_PERCENT` | 10000 | Basis points (100.00%) | -| `CHAIN_MIN_DELEGATION` | 1 VIZ | Minimum delegation amount | -| `CHAIN_CREATE_ACCOUNT_DELEGATION_RATIO` | 10× | Delegation multiplier for account creation | -| `CHAIN_CREATE_ACCOUNT_DELEGATION_TIME` | 30 days | Minimum delegation lock for account creation | -| `CHAIN_MAX_ACCOUNT_WITNESS_VOTES` | 100 | Maximum validators per account | -| `CONSENSUS_VOTE_ACCOUNTING_MIN_RSHARES` | 5000000 | Minimum rshares for award to have effect | -| `CONSENSUS_COMMITTEE_REQUEST_APPROVE_MIN_PERCENT` | 1000 (10%) | Default participation threshold for proposals | -| `CONSENSUS_BANDWIDTH_RESERVE_BELOW` | 500 SHARES | Threshold for bandwidth reserve | -| `CONSENSUS_BANDWIDTH_RESERVE_PERCENT` | 1000 (10%) | Extra bandwidth for small accounts | diff --git a/.qoder/docs/validator-guard.md b/.qoder/docs/validator-guard.md deleted file mode 100644 index 082a14a27f..0000000000 --- a/.qoder/docs/validator-guard.md +++ /dev/null @@ -1,151 +0,0 @@ -# validator guard Plugin - -The `witness_guard` plugin is an automated maintenance tool for VIZ validator node operators. It monitors configured validator accounts and automatically restores their signing keys when they are reset to null (which disables the validator). - -## Purpose - -In VIZ a validator can be disabled (signing key set to null) by manual intervention, security protocols, or certain network conditions. This plugin automates the restore process so the validator stays active without manual monitoring. - -When the plugin detects a null signing key on-chain, it constructs, signs, and broadcasts a `witness_update_operation` to re-enable the validator using the provided private keys. - -## Configuration - -The plugin is configured via `config.ini` or command-line arguments. - -### Options - -| Option | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `validator-guard-enabled` | boolean | `true` | Enables or disables the plugin logic globally. | -| `validator-guard-interval` | uint32 | `20` | Frequency of periodic checks in blocks (20 blocks ≈ 60 seconds). | -| `validator-guard-validator` | vector\ | N/A | A JSON triplet: validator name, signing WIF, active WIF. Repeatable. | -| `validator-guard-disable` | uint32 | `5` | Number of consecutive blocks produced by the same validator before auto-disabling (setting signing key to null). Set to `0` to disable this feature. | - -The plugin also reads the shared `enable-stale-production` option from the Validator Plugin configuration. - -### Enabling the Plugin - -Add it to the active plugins in `config.ini`: - -```ini -plugin = witness_guard -``` - -## Usage Example - -```ini -# Monitor and auto-restore winet1 -validator-guard-validator = ["winet1", "5K_SIGNING_PRIVATE_WIF", "5K_ACTIVE_PRIVATE_WIF"] - -# Monitor multiple validators by repeating the option -validator-guard-validator = ["winet2", "5J_SIGNING_PRIVATE_WIF", "5J_ACTIVE_PRIVATE_WIF"] - -# Check every 10 blocks instead of 20 -validator-guard-interval = 10 -``` - -## Internal Logic - -### Startup - -1. **Key parsing**: Each `validator-guard-validator` entry is parsed as a JSON array of three strings. Both WIF keys are validated. -2. **Stale production detection**: If `enable-stale-production=true` is set in the validator config, auto-restore is initially disabled (see Safety Guards below). -3. **Disable threshold**: If `validator-guard-disable` is greater than 0, the consecutive-block auto-disable feature is enabled (see below). -4. **Authority validation**: After the chain database is open, the plugin verifies that each configured active key actually has authority on-chain. validators whose accounts are not found are removed from monitoring. -5. **Initial check**: An immediate check is attempted. If the node is already in sync, the result is cached so the plugin switches to its normal periodic schedule. - -### Per-Block Signal Handler (`applied_block`) - -On every new block the plugin: - -0. **Consecutive-block auto-disable**: If `validator-guard-disable > 0` and the block was produced by one of our monitored validators, the plugin increments a per-validator consecutive-block counter. When the counter reaches the configured threshold, a `witness_update_operation` with a null signing key is broadcast to disable the validator, and it is marked as auto-disabled. If the block was produced by a *different* validator, all counters are reset to zero (the streak is broken). -1. **Transaction confirmation**: Scans the block for any pending restore transaction IDs. When found, the restore is marked as confirmed and tracking state is cleared. -2. **Look-ahead scheduling**: If any monitored validator is scheduled to produce within the next 3 slots, an immediate check is triggered so the key can be restored before the slot arrives. -3. **Periodic check**: Otherwise runs `check_and_restore_internal` at the configured interval. While the node is still syncing after startup, checks run every 10 blocks instead. - -### Core Check (`check_and_restore_internal`) - -1. **Stale production guard**: If `enable-stale-production` is active and network participation is below 33%, all checks are skipped. Once participation reaches ≥ 33% the stale flag is auto-cleared (same logic as the Validator Plugin) and auto-restore resumes. During emergency consensus mode the guard is bypassed. -2. **Sync check**: Head block time must be within `2 × CHAIN_BLOCK_INTERVAL` seconds of wall-clock time. -3. **Long fork safety**: If the Last Irreversible Block (LIB) is older than 200 seconds, restoration is skipped to avoid acting on a stale fork. -4. **Expiry cleanup**: Stale entries in `_pending_confirmations` are expired so that failed broadcasts can be retried. -5. **validator iteration**: For each configured validator, the on-chain signing key is checked. If the key is present, any pending restore state and auto-disabled flag are cleared. If null and the validator was auto-disabled by the consecutive-block guard, auto-restore is skipped (the operator must investigate and restart). Otherwise, if no restore is currently in-flight (or the previous one expired), `send_witness_update` is called. - -### Restore Transaction (`send_witness_update`) - -1. Builds a `witness_update_operation` preserving the current on-chain URL and setting the signing key to the configured public key. -2. Wraps it in a `signed_transaction` with a 30-second expiration and head block reference. -3. Signs with the configured active private key. -4. Broadcasts via the P2P plugin. -5. Tracks the transaction ID in `_pending_confirmations` and the validator in `_restore_pending` to prevent duplicate broadcasts. - -### Disable Transaction (`send_witness_disable`) - -1. Builds a `witness_update_operation` preserving the current on-chain URL and setting the signing key to **null** (effectively disabling block production). -2. Wraps it in a `signed_transaction` with a 30-second expiration and head block reference. -3. Signs with the configured active private key. -4. Broadcasts via the P2P plugin. -5. Adds the validator to the `_auto_disabled_witnesses` set so that the auto-restore logic does **not** re-enable it automatically. - -## Safety Guards - -| Guard | Behavior | -| :--- | :--- | -| **Stale production** | When `enable-stale-production=true`, auto-restore is disabled to avoid broadcasting on a minority fork. **Auto-cleared** when participation ≥ 33%. | -| **Emergency mode** | During emergency consensus (`dgp.emergency_consensus_active`), the stale production guard is bypassed — key restoration may still be needed for recovery. | -| **Sync check** | Restoration only runs when the node is synchronized (head block recent). | -| **Long fork detection** | If LIB is older than 200 seconds, restoration is skipped. | -| **Authority validation** | At startup, configured active keys are verified against on-chain authority. | -| **Consecutive-block auto-disable** | When a monitored validator produces `validator-guard-disable` consecutive blocks, it is automatically disabled (signing key set to null). Auto-restore is suppressed until the operator manually restores the key (at which point the flag is cleared). | -| **Duplicate prevention** | In-flight restores are tracked with expiration; no duplicate transactions are sent. | - -## Security Considerations - -> [!CAUTION] -> **Private Key Exposure** -> This plugin requires the **Active Private Key** in plain text in `config.ini`. The active key has significant control over your account (transfers, permission changes). Ensure `config.ini` has strictly restricted file system permissions (e.g., `chmod 600 config.ini` on Linux). - -## Logs - -* **Initialization**: - `witness_guard: monitoring validator 'winet1' (signing key: VIZ...)` -* **Stale production detected**: - `witness_guard: enable-stale-production detected — auto-restore is DISABLED until network participation >= 33%` -* **Stale production auto-cleared**: - `witness_guard: network is healthy (participation XX%), auto-clearing stale production override` -* **Restore triggered**: - `witness_guard: 'winet1' has null signing key on-chain — initiating restore` -* **Broadcast sent**: - `witness_guard: broadcasting witness_update [ID: ...] for 'winet1' — restoring key to VIZ...` -* **Confirmed on-chain**: - `witness_guard: CONFIRMED restoration for 'winet1' in block #N [TX: ...]` -* **Long fork warning**: - `witness_guard: POTENTIAL LONG FORK DETECTED! LIB #N is Xs old. Skipping restoration.` -* **Consecutive-block auto-disable triggered**: - `witness_guard: validator '${w}' produced ${c} consecutive blocks — auto-disabling (threshold=${t})` -* **Disable broadcast sent**: - `witness_guard: broadcasting witness_update [ID: ...] for '${w}' — DISABLING (setting key to null)` -* **Auto-restore skipped (auto-disabled validator)**: - `witness_guard: '${w}' was auto-disabled (consecutive block limit), skipping auto-restore` -* **Failure**: - `witness_guard: witness_update FAILED for 'winet1': [error details]` - -## Troubleshooting - -**Error: validator-guard-validator expects [name, signing_wif, active_wif]** -Ensure each entry is a valid JSON array with exactly 3 strings. Use double quotes inside the brackets. - -**Restore not triggering** -1. Check that `validator-guard-enabled` is `true`. -2. Ensure the node is fully synchronized. -3. Verify the account name is a registered validator on the network. -4. If `enable-stale-production=true` is set, auto-restore is disabled until network participation reaches ≥ 33%. - -**Transaction failed** -Check that the `active_wif` belongs to the validator account. If the account's active authority has been changed, the plugin cannot sign the update. The startup log will warn about mismatched keys. - -**validator auto-disabled and not restoring** -If the log shows `was auto-disabled (consecutive block limit), skipping auto-restore`, the validator produced too many consecutive blocks and was automatically disabled as a safety measure. The operator must investigate, manually restore the signing key (or restart the node), at which point the auto-disabled flag clears. - -**Authority warning at startup** -`WARNING: Configured active key for validator 'X' does NOT have authority on-chain` — the configured active WIF does not match any key in the account's active authority. Update the key in `config.ini`. diff --git a/.qoder/docs/validator-plugin-refactoring-2026-05-14.md b/.qoder/docs/validator-plugin-refactoring-2026-05-14.md deleted file mode 100644 index 1ba5dfbe51..0000000000 --- a/.qoder/docs/validator-plugin-refactoring-2026-05-14.md +++ /dev/null @@ -1,300 +0,0 @@ -# Validator Plugin Refactoring: Remove Cached Flags - -## Date: 2026-05-14 - -## Summary - -Refactored Validator Plugin to eliminate cached internal state flags and instead query actual state directly from database and other plugins on every production tick. - -## Problem - -The Validator Plugin was caching critical state in internal variables: -- `_production_enabled` (bool): Whether production should be active -- Indirect checks via `p2p().is_catching_up_after_pause()` for snapshot status - -This created several issues: -1. **Stale state**: Cached flags could become outdated if other plugins changed state -2. **Race conditions**: Multiple sources of truth (cache vs actual state) -3. **Complexity**: Manual flag management in multiple places (initialization, recovery, error handling) -4. **Hidden dependencies**: Not clear which plugin/database state actually determines production readiness - -## Solution - -### 1. Added Public API to Snapshot Plugin - -**File:** `plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp` -```cpp -/// Returns true if a snapshot creation is currently in progress. -/// Used by the Validator Plugin to defer block production during -/// snapshot serialization (avoids write-lock contention). -bool is_snapshot_in_progress() const; -``` - -**File:** `plugins/snapshot/plugin.cpp` -```cpp -bool snapshot_plugin::is_snapshot_in_progress() const { - if (!my) return false; - return my->snapshot_in_progress.load(std::memory_order_relaxed); -} -``` - -### 2. Added Snapshot Plugin Dependency to Validator Plugin - -**File:** `plugins/validator/include/graphene/plugins/validator/validator.hpp` -```cpp -#include - -class witness_plugin final : public appbase::plugin { -public: - APPBASE_PLUGIN_REQUIRES((chain::plugin) (p2p::p2p_plugin) (snapshot::snapshot_plugin)) -``` - -**File:** `plugins/validator/validator.cpp` -```cpp -struct witness_plugin::impl final { - impl(): - p2p_(appbase::app().get_plugin()), - chain_(appbase::app().get_plugin()), - snapshot_(appbase::app().get_plugin()), - production_timer_(appbase::app().get_io_service()) { - } - - graphene::plugins::snapshot::snapshot_plugin& snapshot() { - return snapshot_; - } - - graphene::plugins::snapshot::snapshot_plugin& snapshot_; -``` - -### 3. Removed `_production_enabled` Cached Flag - -**Deleted:** -```cpp -bool _production_enabled = false; // REMOVED -``` - -**Replaced with direct database queries:** - -#### Before (cached flag): -```cpp -if (!_production_enabled) { - if (db.get_slot_time(1) >= now) { - _production_enabled = true; - } else { - return not_synced; - } -} -``` - -#### After (query actual state): -```cpp -// Production readiness determined by: -// 1. DLT sync status: chain().is_syncing() -// 2. Snapshot status: snapshot().is_snapshot_in_progress() -// 3. Emergency mode: dgp.emergency_consensus_active -// 4. Participation rate: db.witness_participation_rate() -// 5. Minority fork recovery: _minority_fork_recovering - -// No cached flag - state queried fresh every tick -``` - -### 4. Updated All Production Readiness Checks - -#### Step 2: DLT Mode Sync Check -**Already correct** - uses `chain().is_syncing()` (no change needed) - -#### Step 3: Snapshot Pause Gate -**Before:** -```cpp -if (p2p().is_catching_up_after_pause()) { - return not_synced; -} -``` - -**After:** -```cpp -// Check snapshot plugin directly for snapshot_in_progress flag -if (snapshot().is_snapshot_in_progress()) { - wlog("Deferring block production: snapshot creation in progress"); - return not_synced; -} - -if (p2p().is_catching_up_after_pause()) { - wlog("Deferring block production: P2P is catching up after snapshot pause"); - return not_synced; -} -``` - -#### Step 4: Hardfork 12 Safety Enforcement -**Before:** -```cpp -if (we_are_emergency_master) { - _production_enabled = true; // Cached flag set -} else if (!_production_enabled) { - if (db.get_slot_time(1) >= now) { - _production_enabled = true; // Cached flag set - } else { - return not_synced; - } -} -``` - -**After:** -```cpp -// No flag setting - production allowed to proceed if checks pass -// State determined by actual database values, not cached flags -bool we_are_emergency_master = - _witnesses.find(CHAIN_EMERGENCY_WITNESS_ACCOUNT) != _witnesses.end(); -// Master produces if emergency active, slaves must sync first -``` - -#### Watchdog Recovery -**Before:** -```cpp -if (!_production_enabled) { - _production_enabled = true; // Force-enable cached flag - did_recover = true; - elog("WATCHDOG-RECOVERY: force-enabled _production_enabled"); -} -``` - -**After:** -```cpp -// No flag to set - recovery clears blocking conditions: -// - _minority_fork_recovering = false -// - P2P catchup flag cleared -// - Chain syncing flag cleared -// Production will automatically resume on next tick if state is healthy -``` - -#### Watchdog Silence Detection -**Before:** -```cpp -if (_ever_produced && _production_enabled) { - auto silent_for = fc::time_point::now() - _last_production_time; - // Check if silent too long -} -``` - -**After:** -```cpp -if (_ever_produced) { - // Check if production should be active by querying actual state - bool should_be_producing = false; - try { - const auto& dgp_watch = database().get_dynamic_global_properties(); - if (!_minority_fork_recovering && !_witnesses.empty()) { - if (dgp_watch.emergency_consensus_active) { - // Emergency mode: should produce if we have emergency key - should_be_producing = (_witnesses.count(CHAIN_EMERGENCY_WITNESS_ACCOUNT) > 0); - } else { - // Normal mode: should produce if participation is healthy - uint32_t prate_watch = database().witness_participation_rate(); - should_be_producing = (prate_watch >= 33 * CHAIN_1_PERCENT); - } - } - } catch (...) {} - - if (should_be_producing) { - auto silent_for = fc::time_point::now() - _last_production_time; - // Check if silent too long - } -} -``` - -### 5. Updated Diagnostic Logging - -**Before:** -```cpp -elog("validator-WATCHDOG: ... prod=${pe} ...", - ("pe", _production_enabled)); -``` - -**After:** -```cpp -elog("validator-WATCHDOG: ... skip_flags=${sf} ...", - ("sf", _production_skip_flags)); -``` - -## Benefits - -### 1. Single Source of Truth -All production decisions now query actual state from: -- **Database**: `get_dynamic_global_properties()`, `witness_participation_rate()`, `has_hardfork()` -- **Chain plugin**: `is_syncing()` -- **P2P plugin**: `is_catching_up_after_pause()` -- **Snapshot plugin**: `is_snapshot_in_progress()` (NEW) - -### 2. No Stale State -Every production tick (250ms) queries fresh state - no risk of cached flags becoming outdated. - -### 3. Simplified Recovery -Watchdog recovery no longer needs to manually set `_production_enabled = true`. Instead, it clears blocking conditions: -- `_minority_fork_recovering = false` -- `chain().clear_syncing()` -- `p2p().clear_catchup_flag()` - -Production automatically resumes on next tick if state is healthy. - -### 4. Clearer Dependencies -Plugin dependencies now explicit in `APPBASE_PLUGIN_REQUIRES`: -```cpp -APPBASE_PLUGIN_REQUIRES((chain::plugin) (p2p::p2p_plugin) (snapshot::snapshot_plugin)) -``` - -### 5. Better Observability -Diagnostic logs now show actual skip flags and state from database, not cached boolean. - -## Migration Notes - -### Configuration Changes -**None** - all config options remain the same: -- `--enable-stale-production` still works (sets `skip_undo_history_check` flag) -- `--required-participation` unchanged -- `--validator`, `--private-key`, `--emergency-private-key` unchanged - -### Behavior Changes -**Minimal** - production logic identical, just queries state differently: -1. Production readiness determined by actual database state, not cached flag -2. Snapshot pause detection now checks snapshot plugin directly (more accurate) -3. Watchdog recovery clears blocking conditions instead of setting enable flag - -### API Changes -**New public method in snapshot plugin:** -```cpp -bool snapshot_plugin::is_snapshot_in_progress() const; -``` - -**New dependency in Validator Plugin:** -```cpp -APPBASE_PLUGIN_REQUIRES((chain::plugin) (p2p::p2p_plugin) (snapshot::snapshot_plugin)) -``` - -## Testing Recommendations - -1. **Normal production**: Verify validator produces blocks normally -2. **Snapshot creation**: Verify production defers during snapshot (check logs for "snapshot creation in progress") -3. **Emergency mode**: Verify emergency master produces regardless of sync state -4. **Minority fork recovery**: Verify production resumes after resync completes -5. **Watchdog recovery**: Verify watchdog can recover from stuck state -6. **DLT mode sync**: Verify DLT slaves defer during sync, master produces - -## Files Modified - -1. `plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp` - Added `is_snapshot_in_progress()` declaration -2. `plugins/snapshot/plugin.cpp` - Implemented `is_snapshot_in_progress()` -3. `plugins/validator/include/graphene/plugins/validator/validator.hpp` - Added snapshot plugin dependency -4. `plugins/validator/validator.cpp` - Major refactoring: - - Added snapshot plugin reference - - Removed `_production_enabled` flag - - Updated all production readiness checks - - Updated diagnostic logging - - Simplified watchdog recovery - -## Backward Compatibility - -✅ **Fully backward compatible** -- No config changes required -- No API breaking changes (only additions) -- Production logic identical, just queries state differently -- Existing deployments will work without modification diff --git a/.qoder/docs/validator-plugin.md b/.qoder/docs/validator-plugin.md deleted file mode 100644 index 39e0564b7c..0000000000 --- a/.qoder/docs/validator-plugin.md +++ /dev/null @@ -1,1076 +0,0 @@ -# Validator Plugin Documentation - -## Overview - -The validator plugin is responsible for block production in the VIZ blockchain. It manages validator scheduling, block signing, broadcast, and implements sophisticated safety mechanisms including minority fork detection, emergency consensus support, and production watchdog recovery. - -**Location:** `plugins/validator/validator.cpp`, `plugins/validator/include/graphene/plugins/validator/validator.hpp` - ---- - -## Configuration Options - -### Block Production Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `enable-stale-production` | bool | false | Enable block production even if the chain is stale. Overrides sync and participation checks. | -| `required-participation` | uint32_t | 33% (33 * CHAIN_1_PERCENT) | Minimum validator participation percentage required to produce blocks. | -| `validator` / `-w` | string (multi) | - | Name of validator controlled by this node. Can be specified multiple times. | -| `private-key` | string (WIF, multi) | - | WIF private key(s) for signing blocks. | -| `emergency-private-key` | string (WIF, multi) | - | WIF private key for emergency consensus production. Auto-adds `CHAIN_EMERGENCY_WITNESS_ACCOUNT` to validator set. | - -### Fork Collision Resolution - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `fork-collision-timeout-blocks` | uint32_t | 21 | Number of consecutive fork-collision deferrals before forcing production. One full validator round = 21 blocks (63 seconds). | - -### NTP Synchronization - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `ntp-server` | string (multi) | pool.ntp.org, time.google.com, time.cloudflare.com | NTP server(s) for time synchronization. | -| `ntp-request-interval` | uint32_t | 900 | Time update request interval in seconds (15 min). | -| `ntp-retry-interval` | uint32_t | 300 | Retry interval when NTP hasn't replied (5 min). | -| `ntp-round-trip-threshold` | uint32_t | 150 | Round-trip delay threshold in ms; slower replies discarded. | -| `ntp-history-size` | uint32_t | 5 | Moving-average history window for NTP delta smoothing. | -| `ntp-rejection-threshold-pct` | uint32_t | 50 | Rejection threshold as percentage of absolute moving average. | -| `ntp-rejection-min-threshold` | uint32_t | 5 | Minimum rejection threshold in ms (applied regardless of percentage). | - -### Debug Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `debug-block-production` | bool | false | Enable verbose debug logging for block production and chain internals. Sets `database::_debug_block_production`. | - ---- - -## Plugin Dependencies - -```cpp -APPBASE_PLUGIN_REQUIRES((chain::plugin) (p2p::p2p_plugin) (snapshot::snapshot_plugin)) -``` - -The validator plugin requires: -- **chain::plugin**: Access to database, fork_db, validator schedule, block generation -- **p2p::p2p_plugin**: Block broadcast, sync status, peer connections, snapshot pause detection -- **snapshot::snapshot_plugin**: Query snapshot creation status via `is_snapshot_in_progress()` - ---- - -## Internal State Variables - -### Timer / Thread -- `production_io_service_` (boost::asio::io_service): **Dedicated** io_service for the production timer — completely separate from the appbase/P2P shared io_service. Declared before `production_timer_` to ensure correct init order. -- `production_io_work_` (unique_ptr\): Keeps the io_service alive while the thread runs. -- `production_io_thread_` (std::thread): Calls `production_io_service_.run()`. Joined in destructor after `production_io_service_.stop()`. -- `production_timer_` (boost::asio::deadline_timer): Timer bound to `production_io_service_`. - -### Production Control -- `_production_skip_flags` (uint32_t): Flags passed to `generate_block()` (e.g., `skip_undo_history_check`) -- `_required_validator_participation` (uint32_t): Participation threshold from config -- `_private_keys` (map): Loaded private keys for signing -- `_validators` (set): Configured validator names (includes `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` if emergency key configured) -- `_last_lag_slot_time` (fc::time_point_sec): Scheduled time of the most recent `lag` condition. Zero when no lag is active. Used by `schedule_production_loop()` to skip ahead past the missed slot and avoid rechecking it every 250ms. - -### Fork Detection & Recovery -- `fork_collision_defer_count_` (uint32_t): Consecutive fork-collision deferrals -- `_fork_collision_timeout_blocks` (uint32_t): Timeout threshold (default: 21) -- `_minority_fork_recovering` (bool): True when recovering from minority fork rollback -- `_minority_fork_recovery_start` (fc::time_point): When minority fork recovery started - -### Stall Detection -- `_slot_zero_streak` (uint32_t): Consecutive `not_time_yet` returns (NTP/clock issues) -- `_slot_zero_streak_start` (fc::time_point): When slot=0 streak started -- `_not_my_turn_streak` (uint32_t): Consecutive slots assigned to other validators -- `_not_my_turn_streak_start` (fc::time_point): When not_my_turn streak started -- `_last_scheduled_validator` (string): Last validator that got the slot - -### Watchdog & Diagnostics -- `_ever_produced` (bool): Whether we've ever produced a block -- `_last_production_time` (fc::time_point): Timestamp of last successful production -- `_last_slot_result` (int): Last result from slot > 0 iteration (excludes `not_time_yet`) -- `_watchdog_debug_enabled` (bool): Latching flag — set to `true` on first watchdog fire; never reset. Enables `database()._debug_block_production` automatically for post-hoc diagnosis. -- `_slot_hijack_count` (uint32_t): Consecutive blocks where committee filled our scheduled slot -- `_slot_hijack_height` (uint32_t): Last block height where hijack detected -- `_last_applied_block_num` (uint64_t): Last applied block number (for missed slot detection) - ---- - -## Execution Flow - -### 1. Plugin Lifecycle - -#### `plugin_initialize()` -**Called during:** Application startup, before any plugin starts - -**Actions:** -1. Create `impl` instance -2. Load validator names from `--validator` option into `_witnesses` set -3. Load `--enable-stale-production` flag → sets `skip_undo_history_check` in `_production_skip_flags` if true -4. Load `--required-participation` → `_required_witness_participation` -5. Parse `--private-key` WIF strings → `_private_keys` map -6. Parse `--emergency-private-key` WIF strings → `_private_keys` map - - **IMPORTANT**: Auto-adds `CHAIN_EMERGENCY_WITNESS_ACCOUNT` to `_witnesses` -7. Configure NTP service from options and call `graphene::time::configure_ntp()` -8. Load `--fork-collision-timeout-blocks` → `_fork_collision_timeout_blocks` -9. Load `--debug-block-production` → `database::_debug_block_production` - -**Does NOT:** Access database, check sync status, or start production timer - ---- - -#### `plugin_startup()` -**Called during:** Application startup, after all plugins initialized - -**Actions:** -1. Start NTP time client: `graphene::time::now()` -2. **Force NTP sync**: `graphene::time::update_ntp_time()` to minimize startup drift -3. Log validator configuration (count, names) -4. If `_witnesses` is not empty: - - Call `p2p().set_block_production(true)` to enable P2P block production mode - - Connect to `database::applied_block` signal → `on_block_applied()` handler - - Set `_last_applied_block_num = database.head_block_num()` - - If `skip_undo_history_check` set in `_production_skip_flags` (from `--enable-stale-production`): - - If `head_block_num() == 0`: Print new chain banner - - **Start production loop**: `schedule_production_loop()` -5. If no validators configured: Log error message - ---- - -#### `plugin_shutdown()` -**Called during:** Application shutdown - -**Actions:** -1. Shutdown NTP: `graphene::time::shutdown_ntp_time()` -2. Cancel production timer if validators configured - ---- - -### 2. Production Loop - -The production loop runs on a **dedicated `production_io_service_`** (not the shared appbase/P2P io_service) with its own OS thread. This isolation means P2P network activity — peer disconnects, TLS handshakes, send-queue drains — cannot delay the 250ms timer callback and cause missed-slot lag. - -``` -production_io_thread_ → production_io_service_.run() - ↓ - production_timer_.async_wait() - ↓ (250ms boundary) - block_production_loop() - ↓ - maybe_produce_block() - ↓ - [result handling + lag skip] - ↓ - schedule_production_loop() // reschedule -``` - -#### `schedule_production_loop()` - -**Lag skip guard (runs first):** -```cpp -if (_last_lag_slot_time != fc::time_point_sec()) { - int64_t time_since_lag_ms = (fc::time_point::now() - - fc::time_point(_last_lag_slot_time)).count() / 1000; - if (time_since_lag_ms < CHAIN_BLOCK_INTERVAL * 1000) { - int64_t skip_ms = (CHAIN_BLOCK_INTERVAL * 1000) - time_since_lag_ms; - production_timer_.expires_from_now(posix_time::milliseconds(skip_ms)); - production_timer_.async_wait(...); - _last_lag_slot_time = fc::time_point_sec(); - return result; - } -} -``` - -After a `lag` condition the current slot is already missed. Without this guard, the loop would recheck the same slot every 250ms and return `lag` again, spinning at high CPU rate until the full 3s slot interval elapses. The guard skips ahead to the start of the next slot. - -**Sleep calculation (normal path):** -```cpp -int64_t next_microseconds = 250000 - (ntp_microseconds % 250000); -if (next_microseconds < 50000) { - next_microseconds += 250000; // minimum 50ms sleep -} -``` - -This aligns production ticks to 250ms boundaries with a +250ms lookahead in `maybe_produce_block()`. - -**Sanity check:** If `next_microseconds > 500000` (500ms), logs warning about NTP backward jump. - ---- - -#### `block_production_loop()` - -**Exception handling:** -- `fc::canceled_exception`: Re-throw (node shutting down) -- `unknown_hardfork_exception`: Log error, re-throw (node out of date) -- `fc::exception`: Log error, return `exception_producing_block` - -**Result handling:** -- `produced`: Reset fork_collision_defer_count, slot_zero_streak, not_my_turn_streak, slot_hijack_count. Set `_ever_produced`, `_last_production_time`. Clear `_minority_fork_recovering` if set. -- `not_synced`: Reset fork_collision_defer_count, slot_zero_streak, not_my_turn_streak -- `not_my_turn`: Reset fork_collision_defer_count, slot_zero_streak. Track `_not_my_turn_streak` (warning at 500 consecutive ≈ 125s) -- `not_time_yet`: **Track `_slot_zero_streak`** only if `now <= head_block_time()` (NTP behind chain). Warnings at 3 (750ms), 10 (2.5s, force NTP resync), 60 (15s), 120 (30s, critical) -- `no_private_key`, `low_participation`, `lag`, `consecutive`, `exception_producing_block`, `fork_collision`, `minority_fork`: Log appropriate messages - -**Watchdog:** If `_ever_produced` and `should_be_producing` (derived from live state) and silence exceeds threshold: - -```cpp -bool should_be_producing = false; -const auto& dgp_watch = database().get_dynamic_global_properties(); -if (!_minority_fork_recovering && !_witnesses.empty()) { - if (dgp_watch.emergency_consensus_active) { - should_be_producing = (_witnesses.count(CHAIN_EMERGENCY_WITNESS_ACCOUNT) > 0); - } else { - uint32_t prate_watch = database().witness_participation_rate(); - should_be_producing = (prate_watch >= 33 * CHAIN_1_PERCENT); - } -} -``` - -- Emergency master: 60s threshold -- Regular validator: 180s threshold -- Logs every 30s once triggered -- **WATCHDOG-RECOVERY**: If conditions met (head advancing < 30s, not syncing, has peers, has active keys): - - Clear `_minority_fork_recovering` - - Clear P2P catchup flag: `p2p().clear_catchup_flag()` - - Clear chain syncing flag: `chain().clear_syncing()` - - Reset streak counters - - Production resumes automatically on next tick (no cached flag to set) - ---- - -#### `maybe_produce_block()` - Main Production Logic - -**This is the core function with all safety checks. Executes in order:** - ---- - -##### Step 1: Capture Time and DGP - -```cpp -fc::time_point now_fine = graphene::time::now(); -fc::time_point_sec now = now_fine + fc::microseconds(250000); -const auto &dgp = db.get_dynamic_global_properties(); -``` - -**Why +250ms lookahead:** Aligns with timer scheduling to ensure we're at slot boundary when production decision is made. - ---- - -##### Step 2: DLT Mode Sync Check (Line ~1098) - -```cpp -if (db._dlt_mode && chain().is_syncing()) { - bool we_are_emergency_master = - dgp.emergency_consensus_active && - _witnesses.find(CHAIN_EMERGENCY_WITNESS_ACCOUNT) != _witnesses.end(); - if (!we_are_emergency_master) { - return block_production_condition::not_synced; - } - // Emergency master: bypass sync check to avoid deadlock -} -``` - -**What it checks:** -- `chain().is_syncing()`: **YES, calls chain plugin** to check `currently_syncing` atomic flag -- **Why:** In DLT mode, producing during sync creates blocks on stale head conflicting with incoming blocks → oscillation bug - -**Emergency master exception:** -- If emergency consensus active AND we have emergency key → production proceeds -- **Why:** Master is sole producer; waiting for sync would deadlock (no blocks arrive to clear syncing flag) - -**Outside DLT mode:** This check is NOT applied. Normal validators must produce on canonical head even while network catching up. - ---- - -##### Step 3: Snapshot Pause / Post-Pause Catchup Gate (Line ~1118) - -```cpp -// Check snapshot plugin directly for snapshot_in_progress flag -if (snapshot().is_snapshot_in_progress()) { - wlog("Deferring block production: snapshot creation in progress"); - return not_synced; -} - -if (p2p().is_catching_up_after_pause()) { - wlog("Deferring block production: P2P is catching up after snapshot pause"); - return not_synced; -} -``` - -**What it checks:** -- `snapshot().is_snapshot_in_progress()`: **YES, calls snapshot plugin** — `snapshot_in_progress` atomic flag (relaxed load) -- `p2p().is_catching_up_after_pause()`: **YES, calls P2P plugin** — returns true if `_block_processing_paused || _catchup_after_pause` - -**Why two checks:** -1. **Snapshot in progress**: snapshot plugin holds DB read lock; producing would cause write-lock starvation -2. **P2P catchup after pause**: snapshot finished, but queued blocks haven't drained yet; producing on stale head → fork - -**Applies to:** ALL validator types (emergency and normal) - -**Flag cleared when:** -- `snapshot_in_progress`: Cleared by snapshot plugin on completion -- `_catchup_after_pause`: Cleared when drain completes + no peer ahead - ---- - -##### Step 4: Hardfork 12 Three-State Safety Enforcement (Line ~1132) - -```cpp -if (db.has_hardfork(CHAIN_HARDFORK_12)) { - if (dgp.emergency_consensus_active) { - // Emergency mode logic - } else { - // Normal mode with participation check - } -} else { - // Pre-HF12 legacy behavior -} -``` - -**Sub-case 4a: Emergency Consensus Active** - -```cpp -bool we_are_emergency_master = - _witnesses.find(CHAIN_EMERGENCY_WITNESS_ACCOUNT) != _witnesses.end(); -if (!we_are_emergency_master) { - // Slave node: must sync first before producing - if (db.get_slot_time(1) < now) { - return block_production_condition::not_synced; - } -} -// Emergency master proceeds unconditionally — no cached flag, state queried fresh every tick -``` - -**Why:** Master MUST produce to avoid deadlock. Slave nodes must still sync first. - ---- - -**Sub-case 4b: Normal Mode (Participation Check)** - -```cpp -uint32_t prate = db.witness_participation_rate(); -if (prate >= 33 * CHAIN_1_PERCENT) { - // HEALTHY NETWORK - _production_skip_flags &= ~skip_undo_history_check; // Re-enable minority fork detection - // Check sync status directly (no cached flag — queried fresh every tick) - if (db.get_slot_time(1) < now) { - return not_synced; - } - // No participation check needed (already >= 33%) -} else { - // DISTRESSED NETWORK (< 33%) - if (!(_production_skip_flags & skip_undo_history_check)) { - // No stale-production override: require sync - if (db.get_slot_time(1) < now) { - return not_synced; - } - } - // enable-stale-production=true: operator override, proceed regardless of sync status - if (prate < _required_witness_participation) { - if (_production_skip_flags & skip_undo_history_check) { - // Operator override: produce anyway to bootstrap stalled network - } else { - return low_participation; // Network partition guard - } - } -} -``` - -**Why 33% threshold:** Below this, node likely in minority network segment. Producing risks two partitions building chains simultaneously. - -**enable-stale-production override:** If set, bypasses participation and sync checks to allow operator to recover fully stalled network. - ---- - -**Sub-case 4c: Pre-HF12 Legacy** - -```cpp -// Check sync status directly (no cached flag) -if (db.get_slot_time(1) < now) { - return not_synced; -} -// No participation check here (done later before block generation) -``` - ---- - -##### Step 5: Block Post Validation Broadcast (Line ~1228) - -```cpp -if(last_block_post_validation_time < now_fine) { - last_block_post_validation_time = now; - // Build scheduled_witnesses_set from witness_schedule_object - // For each validator in _witnesses: - // - Skip if not in current schedule - // - Get block_post_validations from database - // - Sign with validator private key - // - Broadcast via p2p().broadcast_block_post_validation() -} -``` - -**What it does:** Signs and broadcasts post-validation messages for scheduled validators to contribute to LIB advancement. - -**Optimization:** Skips validators not in current schedule (can't contribute to LIB). - ---- - -##### Step 6: Minority Fork Detection (Non-Emergency) (Line ~1318) - -```cpp -if (!dgp.emergency_consensus_active) { - auto fork_head = db.get_fork_db().head(); - // Walk back CHAIN_MAX_WITNESSES (21) blocks in fork_db - // If ALL from our validators → minority fork - if (all_ours && blocks_checked >= CHAIN_MAX_WITNESSES) { - if (_production_skip_flags & skip_undo_history_check) { - // enable-stale-production: continue - } else { - p2p().resync_from_lib(); - _minority_fork_recovering = true; - return minority_fork; - } - } -} -``` - -**Why skipped in emergency mode:** All blocks produced by committee (which may be in `_witnesses`), would always falsely trigger. - -**enable-stale-production override:** Operator can continue producing on minority fork (testnet/bootstrap scenario). - ---- - -##### Step 7: DLT-Specific Minority Fork Detection in Emergency Mode (Line ~1382) - -```cpp -if (dgp.emergency_consensus_active && db._dlt_mode) { - // Check if we are emergency master - bool we_are_master = false; - if (_witnesses.find(CHAIN_EMERGENCY_WITNESS_ACCOUNT) != _witnesses.end()) { - // Check if committee is in current validator schedule - const witness_schedule_object &wso = db.get_witness_schedule_object(); - for (int i = 0; i < wso.num_scheduled_witnesses; i += CHAIN_BLOCK_WITNESS_REPEAT) { - if (wso.current_shuffled_witnesses[i] == CHAIN_EMERGENCY_WITNESS_ACCOUNT) { - we_are_master = true; - break; - } - } - } - - if (!we_are_master) { - // Slave DLT node: run fork_db isolation scan - // If last 21 blocks all from our validators → isolated from master - p2p().resync_from_lib(true /*force_emergency*/); - _minority_fork_recovering = true; - return minority_fork; - } - // Emergency master: skip to avoid false positives -} -``` - -**Why separate check:** In DLT emergency mode, standard check skipped but slave node can still get isolated from master. Uses same 21-block threshold (one full round). - -**Master skip logic:** If committee in schedule AND we have its key → we ARE the master. All blocks being "ours" is expected. - ---- - -##### Step 8: Acquire Operation Guard (Line ~1443) - -```cpp -auto op_guard = db.make_operation_guard(); -``` - -**Why:** Guards lockless reads into shared memory against concurrent resize. Prevents pointer invalidation while reading validator schedule, slot time, etc. - -**Stall detection:** If guard blocks > 100ms, logs warning. If crosses slot boundary and we lost our slot → critical error. - -**Time refresh:** Re-captures `now` after acquiring guard (if guard blocked on DB resize, original `now` is stale). - ---- - -##### Step 9: Slot Assignment Check (Line ~1477) - -```cpp -uint32_t slot = db.get_slot_at_time(now); -if (slot == 0) { - // NTP drift check: warn if local clock > 250ms behind NTP - return not_time_yet; -} - -string scheduled_witness = db.get_scheduled_witness(slot); -if (_witnesses.find(scheduled_witness) == _witnesses.end()) { - return not_my_turn; -} -``` - -**NTP drift warning:** If `ntp_error() > 250ms`, warns about potential silent slot misses. - ---- - -##### Step 10: validator Validation (Line ~1563) - -```cpp -const auto &witness_by_name = db.get_index().indices().get(); -auto itr = witness_by_name.find(scheduled_witness); - -fc::time_point_sec scheduled_time = db.get_slot_time(slot); -graphene::protocol::public_key_type scheduled_key = itr->signing_key; - -// Check if slot already filled -if (scheduled_time <= db.head_block_time()) { - return not_time_yet; // Slot filled during/after snapshot pause -} - -// Check if validator disabled (zero key) -if (scheduled_key == public_key_type()) { - // Log warning (every 60s for regular, 3s for emergency) - return not_my_turn; -} - -// Check if we have private key -auto private_key_itr = _private_keys.find(scheduled_key); -if (private_key_itr == _private_keys.end()) { - return no_private_key; -} -``` - -**Slot already filled check:** Critical for snapshot pause scenario. Another validator may have filled the slot during pause. - ---- - -##### Step 11: Pre-HF12 Participation Check (Line ~1629) - -```cpp -if (!db.has_hardfork(CHAIN_HARDFORK_12)) { - uint32_t prate = db.witness_participation_rate(); - if (prate < _required_witness_participation) { - if (_production_skip_flags & skip_undo_history_check) { - // Operator override - } else { - return low_participation; - } - } -} -``` - -**Why here for pre-HF12:** HF12 moved participation check earlier (Step 4) for better emergency mode handling. - ---- - -##### Step 12: Lag Check (Line ~1644) - -```cpp -if (llabs((scheduled_time - now).count()) > fc::milliseconds(500).count()) { - return lag; // Woke up too late for this slot -} -``` - -**Threshold:** 500ms tolerance. If we're more than 500ms past slot time, skip. - ---- - -##### Step 13: Fork Collision Resolution (Line ~1661) - -```cpp -auto existing_blocks = db.get_fork_db().fetch_block_by_number(db.head_block_num() + 1); -if (existing_blocks.size() > 0) { - // Determine if competing block exists - // Emergency mode: ANY block at this height is competing - // Normal mode: only different validator + different parent - - if (has_competing_block) { - fork_collision_defer_count_++; - - // LEVEL 2: Timeout after 21 deferrals (stuck-head) - if (fork_collision_defer_count_ > _fork_collision_timeout_blocks) { - // Remove dead-fork competing block, produce on our chain - db.get_fork_db().remove_blocks_by_number(db.head_block_num() + 1); - fork_collision_defer_count_ = 0; - // Fall through to produce - } - // LEVEL 1: Vote-weighted comparison (HF12+) - else if (db.has_hardfork(CHAIN_HARDFORK_12)) { - int weight_cmp = db.compare_fork_branches(competing_block->id, db.head_block_id()); - if (weight_cmp < 0) { - // Our fork has MORE weight → produce, remove competing block - } else if (weight_cmp > 0) { - // Competing fork has MORE weight → defer - return fork_collision; - } else { - // Tied → defer, timeout will kick in - return fork_collision; - } - } - // Pre-HF12: defer, timeout still applies - else { - return fork_collision; - } - } -} -``` - -**Two-level resolution:** -1. **Vote-weighted comparison** (HF12+): Compare fork branches by accumulated vote weight -2. **Stuck-head timeout** (all versions): After 21 deferrals (63s), assume competing block is on dead fork - -**Why 21 blocks:** One full validator round. If head hasn't advanced after 21 slots, longer chain had all scheduled validators produce → canonical chain confirmed. - ---- - -##### Step 14: Second Snapshot Pause Check (Line ~1769) - -```cpp -try { - if (p2p().is_catching_up_after_pause()) { - return not_time_yet; // Snapshot started between checks - } -} catch (...) {} -``` - -**Why second check:** Race window ~1 block interval. If snapshot started after first check (~line 1118), producing would cause 2-11s write-lock starvation. - -**Cost:** One missed slot (3s) — far cheaper than full snapshot read hold time. - ---- - -##### Step 15: Generate and Broadcast Block (Line ~1777) - -```cpp -auto block = db.generate_block( - scheduled_time, - scheduled_witness, - private_key_itr->second, - _production_skip_flags -); - -p2p().broadcast_block(block); - -// Seed reconnect if few peers -auto peer_count = p2p().get_connections_count(); -if (peer_count < 2 && !p2p().is_isolated_peers()) { - p2p().reconnect_seeds(); -} - -return produced; -``` - -**Retry logic:** Up to 2 retries on `fc::exception` (clears pending transactions between retries). - -**Exception handling:** -- `shared_memory_corruption_exception`: Call `chain().attempt_auto_recovery()` -- `unlinkable_block_exception`: Fork DB broken → rollback to LIB, resync from P2P -- `fc::exception`: Clear pending transactions, retry - ---- - -### 3. Signal Handler: `on_block_applied()` - -**Connected to:** `database::applied_block` signal during `plugin_startup()` - -**Purpose:** Detect missed slots and slot hijacks for diagnostics. - ---- - -#### Slot Hijack Detection (Runs for every block) - -```cpp -if (database()._dlt_mode && !_witnesses.empty() - && prev_num > 0 && block_num == prev_num + 1) { - const auto& dgp = database().get_dynamic_global_properties(); - if (dgp.emergency_consensus_active) { - // Slot index for the block just applied - uint64_t slot_idx = dgp.current_aslot % nsw; - const std::string& expected_witness = wso.current_shuffled_witnesses[slot_idx]; - - // True hijack: expected slot is ours AND actual producer is not one of our validators - if (_witnesses.count(expected_witness) > 0 - && _witnesses.count(block.validator) == 0) { - _slot_hijack_count++; // Committee / emergency master filled our slot - // Log first 3 hijacks, then once per minute - } else if (_witnesses.count(block.validator) > 0) { - // ANY of our validators produced → reset (false-positive guard) - if (_slot_hijack_count > 0) { - ilog("Hijack counter reset: our validator '${w}' produced...", ...); - } - _slot_hijack_count = 0; - } - } -} -``` - -**What it detects:** In DLT emergency mode, the emergency master may blank our validator's signing_key and fill our scheduled slots with committee blocks. - -**Important:** The reset condition checks `_witnesses.count(block.validator) > 0` — i.e., ANY of our configured validators produced the block, not just the slot-assigned one. Without this, a legitimate block from a different one of our validators would be mis-counted as a hijack. - ---- - -#### Missed Slot Detection - -```cpp -if (block_num > prev_num + 1) { - uint32_t missed_count = block_num - prev_num - 1; - - // Calculate which validators were scheduled for missed slots - for (uint32_t i = 0; i < missed_count && i < 100; ++i) { - uint64_t abs_slot = cur_aslot - missed_count + i; - const std::string &wname = wso.current_shuffled_witnesses[abs_slot % num_witnesses]; - if (_witnesses.count(wname) > 0) { - our_witness_missed = true; - } - } - - if (our_witness_missed) { - // Dump full diagnostic state: - // - Production flags, NTP offset, sync status - // - On-chain signing key status (blanked?) - // - Next slot time, scheduled validator - // - Streak counters - elog("MISSED-SLOT-OUR-validator: ..."); - } -} -``` - -**Why check missed slots:** When incoming blocks reveal gaps, determines if our validator was scheduled for any missed slots and logs full diagnostic state for troubleshooting. - ---- - -## Public API Methods - -### `is_witness_scheduled_soon()` - -**Returns:** `true` if locally-controlled validator is scheduled to produce in next 4 slots (~12 seconds) - -**Implementation:** -1. Check 4 upcoming slots (covers snapshot creation time ~10s + safety margin) -2. For each slot: - - Get scheduled validator name - - Check if in `_witnesses` set - - Look up validator object in database - - Check if signing key is non-zero - - Check if we have corresponding private key -3. Return `true` if any match found - -**Used by:** Snapshot plugin to defer snapshot creation when validator about to produce (avoids fork on stale head) - ---- - -### `is_emergency_master()` - -**Returns:** `true` if this node is the emergency master - -**Conditions:** -1. Holds `emergency-private-key` (`CHAIN_EMERGENCY_WITNESS_ACCOUNT` in `_witnesses`) -2. Committee account is in current validator schedule - -**Why both conditions:** Multiple nodes can have emergency key, but only the one where committee is scheduled should produce solo. Others are followers that must sync. - -**Used by:** -- P2P plugin: Determines if node should wait for network or produce -- Snapshot plugin: Skips stalled sync recovery for master -- Watchdog: Different silence thresholds (60s vs 180s) - ---- - -### `is_emergency_key_configured()` - -**Returns:** `true` if `emergency-private-key` is configured, regardless of schedule - -**Used by:** External diagnostics, P2P hello messages - ---- - -### `get_production_diagnostics()` - -**Returns:** Compact diagnostic string with key production-state flags - -**Format:** `validator[skip_flags=0x0 catching_up=0 head=#123 last_prod=45s_ago minority_rcv=0 slot_hijacks=2]` - -**Used by:** P2P layer when FORWARD stagnation fires with no peer ahead, so stagnation log shows why master isn't filling gap. - ---- - -## Hardfork Checks - -### CHAIN_HARDFORK_12 - -**Location:** Multiple places in `maybe_produce_block()` - -**Changes introduced:** -1. **Three-state safety enforcement** (Step 4): - - Pre-HF12: Simple sync check - - HF12+: Emergency mode detection, participation-based auto-enable, stale-production override logic - -2. **Minority fork detection** (Step 6): - - Pre-HF12: Standard 21-block check - - HF12+: Skipped during emergency consensus, DLT-specific check added - -3. **Participation check position** (Step 11): - - Pre-HF12: Checked just before block generation - - HF12+: Checked early in Step 4 with emergency mode awareness - -4. **Fork collision resolution** (Step 13): - - Pre-HF12: Defer only, timeout applies - - HF12+: Vote-weighted comparison (Level 1) + timeout (Level 2) - -5. **Block post validation**: - - Pre-HF12: LIB advancement via 2/3 validator signatures - - HF12+: LIB advancement via validator participation rate, emergency mode disables post-validation chain - ---- - -## Database Access Patterns - -### Direct Database Reads (via `database()` reference) - -**Frequent (every production tick ~250ms):** -- `db.get_dynamic_global_properties()` — emergency consensus state, head block number/time, current_aslot -- `db.get_slot_at_time(now)` — determine if slot available -- `db.get_scheduled_witness(slot)` — who should produce -- `db.get_slot_time(slot)` — scheduled slot time -- `db.get_witness_schedule_object()` — shuffled validator list, num_scheduled_witnesses -- `db.head_block_num()`, `db.head_block_time()` — current chain state -- `db.get_fork_db().head()`, `db.get_fork_db().fetch_block_by_number()` — fork detection -- `db.get_index().indices().get().find()` — validator signing key status -- `db.witness_participation_rate()` — network health check -- `db.has_hardfork(CHAIN_HARDFORK_12)` — feature gate - -**Infrequent (on events or diagnostics):** -- `db.get_block_post_validations()` — sign and broadcast validations -- `db.compare_fork_branches()` — vote-weight comparison (HF12+) -- `db.get_fork_db().remove()`, `remove_blocks_by_number()` — fork resolution - -**Write operations:** -- `db.generate_block()` — create and sign new block -- `db.clear_pending()` — clear pending transactions on failure - -### Does NOT cache results - -**All database reads are fresh on every call:** -- No caching of validator schedule -- No caching of DGP state -- No caching of fork_db state -- No caching of signing key status - -**Why:** State changes every block (emergency mode can activate/deactivate, validator schedule changes, signing keys can be blanked). Caching would create race conditions and stale decisions. - -**Exception:** `_witnesses` set and `_private_keys` map are loaded once during `plugin_initialize()` and never refreshed (operator must restart to change configuration). - ---- - -## Sync/Forward Status Checks - -### Chain Plugin: `chain().is_syncing()` - -**Called in:** -1. Step 2 (DLT mode sync check) -2. Watchdog recovery -3. Diagnostic logging (missed slot, stall detection) - -**What it checks:** `chain_plugin::currently_syncing` atomic flag - -**Set by:** Chain plugin when processing P2P sync blocks (`accept_block()` with `currently_syncing_flag=true`) - -**Cleared by:** -- Chain plugin when sync completes -- Watchdog recovery (force-clear) - ---- - -### P2P Plugin: `p2p().is_catching_up_after_pause()` - -**Called in:** -1. Step 3 (snapshot pause gate) -2. Step 14 (second snapshot pause check) -3. Watchdog recovery -4. Diagnostic logging -5. `get_production_diagnostics()` - -**What it checks:** `_block_processing_paused || _catchup_after_pause` flags in `dlt_p2p_node` - -**Set by:** -- `pause_block_processing()`: Sets `_block_processing_paused = true` (snapshot creation starting) -- `resume_block_processing()`: Sets `_block_processing_paused = false`, may set `_catchup_after_pause = true` (drain queued blocks) - -**Cleared by:** -- `resume_block_processing()`: After drain completes -- Watchdog recovery: `p2p().clear_catchup_flag()` - ---- - -### Snapshot Plugin: Direct API `snapshot().is_snapshot_in_progress()` - -**Called in:** -1. Step 3 (snapshot pause gate) — first check before P2P catchup check - -**What it checks:** `snapshot_plugin::snapshot_in_progress` atomic flag (relaxed load) - -**Implementation:** -```cpp -bool snapshot_plugin::is_snapshot_in_progress() const { - if (!my) return false; - return my->snapshot_in_progress.load(std::memory_order_relaxed); -} -``` - -**Set by:** Snapshot plugin when serialization starts - -**Cleared by:** Snapshot plugin on completion - ---- - -### Snapshot Plugin: Indirect check via `is_witness_scheduled_soon()` - -**Called by:** Snapshot plugin in `on_applied_block()` before scheduling snapshot - -**Why:** Defer snapshot if validator about to produce (~12s window). Producing during snapshot → read-lock contention, producing after on stale head → fork. - ---- - -## State Machine Summary - -``` -[Startup] - ↓ plugin_initialize() -[Config loaded] - ↓ plugin_startup() -[Production loop running] - ↓ every 250ms -[maybe_produce_block()] - ├─→ not_synced (DLT sync, snapshot pause) - ├─→ not_time_yet (slot=0, NTP drift, slot already filled) - ├─→ not_my_turn (validator disabled, wrong validator scheduled) - ├─→ no_private_key (missing key) - ├─→ low_participation (< 33%, no override) - ├─→ lag (> 500ms past slot time) - ├─→ fork_collision (competing block, defer) - ├─→ minority_fork (21 blocks from our validators, rollback to LIB) - └─→ produced (success, broadcast block) -``` - ---- - -## Critical Safety Mechanisms - -### 1. Minority Fork Detection -- **Trigger:** Last 21 blocks in fork_db all from our validators -- **Action:** Rollback to LIB, resync from P2P network -- **Override:** `enable-stale-production=true` -- **Emergency mode:** Skipped (committee blocks would always trigger), DLT-specific check for slaves - -### 2. Fork Collision Resolution -- **Level 1 (HF12+):** Vote-weight comparison, produce on heavier fork -- **Level 2:** Timeout after 21 deferrals (63s), assume dead fork -- **Emergency mode:** ANY competing block triggers defer - -### 3. Network Partition Guard -- **Trigger:** validator participation < 33% -- **Action:** Stop production (return `low_participation`) -- **Override:** `enable-stale-production=true` for bootstrap/recovery - -### 4. Slot Already Filled Guard -- **Trigger:** `scheduled_time <= head_block_time()` -- **Why:** Another validator filled slot during/after snapshot pause -- **Action:** Skip production (return `not_time_yet`) - -### 5. Production Watchdog -- **Trigger:** No production for 60s (emergency) or 180s (regular); `should_be_producing` true (derived from live DB state) -- **Conditions:** Head advancing, not syncing, has peers, has active keys -- **Action:** Clear blocking conditions (`_minority_fork_recovering`, P2P catchup, chain syncing); production resumes automatically on next tick - -### 6. NTP Stall Detection -- **Trigger:** `slot=0` streak (NTP behind chain time) -- **Thresholds:** - - 3 (750ms): Warning - - 10 (2.5s): Force NTP resync - - 60 (15s): Prolonged stall warning - - 120 (30s): Critical, action required - -### 7. Slot Hijack Detection (DLT Emergency) -- **Trigger:** Committee fills our scheduled slot -- **Action:** Log diagnostics, increment counter -- **Why:** Emergency master blanked our key, producing in our slots - ---- - -## Key Invariants - -1. **Never produce during sync (DLT mode):** Creates blocks on stale head → oscillation bug -2. **Never produce during snapshot pause:** Write-lock deadlock -3. **Never produce if slot already filled:** Creates micro-fork -4. **Emergency master must always produce:** Sole producer, waiting = deadlock -5. **Slave nodes must sync first:** Producing on stale head = minority fork -6. **Participation < 33% = stop production:** Network partition guard (unless override) -7. **21 consecutive blocks from our validators = minority fork:** Rollback to LIB -8. **All database reads are fresh:** No caching, state changes every block - ---- - -## Troubleshooting Guide - -### Problem: Not producing blocks - -**Check logs for:** -- `not_synced`: DLT sync active or snapshot pause → wait for sync/pause to complete -- `not_time_yet`: NTP drift or slot=0 → check NTP offset in logs -- `not_my_turn`: Wrong validator scheduled or key blanked → check `keys=[...]` in watchdog log -- `no_private_key`: Missing private key → check config -- `low_participation`: Network participation < 33% → set `enable-stale-production=true` -- `fork_collision`: Competing block → wait for resolution (21 blocks max) -- `minority_fork`: On wrong fork → resyncing from LIB - -**Diagnostic command:** Check `get_production_diagnostics()` output in P2P stagnation logs. - ---- - -### Problem: Producing on wrong fork - -**Symptoms:** `MINORITY FORK DETECTED` log, blocks not accepted by network - -**Cause:** Isolated from network, only seeing own blocks - -**Action:** -1. Check peer connections -2. Verify network connectivity -3. Plugin will auto-rollback to LIB and resync - ---- - -### Problem: Emergency master not producing - -**Symptoms:** Network stalled, `EMRG-DIAG slot=0` logs - -**Check:** -1. Is emergency key configured? → `--emergency-private-key` -2. Is committee in validator schedule? → Check `witness_schedule_object` -3. Is NTP synchronized? → Check NTP offset -4. Is sync flag stuck? → Watchdog should auto-clear - -**Watchdog recovery:** If conditions met, watchdog will force-reset flags after 60s silence. - ---- - -### Problem: Slot hijacks detected - -**Symptoms:** `SLOT-HIJACK` logs, `slot_hijacks=N` in watchdog diagnostics - -**Cause:** Emergency master blanked our validator key and producing committee blocks in our slots - -**Normal behavior:** In DLT emergency mode, master may blank offline validators to maintain chain progress - -**Action:** -1. Check validator signing key status: `keys=[validator:key=BLANK]` -2. Send `update_witness` transaction to restore key -3. Wait for emergency mode to end (LIB advances) - ---- - -## Related Files - -- **Chain plugin:** `plugins/chain/plugin.cpp`, `plugins/chain/include/graphene/plugins/chain/plugin.hpp` -- **P2P plugin:** `plugins/p2p/p2p_plugin.cpp`, `plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp` -- **Snapshot plugin:** `plugins/snapshot/plugin.cpp`, `plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp` -- **validator guard plugin:** `plugins/witness_guard/witness_guard.cpp` -- **DLT P2P node:** `libraries/network/dlt_p2p_node.cpp`, `libraries/network/include/graphene/network/dlt_p2p_node.hpp` -- **Database:** `libraries/chain/database.cpp` (emergency consensus, hardfork logic, block generation) -- **NTP time:** `libraries/time/time.cpp` (time synchronization) diff --git a/.qoder/docs/virtual-operations.md b/.qoder/docs/virtual-operations.md deleted file mode 100644 index c62a43e6e6..0000000000 --- a/.qoder/docs/virtual-operations.md +++ /dev/null @@ -1,513 +0,0 @@ -# VIZ Blockchain — Virtual Operations - -Virtual operations are generated by the blockchain itself (not broadcast by users). They appear in operation history and can be tracked via account history or operation history APIs. They **cannot** be included in user transactions. - ---- - -## `author_reward_operation` - -**Type ID:** `26` -**Trigger:** Content payout - -Fired when an author receives their reward share from a content payout. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `author` | `account_name_type` | Content author | -| `permlink` | `string` | Content permlink | -| `token_payout` | `asset` (VIZ) | Liquid token portion of payout | -| `vesting_payout` | `asset` (SHARES) | SHARES portion of payout | - -### JSON Example - -```json -[26, { - "author": "alice", - "permlink": "my-article", - "token_payout": "1.500 VIZ", - "vesting_payout": "0.000000 SHARES" -}] -``` - ---- - -## `curation_reward_operation` - -**Type ID:** `27` -**Trigger:** Content payout - -Fired when a curator receives their curation reward. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `curator` | `account_name_type` | Curator account | -| `reward` | `asset` (SHARES) | Curation reward in SHARES | -| `content_author` | `account_name_type` | Author of the curated content | -| `content_permlink` | `string` | Permlink of the curated content | - -### JSON Example - -```json -[27, { - "curator": "bob", - "reward": "0.500000 SHARES", - "content_author": "alice", - "content_permlink": "my-article" -}] -``` - ---- - -## `content_reward_operation` - -**Type ID:** `28` -**Trigger:** Content payout - -Fired when a content post reaches its payout time. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `author` | `account_name_type` | Content author | -| `permlink` | `string` | Content permlink | -| `payout` | `asset` | Total payout amount | - ---- - -## `fill_vesting_withdraw_operation` - -**Type ID:** `29` -**Trigger:** Each vesting withdrawal interval - -Fired each time a vesting withdrawal interval completes (weekly). - -### Fields - -| Field | Type | Description | -|---|---|---| -| `from_account` | `account_name_type` | Account withdrawing | -| `to_account` | `account_name_type` | Account receiving (may differ via route) | -| `withdrawn` | `asset` (SHARES) | SHARES amount withdrawn this interval | -| `deposited` | `asset` (VIZ) | VIZ tokens deposited to `to_account` | - -### JSON Example - -```json -[29, { - "from_account": "alice", - "to_account": "alice", - "withdrawn": "35.714285 SHARES", - "deposited": "10.000 VIZ" -}] -``` - -### Checklist for listeners -- [ ] Fired once per interval (default: every 1 day) for each active withdrawal -- [ ] `to_account` may differ if `set_withdraw_vesting_route_operation` was used -- [ ] If `auto_vest == true` in route, `deposited` will be SHARES, not VIZ - ---- - -## `shutdown_witness_operation` - -**Type ID:** `30` -**Trigger:** validator removed due to vote weight falling below threshold - -### Fields - -| Field | Type | Description | -|---|---|---| -| `owner` | `account_name_type` | validator that was shut down | - ---- - -## `hardfork_operation` - -**Type ID:** `31` -**Trigger:** Network hardfork activation - -### Fields - -| Field | Type | Description | -|---|---|---| -| `hardfork_id` | `uint32_t` | Hardfork number | - ---- - -## `content_payout_update_operation` - -**Type ID:** `32` -**Trigger:** Content payout update - -Fired when content payout is updated (e.g., after vote changes). - -### Fields - -| Field | Type | Description | -|---|---|---| -| `author` | `account_name_type` | Content author | -| `permlink` | `string` | Content permlink | - ---- - -## `content_benefactor_reward_operation` - -**Type ID:** `33` -**Trigger:** Content payout - -Fired for each beneficiary when content is paid out. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `benefactor` | `account_name_type` | Beneficiary account | -| `author` | `account_name_type` | Content author | -| `permlink` | `string` | Content permlink | -| `reward` | `asset` | Beneficiary's reward share | - ---- - -## `return_vesting_delegation_operation` - -**Type ID:** `34` -**Trigger:** End of vesting delegation limbo period (1 week after delegation removal) - -### Fields - -| Field | Type | Description | -|---|---|---| -| `account` | `account_name_type` | Account receiving the returned SHARES | -| `vesting_shares` | `asset` (SHARES) | SHARES returned from limbo | - -### JSON Example - -```json -[34, { - "account": "alice", - "vesting_shares": "500.000000 SHARES" -}] -``` - ---- - -## `committee_cancel_request_operation` - -**Type ID:** `38` -**Trigger:** Committee request expires without enough votes - -### Fields - -| Field | Type | Description | -|---|---|---| -| `request_id` | `uint32_t` | ID of cancelled request | - ---- - -## `committee_approve_request_operation` - -**Type ID:** `39` -**Trigger:** Committee request reaches required approval threshold - -### Fields - -| Field | Type | Description | -|---|---|---| -| `request_id` | `uint32_t` | ID of approved request | - ---- - -## `committee_payout_request_operation` - -**Type ID:** `40` -**Trigger:** Committee request payout is processed - -### Fields - -| Field | Type | Description | -|---|---|---| -| `request_id` | `uint32_t` | ID of paid request | - ---- - -## `committee_pay_request_operation` - -**Type ID:** `41` -**Trigger:** Worker receives payment from committee - -### Fields - -| Field | Type | Description | -|---|---|---| -| `worker` | `account_name_type` | Worker account receiving payment | -| `request_id` | `uint32_t` | Committee request ID | -| `tokens` | `asset` (VIZ) | Amount paid to worker | - -### JSON Example - -```json -[41, { - "worker": "alice", - "request_id": 42, - "tokens": "250.000 VIZ" -}] -``` - ---- - -## `witness_reward_operation` - -**Type ID:** `42` -**Trigger:** Block production reward - -Fired when a validator receives their block production reward. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `validator` | `account_name_type` | validator account | -| `shares` | `asset` (SHARES) | Reward in SHARES | - -### JSON Example - -```json -[42, { - "validator": "alice", - "shares": "1.234567 SHARES" -}] -``` - ---- - -## `receive_award_operation` - -**Type ID:** `48` -**Trigger:** `award_operation` or `fixed_award_operation` - -Fired when the receiver gets their award. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `initiator` | `account_name_type` | Account that gave the award | -| `receiver` | `account_name_type` | Account that received the award | -| `custom_sequence` | `uint64_t` | App-defined sequence from the award op | -| `memo` | `string` | Memo from the award op | -| `shares` | `asset` (SHARES) | SHARES received | - -### JSON Example - -```json -[48, { - "initiator": "alice", - "receiver": "bob", - "custom_sequence": 0, - "memo": "great article!", - "shares": "5.000000 SHARES" -}] -``` - ---- - -## `benefactor_award_operation` - -**Type ID:** `49` -**Trigger:** `award_operation` or `fixed_award_operation` with beneficiaries - -Fired for each beneficiary when an award is given. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `initiator` | `account_name_type` | Account that gave the award | -| `benefactor` | `account_name_type` | Beneficiary account | -| `receiver` | `account_name_type` | Primary receiver of the award | -| `custom_sequence` | `uint64_t` | App-defined sequence | -| `memo` | `string` | Memo from the award op | -| `shares` | `asset` (SHARES) | SHARES received by beneficiary | - ---- - -## `paid_subscription_action_operation` - -**Type ID:** `52` -**Trigger:** `paid_subscribe_operation` or auto-renewal payment - -Fired on each subscription payment. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `subscriber` | `account_name_type` | Subscriber account | -| `account` | `account_name_type` | Subscription provider | -| `level` | `uint16_t` | Subscription level | -| `amount` | `asset` (VIZ) | Payment amount | -| `period` | `uint16_t` | Number of periods | -| `summary_duration_sec` | `uint64_t` | Total subscription duration in seconds | -| `summary_amount` | `asset` (VIZ) | Total amount paid to date | - -### JSON Example - -```json -[52, { - "subscriber": "bob", - "account": "alice", - "level": 2, - "amount": "20.000 VIZ", - "period": 1, - "summary_duration_sec": 2592000, - "summary_amount": "20.000 VIZ" -}] -``` - ---- - -## `cancel_paid_subscription_operation` - -**Type ID:** `53` -**Trigger:** Subscription expiry without renewal, or insufficient funds for auto-renewal - -### Fields - -| Field | Type | Description | -|---|---|---| -| `subscriber` | `account_name_type` | Subscriber account | -| `account` | `account_name_type` | Subscription provider | - ---- - -## `account_sale_operation` - -**Type ID:** `57` -**Trigger:** `buy_account_operation` completes successfully - -Fired when an account is sold. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `account` | `account_name_type` | Account that was sold | -| `price` | `asset` (VIZ) | Sale price | -| `buyer` | `account_name_type` | Buyer account | -| `seller` | `account_name_type` | Seller (payment recipient) | - -### JSON Example - -```json -[57, { - "account": "alice", - "price": "1000.000 VIZ", - "buyer": "bob", - "seller": "alice" -}] -``` - ---- - -## `expire_escrow_ratification_operation` - -**Type ID:** `59` -**Trigger:** Escrow not ratified by both parties before `ratification_deadline` - -Fired when an escrow expires before being fully approved. Funds are returned to `from`. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `from` | `account_name_type` | Original escrow sender | -| `to` | `account_name_type` | Original intended recipient | -| `agent` | `account_name_type` | Escrow agent | -| `escrow_id` | `uint32_t` | Escrow ID | -| `token_amount` | `asset` (VIZ) | Returned token amount | -| `fee` | `asset` (VIZ) | Fee returned (agent not paid since not approved) | -| `ratification_deadline` | `time_point_sec` | Deadline that was missed | - ---- - -## `bid_operation` - -**Type ID:** `62` -**Trigger:** New bid placed on an account for sale (HF11) - -### Fields - -| Field | Type | Description | -|---|---|---| -| `account` | `account_name_type` | Account being bid on | -| `bidder` | `account_name_type` | Account placing the bid | -| `bid` | `asset` (VIZ) | Bid amount | - ---- - -## `outbid_operation` - -**Type ID:** `63` -**Trigger:** A previous bidder is outbid (HF11) - -Fired when a previous bid is replaced by a higher bid. The outbid amount is returned. - -### Fields - -| Field | Type | Description | -|---|---|---| -| `account` | `account_name_type` | Account being bid on | -| `bidder` | `account_name_type` | Account that was outbid | -| `bid` | `asset` (VIZ) | Returned bid amount | - ---- - -## Implementation Notes for Libraries - -### Listening to Virtual Operations - -```php -// PHP: subscribe to account history -$history = $api->get_account_history('alice', -1, 100); -foreach ($history as [$index, $entry]) { - $opType = $entry['op'][0]; - $opData = $entry['op'][1]; - switch ($opType) { - case 'receive_award_operation': - // handle award received - break; - case 'fill_vesting_withdraw_operation': - // handle withdrawal - break; - } -} -``` - -```js -// Node.js: stream operations -client.database.getAccountHistory('alice', -1, 100).then(history => { - for (const [index, entry] of history) { - const [opType, opData] = entry.op; - switch (opType) { - case 'receive_award': - // handle award - break; - case 'witness_reward': - // handle validator reward - break; - } - } -}); -``` - -### Checklist for Virtual Operation Handling -- [ ] Virtual operations are read-only — never include them in transactions -- [ ] Filter by operation type in account/operation history -- [ ] Virtual ops share the same type variant as regular ops — use the same deserialization -- [ ] `summary_duration_sec` in paid subscriptions may be 0 for first payment -- [ ] Multiple virtual ops may fire in the same block for the same account diff --git a/.qoder/docs/witness-guard-spec.json b/.qoder/docs/witness-guard-spec.json deleted file mode 100644 index b220a0ca8b..0000000000 --- a/.qoder/docs/witness-guard-spec.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "witness-guard-spec.json", - "title": "Witness Guard Plugin Specification", - "description": "Technical specification for the automated witness key restoration plugin", - "version": "2.0.0", - - "definitions": { - "witness_entry": { - "type": "array", - "description": "A JSON array representing a witness configuration triplet", - "items": [ - { "name": "account_name", "type": "string", "description": "The name of the witness account to monitor" }, - { "name": "signing_wif", "type": "string", "description": "The WIF private key to be restored as the signing key" }, - { "name": "active_wif", "type": "string", "description": "The WIF private key with active authority to sign the update transaction" } - ], - "example": "[\"mywitness\", \"5Ksigning...\", \"5Kactive...\"]" - } - }, - - "configuration": { - "options": [ - { - "name": "witness-guard-enabled", - "type": "boolean", - "default": true, - "description": "Global toggle for the plugin logic" - }, - { - "name": "witness-guard-witness", - "type": "vector", - "description": "List of witness triplets in JSON format. Can be specified multiple times.", - "ref": "#/definitions/witness_entry" - }, - { - "name": "witness-guard-interval", - "type": "uint32", - "default": 20, - "description": "Frequency of periodic checks measured in blocks (20 blocks ≈ 60 seconds)" - }, - { - "name": "witness-guard-disable", - "type": "uint32", - "default": 5, - "description": "Number of consecutive blocks produced by the same monitored witness before automatically disabling that witness (setting signing key to null). Set to 0 to disable this feature." - } - ], - "external_options_read": [ - { - "name": "enable-stale-production", - "type": "boolean", - "default": false, - "description": "Read from the shared witness config. When true, auto-restore is disabled until network participation reaches >= 33%, at which point the flag is auto-cleared." - } - ] - }, - - "algorithms": { - "check_and_restore": { - "description": "Core loop: validates preconditions and restores null signing keys", - "steps": [ - { "step": 1, "action": "Stale production guard: if enable-stale-production is active AND participation < 33% AND not emergency mode, skip all checks. If participation >= 33%, auto-clear the stale flag and continue." }, - { "step": 2, "action": "Sync check: head_block_time must be within 2 * CHAIN_BLOCK_INTERVAL seconds of wall clock" }, - { "step": 3, "action": "Long fork safety: if Last Irreversible Block is older than 200 seconds, skip restoration" }, - { "step": 4, "action": "Expire stale pending-confirmation trackers (failed broadcasts) to allow retries" }, - { "step": 5, "action": "For each configured witness: look up witness object by name in witness_index" }, - { "step": 6, "condition": "signing_key is NOT null", "action": "Clear any pending restore state and auto-disabled flag for this witness" }, - { "step": 6.5, "condition": "signing_key IS null AND witness is in _auto_disabled_witnesses", "action": "Skip auto-restore — the witness was disabled by the consecutive-block guard; operator must manually intervene" }, - { "step": 7, "condition": "signing_key IS null AND no in-flight restore (or previous restore expired)", "action": "Invoke send_witness_update" } - ], - "returns": "true if node was in sync and full check was performed, false otherwise" - }, - - "send_witness_update": { - "description": "Constructs, signs, and broadcasts a witness_update_operation to restore the signing key", - "steps": [ - { "step": 1, "action": "Build witness_update_operation: preserve current URL, set block_signing_key to configured public key" }, - { "step": 2, "action": "Create signed_transaction with 30-second expiration and head block reference" }, - { "step": 3, "action": "Sign the transaction with the configured active private key" }, - { "step": 4, "action": "Broadcast via P2P plugin" }, - { "step": 5, "action": "Track transaction ID in _pending_confirmations and witness in _restore_pending" } - ] - }, - - "applied_block_handler": { - "description": "Signal handler connected to database.applied_block, runs on every new block", - "steps": [ - { "step": 0, "action": "Consecutive-block auto-disable: if _disable_threshold > 0 and block was produced by a monitored witness, increment its consecutive counter. If counter >= threshold and witness not already auto-disabled, broadcast send_witness_disable. If block was by a different witness, reset all counters to 0." }, - { "step": 1, "action": "Scan the block's transactions for any pending confirmation IDs; on match, mark witness restore as confirmed and clear tracking state" }, - { "step": 2, "action": "Look-ahead: if any monitored witness is scheduled in the next 3 slots, trigger an immediate check" }, - { "step": 3, "action": "Otherwise: if initial sync not yet done, probe every 10 blocks; else run periodic check every _check_interval blocks" } - ] - }, - - "send_witness_disable": { - "description": "Constructs, signs, and broadcasts a witness_update_operation with null signing key to disable block production", - "steps": [ - { "step": 1, "action": "Build witness_update_operation: preserve current URL, set block_signing_key to null" }, - { "step": 2, "action": "Create signed_transaction with 30-second expiration and head block reference" }, - { "step": 3, "action": "Sign the transaction with the configured active private key" }, - { "step": 4, "action": "Broadcast via P2P plugin" }, - { "step": 5, "action": "Add the witness to _auto_disabled_witnesses set to prevent auto-restore" } - ] - }, - - "startup_authority_validation": { - "description": "Runs at plugin_startup after the chain database is open", - "steps": [ - { "step": 1, "action": "For each configured witness, look up account_authority_object on chain" }, - { "step": 2, "action": "Verify the configured active key is present in the account's active key_auths" }, - { "step": 3, "action": "Log a warning if the key lacks authority; remove the witness from monitoring if the account does not exist" } - ] - } - }, - - "internal_state": { - "_witness_configs": { - "type": "std::map", - "description": "Configured witnesses: maps witness account name to its signing and active key pair" - }, - "_restore_pending": { - "type": "std::map", - "description": "Witnesses with an in-flight restore transaction: maps witness name to the transaction's expiration time. Prevents duplicate broadcasts." - }, - "_pending_confirmations": { - "type": "std::map>", - "description": "Transaction IDs awaiting block inclusion: maps tx_id to (witness_name, expiration). Used to confirm restore transactions landed on-chain. Capped at 1000 entries." - }, - "_stale_production_config": { - "type": "bool", - "default": false, - "description": "Mirrors enable-stale-production from witness config. Auto-cleared to false when network participation reaches >= 33%, re-enabling key restoration." - }, - "_initial_check_done": { - "type": "bool", - "default": false, - "description": "Set to true once the node is confirmed in sync at startup. Until then, the plugin probes every 10 blocks instead of using the configured interval." - }, - "_disable_threshold": { - "type": "uint32_t", - "default": 5, - "description": "Number of consecutive blocks by the same monitored witness before auto-disabling it. Mirrors witness-guard-disable config option. 0 = feature disabled." - }, - "_consecutive_blocks": { - "type": "std::map", - "description": "Per-witness counter of consecutive blocks produced. Reset to 0 for all witnesses when a block from a non-monitored (or different) witness is seen." - }, - "_auto_disabled_witnesses": { - "type": "std::set", - "description": "Witnesses that have been auto-disabled by the consecutive-block guard. Auto-restore is suppressed for these witnesses. Cleared when the on-chain key is manually restored (non-null key detected)." - } - }, - - "safety_guards": { - "stale_production": "When enable-stale-production=true, auto-restore is disabled to avoid broadcasting on a minority fork. Auto-cleared when participation >= 33% (same logic as witness plugin).", - "emergency_mode": "During emergency consensus (dgp.emergency_consensus_active), the stale production guard is bypassed — key restoration may still be needed for recovery.", - "sync_check": "Restoration only runs when head_block_time is within 2 * CHAIN_BLOCK_INTERVAL of wall clock time.", - "long_fork_detection": "If the Last Irreversible Block is older than 200 seconds, restoration is skipped to avoid acting on a stale fork.", - "authority_validation": "At startup, the plugin verifies the configured active key has authority on-chain; warns or removes witnesses that would fail.", - "consecutive_block_auto_disable": "When a monitored witness produces witness-guard-disable consecutive blocks, the plugin auto-disables it by broadcasting a null signing key. Auto-restore is suppressed for that witness until the operator manually restores the key." - } -} diff --git a/.qoder/docs/witness-to-validator-migration-reference.md b/.qoder/docs/witness-to-validator-migration-reference.md deleted file mode 100644 index f98db5e4eb..0000000000 --- a/.qoder/docs/witness-to-validator-migration-reference.md +++ /dev/null @@ -1,501 +0,0 @@ -# VIZ Blockchain — validator → Validator Migration Reference - -## Quick Summary - -The VIZ blockchain is renaming "validator" terminology to "validator" across the entire stack. This document is a reference for JS/PHP library developers to support both old and new names during migration. - -**Key principle: binary wire format uses integer type IDs, not string names. Submitting transactions by integer ID never breaks. Only JSON string names change.** - ---- - -## 0. What Libraries Must Change Themselves vs What They Just Relay - -This distinction is the most important rule for library developers. - -### Must change (library constructs these from scratch): - -| What | Where in library code | -|------|----------------------| -| Operation name→ID mapping table | Serialization/deserialization layer | -| Field names inside type 7 and type 42 operations | Operation builders, schema definitions | -| Chain properties field names (`inflation_witness_percent`, etc.) | `chain_properties_update` builder | -| TypeScript interfaces / PHP classes for operation structs | Type definitions | -| TypeScript interfaces / PHP classes for `validator_schedule_object` | Type definitions | - -### Do NOT need to change (library receives from node and relays): - -| What | Reason | -|------|--------| -| Block header fields | **Done.** Node now returns `validator` / `validator_signature`. Libraries that relay raw block objects with no field-specific code need no changes. Code explicitly accessing `.validator` / `.witness_signature` on a block header must be updated to `.validator` / `.validator_signature`. | -| Raw API response objects (beyond type definitions) | JSON deserialization is dynamic — field access works regardless of name if library doesn't validate field names | -| Historical transaction data from node | Same — the node returns it, library relays it | - -**Practical rule:** if your library has a hardcoded string `"validator"` as a field name when *building* a JSON object to *send* to a node — that string must be updated. If the string `"validator"` only appears in a *comment*, a *display label*, or a *type definition that only affects IDE autocomplete* — it can wait. - ---- - -## Current Status (What Has Been Done vs What Is Planned) - -| Layer | Status | Visible to JS/PHP? | -|-------|--------|---------------------| -| Internal C++ methods (e.g., `is_validator_scheduled_soon`) | **Done** | No | -| Internal enums (`block_validation_condition`) | **Done** | No | -| Internal skip flags (`skip_validator_signature`) | **Done** | No | -| Block header fields (`validator`, `validator_signature`) | **Done** | Yes — block responses | -| Dynamic global property (`current_validator`) | **Done** | Yes — `get_dynamic_global_properties` | -| Protocol operation struct names and JSON names | **Done** | Yes — JSON name in transactions | -| Operation field names inside types 7 and 42 | **Done** | Yes — field names in operation body | -| Chain properties field names | **Done** | Yes — JSON field names in governance ops | -| API method names (`get_active_validators`, etc.) | **Done** | Yes — JSON-RPC calls | -| Chain object types (`validator_object`, etc.) | **Done** | Yes — API response type names | -| CLI wallet commands (`get_active_validators`, etc.) | **Done** | Yes — if using CLI wallet | -| Physical file renames (`.hpp`/`.cpp`, directories) | **Done** | No — internal build only | -| Plugin directory and CMake target renames | **Done** | No — internal build | -| Config key renames (`plugin = validator`, etc.) | **Done** | Yes — node operators must update `config.ini` | -| API namespace (`validator_api`) | **Done** | Yes — JSON-RPC `"api"` field (see Section 2) | -| `account_api_object` fields (`validators_voted_for`, `validators_vote_weight`, `validator_votes`) | **Done** | Yes — `get_accounts` response | -| `get_config` keys (`CHAIN_MAX_VALIDATORS`, `CHAIN_HARDFORK_REQUIRED_VALIDATORS`, etc.) | **Done** | Yes — `get_config` response | -| Config constants (`CHAIN_MAX_VALIDATORS`, `CHAIN_BLOCK_VALIDATOR_REPEAT`, `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT`, etc.) | **Done** | No — internal C++ only | - ---- - -## 1. Protocol Operations (JSON-RPC Transaction Submission) - -These are the operations users submit in transactions. The **integer type ID never changes** — only the JSON string name changes. - -### Operations to Rename - -| Type ID | Current JSON Name (old) | New JSON Name | Virtual? | Fields (unchanged) | -|---------|------------------------|---------------|----------|---------------------| -| `6` | `witness_update` | `validator_update` | no | `owner`, `url`, `block_signing_key` | -| `7` | `account_witness_vote` | `account_validator_vote` | no | `account`, **`validator` → `validator`**, `approve` | -| `8` | `account_witness_proxy` | `account_validator_proxy` | no | `account`, `proxy` | -| `30` | `shutdown_witness` | `shutdown_validator` | **yes** | `owner` | -| `42` | `witness_reward` | `validator_reward` | **yes** | **`validator` → `validator`**, `shares` | - -> **Field renames inside operations:** In type 7 (`account_validator_vote`) the field `validator` (the target account name) is renamed to `validator`. In type 42 (`validator_reward`) the field `validator` is renamed to `validator`. The node accepts both old and new field names in incoming JSON, but responses use new names only. - -### What JS/PHP Developers Must Handle - -**Sending transactions (2 safe approaches):** - -```js -// Approach A: Use integer type ID (always safe, never breaks) -const op = [6, { - owner: 'alice', - url: 'https://alice.example.com', - block_signing_key: 'VIZ5hq...', -}]; - -// Approach B: Use string name (need to support both old and new) -const op = ['validator_update', { // new name - owner: 'alice', - url: 'https://alice.example.com', - block_signing_key: 'VIZ5hq...', -}]; -``` - -**Sending type 7 (vote) with updated field name:** - -```js -// Old (still accepted by node, but deprecated): -const op = [7, { account: 'alice', validator: 'bob', approve: true }]; - -// New (correct): -const op = [7, { account: 'alice', validator: 'bob', approve: true }]; -``` - -**Receiving transactions (operation history, block parsing):** - -```js -// Old server response: -["witness_update", { "owner": "alice", ... }] - -// New server response: -["validator_update", { "owner": "alice", ... }] - -// Your code must accept BOTH names for the same operation -``` - -**Server-side fallback:** The C++ node will accept both old and new JSON names in incoming transactions. But responses will use **new names only**. - -### Implementation Pattern for JS/PHP - -```js -// Operation name mapping (accept both, send new) -const OP_NAME_MAP = { - 'witness_update': 'validator_update', - 'account_witness_vote': 'account_validator_vote', - 'account_witness_proxy': 'account_validator_proxy', - 'shutdown_witness': 'shutdown_validator', - 'witness_reward': 'validator_reward', -}; - -// Reverse map (for receiving — normalize old names to new) -const OP_ALIAS_MAP = { - 'witness_update': 'validator_update', - 'account_witness_vote': 'account_validator_vote', - 'account_witness_proxy': 'account_validator_proxy', - 'shutdown_witness': 'shutdown_validator', - 'witness_reward': 'validator_reward', -}; - -// Type ID constants (never change) -const OP_TYPE_ID = { - validator_update: 6, - account_validator_vote: 7, - account_validator_proxy: 8, - shutdown_validator: 30, - validator_reward: 42, -}; -``` - -```php -// PHP equivalent -const OP_NAME_MAP = [ - 'witness_update' => 'validator_update', - 'account_witness_vote' => 'account_validator_vote', - 'account_witness_proxy' => 'account_validator_proxy', - 'shutdown_witness' => 'shutdown_validator', - 'witness_reward' => 'validator_reward', -]; - -const OP_TYPE_ID = [ - 'validator_update' => 6, - 'account_validator_vote' => 7, - 'account_validator_proxy' => 8, - 'shutdown_validator' => 30, - 'validator_reward' => 42, -]; -``` - ---- - -## 2. API Methods (JSON-RPC Calls) - -### API Namespace - -**Done.** The JSON-RPC namespace is now **`"validator_api"`**. Old clients still using `"witness_api"` will fail — they must update: - -```json -{ "api": "validator_api", "method": "get_active_validators", "params": [] } -``` - -Implementation pattern for dual support during library migration: - -```js -async function callApi(method, params) { - try { - return await rpc({ api: 'validator_api', method, params }); - } catch (e) { - // Fallback for old nodes not yet upgraded - return await rpc({ api: 'witness_api', method, params }); - } -} -``` - -### Methods to Rename - -| Current Name (old) | New Name | Returns (unchanged) | -|-------------------|----------|---------------------| -| `get_active_witnesses` | `get_active_validators` | `vector` | -| `get_witness_schedule` | `get_validator_schedule` | `witness_schedule_object` → `validator_schedule_object` | -| `get_witnesses` | `get_validators` | `vector>` → `validator_api_object` | -| `get_witness_by_account` | `get_validator_by_account` | `optional` → `validator_api_object` | -| `get_witnesses_by_vote` | `get_validators_by_vote` | `vector` → `validator_api_object` | -| `get_witnesses_by_counted_vote` | `get_validators_by_counted_vote` | `vector` → `validator_api_object` | -| `get_witness_count` | `get_validator_count` | `uint64_t` | -| `lookup_witness_accounts` | `lookup_validator_accounts` | `set` | - -### What JS/PHP Developers Must Handle - -**Server-side fallback:** Old method names will remain as deprecated aliases for one release cycle. Calling `get_active_witnesses` will still work but will log a deprecation warning on the server. - -### Implementation Pattern for JS/PHP - -```js -// Dual-support API wrapper -class VizApi { - async getActiveValidators() { - try { - return await this.call('get_active_validators'); - } catch (e) { - // Fallback to old name for older nodes - return await this.call('get_active_witnesses'); - } - } - - async getValidatorByAccount(account) { - try { - return await this.call('get_validator_by_account', [account]); - } catch (e) { - return await this.call('get_witness_by_account', [account]); - } - } - - // ... same pattern for all renamed methods -} -``` - ---- - -## 3. API Response Objects - -### Object Types to Rename - -| Current Name (old) | New Name | Key Fields (unchanged) | -|-------------------|----------|------------------------| -| `witness_object` | `validator_object` | `id`, `owner`, `url`, `signing_key`, `votes`, `schedule` | -| `witness_schedule_object` | `validator_schedule_object` | `current_shuffled_validators[]`, `num_scheduled` | -| `witness_api_object` | `validator_api_object` | All fields same as `witness_object` + computed fields | - -### Field Renames in Response Objects - -| Object | Current Field Name (old) | New Field Name | -|--------|-------------------------|----------------| -| `validator_schedule_object` | `current_shuffled_witnesses` | `current_shuffled_validators` | -| `validator_schedule_object` | `num_scheduled_witnesses` | `num_scheduled_validators` | - -### What JS/PHP Developers Must Handle - -```js -// Old response: -{ - "current_shuffled_witnesses": ["alice", "bob", ...], - "num_scheduled_witnesses": 21 -} - -// New response: -{ - "current_shuffled_validators": ["alice", "bob", ...], - "num_scheduled_validators": 21 -} - -// Safe accessor pattern -function getShuffledValidators(schedule) { - return schedule.current_shuffled_validators - || schedule.current_shuffled_witnesses; // fallback for old nodes -} -``` - ---- - -## 4. Operation Field Names (Changing and Unchanged) - -### Fields Being Renamed - -| Field (old) | Field (new) | Operation | Note | -|-------------|-------------|-----------|------| -| `validator` | `validator` | `account_validator_vote` (type 7) | Target account name | -| `validator` | `validator` | `validator_reward` (type 42) | Virtual op — library receives, not constructs | - -The node accepts both old and new field names in incoming JSON (backward compat). Responses always use new names. - -### Fields That Stay Unchanged - -| Field | Operation | Why It Stays | -|-------|-----------|-------------| -| `owner` | `validator_update` (type 6) | Describes the account, not the role | -| `url` | `validator_update` (type 6) | URL is a URL | -| `block_signing_key` | `validator_update` (type 6) | Describes the cryptographic key purpose | -| `account` | `account_validator_vote` (type 7) | Describes the voting account | -| `proxy` | `account_validator_proxy` (type 8) | Describes the proxy account | -| `approve` | `account_validator_vote` (type 7) | Boolean flag | -| `shares` | `validator_reward` (type 42) | Vesting shares amount | - ---- - -## 5. Chain Properties Field Renames - -**This section is critical for library developers.** The `chain_properties_update_operation` and `versioned_chain_properties_update_operation` carry governance parameters with `validator` in their names. These field names change in JSON. Libraries that construct these operations must update field names. - -**Binary format is safe** — field order is preserved in binary serialization, names are not written. Only JSON field names change. - -### Fields Being Renamed - -| Old Field Name | New Field Name | In Struct | -|----------------|----------------|-----------| -| `inflation_witness_percent` | `inflation_validator_percent` | `chain_properties_hf4` | -| `witness_miss_penalty_percent` | `validator_miss_penalty_percent` | `chain_properties_hf6` | -| `witness_miss_penalty_duration` | `validator_miss_penalty_duration` | `chain_properties_hf6` | -| `witness_declaration_fee` | `validator_declaration_fee` | `chain_properties_hf9` | - -### What JS/PHP Developers Must Handle - -```js -// Old (still accepted by node with compat layer): -const props = { - inflation_witness_percent: 1500, - witness_miss_penalty_percent: 100, - witness_miss_penalty_duration: 86400, - witness_declaration_fee: { amount: '10000', asset: 'VIZ' }, -}; - -// New (correct): -const props = { - inflation_validator_percent: 1500, - validator_miss_penalty_percent: 100, - validator_miss_penalty_duration: 86400, - validator_declaration_fee: { amount: '10000', asset: 'VIZ' }, -}; -``` - -```php -// PHP equivalent -$props = [ - 'inflation_validator_percent' => 1500, - 'validator_miss_penalty_percent' => 100, - 'validator_miss_penalty_duration' => 86400, - 'validator_declaration_fee' => ['amount' => '10000', 'asset' => 'VIZ'], -]; -``` - ---- - -## 6. Config Keys (For Node Operators) - -Not directly relevant to JS/PHP libraries, but included for completeness: - -| Current Config Key (old) | New Config Key | -|--------------------------|----------------| -| `plugin = validator` | `plugin = validator` | -| `plugin = witness_api` | `plugin = validator_api` | -| `plugin = witness_guard` | `plugin = validator_guard` | -| `--validator = "name"` | `--validator = "name"` | -| `validator-guard-enabled` | `validator-guard-enabled` | -| `validator-guard-disable` | `validator-guard-disable` | -| `validator-guard-interval` | `validator-guard-interval` | -| `validator-guard-validator` | `validator-guard-validator` | - ---- - -## 7. CLI Wallet Commands (If Using CLI Wallet) - -| Current Command (old) | New Command | Notes | -|----------------------|-------------|-------| -| `list_witnesses()` | `list_validators()` | Read-only | -| `get_witness()` | `get_validator()` | Read-only | -| `get_active_witnesses()` | `get_active_validators()` | Read-only | -| `update_witness()` | `update_validator()` | Sends type 6 | -| `vote_for_witness()` | `vote_for_validator()` | Sends type 7 | -| `set_voting_proxy()` | `set_voting_proxy()` | Command name stays, sends type 8 | - ---- - -## 8. What NEVER Changes - -| Item | Why | -|------|-----| -| Integer type IDs (6, 7, 8, 30, 42) | Binary wire format uses integer indices | -| Binary serialization of operations | Struct field order is preserved; field names are not written to binary | -| Field names `block_signing_key`, `url`, `approve`, `proxy` | Describe data, not the role | -| Null key for deactivation: `VIZ1111111111111111111111111111111114T1Anm` | Same null key format | -| Signing authority level (`active`) | Operations still require active authority | -| Block interval, slot scheduling, consensus rules | Unchanged | - -> **Block header fields** are now `validator` and `validator_signature` in all node responses. Binary wire format is unchanged — field names are not serialized, only values by position. Libraries relaying raw block objects reflect new names automatically; no version negotiation needed. - ---- - -## 9. Migration Strategy for Library Developers - -### Phase A — Prepare (Before Node Upgrade) - -1. Add dual-name support for operation type identification: - - Accept both `witness_update` and `validator_update` as name for type ID 6 - - Accept both `account_witness_vote` and `account_validator_vote` as name for type ID 7 - - Same for types 8, 30, 42 -2. Update field names in operation builders: - - Type 7: accept both `validator` and `validator` for the target account field; send `validator` - - Chain properties: add new field names; keep old for backward compat with old nodes -3. Add dual-name support for API methods: - - Try new method name first, fall back to old name -4. Add dual field access for response objects: - - Check `current_shuffled_validators` first, fall back to `current_shuffled_witnesses` -5. **Send transactions using integer type IDs** for maximum compatibility - -### Phase B — After Node Upgrade - -1. Default to new names for sending -2. Keep old name acceptance for receiving (history may contain old-format blocks) -3. Release library update with both names supported - -### Phase C — Cleanup (After All Nodes Upgraded) - -1. Remove old name fallbacks -2. Use only new names throughout - ---- - -## 10. Complete Quick-Reference Table - -### Operations - -| Type ID | Old JSON Name | New JSON Name | Field changes | -|---------|--------------|---------------|---------------| -| 6 | `witness_update` | `validator_update` | none | -| 7 | `account_witness_vote` | `account_validator_vote` | `validator` → `validator` | -| 8 | `account_witness_proxy` | `account_validator_proxy` | none | -| 30 | `shutdown_witness` | `shutdown_validator` | none | -| 42 | `witness_reward` | `validator_reward` | `validator` → `validator` | - -### API Methods - -| Old Name | New Name | -|----------|----------| -| `get_active_witnesses` | `get_active_validators` | -| `get_witness_schedule` | `get_validator_schedule` | -| `get_witnesses` | `get_validators` | -| `get_witness_by_account` | `get_validator_by_account` | -| `get_witnesses_by_vote` | `get_validators_by_vote` | -| `get_witnesses_by_counted_vote` | `get_validators_by_counted_vote` | -| `get_witness_count` | `get_validator_count` | -| `lookup_witness_accounts` | `lookup_validator_accounts` | - -### Response Fields - -| Old Field | New Field | In Object | -|-----------|-----------|-----------| -| `current_shuffled_witnesses` | `current_shuffled_validators` | `validator_schedule_object` | -| `num_scheduled_witnesses` | `num_scheduled_validators` | `validator_schedule_object` | - -### Block Header Fields - -| Old Field | New Field | -|-----------|-----------| -| `validator` | `validator` | -| `witness_signature` | `validator_signature` | - -### Dynamic Global Property Fields - -| Old Field | New Field | -|-----------|-----------| -| `current_witness` | `current_validator` | - -### Chain Properties Fields - -| Old Field | New Field | In Struct | -|-----------|-----------|-----------| -| `inflation_witness_percent` | `inflation_validator_percent` | `chain_properties_hf4` | -| `witness_miss_penalty_percent` | `validator_miss_penalty_percent` | `chain_properties_hf6` | -| `witness_miss_penalty_duration` | `validator_miss_penalty_duration` | `chain_properties_hf6` | -| `witness_declaration_fee` | `validator_declaration_fee` | `chain_properties_hf9` | - -### Account Object Fields (`get_accounts` response) - -| Old Field | New Field | Notes | -|-----------|-----------|-------| -| `witnesses_voted_for` | `validators_voted_for` | Count of validators the account voted for | -| `witnesses_vote_weight` | `validators_vote_weight` | Cached voting weight | -| `witness_votes` | `validator_votes` | Set of validator account names voted for | - -### `get_config` Response Keys - -| Old Key | New Key | -|---------|---------| -| `CHAIN_HARDFORK_REQUIRED_WITNESSES` | `CHAIN_HARDFORK_REQUIRED_VALIDATORS` | -| `CHAIN_MAX_ACCOUNT_WITNESS_VOTES` | `CHAIN_MAX_ACCOUNT_VALIDATOR_VOTES` | -| `CHAIN_MAX_WITNESSES` | `CHAIN_MAX_VALIDATORS` | -| `CHAIN_MAX_SUPPORT_WITNESSES` | `CHAIN_MAX_SUPPORT_VALIDATORS` | -| `CHAIN_MAX_TOP_WITNESSES` | `CHAIN_MAX_TOP_VALIDATORS` | -| `CHAIN_MAX_WITNESS_URL_LENGTH` | `CHAIN_MAX_VALIDATOR_URL_LENGTH` | diff --git a/.qoder/docs/witness-to-validator-rename.md b/.qoder/docs/witness-to-validator-rename.md deleted file mode 100644 index 100a1aad34..0000000000 --- a/.qoder/docs/witness-to-validator-rename.md +++ /dev/null @@ -1,402 +0,0 @@ -# Naming Analysis: VIZ "validator" → "Validator" Rename Proposal - -## 1. What VIZ validators Actually Do - -Looking at the codebase, VIZ validators perform two distinct roles: - -**Block production** — scheduled in rotation, they run `block_production_loop()`, sign blocks with `block_signing_key`, and broadcast them via P2P. - -**Block post validation (BPV)** — they sign `block_post_validation_object`s to confirm blocks produced by other validators, which drives LIB (Last Irreversible Block) advancement. - -So validators are both producers and validators — which is exactly why "validator" is semantically weak. It sounds passive ("I saw this happen") and says nothing about their active role in consensus. - ---- - -## 2. What XRPL Calls These Nodes - -XRPL uses: - -| XRPL Term | Meaning | -|-----------|---------| -| **Validator** | A server actively participating in consensus — proposes, votes on, and confirms transaction sets | -| **UNL (Unique Node List)** | The trusted set of validators a node relies on | -| **Validation vote** | A cryptographic fingerprint published post-consensus round | -| **Participant** | Generic term for any consensus node | - -XRPL does not split "producer" from "validator" — validators do both. This matches exactly how VIZ validators work. - -The same pattern holds across other major PoS ecosystems: -- **Ethereum PoS** — validators attest and propose blocks -- **Cosmos / Tendermint** — validators propose and pre-vote/pre-commit blocks -- **Polkadot** — validators produce and attest parachain blocks - ---- - ---- - -## 2b. Current Implementation Status (2026-05-17) - -### Done - -| Item | Notes | -|------|-------| -| Protocol operations (types 6, 7, 8, 30, 42) | Renamed; old-name alias table in `operation_util_impl.cpp` | -| Operation field names (`validator` → `validator` in types 7 and 42) | Node accepts both old and new on input | -| Chain properties fields (`inflation_validator_percent`, etc.) | Old names accepted in snapshot import | -| Chain objects (`validator_object`, `validator_schedule_object`) | C++ types renamed; files not renamed yet | -| Schedule fields (`current_shuffled_validators`, `num_scheduled_validators`) | Done | -| API object (`validator_api_object`) | Done | -| API methods (`get_active_validators`, `get_validator_by_account`, etc.) | Done | -| CLI wallet commands (`get_active_validators`, `vote_for_validator`, etc.) | Done | -| Block header fields (`validator`, `validator_signature`) | Done | -| Dynamic global property (`current_validator`) | Done | -| Internal skip flag (`skip_validator_signature`) | Done | -| P2P function signatures (`validator_signature` parameter) | Done | -| Internal plugin methods (`block_validation_loop`, `maybe_validate_block`, etc.) | Done | -| Internal enum (`block_validation_condition`) | Done | -| Snapshot backward compat | Old field names accepted on import; new names on export | -| Operation name alias table | `resolve_operation_name()` in `operation_util_impl.cpp` | - -### Deferred to Future PR - -Nothing remaining. All renames complete. - -### Explicitly Kept (not renamed) - -- `witness_vote_object` — internal vote-tracking object; not exposed by name in protocol -- `witness_penalty_expire_object` — internal object; not exposed in protocol -- `witness_penalty_expire_object::validator` field — internal back-reference, not a block header field -- `witness_vote_index`, `by_account_witness` — chainbase index tags; tied to `witness_vote_object`, not renamed - ---- - -## 3. How Operations Are Serialized (Critical for Compatibility) - -Understanding the wire format determines what is and is not a breaking change. - -### Binary / wire protocol - -Operations in `fc::static_variant` are serialized as `[integer_index, {fields}]`: - -```cpp -// thirdparty/fc/include/fc/static_variant.hpp -void from_variant(const fc::variant &v, fc::static_variant &s) { - auto ar = v.get_array(); - s.set_which(ar[0].as_uint64()); // INTEGER index — not the struct name - s.visit(to_static_variant(ar[1])); -} -``` - -**Renaming C++ struct names has zero impact on the binary wire format** as long as the order in the `static_variant` list in `operations.hpp` is preserved. - -### JSON-RPC name format - -Operation names exposed via JSON-RPC are derived from the C++ type name by `name_from_type()`: - -```cpp -// libraries/protocol/operation_util_impl.cpp -std::string name_from_type(const std::string &type_name) { - auto start = type_name.find_last_of(':') + 1; - auto end = type_name.find_last_of('_'); - return type_name.substr(start, end - start); - // "graphene::protocol::witness_update_operation" → "witness_update" -} -``` - -**Renaming a struct changes its JSON name.** Any JS/PHP/Python client that submits transactions using string operation names (e.g. `["witness_update", {...}]`) will break if the struct is renamed without a compatibility layer. - ---- - -## 4. Backward-Compatibility Fallback (Old Client Support) - -**Yes, a fallback is feasible without a hardfork.** - -The approach: add a static alias table in the JSON deserialization path that maps old string names to new ones before the type lookup. Clients sending `"witness_update"` would be transparently remapped to `"validator_update"`. - -### Implementation location - -The alias mapping belongs in the operation variant `from_variant` path, in: -- `libraries/protocol/operation_util_impl.cpp` — `name_from_type()` or a new `resolve_operation_name()` wrapper -- Or in the JSON-RPC layer that dispatches incoming transaction broadcasts - -### Alias table (old → new) - -| Old JSON name | New JSON name | -|--------------|---------------| -| `witness_update` | `validator_update` | -| `account_witness_vote` | `account_validator_vote` | -| `account_witness_proxy` | `account_validator_proxy` | -| `shutdown_witness` | `shutdown_validator` | -| `witness_reward` | `validator_reward` | - -### Behavior - -- Nodes running the renamed version accept **both** old and new operation names in incoming JSON. -- Nodes serialize outgoing JSON using **new names only**. -- Binary wire format is unchanged — integer indices are stable. -- No hardfork needed for the fallback layer itself. -- JS/PHP/CLI clients that have not been updated continue to work transparently. -- The fallback can be removed in a future release after all clients are migrated. - ---- - -## 5. Full Rename Tables - -### 5.1 Protocol Operations - -These are the on-chain operations. The C++ struct rename is a code-only change (binary wire index preserved); the JSON name fallback handles old clients. - -| Current struct name | New struct name | JSON old name | JSON new name | Type ID | Virtual? | -|--------------------|-----------------|--------------|---------------|---------|----------| -| `witness_update_operation` | `validator_update_operation` | `witness_update` | `validator_update` | 6 | no | -| `account_witness_vote_operation` | `account_validator_vote_operation` | `account_witness_vote` | `account_validator_vote` | 7 | no | -| `account_witness_proxy_operation` | `account_validator_proxy_operation` | `account_witness_proxy` | `account_validator_proxy` | 8 | no | -| `shutdown_witness_operation` | `shutdown_validator_operation` | `shutdown_witness` | `shutdown_validator` | 50 | yes | -| `witness_reward_operation` | `validator_reward_operation` | `witness_reward` | `validator_reward` | 66 | yes | - -> `chain_properties_update_operation` and `versioned_chain_properties_update_operation` — these are submitted by validators but describe chain property voting, not the validator role itself. **Keep names as-is.** - -### 5.2 Core Objects and Types - -| Current Name | New Name | File | -|-------------|----------|------| -| `witness_object` | `validator_object` | `libraries/chain/include/graphene/chain/witness_objects.hpp` | -| `witness_schedule_object` | `validator_schedule_object` | `libraries/chain/include/graphene/chain/witness_objects.hpp` | -| `witness_schedule_type` | `validator_schedule_type` | `libraries/chain/include/graphene/chain/witness_objects.hpp` | -| `current_shuffled_witnesses[]` | `current_shuffled_validators[]` | field in `validator_schedule_object` | -| `block_post_validation_object` | `validator_confirmation_object` | `libraries/chain/include/graphene/chain/chain_objects.hpp` | -| `witness_api_object` | `validator_api_object` | `libraries/api/include/graphene/api/witness_api_object.hpp` | - -### 5.3 Internal Enum (`block_production_condition`) - -| Current Name | New Name | File | -|-------------|----------|------| -| namespace `block_production_condition` | `block_validation_condition` | `plugins/validator/include/graphene/plugins/validator/validator.hpp` | -| `block_production_condition_enum` | `block_validation_condition_enum` | same | -| `exception_producing_block` | `exception_validating_block` | same | - -All other enum values (`produced`, `not_synced`, `not_my_turn`, `not_time_yet`, `no_private_key`, `low_participation`, `lag`, `consecutive`, `fork_collision`, `minority_fork`) need no rename. - -### 5.4 Plugin Internal Methods - -| Current Name | New Name | File | -|-------------|----------|------| -| `block_production_loop()` | `block_validation_loop()` | `plugins/validator/validator.cpp` | -| `maybe_produce_block()` | `maybe_validate_block()` | `plugins/validator/validator.cpp` | -| `is_witness_scheduled_soon()` | `is_validator_scheduled_soon()` | `plugins/validator/validator.hpp` | - -### 5.5 Plugins (Directories and CMake Targets) - -| Current Name | New Name | -|-------------|----------| -| `witness_plugin` | `validator_plugin` | -| `plugins/validator/` | `plugins/validator/` | -| `witness_api_plugin` | `validator_api_plugin` | -| `plugins/witness_api/` | `plugins/validator_api/` | -| `witness_guard_plugin` | `validator_guard_plugin` | -| `plugins/witness_guard/` | `plugins/validator_guard/` | - -### 5.6 validator API Endpoints (JSON-RPC) - -| Current Name | New Name | -|-------------|----------| -| `get_active_witnesses()` | `get_active_validators()` | -| `get_witness_schedule()` | `get_validator_schedule()` | -| `get_witnesses()` | `get_validators()` | -| `get_witness_by_account()` | `get_validator_by_account()` | -| `get_witnesses_by_vote()` | `get_validators_by_vote()` | -| `get_witnesses_by_counted_vote()` | `get_validators_by_counted_vote()` | -| `get_witness_count()` | `get_validator_count()` | -| `lookup_witness_accounts()` | `lookup_validator_accounts()` | - -> API endpoint fallback: keep old method names as deprecated aliases that forward to new implementations for one release cycle. - -### 5.7 CLI Wallet Commands - -| Current Command | New Command | Operation Used | -|----------------|-------------|----------------| -| `list_witnesses()` | `list_validators()` | — (read) | -| `get_witness()` | `get_validator()` | — (read) | -| `get_active_witnesses()` | `get_active_validators()` | — (read) | -| `update_witness()` | `update_validator()` | `validator_update_operation` | -| `vote_for_witness()` | `vote_for_validator()` | `account_validator_vote_operation` | -| `set_voting_proxy()` | `set_voting_proxy()` | `account_validator_proxy_operation` — command name stays | - -File: `libraries/wallet/wallet.cpp` and `libraries/wallet/include/graphene/wallet/wallet.hpp` - -### 5.8 Configuration Keys - -| Current Key | New Key | File | -|------------|---------|------| -| `plugin = validator` | `plugin = validator` | `config_witness.ini` | -| `plugin = witness_guard` | `plugin = validator_guard` | `config_witness.ini` | -| `plugin = witness_api` | `plugin = validator_api` | `config_witness.ini` | -| `--validator = "name"` | `--validator = "name"` | `config_witness.ini` | -| `validator-guard-enabled` | `validator-guard-enabled` | `config_witness.ini` | -| `validator-guard-disable` | `validator-guard-disable` | `config_witness.ini` | -| `validator-guard-interval` | `validator-guard-interval` | `config_witness.ini` | -| `validator-guard-validator` | `validator-guard-validator` | `config_witness.ini` | - -Config keys fallback: on startup, if an old key is detected emit a warning — `"Config key 'validator' is deprecated, use 'validator'"` — and continue reading the value. - ---- - -## 6. External Client Libraries - -JS and PHP libraries are external repositories not in this codebase. They reference: -- Operation names as strings: `"witness_update"`, `"account_witness_vote"`, `"account_witness_proxy"` -- API method names: `get_active_witnesses()`, `get_witness_by_account()`, etc. - -### Impact without fallback - -| Client action | Breaks without fallback? | -|--------------|--------------------------| -| Submit `witness_update` transaction by string name | Yes | -| Submit transaction by integer type ID (6, 7, 8) | No — wire format unchanged | -| Call `get_active_witnesses()` API | Yes | -| Read operation from block history | No — history uses integer IDs | - -### Impact with fallback (Section 4) - -| Client action | Breaks with fallback? | -|--------------|----------------------| -| Submit `witness_update` by string name | No — alias maps to new name | -| Call `get_active_witnesses()` | No — old endpoint aliased | -| Receive response containing `validator_update` instead of `witness_update` | Yes — clients parsing response type names will see the new name | - -### Required changes in JS/PHP libs - -Even with server-side fallback, clients will receive **responses** with new names. The minimum update per library: - -| What to update | Detail | -|---------------|--------| -| Operation name constants | `"witness_update"` → `"validator_update"` etc. | -| API method names in client code | `getActiveWitnesses()` → `getActiveValidators()` etc. | -| Response field parsing | Any code checking `op[0] === "witness_update"` | -| Type constants / enums | Any named constants for operation types | - ---- - -## 7. Terms to Keep Unchanged - -| Identifier | Why it stays | -|------------|-------------| -| `block_signing_key` / `signing_key` | Accurately describes the cryptographic key used to sign blocks and post-validations | -| `delegate_vesting_shares_operation` | "Delegate" is already taken in VIZ for vesting share delegation — **do not use "delegate" as the consensus-role name** | -| `chain_properties_update_operation` | Describes chain governance, not the validator role | -| `versioned_chain_properties_update_operation` | Same | -| Enum values `top`, `support`, `none` | Scheduling tier names, not role names | - ---- - -## 8. Implementation Phases - -### Phase 1 — Internal rename (zero breaking changes) ✅ Done - -1. ✅ Rename `block_production_condition` namespace, enum, and `exception_producing_block` in `validator.hpp` + `validator.cpp`. -2. ✅ Rename internal method names: `block_production_loop`, `maybe_produce_block`, `is_witness_scheduled_soon`. -3. ⏳ Rename `block_post_validation_object` → `validator_confirmation_object` — deferred with physical file renames. -4. ✅ Rename `current_shuffled_witnesses[]` field. -5. ✅ Build verified. -6. ✅ `.qoder/` documentation updated. - -### Phase 2 — API and config rename (with fallbacks) ✅ Done - -1. ✅ Add operation name alias table in `operation_util_impl.cpp` — old JSON names → new names. -2. ✅ Rename `witness_update_operation` → `validator_update_operation` and the other four operations (Section 5.1). Binary type IDs preserved. -3. ✅ Add deprecated endpoint aliases in `validator_api` plugin for all `get_witness_*` methods. -4. ✅ Rename CLI wallet commands; keep old names as deprecated aliases. -5. ✅ Config keys updated (`plugin = validator`, `plugin = validator_api`, `plugin = validator_guard`). `--validator` kept as deprecated alias for `--validator` in config.ini backward compat. -6. ✅ Plugin directories renamed: `plugins/validator/`, `plugins/validator_api/`, `plugins/validator_guard/`. CMake targets updated. -7. ✅ Rename `witness_object`, `witness_schedule_object`, `witness_api_object` (C++ types and files). -8. ✅ `config_witness.ini` and all other `config*.ini` updated to new plugin names. -9. ✅ Rename block header fields: `validator`, `validator_signature`. -10. ✅ Rename dynamic global property field: `current_validator`. -11. ✅ Rename skip flag: `skip_validator_signature`. -12. ✅ Plugin namespaces: `validator_plugin`, `validator_api`, `validator_guard`. `plugin_name` strings updated. - -### Phase 2 — Additional API-visible fields ✅ Done - -1. ✅ Rename `account_object` fields: `witnesses_voted_for` → `validators_voted_for`, `witnesses_vote_weight` → `validators_vote_weight`; methods `witness_vote_weight()` → `validator_vote_weight()`, `witness_vote_fair_weight()` → `validator_vote_fair_weight()`, `witness_vote_fair_weight_prehf5()` → `validator_vote_fair_weight_prehf5()`. -2. ✅ Rename `account_api_object` fields: `witnesses_voted_for`, `witnesses_vote_weight`, `witness_votes` → `validators_voted_for`, `validators_vote_weight`, `validator_votes`. -3. ✅ Rename `config.hpp`/`config_testnet.hpp` constants: `CHAIN_MAX_WITNESSES` → `CHAIN_MAX_VALIDATORS`, `CHAIN_BLOCK_WITNESS_REPEAT` → `CHAIN_BLOCK_VALIDATOR_REPEAT`, `CHAIN_EMERGENCY_WITNESS_ACCOUNT` → `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT`, `CHAIN_HARDFORK_REQUIRED_WITNESSES` → `CHAIN_HARDFORK_REQUIRED_VALIDATORS`, `CHAIN_MAX_ACCOUNT_WITNESS_VOTES` → `CHAIN_MAX_ACCOUNT_VALIDATOR_VOTES`, `CHAIN_MAX_WITNESS_URL_LENGTH` → `CHAIN_MAX_VALIDATOR_URL_LENGTH`, `CONSENSUS_WITNESS_MISS_PENALTY_*` → `CONSENSUS_VALIDATOR_MISS_PENALTY_*`, `CONSENSUS_WITNESS_DECLARATION_FEE` → `CONSENSUS_VALIDATOR_DECLARATION_FEE` etc. -4. ✅ `get_config.cpp` API key strings updated to new names. -5. ✅ Snapshot `import_accounts` — backward compat for old `witnesses_voted_for` key. - -### Phase 3 — External library updates - -1. Update JS client library: operation name constants, API method names, response parsing, block header field names, `get_config` key names, `account_api_object` field names. -2. Update PHP client library: same scope. -3. After both libraries are released, schedule removal of the server-side fallback aliases. - ---- - -## 9. Files Affected (Source Code) - -| File | Status | Scope | -|------|--------|-------| -| `plugins/validator/include/graphene/plugins/validator/validator.hpp` | ✅ Done | Enum namespace, method declarations | -| `plugins/validator/validator.cpp` | ✅ Done | All enum references, method definitions, block field accesses | -| `plugins/validator_guard/validator_guard.cpp` | ✅ Done | Object types, block field accesses | -| `plugins/validator_guard/include/.../validator_guard.hpp` | ✅ Done | Class names, config declarations | -| `plugins/validator_api/plugin.cpp` | ✅ Done | API method names + deprecated aliases | -| `plugins/validator_api/include/.../plugin.hpp` | ✅ Done | API method declarations | -| `plugins/p2p/p2p_plugin.cpp` | ✅ Done | `validator_signature` parameter | -| `plugins/p2p/include/.../p2p_plugin.hpp` | ✅ Done | `validator_signature` parameter | -| `plugins/chain/plugin.cpp` | ✅ Done | Block field access | -| `plugins/snapshot/plugin.cpp` | ✅ Done | Object types, field accesses, backward compat import | -| `plugins/database_api/api.cpp` | ✅ Done | Object type references | -| `plugins/account_history/plugin.cpp` | ✅ Done | Operation visitor method names | -| `libraries/chain/include/graphene/chain/validator_objects.hpp` | ✅ Done | Object type names, field names, CHAIN_MAX_VALIDATORS array size | -| `libraries/chain/include/graphene/chain/global_property_object.hpp` | ✅ Done | `current_validator` field + FC_REFLECT | -| `libraries/chain/include/graphene/chain/chain_objects.hpp` | ✅ Done | `validator_confirmation_object`, `validator_confirmation_index` | -| `libraries/chain/database.cpp` | ✅ Done | All object and block field references | -| `libraries/chain/database.hpp` | ✅ Done | `skip_validator_signature` flag | -| `libraries/protocol/include/graphene/protocol/block_header.hpp` | ✅ Done | `validator`, `validator_signature` fields | -| `libraries/protocol/block.cpp` | ✅ Done | `validator_signature` references | -| `libraries/protocol/include/graphene/protocol/chain_operations.hpp` | ✅ Done | Operation struct names, field names | -| `libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp` | ✅ Done | Virtual operation struct names | -| `libraries/protocol/include/graphene/protocol/operations.hpp` | ✅ Done | static_variant list — struct names only, order unchanged | -| `libraries/protocol/operation_util_impl.cpp` | ✅ Done | Alias table for old JSON names | -| `libraries/api/include/graphene/api/witness_api_object.hpp` | ✅ Done | `validator_api_object` type | -| `libraries/api/witness_api_object.cpp` | ✅ Done | Constructor, field assignments | -| `libraries/api/include/graphene/api/chain_api_properties.hpp` | ✅ Done | Chain properties field names | -| `libraries/api/chain_api_properties.cpp` | ✅ Done | Field assignments | -| `libraries/network/dlt_p2p_node.cpp` | ✅ Done | Block field accesses, `validator_signature` parameter | -| `libraries/network/include/graphene/network/dlt_p2p_node.hpp` | ✅ Done | `validator_signature` parameter | -| `plugins/validator/CMakeLists.txt` | ✅ Done | Target `graphene_validator`, new source/header paths | -| `plugins/validator_api/CMakeLists.txt` | ✅ Done | Target `graphene_validator_api` | -| `plugins/validator_guard/CMakeLists.txt` | ✅ Done | Target `graphene_validator_guard` | -| `plugins/p2p/CMakeLists.txt` | ✅ Done | Include path `../validator/include` | -| `plugins/snapshot/CMakeLists.txt` | ✅ Done | Link `graphene_validator` | -| `programs/vizd/CMakeLists.txt` | ✅ Done | Links `graphene::validator`, `graphene::validator_api`, `graphene::validator_guard` | -| `programs/cli_wallet/CMakeLists.txt` | ✅ Done | Link `graphene::validator_api` | -| `libraries/wallet/wallet.cpp` | ✅ Done | CLI wallet command implementations | -| `libraries/wallet/include/graphene/wallet/wallet.hpp` | ✅ Done | CLI wallet method declarations | -| `libraries/wallet/include/graphene/wallet/remote_node_api.hpp` | ✅ Done | Remote API method names | -| `libraries/chain/include/graphene/chain/account_object.hpp` | ✅ Done | `witnesses_voted_for`→`validators_voted_for`, `witnesses_vote_weight`→`validators_vote_weight`, methods | -| `libraries/api/include/graphene/api/account_api_object.hpp` | ✅ Done | Same fields + `witness_votes`→`validator_votes` | -| `libraries/api/account_api_object.cpp` | ✅ Done | Field assignments | -| `libraries/protocol/include/graphene/protocol/config.hpp` | ✅ Done | All `validator` constants → `VALIDATOR` | -| `libraries/protocol/include/graphene/protocol/config_testnet.hpp` | ✅ Done | Same | -| `libraries/protocol/get_config.cpp` | ✅ Done | API string keys updated | -| `share/vizd/config/config_witness.ini` | ✅ Done | Plugin names → `validator`, `validator_api`, `validator_guard` | -| `share/vizd/config/config.ini` | ✅ Done | Plugin names | -| `share/vizd/config/config_debug.ini` | ✅ Done | Plugin names | -| `share/vizd/config/config_debug_mongo.ini` | ✅ Done | Plugin names | -| `share/vizd/config/config_mongo.ini` | ✅ Done | Plugin names | -| `share/vizd/config/config_stock_exchange.ini` | ✅ Done | Plugin names | -| `share/vizd/config/config_testnet.ini` | ✅ Done | Plugin names | - ---- - -## 10. Summary - -`validator` → `validator` is the right rename. It: - -- Matches XRPL, Ethereum PoS, Cosmos, and Polkadot terminology -- Accurately describes both the block production and post-validation duties -- Removes the passive/observational connotation of "validator" -- Makes `block_post_validation_object` → `validator_confirmation_object` semantically clear - -**The rename is safe for unupdated JS/PHP clients** — the binary wire format uses integer type IDs, not string names, so old clients submitting transactions continue to work. A server-side name alias table handles the JSON string name fallback at zero cost. The only visible breakage for old clients is in response parsing, where they may encounter new names (`validator_update` instead of `witness_update`) in block history reads. diff --git a/.qoder/plan/dlt-snapshot-plugin.md b/.qoder/plan/dlt-snapshot-plugin.md deleted file mode 100644 index cc69c8fbd8..0000000000 --- a/.qoder/plan/dlt-snapshot-plugin.md +++ /dev/null @@ -1,862 +0,0 @@ -# DLT Snapshot Plugin — Research Document - -## 1. Overview - -This document defines the design for a **trust state / snapshot plugin** that enables the VIZ blockchain node to operate in **DLT mode** — starting instantly from a serialized state snapshot without replaying the entire block history. - -### Motivation - -Currently, starting a VIZ node requires either: -- Full sync from genesis (replay all blocks) -- Reindex from block_log (still replay all blocks) - -Both approaches are slow and resource-intensive. For many use cases (API nodes, witness nodes, monitoring), the full block history is unnecessary. A snapshot of the consensus state at a specific block allows: - -- Near-instant node startup -- Minimal disk usage (no block_log required for historical blocks) -- Fast recovery and deployment -- Ability to skip synchronization of ancient blocks entirely - -### Key Principle - -The snapshot must contain **all state required to continue block processing and validation** from the snapshot block forward. Any object that participates in consensus evaluation, witness scheduling, balance tracking, or authority verification must be included. Objects that exist only for API queries or historical lookup can be excluded. - ---- - -## 2. Object Categories - -### Legend - -| Category | Description | -|----------|-------------| -| **CRITICAL** | Directly required for consensus validation and block processing. Missing = node cannot validate blocks. | -| **IMPORTANT** | Required for correct state transitions during block processing. Missing = incorrect behavior on specific operations. | -| **OPTIONAL** | Useful for completeness but not strictly required for consensus. Can be reconstructed or skipped. | -| **EXCLUDED** | Not needed for DLT mode. Plugin-level, API-only, or purely historical data. | - ---- - -## 3. CRITICAL Objects (Must Be in Snapshot) - -These objects are directly involved in block validation, consensus, and state calculation. Without them, the node cannot process new blocks. - -### 3.1 `dynamic_global_property_object` - -- **File**: `libraries/chain/include/graphene/chain/global_property_object.hpp` -- **Index**: `dynamic_global_property_index` (by_id) -- **Why critical**: The single most important object. Tracks head block number/id, current supply, total vesting, reward fund, participation rate, last irreversible block, reserve ratio, and inflation parameters. All block processing begins by reading this object. -- **Fields to snapshot**: ALL fields - - `id`, `head_block_number`, `head_block_id`, `genesis_time`, `time`, `current_witness` - - `committee_fund`, `committee_requests`, `current_supply`, `total_vesting_fund`, `total_vesting_shares` - - `total_reward_fund`, `total_reward_shares` - - `average_block_size`, `maximum_block_size`, `current_aslot` - - `recent_slots_filled`, `participation_count` - - `last_irreversible_block_num`, `last_irreversible_block_id` - - `last_irreversible_block_ref_num`, `last_irreversible_block_ref_prefix` - - `max_virtual_bandwidth`, `current_reserve_ratio` - - `vote_regeneration_per_day`, `bandwidth_reserve_candidates` - - `inflation_calc_block_num`, `inflation_witness_percent`, `inflation_ratio` -- **Instance count**: Exactly 1 - -### 3.2 `witness_schedule_object` - -- **File**: `libraries/chain/include/graphene/chain/witness_objects.hpp` -- **Index**: `witness_schedule_index` (by_id) -- **Why critical**: Contains the current witness rotation schedule, virtual time for DPOS scheduling, median chain properties, and majority version. Required to determine which witness produces the next block. -- **Fields to snapshot**: ALL fields - - `id`, `current_virtual_time`, `next_shuffle_block_num` - - `current_shuffled_witnesses`, `num_scheduled_witnesses` - - `median_props`, `majority_version` -- **Instance count**: Exactly 1 - -### 3.3 `hardfork_property_object` - -- **File**: `libraries/chain/hardfork.d/0-preamble.hf` -- **Index**: `hardfork_property_index` (by_id) -- **Why critical**: Tracks which hardforks have been applied and the current/next hardfork versions. Determines which code paths are active during block processing. -- **Fields to snapshot**: ALL fields - - `id`, `processed_hardforks`, `last_hardfork` - - `current_hardfork_version`, `next_hardfork`, `next_hardfork_time` -- **Instance count**: Exactly 1 - -### 3.4 `account_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `account_index` (by_id, by_name, by_next_vesting_withdrawal, by_account_on_sale, by_account_on_auction, by_account_on_sale_start_time, by_subaccount_on_sale) -- **Why critical**: Contains all balance information (liquid, vesting, delegated), voting power (energy), witness voting weight (proxied_vsf_votes), bandwidth tracking, and account sale/auction state. Required for every operation involving transfers, voting, vesting, bandwidth. -- **Fields to snapshot**: ALL fields - - `id`, `name`, `memo_key`, `proxy`, `referrer` - - `last_account_update`, `created`, `recovery_account`, `last_account_recovery` - - `subcontent_count`, `vote_count`, `content_count`, `awarded_rshares` - - `custom_sequence`, `custom_sequence_block_num` - - `energy`, `last_vote_time` - - `balance`, `vesting_shares`, `delegated_vesting_shares`, `received_vesting_shares` - - `vesting_withdraw_rate`, `next_vesting_withdrawal`, `withdrawn`, `to_withdraw`, `withdraw_routes` - - `curation_rewards`, `posting_rewards`, `receiver_awards`, `benefactor_awards` - - `proxied_vsf_votes`, `witnesses_voted_for`, `witnesses_vote_weight` - - `last_root_post`, `last_post` - - `average_bandwidth`, `lifetime_bandwidth`, `last_bandwidth_update` - - `valid` - - `account_seller`, `account_offer_price`, `account_on_sale`, `account_on_sale_start_time` - - `reserved_balance` - - `target_buyer`, `account_on_auction`, `current_bid`, `current_bidder`, `current_bidder_key`, `last_bid` - - `subaccount_seller`, `subaccount_offer_price`, `subaccount_on_sale` -- **Instance count**: Equal to total accounts on chain - -### 3.5 `account_authority_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `account_authority_index` (by_id, by_account, by_last_master_update) -- **Why critical**: Contains master/active/regular authorities for every account. Required for transaction signature verification and authority checks. -- **Fields to snapshot**: ALL fields - - `id`, `account`, `master`, `active`, `regular`, `last_master_update` -- **Instance count**: Equal to total accounts on chain - -### 3.6 `witness_object` - -- **File**: `libraries/chain/include/graphene/chain/witness_objects.hpp` -- **Index**: `witness_index` (by_id, by_name, by_vote_name, by_counted_vote_name, by_schedule_time, by_work) -- **Why critical**: Contains witness signing keys, vote counts, virtual scheduling state, and properties. Required for block validation (signing key lookup) and witness scheduling. -- **Fields to snapshot**: ALL fields - - `id`, `owner`, `created`, `url`, `total_missed` - - `last_aslot`, `last_confirmed_block_num`, `current_run`, `last_supported_block_num` - - `signing_key`, `props` - - `votes`, `penalty_percent`, `counted_votes`, `schedule` - - `virtual_last_update`, `virtual_position`, `virtual_scheduled_time` - - `last_work`, `running_version`, `hardfork_version_vote`, `hardfork_time_vote` -- **Instance count**: Equal to registered witnesses - -### 3.7 `witness_vote_object` - -- **File**: `libraries/chain/include/graphene/chain/witness_objects.hpp` -- **Index**: `witness_vote_index` (by_id, by_account_witness, by_witness_account) -- **Why critical**: Links accounts to their witness votes. Required when adjusting witness vote weights on account vesting changes. -- **Fields to snapshot**: ALL fields - - `id`, `witness`, `account` -- **Instance count**: Equal to total witness votes - -### 3.8 `block_summary_object` - -- **File**: `libraries/chain/include/graphene/chain/block_summary_object.hpp` -- **Index**: `block_summary_index` (by_id) -- **Why critical**: Used for TaPOS (Transactions as Proof of Stake) validation. Transactions reference past blocks; without these, TaPOS checks fail. -- **Fields to snapshot**: ALL fields - - `id`, `block_id` -- **Instance count**: 65536 (0x10000, fixed circular buffer) -- **Note**: Only summaries for recent blocks matter. The circular buffer means older entries are overwritten. - -### 3.9 `content_object` - -- **File**: `libraries/chain/include/graphene/chain/content_object.hpp` -- **Index**: `content_index` (by_id, by_cashout_time, by_permlink, by_root, by_parent; plus by_last_update, by_author_last_update in non-low-mem) -- **Why critical**: Content objects track rshares, cashout times, and payout calculations. Active content with pending payouts directly affects inflation and reward distribution. The `by_permlink` and `by_parent` indexes are used by consensus evaluators. -- **Fields to snapshot**: ALL fields - - `id`, `parent_author`, `parent_permlink`, `author`, `permlink` - - `last_update`, `created`, `active`, `last_payout` - - `depth`, `children`, `children_rshares` - - `net_rshares`, `abs_rshares`, `vote_rshares` - - `cashout_time`, `total_vote_weight` - - `curation_percent`, `consensus_curation_percent` - - `payout_value`, `shares_payout_value`, `curator_payout_value`, `beneficiary_payout_value` - - `author_rewards`, `net_votes`, `root_content`, `beneficiaries` - -### 3.10 `content_vote_object` - -- **File**: `libraries/chain/include/graphene/chain/content_object.hpp` -- **Index**: `content_vote_index` (by_id, by_content_voter, by_voter_content, by_voter_last_update, by_content_weight_voter) -- **Why critical**: Tracks individual votes on content. Required for curation reward calculations and vote-changing logic. -- **Fields to snapshot**: ALL fields - - `id`, `voter`, `content`, `weight`, `rshares`, `vote_percent`, `last_update`, `num_changes` - -### 3.11 `block_post_validation_object` - -- **File**: `libraries/chain/include/graphene/chain/chain_objects.hpp` -- **Index**: `block_post_validation_index` (by_id) -- **Why critical**: Tracks block post-validation state used in consensus for witness participation verification. -- **Fields to snapshot**: ALL fields - - `id`, `block_num`, `block_id`, `current_shuffled_witnesses`, `current_shuffled_witnesses_validations` - ---- - -## 4. IMPORTANT Objects (Required for Correct State Transitions) - -These objects contain state that will be acted upon during normal block processing. If missing, specific operations will fail or produce incorrect results. - -### 4.1 `transaction_object` - -- **File**: `libraries/chain/include/graphene/chain/transaction_object.hpp` -- **Index**: `transaction_index` (by_id, by_trx_id, by_expiration) -- **Why important**: Prevents duplicate transaction execution. Without it, replayed transactions could be applied twice. -- **Fields to snapshot**: ALL fields - - `id`, `packed_trx`, `trx_id`, `expiration` -- **Note**: Only transactions not yet expired need to be included. Expired ones are cleaned up each block. At snapshot time, only include transactions with `expiration > snapshot_block_time`. - -### 4.2 `vesting_delegation_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `vesting_delegation_index` (by_id, by_delegation, by_received) -- **Why important**: Tracks active vesting share delegations. Required for correct `effective_vesting_shares` calculation and delegation operations. -- **Fields to snapshot**: ALL fields - - `id`, `delegator`, `delegatee`, `vesting_shares`, `min_delegation_time` - -### 4.3 `vesting_delegation_expiration_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `vesting_delegation_expiration_index` (by_id, by_expiration, by_account_expiration) -- **Why important**: Tracks pending delegation expirations. These are processed in `clear_expired_delegations()` during block processing. -- **Fields to snapshot**: ALL fields - - `id`, `delegator`, `vesting_shares`, `expiration` - -### 4.4 `fix_vesting_delegation_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `fix_vesting_delegation_index` (by_id) -- **Why important**: Permanent delegation fix records. Required for correct vesting share accounting. -- **Fields to snapshot**: ALL fields - - `id`, `delegator`, `delegatee`, `vesting_shares` - -### 4.5 `withdraw_vesting_route_object` - -- **File**: `libraries/chain/include/graphene/chain/chain_objects.hpp` -- **Index**: `withdraw_vesting_route_index` (by_id, by_withdraw_route, by_destination) -- **Why important**: Defines routing for vesting withdrawals. Required for `process_vesting_withdrawals()`. -- **Fields to snapshot**: ALL fields - - `id`, `from_account`, `to_account`, `percent`, `auto_vest` - -### 4.6 `escrow_object` - -- **File**: `libraries/chain/include/graphene/chain/chain_objects.hpp` -- **Index**: `escrow_index` (by_id, by_from_id, by_to, by_agent, by_ratification_deadline) -- **Why important**: Active escrow contracts. Processed during `expire_escrow_ratification()`. -- **Fields to snapshot**: ALL fields - - `id`, `escrow_id`, `from`, `to`, `agent` - - `ratification_deadline`, `escrow_expiration` - - `token_balance`, `pending_fee` - - `to_approved`, `agent_approved`, `disputed` - -### 4.7 `proposal_object` - -- **File**: `libraries/chain/include/graphene/chain/proposal_object.hpp` -- **Index**: `proposal_index` (by_id, by_account, by_expiration) -- **Why important**: Active proposals. Processed during `clear_expired_proposals()`. -- **Fields to snapshot**: ALL fields - - `id`, `author`, `title`, `memo` - - `expiration_time`, `review_period_time` - - `proposed_operations` - - `required_active_approvals`, `available_active_approvals` - - `required_master_approvals`, `available_master_approvals` - - `required_regular_approvals`, `available_regular_approvals` - - `available_key_approvals` - -### 4.8 `required_approval_object` - -- **File**: `libraries/chain/include/graphene/chain/proposal_object.hpp` -- **Index**: `required_approval_index` (by_id, by_account) -- **Why important**: Links accounts to proposals requiring their approval. Required for proposal authorization checking. -- **Fields to snapshot**: ALL fields - - `id`, `account`, `proposal` - -### 4.9 `committee_request_object` - -- **File**: `libraries/chain/include/graphene/chain/committee_objects.hpp` -- **Index**: `committee_request_index` (by_id, by_request_id, by_status, by_creator, by_worker, by_creator_url) -- **Why important**: Active committee requests. Processed during `committee_processing()`. -- **Fields to snapshot**: ALL fields - - `id`, `request_id`, `url`, `creator`, `worker` - - `required_amount_min`, `required_amount_max` - - `start_time`, `duration`, `end_time` - - `status`, `votes_count`, `conclusion_time` - - `conclusion_payout_amount`, `payout_amount`, `remain_payout_amount` - - `last_payout_time`, `payout_time` - -### 4.10 `committee_vote_object` - -- **File**: `libraries/chain/include/graphene/chain/committee_objects.hpp` -- **Index**: `committee_vote_index` (by_id, by_voter, by_request_id) -- **Why important**: Committee votes on active requests. -- **Fields to snapshot**: ALL fields - - `id`, `request_id`, `voter`, `vote_percent`, `last_update` - -### 4.11 `invite_object` - -- **File**: `libraries/chain/include/graphene/chain/invite_objects.hpp` -- **Index**: `invite_index` (by_id, by_invite_key, by_status, by_creator, by_receiver) -- **Why important**: Active invites with balance. Processed during `clear_used_invites()`. -- **Fields to snapshot**: ALL fields - - `id`, `creator`, `receiver`, `invite_key`, `invite_secret` - - `balance`, `claimed_balance` - - `create_time`, `claim_time`, `status` - -### 4.12 `award_shares_expire_object` - -- **File**: `libraries/chain/include/graphene/chain/chain_objects.hpp` -- **Index**: `award_shares_expire_index` (by_id, by_expiration) -- **Why important**: Pending award share expirations. Processed during `expire_award_shares_processing()`. -- **Fields to snapshot**: ALL fields - - `id`, `expires`, `rshares` - -### 4.13 `paid_subscription_object` - -- **File**: `libraries/chain/include/graphene/chain/paid_subscription_objects.hpp` -- **Index**: `paid_subscription_index` (by_id, by_creator) -- **Why important**: Active paid subscription offers. Required for `paid_subscribe_processing()`. -- **Fields to snapshot**: ALL fields - - `id`, `creator`, `url`, `levels`, `amount`, `period`, `update_time` - -### 4.14 `paid_subscribe_object` - -- **File**: `libraries/chain/include/graphene/chain/paid_subscription_objects.hpp` -- **Index**: `paid_subscribe_index` (by_id, by_subscriber, by_creator, by_next_time, by_subscribe) -- **Why important**: Active subscriptions with pending payments. Required for `paid_subscribe_processing()`. -- **Fields to snapshot**: ALL fields - - `id`, `subscriber`, `creator`, `level`, `amount`, `period` - - `start_time`, `next_time`, `end_time`, `active`, `auto_renewal` - -### 4.15 `witness_penalty_expire_object` - -- **File**: `libraries/chain/include/graphene/chain/witness_objects.hpp` -- **Index**: `witness_penalty_expire_index` (by_id, by_account, by_expiration) -- **Why important**: Tracks witness penalties that will expire. Affects witness schedule calculations. -- **Fields to snapshot**: ALL fields - - `id`, `witness`, `penalty_percent`, `expires` - ---- - -## 5. OPTIONAL Objects (Can Be Deferred or Partially Included) - -These objects are useful for full functionality but are not strictly required for consensus. - -### 5.1 `account_metadata_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `account_metadata_index` (by_id, by_account) -- **Why optional**: Stores JSON metadata for accounts. Only created in non-low-mem builds (`#ifndef IS_LOW_MEM`). Not used in consensus. -- **Recommendation**: Include for API completeness; exclude in minimal DLT mode. - -### 5.2 `content_type_object` - -- **File**: `libraries/chain/include/graphene/chain/content_object.hpp` -- **Index**: `content_type_index` (by_id, by_content) -- **Why optional**: Stores content body/title/json_metadata. Only created in non-low-mem builds. Not used in consensus. -- **Recommendation**: Include for API completeness; exclude in minimal DLT mode. - -### 5.3 `master_authority_history_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `master_authority_history_index` (by_id, by_account) -- **Why optional**: Tracks historical master authority changes for account recovery. Only needed if recovery of old authority is requested. -- **Recommendation**: Include — it is referenced during account recovery processing. - -### 5.4 `account_recovery_request_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `account_recovery_request_index` (by_id, by_account, by_expiration) -- **Why optional**: Tracks pending recovery requests. Processed during `account_recovery_processing()`. If excluded, pending recoveries would be lost. -- **Recommendation**: Include — active recovery requests should be preserved. - -### 5.5 `change_recovery_account_request_object` - -- **File**: `libraries/chain/include/graphene/chain/account_object.hpp` -- **Index**: `change_recovery_account_request_index` (by_id, by_account, by_effective_date) -- **Why optional**: Tracks pending recovery account changes. Processed when effective date is reached. -- **Recommendation**: Include — pending changes should be preserved. - -### 5.6 `custom_protocol_object` (Plugin-Conditional) - -- **File**: `plugins/custom_protocol_api/include/graphene/plugins/custom_protocol_api/custom_protocol_api_object.hpp` -- **Index**: `custom_protocol_index` (by_id, by_account_custom_sequence_block_num) -- **Why optional**: Stores per-account custom sequence counters (`custom_sequence`, `custom_sequence_block_num`). Not used in consensus directly, but **requires full replay to reconstruct** if lost. Without it, applications relying on custom protocol sequencing will see incorrect sequence numbers. -- **Conditional inclusion logic**: - - **Export**: Include in snapshot only if the `custom_protocol_api` plugin is enabled on the exporting node. If the plugin is disabled, its index does not exist in chainbase and there is nothing to export. - - **Import**: Load from snapshot only if the `custom_protocol_api` plugin is enabled on the importing node. If the plugin is disabled, skip this section — the data will not be loaded and the plugin's index will remain empty (applications using the plugin will need a full replay to populate it). -- **Fields to snapshot**: ALL fields - - `id`, `account`, `custom_protocol_id`, `custom_sequence`, `custom_sequence_block_num` - ---- - -## 6. EXCLUDED Objects and Indexes - -### 6.1 `block_stats_object` - -- **Status**: Declared in `object_type` enum but no concrete class definition found in codebase. Likely a placeholder. -- **Decision**: Exclude from snapshot. - -### 6.2 Plugin Indexes (All Excluded from Consensus Snapshot) - -These indexes are maintained by plugins for API purposes only. They do not affect consensus and can be rebuilt after snapshot load. - -| Plugin | Indexes | Reason to Exclude | -|--------|---------|-------------------| -| follow | `follow_index`, `feed_index`, `blog_index`, `follow_count_index`, `blog_author_stats_index` | Social graph data, not consensus | -| account_history | `account_history_index`, `account_range_index` | Historical operation index, not consensus | -| account_by_key | `key_lookup_index` | Key-to-account lookup, can be rebuilt | -| tags | `tag_index`, `tag_stats_index`, `author_tag_stats_index`, `language_index` | Content tagging, not consensus | -| private_message | `message_index` | Messaging data, not consensus | -| operation_history | `operation_index` | Historical operation records, not consensus | - -**Note**: `custom_protocol_api` has been moved to OPTIONAL (section 5.6) because its data requires full replay to reconstruct. It is included conditionally based on plugin enablement on the exporting and importing nodes. - -### 6.3 Non-Persistent Runtime State (Excluded) - -| State | Location | Reason | -|-------|----------|--------| -| `_pending_tx` | `database.hpp:428` | In-flight transactions, will be resubmitted | -| `_popped_tx` | `database.hpp:427` | Temporary cache, not needed | -| `_current_trx_id` | `database.hpp:535` | Transient, reset on startup | -| `_current_block_num`, `_current_trx_in_block`, `_current_op_in_trx`, `_current_virtual_op` | `database.hpp:536-539` | Transient block processing state | -| `_checkpoints` | `database.hpp:541` | Node-specific configuration, not state | -| `_custom_operation_interpreters` | `database.hpp:556` | Plugin-registered, rebuilt on startup | - ---- - -## 7. Non-Object State Required in Snapshot - -Beyond chainbase objects, additional state must be captured for the node to resume correctly. - -### 7.1 Snapshot Header (Metadata) - -``` -{ - "version": 1, // Snapshot format version - "chain_id": "...", // Chain identifier for validation - "snapshot_block_num": 12345678, // Block number at snapshot time - "snapshot_block_id": "...", // Block hash at snapshot time - "snapshot_block_time": "...", // Timestamp of snapshot block - "last_irreversible_block_num": 12345000, // LIB at snapshot time - "last_irreversible_block_id": "...", // LIB hash - "snapshot_creation_time": "...", // When snapshot was created - "object_counts": { // For validation during load - "account_object": 5000, - "witness_object": 50, - ... - } -} -``` - -### 7.2 Fork Database Seed - -The fork database (`_fork_db`) contains blocks that are not yet irreversible. After loading a snapshot, the fork database should be seeded with the head block: - -``` -{ - "fork_db_head_block": { ... } // The signed_block at snapshot_block_num -} -``` - -This is necessary because `_fork_db.start_block()` is called with the head block during `database::open()`. - -### 7.3 Block Log Position - -The node needs to know where to start syncing from the network: - -``` -{ - "block_log_head_block_num": 12345678, // Last block in block_log (if available) - "block_log_head_block_id": "..." // Its hash -} -``` - -In DLT mode, the block_log may be truncated or absent. The node will sync remaining blocks from P2P network starting from `last_irreversible_block_num + 1`. - -### 7.4 Hardfork State Derivation - -The `_hardfork_times[]` and `_hardfork_versions[]` arrays are populated from code in `init_hardforks()` and `apply_hardfork()`. These are **not** stored in the database — they are compiled into the binary. The snapshot only needs the `hardfork_property_object` which records which hardforks have been applied. - -**Important**: The snapshot must be loaded by a binary whose compiled hardfork history is at least as recent as `hardfork_property_object.last_hardfork`. Otherwise, the node cannot correctly process the chain. - ---- - -## 8. Snapshot Format Specification - -### 8.1 Format Options - -| Format | Pros | Cons | -|--------|------|------| -| **JSON** | Human-readable, debuggable, compatible with existing fc::reflect | Large file size, slow serialization | -| **Binary (fc::raw)** | Compact, fast, already used for chainbase | Not human-readable, version-sensitive | -| **JSON + Binary hybrid** | Header in JSON, data in binary | Complexity | - -### 8.2 Recommended Format: Binary with JSON Header - -``` -[JSON Header] -[separator: \0\0\0\0] -[binary section: fc::raw packed objects by type] -[binary section: fork_db seed block] -[checksum: SHA256 of all preceding data] -``` - -Each object type section: - -``` -[uint32_t: object_type enum value] -[uint32_t: number of objects] -[fc::raw packed object 1] -[fc::raw packed object 2] -... -``` - -### 8.3 File Extension - -`.viz-snapshot` (e.g., `snapshot-12345678.viz-snapshot`) - ---- - -## 9. Snapshot Creation Process - -### 9.1 When to Create a Snapshot - -The snapshot should be created at an **irreversible block** boundary. This ensures: -- No undo state needs to be captured -- The state is final and will not be reverted -- Fork database is clean at this point - -### 9.2 Snapshot Creation Approaches - -There are several approaches to creating a snapshot while maintaining consistency: - -#### 9.2.1 Full Node Pause (Simple, with Downtime) - -``` -1. Stop accepting new blocks from P2P -2. Wait for current block processing to complete -3. Lock all operations (API + block application) -4. Create snapshot -5. Resume operations -``` - -**Pros**: Simple to implement, guaranteed consistency -**Cons**: Node unavailable during snapshot creation (several seconds) - -#### 9.2.2 Read Lock Without API Pause (Recommended) - -Use `with_strong_read_lock` at the irreversible block boundary. This is the recommended approach. - -``` -1. Wait for current block to become irreversible (LIB) - - Irreversible blocks cannot be reorganized - - This guarantees state consistency -2. Call with_strong_read_lock([&]() { ... }) - - API read requests continue to work - - Only new block application is blocked (write) -3. Serialize all objects to file -4. Release read lock automatically when lambda exits -5. Continue P2P sync (buffered blocks are applied in batch) -``` - -**Pros**: -- API remains available for reads -- Minimal downtime — only during serialization -- Consistency guaranteed by read lock + irreversible boundary - -**Cons**: -- New blocks not applied for several seconds -- P2P buffers incoming blocks, then applies them in batch - -**Implementation Example**: - -```cpp -void snapshot_plugin::create_snapshot(const fc::path& output_path) { - auto& db = _chain_db->db(); - - // 1. Get current LIB - uint32_t lib = db.last_non_undoable_block_num(); - auto lib_block = db.fetch_block_by_number(lib); - - // 2. Acquire strong read lock - db.with_strong_read_lock([&]() { - // 3. Write header with LIB info - snapshot_header header; - header.snapshot_block_num = lib; - header.snapshot_block_id = lib_block->id(); - header.last_irreversible_block_num = lib; - header.last_irreversible_block_id = lib_block->id(); - header.chain_id = db.get_chain_id(); - header.snapshot_creation_time = fc::time_point::now(); - - // 4. Serialize all object types (CRITICAL + IMPORTANT + OPTIONAL) - std::vector payload; - payload.reserve(64 * 1024 * 1024); // pre-allocate - - // Export each index... - export_section(db, payload); - export_section(db, payload); - // ... all other indexes ... - - // 5. Write fork_db seed block (the LIB block itself) - fc::raw::pack(payload, *lib_block); - - // 6. Compute SHA256 checksum - auto checksum = fc::sha256::hash(payload.data(), payload.size()); - header.payload_sha256 = checksum; - - // 7. Write to file (magic + version + header + payload) - write_snapshot_file(output_path, header, payload); - }); - - // 8. Read lock released automatically here - // 9. Node continues normal operation -} -``` - -**Key Points**: -- `with_strong_read_lock` is already used in VIZ codebase (see `database.cpp`) -- Lock is acquired at the database level, preventing any writes -- API read operations (`with_strong_read_lock` on their own) can proceed in parallel -- The lock duration depends on snapshot size (~44 MB takes ~1-3 seconds to serialize) -- P2P layer buffers incoming blocks during this time and applies them after lock release - -#### 9.2.3 Asynchronous Copy-on-Write (Complex) - -Make a file-level copy of shared_memory, then serialize from the copy. - -**Pros**: No blocking at all -**Cons**: Complex implementation, requires filesystem support, disk space doubled temporarily - -### 9.3 Creation Algorithm (Detailed) - -``` -1. Wait for block to become irreversible (or use current LIB) -2. Acquire strong read lock on database -3. Write JSON header with metadata -4. For each object type in CRITICAL + IMPORTANT categories: - a. Iterate all objects via get_index().indices() - b. Serialize each object using fc::raw::pack() - c. Write section to file -5. Write fork_db head block (the signed_block at LIB) -6. Compute and write checksum -7. Release read lock -8. Validate snapshot by loading it in a test instance -``` - -### 9.4 Optimization: Filter Expired Objects - -At snapshot time, skip objects that are already expired: -- `transaction_object` with `expiration <= snapshot_time` -- `vesting_delegation_expiration_object` with `expiration <= snapshot_time` -- `account_recovery_request_object` with `expires <= snapshot_time` -- `award_shares_expire_object` with `expires <= snapshot_time` -- `invite_object` with `status == used` - ---- - -## 10. Snapshot Loading Process (DLT Mode Startup) - -### 10.1 Loading Algorithm - -``` -1. Open chainbase database with shared_memory_file -2. Call init_schema() and initialize_indexes() -3. Read snapshot file header, validate version and chain_id -4. For each object type section in the snapshot: - a. Create objects in chainbase using create() - b. Validate object IDs match expected sequence -5. After all objects loaded: - a. Validate dynamic_global_property_object exists and is consistent - b. Validate witness_schedule_object exists - c. Validate hardfork_property_object exists - d. Verify object_counts match header -6. Call init_hardforks() (populates in-memory arrays from code) -7. Call initialize_evaluators() -8. Open block_log (if exists) or create empty one -9. Seed fork_db with head block from snapshot -10. Set chainbase revision to head_block_number -11. Begin P2P sync from last_irreversible_block_num + 1 -``` - -### 10.2 Validation After Load - -After loading, the following invariants must hold: - -1. `dynamic_global_property_object.head_block_number` == `snapshot_block_num` (from header) -2. `dynamic_global_property_object.head_block_id` == `snapshot_block_id` (from header) -3. `hardfork_property_object.current_hardfork_version` >= compiled minimum -4. Sum of all `account_object.balance` + `committee_fund` + ... == `current_supply` -5. Sum of all `account_object.vesting_shares` == `total_vesting_shares` -6. At least one `witness_object` with valid `signing_key` - -### 10.3 DLT Mode P2P Sync - -After snapshot load, the node must sync blocks from `last_irreversible_block_num + 1` to the current chain head: - -``` -1. Connect to P2P peers -2. Request blocks starting from last_irreversible_block_num + 1 -3. For each received block: - a. Validate block header (witness, timestamp) - b. Apply block with normal validation - c. Add to block_log (if running with block_log) -4. Once caught up, operate normally -``` - ---- - -## 11. Implementation: Plugin Architecture - -### 11.1 Plugin Design - -The snapshot functionality should be implemented as an `appbase::plugin` named `snapshot_plugin`. - -``` -snapshot_plugin - |-- snapshot_creation (command: create snapshot) - |-- snapshot_loading (startup: --snapshot=path/to/snapshot) - |-- snapshot_validation (verify loaded state) -``` - -### 11.2 CLI Options - -``` ---snapshot=path/to/snapshot.viz-snapshot Load from snapshot instead of replay ---create-snapshot=path/to/output Create snapshot at current head block ---snapshot-at-block=N Create snapshot when block N is reached -``` - -### 11.3 Integration with database::open() - -Modified startup flow: - -``` -if (snapshot_path specified) { - database::open_snapshot(snapshot_path); // Load from snapshot - // Skip reindex, skip block_log verification - // Start P2P sync from LIB + 1 -} else { - database::open(data_dir, ...); // Normal startup -} -``` - -### 11.4 Key Implementation Files to Modify/Create - -| File | Action | Description | -|------|--------|-------------| -| `plugins/snapshot/CMakeLists.txt` | Create | Build configuration | -| `plugins/snapshot/snapshot_plugin.hpp` | Create | Plugin header | -| `plugins/snapshot/snapshot_plugin.cpp` | Create | Plugin implementation (create/load/validate) | -| `plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp` | Create | Serialization/deserialization logic | -| `libraries/chain/database.hpp` | Modify | Add `open_snapshot()` method | -| `libraries/chain/database.cpp` | Modify | Implement `open_snapshot()` | -| `programs/vizd/main.cpp` | Modify | Add --snapshot CLI option | - ---- - -## 12. Complete Object List Summary - -### CRITICAL (Must Include) - -| # | Object Type | Index Type | Key Fields | Est. Count | -|---|-------------|------------|------------|------------| -| 1 | `dynamic_global_property_object` | `dynamic_global_property_index` | head_block, supply, vesting totals | 1 | -| 2 | `witness_schedule_object` | `witness_schedule_index` | current schedule, virtual time | 1 | -| 3 | `hardfork_property_object` | `hardfork_property_index` | processed_hardforks, versions | 1 | -| 4 | `account_object` | `account_index` | balances, vesting, energy, proxy | ~accounts | -| 5 | `account_authority_object` | `account_authority_index` | master/active/regular auth | ~accounts | -| 6 | `witness_object` | `witness_index` | signing_key, votes, schedule | ~witnesses | -| 7 | `witness_vote_object` | `witness_vote_index` | account-witness link | ~votes | -| 8 | `block_summary_object` | `block_summary_index` | block_id (TaPOS) | 65536 | -| 9 | `content_object` | `content_index` | rshares, cashout, payouts | ~content | -| 10 | `content_vote_object` | `content_vote_index` | vote weight, rshares | ~votes | -| 11 | `block_post_validation_object` | `block_post_validation_index` | witness validations | ~recent blocks | - -### IMPORTANT (Should Include) - -| # | Object Type | Index Type | Key Fields | Est. Count | -|---|-------------|------------|------------|------------| -| 12 | `transaction_object` | `transaction_index` | trx_id, expiration | ~pending | -| 13 | `vesting_delegation_object` | `vesting_delegation_index` | delegator, delegatee, shares | ~delegations | -| 14 | `vesting_delegation_expiration_object` | `vesting_delegation_expiration_index` | expiration, delegator | ~pending | -| 15 | `fix_vesting_delegation_object` | `fix_vesting_delegation_index` | delegator, delegatee | ~fixes | -| 16 | `withdraw_vesting_route_object` | `withdraw_vesting_route_index` | from, to, percent | ~routes | -| 17 | `escrow_object` | `escrow_index` | from, to, agent, balances | ~escrows | -| 18 | `proposal_object` | `proposal_index` | author, title, ops, approvals | ~proposals | -| 19 | `required_approval_object` | `required_approval_index` | account, proposal | ~approvals | -| 20 | `committee_request_object` | `committee_request_index` | creator, worker, amounts | ~requests | -| 21 | `committee_vote_object` | `committee_vote_index` | voter, request, percent | ~votes | -| 22 | `invite_object` | `invite_index` | creator, key, balance | ~invites | -| 23 | `award_shares_expire_object` | `award_shares_expire_index` | expires, rshares | ~pending | -| 24 | `paid_subscription_object` | `paid_subscription_index` | creator, levels, period | ~subscriptions | -| 25 | `paid_subscribe_object` | `paid_subscribe_index` | subscriber, creator, timing | ~subscribers | -| 26 | `witness_penalty_expire_object` | `witness_penalty_expire_index` | witness, penalty, expires | ~penalties | - -### OPTIONAL (Include for Full Mode) - -| # | Object Type | Index Type | Key Fields | Est. Count | -|---|-------------|------------|------------|------------| -| 27 | `account_metadata_object` | `account_metadata_index` | account, json_metadata | ~accounts | -| 28 | `content_type_object` | `content_type_index` | content, body, title | ~content | -| 29 | `master_authority_history_object` | `master_authority_history_index` | account, prev_master | ~history | -| 30 | `account_recovery_request_object` | `account_recovery_request_index` | account, new_master | ~pending | -| 31 | `change_recovery_account_request_object` | `change_recovery_account_request_index` | account, recovery, date | ~pending | -| 32 | `custom_protocol_object` | `custom_protocol_index` | account, custom_sequence | ~accounts (plugin-conditional) | - -### EXCLUDED - -| # | Object Type | Reason | -|---|-------------|--------| -| - | `block_stats_object` | No implementation found | -| - | Plugin indexes (follow, tags, etc.) | Non-consensus, API-only, rebuildable | - ---- - -## 13. Estimated Snapshot Size - -Based on typical VIZ chain state: - -| Category | Object Count | Avg Size (bytes) | Total | -|----------|-------------|-------------------|-------| -| `dynamic_global_property_object` | 1 | ~200 | ~0.2 KB | -| `witness_schedule_object` | 1 | ~500 | ~0.5 KB | -| `hardfork_property_object` | 1 | ~200 | ~0.2 KB | -| `account_object` | ~5,000 | ~800 | ~4 MB | -| `account_authority_object` | ~5,000 | ~300 | ~1.5 MB | -| `witness_object` | ~100 | ~600 | ~60 KB | -| `witness_vote_object` | ~5,000 | ~30 | ~150 KB | -| `block_summary_object` | 65,536 | ~40 | ~2.5 MB | -| `content_object` | ~50,000 | ~500 | ~25 MB | -| `content_vote_object` | ~200,000 | ~50 | ~10 MB | -| `transaction_object` | ~100 | ~500 | ~50 KB | -| `vesting_delegation_object` | ~2,000 | ~80 | ~160 KB | -| Other IMPORTANT objects | ~1,000 | ~200 | ~200 KB | -| **TOTAL** | | | **~44 MB** | - -With compression (zstd/lz4): estimated **~8-15 MB** - -This is dramatically smaller than the full shared_memory file (which can be several GB) and the block_log. - ---- - -## 14. Security Considerations - -### 14.1 Snapshot Trust Model - -The snapshot is a **trusted state**. Loading a snapshot means trusting the creator of the snapshot. This is acceptable for: - -- Node operators creating their own snapshots -- Community-provided snapshots with known hashes -- Snapshots verifiable against a known block hash - -### 14.2 Snapshot Integrity - -- SHA256 checksum of the entire snapshot file -- Chain ID verification to prevent cross-chain loading -- Block ID verification against known checkpoints (if available) -- Supply invariant checks after loading - -### 14.3 Snapshot Authenticity - -For production deployments, snapshot files should be distributed with: -- A SHA256 hash signed by a trusted key -- Or a Merkle proof linking the snapshot to a known block hash - ---- - -## 15. Open Questions and Future Work - -1. **Incremental snapshots**: Support creating delta snapshots from a previous snapshot (reduce creation time and storage) -2. **Snapshot compression**: Integrate zstd compression for smaller files -3. **Partial snapshot loading**: Load only specific object types (e.g., only consensus-critical for minimal nodes) -4. **Snapshot streaming**: Stream snapshot creation to avoid holding entire state in memory -5. **Block log pruning**: After snapshot load, trim block_log to only keep blocks after LIB -6. **Automatic snapshot schedule**: Periodically create snapshots at configurable intervals -7. **Snapshot validation tool**: Standalone tool to verify a snapshot file without loading it into a running node -8. **Cross-version compatibility**: Handle snapshot migration when object schemas change between hardforks diff --git a/.qoder/plan/mini-plan-new-hardfork-split-validator-reward.md b/.qoder/plan/mini-plan-new-hardfork-split-validator-reward.md deleted file mode 100644 index d14a076240..0000000000 --- a/.qoder/plan/mini-plan-new-hardfork-split-validator-reward.md +++ /dev/null @@ -1,168 +0,0 @@ -# Механизм распределения наград делегаторам (Reward Sharing) - -## 1. Основные понятия и текущая модель - -### 1.1. Глоссарий -| Термин | Описание | -|---|---| -| **SHARES** | Нативная единица учёта ценности в сети (аналог токена). | -| **Делегат (Validator)** | Нода, выбранная в активный пул и участвующая в подписи блоков. | -| **Делегатор (Voter)** | Пользователь, отдавший свои голоса (стейк) за конкретного делегата. | -| **Раунд** | Цикл из 21 блока, в котором происходит ротация подписывающих нод. | -| **Эпоха раздачи (Distribution Epoch)** | Период накопления наград делегатами, по истечении которого запускается расчёт и выплата делегаторам. | -| **Комиссия делегата (Commission Rate)** | Процент от награды за блоки, который делегат обязуется распределить среди своих делегаторов. Устанавливается самим делегатом. | -| **Вес голоса (Vote Weight)** | Количество SHARES, делегированных пользователем данному делегату. | - -### 1.2. Текущая механика производства блоков -- Активный пул состоит из **Top-11** делегатов и **10** резервных (саппорт) делегатов. -- Каждые **21 блок** (один раунд) происходит ротация согласно механике «перевёрнутой горы». -- За каждый успешно подписанный блок делегат получает фиксированную награду: **0.199998 SHARES**. -- Если делегат выбывает (отключился), его место в раунде подхватывает один из резервных делегатов, конкурирующих между собой. - -### 1.3. Проблема распределения «на лету» -Раздавать долю от `0.199998 SHARES` (например, 10% = 0.019999 SHARES) после каждого блока между всеми делегаторами — нерационально: -- Сумма слишком мала, возникают ошибки округления. -- Высокая вычислительная нагрузка на ноду при переборе списка делегаторов в каждом блоке. -- Фрагментация балансов (пыль). - ---- - -## 2. Предлагаемая схема накопления и раздачи - -### 2.1. Эпоха раздачи (Distribution Epoch) - -**Параметр консенсуса:** `distribution_epoch_length` — интервал, через который происходит расчёт и выплата доли делегаторам. - -**Начальное предложение:** `1 день` (≈ 28 800 блоков при 3-секундном блоке). - -Значение выбирается консенсусом делегатов и может быть изменено через голосование (governance). Допустимые значения: `1 день`, `5 дней`, `10 дней`. - -### 2.2. Накопление наград делегатом - -Каждый делегат имеет два счёта учёта наград: - -1. **Собственный счёт делегата (`validator_self_reward`)** — та часть награды, которую делегат оставляет себе (комиссия). -2. **Счёт для делегаторов (`validator_delegator_reward`)** — та часть, которая будет распределена между проголосовавшими за него пользователями. - -При получении награды за блок (`0.199998 SHARES`) происходит немедленное разделение: - -``` -block_reward = 0.199998 SHARES -commission_rate = делегат.комиссия // например, 50% → 0.5 - -delegator_share = block_reward * commission_rate -validator_share = block_reward - delegator_share - -validator_self_reward += validator_share -validator_delegator_reward += delegator_share -``` - -Оба счёта существуют **виртуально**, то есть балансы делегаторов не обновляются до окончания эпохи. - -### 2.3. Калькулятор выплат по окончании эпохи - -При наступлении блока-экспирации (конец эпохи раздачи) для каждого делегата запускается **калькулятор выплат**: - -#### Входные данные для делегата `V`: -- `accumulated_reward` — сумма на счёте `validator_delegator_reward` за истекшую эпоху. -- `commission_rate` — комиссия делегата, зафиксированная **на момент начала эпохи** (снапшот). -- `voters[]` — список делегаторов с их весами голосов (снапшот на начало эпохи). -- `total_vote_weight` — сумма весов всех делегаторов делегата `V`. - -#### Алгоритм расчёта: - -``` -total_distributed = 0 - -для каждого voter из voters[]: - voter_share = accumulated_reward * (voter.weight / total_vote_weight) - - если voter_share < MIN_PAYOUT (0.000001 SHARES): - пропустить (не начислять) - иначе: - начислить voter_share на баланс voter - total_distributed += voter_share - -// Нераспределённый остаток -undistributed = accumulated_reward - total_distributed - -// Остаток возвращается делегату -validator_self_reward += undistributed -``` - -#### Ограничения и защита: -- **Минимальная выплата (`MIN_PAYOUT`):** `0.000001 SHARES`. Если расчётная доля делегатора меньше этого значения, начисление не производится, сумма остаётся в нераспределённом остатке и возвращается делегату. -- **Снапшот голосов:** Состав делегаторов и их веса фиксируются **на начало эпохи**. Изменения в голосовании (переголосование, добавление/вывод стейка) в течение эпохи не влияют на текущую раздачу; они вступят в силу со следующей эпохи. - -### 2.4. Сброс после раздачи - -После завершения расчётов: -- `validator_delegator_reward` обнуляется. -- Делается новый снапшот голосов на следующую эпоху. -- Начинается новое накопление с новым значением `commission_rate` (если делегат его изменил). - ---- - -## 3. Числовой пример - -### Исходные данные -- Длина эпохи: `1 день` = `28 800 блоков`. -- Делегат «Alice» имеет комиссию `50%`. -- За эпоху Alice подписала `2 400 блоков` (участвовала не во всех раундах). - -### Накопление -``` -block_reward = 0.199998 -signed_blocks = 2400 - -total_block_reward = 2400 * 0.199998 = 479.9952 SHARES - -delegator_share = 479.9952 * 0.5 = 239.9976 SHARES -validator_share = 479.9952 * 0.5 = 239.9976 SHARES -``` - -На счетах Alice на конец эпохи: -- `validator_self_reward` = 239.9976 SHARES -- `validator_delegator_reward` = 239.9976 SHARES - -### Распределение среди делегаторов -У Alice три делегатора (снапшот на начало эпохи): - -| Делегатор | Вес голоса (SHARES) | Доля | -|---|---|---| -| Bob | 100 000 | 66.67% | -| Carol | 40 000 | 26.67% | -| Dave | 10 000 | 6.67% | -| **Total** | **150 000** | 100% | - -Расчёт: -``` -Bob = 239.9976 * 0.6667 = 159.9984 SHARES -Carol = 239.9976 * 0.2667 = 63.9936 SHARES -Dave = 239.9976 * 0.0667 = 16.0056 SHARES - (проверка: 159.9984 + 63.9936 + 16.0056 ≈ 239.9976) -``` - -Все доли больше `0.000001 SHARES`, нераспределённого остатка нет. - -### Ситуация с пылью -Если бы у Dave было `0.0001 SHARES` веса, его расчётная доля составила бы `≈ 0.00000016 SHARES`, что меньше `0.000001`. Dave ничего не получает, его доля возвращается Alice. - ---- - -## 4. Преимущества схемы - -1. **Эффективность для C++ ноды:** Никаких операций записи в состоянии для тысяч делегаторов на каждом блоке — только один проход в конце эпохи. -2. **Гибкость:** Каждый делегат сам устанавливает комиссию, конкурируя за голоса делегаторов. -3. **Защита от пыли:** Минимальный порог выплат предотвращает замусоривание состояния и нерациональные вычисления. -4. **Справедливость:** Снапшот на начало эпохи защищает от манипуляций с переголосованием «в последний момент». -5. **Простота аудита:** Все награды прозрачно распределяются по фиксированному алгоритму; нераспределённый остаток возвращается делегату. - ---- - -## 5. Дальнейшие шаги - -- [ ] Вынести `distribution_epoch_length` в параметры консенсуса с возможностью изменения через governance-голосование. -- [ ] Добавить в структуру делегата поля: `commission_rate`, `validator_delegator_reward`, снапшот голосов на начало эпохи. -- [ ] Реализовать функцию `process_distribution_epoch()` в коде C++ ноды, вызываемую по детерминированному условию (конец эпохи). -- [ ] Покрыть функциональность юнит-тестами с проверкой граничных условий (нулевой вес, минимальные выплаты, смена комиссии). \ No newline at end of file diff --git a/.qoder/plan/rename-block-production-condition.md b/.qoder/plan/rename-block-production-condition.md deleted file mode 100644 index ca52d9c1f2..0000000000 --- a/.qoder/plan/rename-block-production-condition.md +++ /dev/null @@ -1,127 +0,0 @@ -# Rename: `block_production_condition` → `block_validation_condition` - -## Overview - -Witnesses in VIZ do not *produce* blocks autonomously — they *validate* the chain state and sign the next block when it is their scheduled turn. The term "production" is misleading; "validation" better reflects the semantic role. This plan renames the namespace, the enum type, and all enum values that use the old name across the codebase. - ---- - -## Scope of Changes - -### Source files (code changes required) - -| File | What changes | -|------|-------------| -| [plugins/witness/include/graphene/plugins/witness/witness.hpp](../../plugins/witness/include/graphene/plugins/witness/witness.hpp) | Rename namespace `block_production_condition` → `block_validation_condition`; rename enum `block_production_condition_enum` → `block_validation_condition_enum`; rename enum value `exception_producing_block` → `exception_validating_block` | -| [plugins/witness/witness.cpp](../../plugins/witness/witness.cpp) | Update all uses of the namespace, the enum type, all enum value references, method names `block_production_loop` → `block_validation_loop`, `maybe_produce_block` → `maybe_validate_block` | - -### Documentation files (text search-and-replace) - -| File | Action | -|------|--------| -| [.qoder/repowiki/en/content/Witness.md](./../repowiki/en/content/Witness.md) | Update all references to old names | -| [.qoder/research/consensus-emergency-recovery.md](./../research/consensus-emergency-recovery.md) | Update code snippets and prose | -| [.qoder/docs/fork-collision-hardfork-proposal.md](./../docs/fork-collision-hardfork-proposal.md) | Update code snippets | -| [.qoder/docs/consensus-emergency-params.md](./../docs/consensus-emergency-params.md) | Update code snippets | -| [.qoder/plans/Fork_Collision_Resolution_Fix_24537a6e.md](./../plans/Fork_Collision_Resolution_Fix_24537a6e.md) | Update code snippets | - ---- - -## Detailed Changes - -### 1. `witness.hpp` — Namespace and enum rename - -**File:** `plugins/witness/include/graphene/plugins/witness/witness.hpp` - -```cpp -// BEFORE -namespace block_production_condition { - enum block_production_condition_enum { - produced = 0, - not_synced = 1, - not_my_turn = 2, - not_time_yet = 3, - no_private_key = 4, - low_participation = 5, - lag = 6, - consecutive = 7, - exception_producing_block = 8, - fork_collision = 9, - minority_fork = 10 - }; -} - -// AFTER -namespace block_validation_condition { - enum block_validation_condition_enum { - produced = 0, - not_synced = 1, - not_my_turn = 2, - not_time_yet = 3, - no_private_key = 4, - low_participation = 5, - lag = 6, - consecutive = 7, - exception_validating_block = 8, - fork_collision = 9, - minority_fork = 10 - }; -} -``` - -### 2. `witness.cpp` — All references - -**File:** `plugins/witness/witness.cpp` - -Rename map (search → replace): - -| Old | New | -|-----|-----| -| `block_production_condition::block_production_condition_enum` | `block_validation_condition::block_validation_condition_enum` | -| `block_production_condition::` | `block_validation_condition::` | -| `block_production_condition_enum` | `block_validation_condition_enum` | -| `exception_producing_block` | `exception_validating_block` | -| `block_production_loop` (method name) | `block_validation_loop` | -| `maybe_produce_block` (method name) | `maybe_validate_block` | - -> **Note:** `maybe_produce_block` appears in both the forward declaration (line ~102–104) and the definition (line ~660). Both must be updated together or the build will fail. - -### 3. Documentation files — Text substitution - -For each doc file listed in the scope table, apply the same rename map as a plain-text search-and-replace. No structural changes to the documents are needed — only identifier names inside code blocks and prose references. - ---- - -## Enum Values That Stay Unchanged - -These enum values already describe the *reason for not validating* (or the outcome), not the act of production, so they need no renaming: - -- `produced` — outcome, keep as-is -- `not_synced` -- `not_my_turn` -- `not_time_yet` -- `no_private_key` -- `low_participation` -- `lag` -- `consecutive` -- `fork_collision` -- `minority_fork` - -Only `exception_producing_block` → `exception_validating_block` changes because the word "producing" appears in it. - ---- - -## Implementation Steps - -1. **`witness.hpp`** — rename namespace, enum type, and `exception_producing_block`. -2. **`witness.cpp`** — rename all namespace-qualified references, both method declarations and definitions. -3. **Build** — compile to confirm zero errors before touching docs. -4. **Docs** — apply text substitution to the five documentation files. - ---- - -## Risk - -- Low. This is a pure rename with no behavioral change. -- The numeric values of enum members are preserved, so any serialized state that stores these as integers (e.g. log output) is unaffected. -- No public API or wire protocol uses this enum — it is internal to the witness plugin. diff --git a/.qoder/plans/DLT_P2P_Fixes_5d013323.md b/.qoder/plans/DLT_P2P_Fixes_5d013323.md deleted file mode 100644 index 34892e0aa2..0000000000 --- a/.qoder/plans/DLT_P2P_Fixes_5d013323.md +++ /dev/null @@ -1,496 +0,0 @@ -# DLT P2P Fixes — Implementation Plan - -Fixes all issues found during review verification of `.qoder/docs/dlt-p2p-network-redesign-review.md`. - ---- - -## Task 1: Compile Error — `peer_dlt_latest_block` field name mismatch (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -The code references `peer_dlt_latest_block` but the field in `dlt_peer_state` is named `peer_dlt_latest`. - -**Changes**: -- Line 523: `it->second.peer_dlt_latest_block` → `it->second.peer_dlt_latest` -- Line 531: `s.peer_dlt_latest_block` → `s.peer_dlt_latest` - ---- - -## Task 2: Functional Bug — `broadcast_block_post_validation()` corrupts peer_head_num (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -`broadcast_block_post_validation()` at line 805 sets `msg.head_block_num = 0` with comment "filled by receiver from block_id". But `on_dlt_fork_status()` at line 699 stores it as `peer_head_num = 0`, corrupting peer state. - -**Fix**: Extract block number from the `block_id` using `block_header::num_from_id()`: -```cpp -// Line 804-805: replace -msg.head_block_id = block_id; -msg.head_block_num = 0; // filled by receiver from block_id -// With: -msg.head_block_id = block_id; -msg.head_block_num = block_header::num_from_id(block_id); -``` - -Also add the necessary include if not present: `#include ` (likely already available through `graphene/protocol/block.hpp`). - ---- - -## Task 3: Fork Resolution Non-Functional — always picks first branch (P0) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -`compute_branch_info()` returns `total_vote_weight = 0` for every branch. The comparison `0 > 0` is always false, so the first branch in `tips` always wins. The delegate already provides `compare_fork_branches()` which does the correct vote-weighted comparison with +10% longer-chain bonus. - -**Fix**: Replace `compute_branch_info()` iterations in `resolve_fork()` with `compare_fork_branches()`: - -Replace lines 1130-1139: -```cpp -// Find the heaviest branch -dlt_fork_branch_info winner; -bool first = true; -for (const auto& tip : tips) { - auto info = compute_branch_info(tip); - if (first || info.total_vote_weight > winner.total_vote_weight) { - winner = info; - first = false; - } -} -``` - -With: -```cpp -// Find the heaviest branch using vote-weighted comparison -block_id_type winner_tip = tips[0]; -for (size_t i = 1; i < tips.size(); ++i) { - if (_delegate->compare_fork_branches(tips[i], winner_tip) > 0) { - winner_tip = tips[i]; - } -} -dlt_fork_branch_info winner; -winner.tip = winner_tip; -``` - -The rest of `resolve_fork()` already uses `winner.tip` for hysteresis and fork switching, so it will work with the new `winner_tip`. - -After this change, `compute_branch_info()` is no longer called from `resolve_fork()`. It can be kept for potential future use or removed. - ---- - -## Task 4: `expected_next_block` tracking never used (P1) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -The field `expected_next_block` is declared in `dlt_peer_state` but never set or validated. Per plan section 3.7: reject blocks that skip too far ahead (hole-creation attack prevention). - -**Changes**: - -In `on_dlt_block_range_reply()` (after line 633, inside the block loop), add ordering validation before `accept_block()`: -```cpp -// Validate block ordering -if (state.expected_next_block != 0 && block.block_num() != state.expected_next_block) { - wlog(DLT_LOG_RED "Block #${n} from ${ep} out of order (expected #${e})" DLT_LOG_RESET, - ("n", block.block_num())("ep", state.endpoint)("e", state.expected_next_block)); - record_packet_result(peer, false); - continue; -} -``` - -After successful `accept_block()`, set the expected next block: -```cpp -state.expected_next_block = block.block_num() + 1; -``` - -Same for `on_dlt_block_reply()` (line 673): add the same validation before `accept_block()`. - -On disconnect (`handle_disconnect()`), reset: `state.expected_next_block = 0;` - ---- - -## Task 5: `pending_block_batch` timeout + `block_validation_timeout()` (P1) - -**Files**: `libraries/network/include/graphene/network/dlt_p2p_node.hpp`, `libraries/network/dlt_p2p_node.cpp` - -The field `pending_block_batch_time` and helper `has_pending_batch_timeout()` exist but are never used. The `block_validation_timeout()` method is not implemented. - -**Changes**: - -**Header** (`dlt_p2p_node.hpp`): Add method declaration in the private section (after `periodic_task()` line 194): -```cpp -void block_validation_timeout(); -``` - -**Implementation** (`dlt_p2p_node.cpp`): - -1. In `on_dlt_block_range_reply()` (line 615), before the block processing loop, set the batch start time: -```cpp -state.pending_block_batch_time = fc::time_point::now(); -``` - -2. After the block processing loop completes successfully, clear it: -```cpp -state.pending_block_batch_time = fc::time_point(); -``` - -3. Implement `block_validation_timeout()`: -```cpp -void dlt_p2p_node::block_validation_timeout() { - for (auto& [id, state] : _peer_states) { - if (state.has_pending_batch_timeout()) { - wlog(DLT_LOG_RED "Block validation timeout for peer ${ep} (30s)" DLT_LOG_RESET, - ("ep", state.endpoint)); - record_packet_result(id, false); - state.pending_block_batch_time = fc::time_point(); - // If already at threshold, soft_ban will happen via record_packet_result - } - } -} -``` - -4. Call from `periodic_task()` (after `sync_stagnation_check()` line 1274): -```cpp -block_validation_timeout(); -``` - ---- - -## Task 6: Fork window reset on non-confirmation (P1) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -In `track_fork_state()` (lines 1116-1120), `_fork_detected = false` is set after `resolve_fork()` regardless of whether resolution succeeded. When hysteresis is not met, this causes a fresh 42-block countdown instead of continuous retry. - -**Fix**: Move `_fork_detected = false` into `resolve_fork()` — only clear it when resolution actually completes. - -Change `track_fork_state()` (lines 1116-1120): -```cpp -// Before: -if (_fork_detected && - block.block_num() - _fork_detection_block_num >= FORK_RESOLUTION_BLOCK_THRESHOLD) { - resolve_fork(); - _fork_detected = false; -} - -// After: -if (_fork_detected && - block.block_num() - _fork_detection_block_num >= FORK_RESOLUTION_BLOCK_THRESHOLD) { - resolve_fork(); - // _fork_detected is cleared inside resolve_fork() only when resolution completes -} -``` - -In `resolve_fork()`, add `_fork_detected = false` at the two exit points where resolution actually completes: - -1. After line 1127 (`_fork_status = DLT_FORK_STATUS_NORMAL; return;` — branch count < 2, fork is over): -```cpp -if (tips.size() < 2) { - _fork_status = DLT_FORK_STATUS_NORMAL; - _fork_detected = false; // fork resolved: only 1 branch remains - return; -} -``` - -2. At the end of the function (after the hysteresis is confirmed and fork switch is executed, line 1169): -```cpp -// Reset hysteresis -_fork_resolution_state = dlt_fork_resolution_state(); -_fork_detected = false; // fork resolution completed -``` - -Do NOT set `_fork_detected = false` at the early return (line 1153) where hysteresis is not confirmed — that's the whole point. - ---- - -## Task 7: `switch_to_fork()` in delegate — only pops one block (P1) - -**File**: `plugins/p2p/p2p_plugin.cpp` (lines 189-201) - -Current implementation calls `pop_block()` once but never re-pushes blocks from the new fork. The chain's `_push_block()` already has a full fork-switch implementation (database.cpp:1590-1730) that handles pop-until-common-ancestor + re-apply-new-branch + LIB guard + DLT crash prevention. - -**Fix**: Replace the simplified `switch_to_fork()` with a call to `push_block()` which triggers the chain's built-in fork switch: - -```cpp -void switch_to_fork(const block_id_type& new_head) override { - try { - auto& fdb = chain.db().get_fork_db(); - auto block = fdb.fetch_block(new_head); - if (block) { - ilog("Switching to fork with head ${id}", ("id", new_head)); - // The chain's push_block() handles full fork switch: - // pop-until-common-ancestor, re-apply new branch, - // LIB guard, DLT crash prevention - chain.db().push_block(*block); - } - } catch (const fc::exception& e) { - wlog("Error switching to fork: ${e}", ("e", e.to_detail_string())); - } -} -``` - -This requires adding the include for the block type (already available through `graphene/chain/database.hpp`). - ---- - -## Task 8: `is_head_on_branch()` too simplistic (P1) - -**File**: `plugins/p2p/p2p_plugin.cpp` (lines 203-206) - -Current implementation does `tip == head_block_id()` — misses the case where our head IS on the branch but not at its tip (e.g., our head is block 100, tip is block 105 on the same branch). - -**Fix**: Use `fork_db.fetch_branch_from()` to check if our head is an ancestor of the tip: - -```cpp -bool is_head_on_branch(const block_id_type& tip) const override { - if (tip == chain.db().head_block_id()) return true; - try { - auto& fdb = chain.db().get_fork_db(); - if (!fdb.is_known_block(tip) || !fdb.is_known_block(chain.db().head_block_id())) - return false; - auto branches = fdb.fetch_branch_from(tip, chain.db().head_block_id()); - // If our head is in the "old" branch (branches.second), we're on the same branch - // as the tip — they share a common ancestor and our head is below the tip - return !branches.second.empty(); - } catch (...) { - return false; - } -} -``` - ---- - -## Task 9: Spam strikes not incremented for expired/TaPoS-invalid rejections (P2) - -**File**: `libraries/network/dlt_p2p_node.cpp` - -In `add_to_mempool()`: -- Line 967: expired transaction returns false without `record_packet_result(sender, false)` -- Line 984: TaPoS-invalid transaction returns false without `record_packet_result(sender, false)` - -**Fix**: - -Line 967, replace: -```cpp -if (trx.expiration < fc::time_point_sec(fc::time_point::now())) return false; -``` -With: -```cpp -if (trx.expiration < fc::time_point_sec(fc::time_point::now())) { - if (from_peer && sender != INVALID_PEER_ID) record_packet_result(sender, false); - return false; -} -``` - -Line 984, replace: -```cpp -if (!is_tapos_valid(trx)) return false; -``` -With: -```cpp -if (!is_tapos_valid(trx)) { - if (from_peer && sender != INVALID_PEER_ID) record_packet_result(sender, false); - return false; -} -``` - ---- - -## Task 10: `periodic_dlt_prune_check()` is a no-op (P2) - -**File**: `libraries/network/dlt_p2p_node.cpp` (lines 1224-1227) - -Currently empty. Should trigger batch pruning when the DLT block log exceeds `_dlt_block_log_max_blocks`. - -**Fix**: -```cpp -void dlt_p2p_node::periodic_dlt_prune_check() { - if (!_delegate) return; - uint32_t earliest = _delegate->get_dlt_earliest_block(); - uint32_t latest = _delegate->get_dlt_latest_block(); - if (latest == 0 || earliest == 0) return; - - uint32_t current_range = latest - earliest + 1; - if (current_range <= _dlt_block_log_max_blocks) return; - - // Only prune in batches of DLT_PRUNE_BATCH_SIZE (10000) - if (latest - _last_prune_block_num < DLT_PRUNE_BATCH_SIZE) return; - - ilog(DLT_LOG_GREEN "DLT block log exceeds max (${r} > ${m}), pruning ${b} blocks" DLT_LOG_RESET, - ("r", current_range)("m", _dlt_block_log_max_blocks)("b", DLT_PRUNE_BATCH_SIZE)); - - // The actual pruning is done at the chain level via truncate_before() - // We signal the delegate to prune, passing the new start block number - uint32_t new_start = earliest + DLT_PRUNE_BATCH_SIZE; - // Delegate method needed — for now, log the intent - // TODO: Add dlt_p2p_delegate::prune_dlt_block_log(uint32_t new_start) - _last_prune_block_num = latest; -} -``` - -This is a partial implementation — a new delegate method `prune_dlt_block_log()` would be needed for the full chain. Mark as partial with a TODO. - ---- - -## Task 11: `has_emergency_private_key()` returns false (P2) - -**File**: `plugins/p2p/p2p_plugin.cpp` (lines 80-83) - -Currently always returns `false`. Should check if the witness plugin has an emergency private key configured. - -**Fix**: Query the witness plugin: -```cpp -bool has_emergency_private_key() const override { - auto* wit_plug = appbase::app().find_plugin(); - if (wit_plug) { - return wit_plug->is_emergency_key_configured(); - } - return false; -} -``` - -This requires: -1. Adding `#include ` to p2p_plugin.cpp -2. Adding `bool is_emergency_key_configured() const;` to the witness_plugin public API -3. Implementing it in the witness plugin (check if emergency master key is set) - -Since this crosses plugin boundaries and requires changes to the witness plugin, mark as requiring coordination. The minimal change is to add the include and the query, with a stub on the witness side that returns `true` if the key is in the config. - ---- - -## Task 12: `accept_block()` ignores `sync_mode` parameter (P2) - -**File**: `plugins/p2p/p2p_plugin.cpp` (lines 139-151) - -Currently always calls `push_block()` the same way regardless of `sync_mode`. In sync mode, blocks are being applied in bulk and certain expensive checks could be skipped. - -**Fix**: Pass `skip` flags to `push_block()` based on sync mode: -```cpp -bool accept_block(const signed_block& block, bool sync_mode) override { - try { - uint32_t skip = graphene::chain::database::skip_nothing; - if (sync_mode) { - // During bulk sync, skip expensive checks that are redundant - // for blocks we trust from our fork peers - skip = graphene::chain::database::skip_witness_signature - | graphene::chain::database::skip_transaction_signatures; - } - chain.db().push_block(block, skip); - return false; // fork detection done via on_block_applied callback - } catch (const graphene::chain::unlinkable_block_exception&) { - wlog("Unlinkable block #${n}, storing in fork_db", ("n", block.block_num())); - chain.db().get_fork_db().push_block(block); - return false; - } catch (const fc::exception& e) { - wlog("Error accepting block #${n}: ${e}", ("n", block.block_num())("e", e.to_detail_string())); - return false; - } -} -``` - -Note: The `skip_witness_signature` flag must be used carefully — it should only be applied for blocks from fork-aligned peers. The current design already ensures this because only fork-aligned peers exchange blocks. - ---- - -## Task 13: `resync_from_lib()` is shallow (P2) - -**Files**: `plugins/p2p/p2p_plugin.cpp` (lines 215-217, 437-441), `libraries/network/dlt_p2p_node.cpp` (lines 847-856) - -The delegate-level `resync_from_lib()` is empty. The node-level version just calls `transition_to_sync()` + re-requests blocks. A proper resync from LIB should: -1. Pop blocks back to LIB -2. Reset fork tracking state -3. Re-request blocks from LIB+1 - -**Fix for dlt_p2p_node::resync_from_lib()** (line 847): -```cpp -void dlt_p2p_node::resync_from_lib(bool force_emergency) { - ilog(DLT_LOG_GREEN "DLT P2P: resync from LIB requested (force_emergency=${f})" DLT_LOG_RESET, - ("f", force_emergency)); - - // Reset fork tracking - _fork_detected = false; - _fork_detection_block_num = 0; - _fork_resolution_state = dlt_fork_resolution_state(); - _fork_status = DLT_FORK_STATUS_NORMAL; - - transition_to_sync(); - - // Re-send hello to all peers to get updated chain state - auto hello = build_hello_message(); - for (auto& [id, state] : _peer_states) { - if (state.lifecycle_state == DLT_PEER_LIFECYCLE_ACTIVE || - state.lifecycle_state == DLT_PEER_LIFECYCLE_SYNCING) { - send_message(id, message(hello)); - request_blocks_from_peer(id); - } - } -} -``` - -The delegate-level `resync_from_lib()` can remain empty since the P2P node handles the logic internally. The chain-level block popping is handled by the caller (witness plugin) before invoking `resync_from_lib()`. - ---- - -## Task 14: Review Document Fixes - -**File**: `.qoder/docs/dlt-p2p-network-redesign-review.md` - -### 14a: Factual error in GAP 4 (line 116) - -Current text: -> "The plan's pseudocode does NOT set `_fork_detected = false` inside `track_fork_state()` after calling `resolve_fork()`." - -This is incorrect — the plan's pseudocode (plan lines 643-647) DOES set `_fork_detected = false`. Both the plan's pseudocode and implementation share the same issue. - -Replace line 116 with: -> "Both the plan's pseudocode and the implementation set `_fork_detected = false` after `resolve_fork()` — but this contradicts the plan's own design intent in section 3.7, which specifies that only the confirmation counter should reset on lead flip, not the 42-block detection window." - -### 14b: Severity inconsistency — GAP 5 - -- Line 124 heading: "(P0 Anti-Spam)" -- Line 191 summary: "P2 (nice to fix)" - -Align both to P0 (matches plan's P0 rating for mempool DoS protection): -Change line 191 from: -``` -| **P2 (nice to fix)** | GAP 5 — incomplete spam strikes; Known gaps 1-6 (already documented) | -``` -To: -``` -| **P0 (must fix)** | GAP 5 — incomplete spam strikes (plan rates this P0) | -| **P2 (nice to fix)** | Known gaps 1-6 (already documented) | -``` - -### 14c: Severity inconsistency — GAP 6 - -- Line 144 heading: "(P1 Fork)" -- Line 189 summary: "P0 (must fix)" - -Align heading to P0 (fork resolution is non-functional): -Change line 144 from `### GAP 6: Fork resolution winner always picks first branch (P1 Fork)` to `### GAP 6: Fork resolution winner always picks first branch (P0 Fork)` - -### 14d: Upgrade minor observation #2 - -Minor observation #2 says `broadcast_block_post_validation()` sends a `dlt_fork_status_message` "functional but semantically imprecise." But `msg.head_block_num = 0` actually corrupts `peer_head_num` in the receiver — this is a functional bug, not just semantic imprecision. - -Replace line 178 with: -> `broadcast_block_post_validation()` sends a `dlt_fork_status_message` with `head_block_num = 0` — the receiver stores this as `peer_head_num = 0`, corrupting peer state tracking. This is a functional bug, not just semantic imprecision. - ---- - -## Execution Order - -Tasks should be executed in this order to minimize conflicts: - -1. **Task 1** (compile error) — must be first, code won't compile without it -2. **Task 2** (head_block_num = 0) — simple one-line fix -3. **Task 3** (fork resolution) — highest-impact logic fix -4. **Task 6** (fork window reset) — related to Task 3, same area -5. **Task 5** (block validation timeout) — new method + wiring -6. **Task 4** (expected_next_block) — validation additions -7. **Task 7** (switch_to_fork delegate) — delegate fix -8. **Task 8** (is_head_on_branch) — delegate fix -9. **Task 9** (spam strikes) — simple additions -10. **Task 10** (prune check) — partial implementation -11. **Task 11** (emergency key) — cross-plugin, partial -12. **Task 12** (accept_block sync_mode) — optimization -13. **Task 13** (resync_from_lib) — node logic -14. **Task 14** (review doc) — documentation fixes, last \ No newline at end of file diff --git a/.qoder/plans/Fork_Collision_Resolution_Fix_24537a6e.md b/.qoder/plans/Fork_Collision_Resolution_Fix_24537a6e.md deleted file mode 100644 index 6252b053c7..0000000000 --- a/.qoder/plans/Fork_Collision_Resolution_Fix_24537a6e.md +++ /dev/null @@ -1,328 +0,0 @@ - -# Fork Collision Resolution Fix - -## Problem -When a witness node detects a fork collision (competing block at same height in fork_db), it defers block production forever. The competing block from the dead fork is never removed from fork_db, so `maybe_produce_block()` keeps returning `fork_collision` every 250ms. Meanwhile, blocks from the longer/healthier chain can't be applied because the head is stuck, creating a growing gap that eventually makes fork switching impossible. - -## Root Cause Analysis - -### How vote-weighted fork comparison works (existing HF12 code) - -`compute_branch_weight()` in `database.cpp:_push_block()` (lines 1300-1314): -1. `fetch_branch_from(tip_a, tip_b)` walks both chains back to their **common ancestor** -2. Returns two vectors: `branches.first` = all blocks from fork A's tip to common ancestor, `branches.second` = same for fork B -3. For each branch, iterates through **ALL blocks** in that branch -4. For each block, gets the `witness` name (who produced that block) -5. Looks up `witness_object.votes` from current DB state — this is **on-chain stake vote count** (how many VIZ tokens voted for that witness), NOT the scheduled slot -6. Uses `flat_set` to count each witness **only once** (even if it produced multiple blocks) -7. Skips `CHAIN_EMERGENCY_WITNESS_ACCOUNT` -8. The branch with higher total vote weight wins; ties broken by longer chain -9. **NEW: Longer chain gets +10% bonus on its vote weight** — each block produced is a consensus "vote" by the producing witness. Witnesses on the longer chain didn't defer and kept producing by consensus rules, which is strong evidence of network support. - -**Answer: it sums `wit_obj.votes` (on-chain stake) for ALL unique witnesses that produced blocks in the divergent portion of each fork, not just the top block. The longer chain gets a +10% bonus on its total weight.** - -### Where the witness plugin goes wrong -In `witness.cpp:maybe_produce_block()` (lines 549-577), the fork collision check does NOT use vote-weight comparison: -- It only checks "does any competing block exist at `head_block_num+1` with a different parent?" -- If yes -> blindly defers, no evaluation of which fork is better -- It never asks: "which fork has more vote weight? Should I produce on my fork or switch?" -- This creates a deadlock: the witness waits for fork resolution, but fork resolution only happens via `_push_block()` which can't run because the head is stuck - -### The deadlock sequence -1. Head at 79402010, competing block at 79402011 from dead fork in fork_db -2. Witness scheduled at 79402011 -> sees competing block -> defers -3. P2P blocks 79402012+ arrive but can't be pushed (gap, parent unknown in fork_db) -4. `_push_block()` never gets a chance to do vote-weight comparison -5. Witness keeps deferring every 250ms -> permanently stuck -6. Gap grows until fork_db window (2400 blocks) is exceeded -> no recovery possible - -### Critical constraint: `compute_branch_weight` CANNOT solve the stuck scenario -In your scenario (head at 79402010, network at 79404861+), the longer chain's blocks are NOT in fork_db — they were rejected because the parent was unknown (gap too large). So `fetch_branch_from()` cannot compute the branch for the longer chain. The existing vote-weight comparison **requires both chain tips to exist in fork_db**. - -This means we need TWO levels of fix: -- **Level 1 (immediate)**: When fork_db has competing blocks at `head+1`, use vote-weight comparison to decide whether to produce or switch. This handles the case where BOTH forks are in fork_db. -- **Level 2 (stuck recovery)**: When the witness has been deferring and head is stuck (not advancing), bypass the collision check entirely — the competing block is from a dead fork that clearly lost (the network moved on without it). Produce on our fork. - -## Fix Strategy: Two-Level Fork Decision in Witness Plugin - -### Level 1: Vote-weighted comparison (when both forks are in fork_db) -When a competing block at `head+1` exists in fork_db: -1. **Evaluate fork weights** using `compute_branch_weight()` logic (same as `_push_block()`) -2. **If our fork has more vote weight** -> produce on our fork (the competing block is from a losing fork) -3. **If the competing fork has more vote weight** -> switch to it first, then produce on it -4. **If tied** -> defer briefly (1-2 slots) - -### Level 2: Stuck-head timeout (when fork_db comparison is impossible) -When the witness has been deferring for N consecutive slots and head hasn't advanced: -1. The competing block is clearly from a dead fork (network moved on) -2. Remove the competing block(s) from fork_db -3. Produce on our fork -4. This handles the case where the longer chain's blocks aren't in fork_db - -### Level 3: Prune dead-fork entries on block apply (defensive) -When `_push_block()` successfully applies a block, remove competing blocks at that height from fork_db that are from dead forks - -## Detailed Changes - -### 1. `libraries/chain/include/graphene/chain/database.hpp` — Expose fork comparison as public method - -```cpp -/// Compare two fork branches by vote weight (HF12 logic). -/// Sums wit_obj.votes (on-chain stake) for all unique witnesses in each branch, -/// from the tip back to the common ancestor. -/// The longer chain gets a +10% bonus on its total weight (reflects that more -/// witnesses kept producing on it by consensus rules without deferring). -/// Returns: >0 if branch_a is heavier, <0 if branch_b is heavier, 0 if tied -/// Returns 0 if either tip is not in fork_db (cannot compare) -int compare_fork_branches(const block_id_type& branch_a_tip, const block_id_type& branch_b_tip) const; -``` - -### 2. `libraries/chain/database.cpp` — Implement `compare_fork_branches()` - -Extract `compute_branch_weight` lambda from lines 1300-1314 into the new public method. -Add +10% bonus to the longer chain's weight. -Wrap in try/catch to return 0 when `fetch_branch_from()` fails (one tip not in fork_db). -Refactor `_push_block()` to call `compare_fork_branches()` instead of inline code. - -```cpp -int database::compare_fork_branches(const block_id_type& branch_a_tip, const block_id_type& branch_b_tip) const { - try { - if (!_fork_db.is_known_block(branch_a_tip) || !_fork_db.is_known_block(branch_b_tip)) - return 0; // Cannot compare — one or both tips not in fork_db - - auto branches = _fork_db.fetch_branch_from(branch_a_tip, branch_b_tip); - - auto compute_branch_weight = [&](const fork_database::branch_type& branch) -> share_type { - flat_set seen_witnesses; - share_type total_weight = 0; - for (const auto& item : branch) { - const auto& wit_name = item->data.witness; - if (wit_name == CHAIN_EMERGENCY_WITNESS_ACCOUNT) continue; - if (seen_witnesses.insert(wit_name).second) { - try { - const auto& wit_obj = get_witness(wit_name); - total_weight += wit_obj.votes; - } catch (...) {} - } - } - return total_weight; - }; - - share_type weight_a = compute_branch_weight(branches.first); - share_type weight_b = compute_branch_weight(branches.second); - - // Longer chain gets +10% bonus on its vote weight. - // Each block produced is a consensus "vote" — witnesses on the longer - // chain didn't defer and kept producing by consensus rules. - // This reflects the stronger network support signal. - auto a_num = block_header::num_from_id(branch_a_tip); - auto b_num = block_header::num_from_id(branch_b_tip); - if (a_num > b_num) { - weight_a = weight_a + weight_a / 10; // +10% - } else if (b_num > a_num) { - weight_b = weight_b + weight_b / 10; // +10% - } - - if (weight_a > weight_b) return 1; // branch_a is heavier - if (weight_b > weight_a) return -1; // branch_b is heavier - return 0; // tied - } catch (...) { - return 0; // Cannot compare - } -} -``` - -### 3. `libraries/chain/include/graphene/chain/fork_database.hpp` — Add `remove_blocks_by_number()` - -```cpp -void remove_blocks_by_number(uint32_t num); -``` - -### 4. `libraries/chain/fork_database.cpp` — Implement `remove_blocks_by_number()` - -```cpp -void fork_database::remove_blocks_by_number(uint32_t num) { - auto blocks = fetch_block_by_number(num); - for (const auto& b : blocks) { - _index.get().erase(b->id); - } -} -``` - -### 5. `libraries/chain/database.cpp` — Prune dead-fork blocks after apply - -In `_push_block()`, after successfully applying a block that extends the current chain (no fork switch, around line 1417), add: - -```cpp -// Prune stale competing blocks from dead forks at this height -auto competing = _fork_db.fetch_block_by_number(new_block.block_num()); -for (const auto& cb : competing) { - if (cb->id != new_head->id && cb->data.previous != head_block_id()) { - wlog("Pruning stale competing block ${id} at height ${n} from fork_db (dead fork)", - ("id", cb->id)("n", new_block.block_num())); - _fork_db.remove(cb->id); - } -} -``` - -### 6. `plugins/witness/include/graphene/plugins/witness/witness.hpp` — Add config - -No header changes needed; the timeout is internal to `impl`. - -### 7. `plugins/witness/witness.cpp` — Two-level fork decision - -**Add to impl struct:** -```cpp -std::atomic fork_collision_defer_count_{0}; -uint32_t _fork_collision_timeout_blocks = 21; // safety timeout: one full witness round (21 blocks = 63s) -fc::time_point _fork_collision_start_time; // when we first started deferring -uint32_t _fork_collision_head_num = 0; // head_block_num when collision started -``` - -**Add CLI option:** `--fork-collision-timeout-blocks` (default: 21, i.e. one full witness schedule round = 63 seconds. After a full round, all scheduled witnesses have produced on the longer chain, confirming it's canonical.) - -**Replace the fork collision block (lines 545-578) with two-level logic:** - -```cpp -// Check if a competing block already exists in the fork database for this block height. -{ - auto existing_blocks = db.get_fork_db().fetch_block_by_number(db.head_block_num() + 1); - if (existing_blocks.size() > 0) { - bool has_competing_block = false; - item_ptr competing_block; - - if (dgp.emergency_consensus_active) { - has_competing_block = true; - competing_block = existing_blocks[0]; - } else { - for (const auto &eb : existing_blocks) { - if (eb->data.witness != scheduled_witness && - eb->data.previous != db.head_block_id()) { - has_competing_block = true; - competing_block = eb; - break; - } - } - } - - if (has_competing_block && competing_block) { - fork_collision_defer_count_++; - - // LEVEL 2: Stuck-head timeout - // If we've been deferring and the head hasn't advanced, the competing - // block is from a dead fork. The network has moved on without it. - // After 21 consecutive deferrals (one full witness round = 63s), - // we can be sure the longer chain had all scheduled witnesses - // produce on it — confirming it's the canonical chain. - if (fork_collision_defer_count_ > _fork_collision_timeout_blocks) { - wlog("Fork collision timeout exceeded (${n} deferrals, head stuck at ${h}). " - "Removing dead-fork competing block and producing on our chain.", - ("n", fork_collision_defer_count_.load())("h", db.head_block_num())); - db.get_fork_db().remove_blocks_by_number(db.head_block_num() + 1); - // Fall through to produce block - } - // LEVEL 1: Vote-weighted comparison (when both forks are in fork_db) - else if (db.has_hardfork(CHAIN_HARDFORK_12)) { - int weight_cmp = db.compare_fork_branches( - competing_block->id, db.head_block_id()); - - if (weight_cmp < 0) { - // Our fork has MORE vote weight -> produce on our fork - wlog("Our fork has more vote weight at height ${h}. " - "Producing despite competing block from weaker fork.", - ("h", db.head_block_num() + 1)); - // Remove the losing competing block - db.get_fork_db().remove(competing_block->id); - // Fall through to produce block - } else if (weight_cmp > 0) { - // Competing fork has MORE vote weight - // The competing branch is in fork_db and has more support. - // We should switch to it. The normal _push_block path will - // handle the switch when the competing block's children arrive. - // For now, defer to let the fork switch happen naturally. - capture("height", db.head_block_num() + 1)("scheduled_witness", scheduled_witness); - wlog("Competing fork at height ${h} has more vote weight. " - "Deferring to allow fork switch to stronger chain.", - ("h", db.head_block_num() + 1)); - return block_production_condition::fork_collision; - } else { - // Tied (or comparison impossible — one tip not in fork_db) - // Defer briefly, timeout will kick in - capture("height", db.head_block_num() + 1)("scheduled_witness", scheduled_witness); - wlog("Fork collision at height ${h} with tied/unknown vote weight. " - "Deferring (attempt ${n}/${max}).", - ("h", db.head_block_num() + 1) - ("n", fork_collision_defer_count_.load()) - ("max", _fork_collision_timeout_blocks)); - return block_production_condition::fork_collision; - } - } - // Pre-HF12: original defer behavior with timeout - else { - capture("height", db.head_block_num() + 1)("scheduled_witness", scheduled_witness); - return block_production_condition::fork_collision; - } - } - } -} -``` - -**Reset `fork_collision_defer_count_`** to 0 in `block_production_loop()` when result is: -- `produced` — block was made, no collision -- `not_my_turn` / `not_time_yet` — normal skips, collision resolved -- Any result where `db.head_block_num()` has changed since last check - -## File Change Summary - -| File | Change | -|------|--------| -| `libraries/chain/include/graphene/chain/database.hpp` | Add `compare_fork_branches()` declaration | -| `libraries/chain/database.cpp` | Extract `compute_branch_weight` into `compare_fork_branches()`, prune dead fork blocks after apply | -| `libraries/chain/include/graphene/chain/fork_database.hpp` | Add `remove_blocks_by_number()` declaration | -| `libraries/chain/fork_database.cpp` | Implement `remove_blocks_by_number()` | -| `plugins/witness/witness.cpp` | Two-level fork decision: vote-weighted comparison + stuck-head timeout | - -## Key Design Decisions - -1. **`wit_obj.votes` = on-chain stake** — the vote-weight comparison sums the stake (VIZ tokens) that voted for each unique witness that produced blocks in the divergent portion of each fork. It is NOT the scheduled slot count. -2. **All blocks in the divergent branch count** — not just the top block. `fetch_branch_from()` walks from each tip back to the common ancestor, and all unique witnesses on each branch contribute their stake weight. -3. **Longer chain gets +10% bonus** — each block is a consensus "vote" by the producing witness. Witnesses on the longer chain didn't defer and kept producing by consensus rules. The +10% bonus ensures that a slightly shorter chain with slightly more stake cannot override a clearly longer chain that more witnesses kept building on. -4. **Our fork wins ties** — when vote weights (including bonus) are equal, we produce on our own chain (less disruptive) -5. **Competing fork with more weight -> defer for switch** — if the other chain has more support, we defer to allow `_push_block()` to naturally switch us when more blocks arrive -6. **Stuck-head timeout = 21 blocks (one full witness round)** — after 21 consecutive deferrals (63 seconds), all scheduled witnesses have had a chance to produce on the longer chain. We can be confident the longer chain is canonical. The competing block from the dead fork is removed and production resumes. -7. **Pruning on block apply** — when a block is applied, competing blocks from dead forks at that height are removed, preventing them from causing false collision detection in the future - -## Verification -- Short micro-forks (1-2 blocks): vote-weight comparison resolves quickly, no stuck -- Our fork has more votes: witness produces immediately -- Competing fork has more votes: witness defers for natural switch via `_push_block()` -- Stuck head (your scenario): timeout after 21 slots (one full witness round) removes dead-fork block, witness produces -- Stale fork_db entries pruned on each block apply -- Emergency mode: existing behavior preserved (any competing block = defer, timeout applies) - ---- - -## Implementation Status - -All planned changes have been implemented. Deviations from the original plan are noted below. - -| # | Planned Change | File | Status | Notes | -|---|---------------|------|--------|-------| -| 1 | Add `compare_fork_branches()` declaration | `database.hpp` | Done | Matches plan exactly | -| 2 | Implement `compare_fork_branches()` with +10% longer-chain bonus | `database.cpp` | Done | Matches plan; refactored `_push_block()` to use it instead of inline lambda | -| 3 | Refactor `_push_block()` HF12 fork-switch to use `compare_fork_branches()` | `database.cpp` | Done | Replaced 26-line inline lambda with 4-line call to `compare_fork_branches()` | -| 4 | Add `remove_blocks_by_number()` declaration | `fork_database.hpp` | Done | Matches plan exactly | -| 5 | Implement `remove_blocks_by_number()` | `fork_database.cpp` | Done | Matches plan exactly | -| 6 | Prune dead-fork blocks after block apply | `database.cpp` | Done | Slight deviation: uses `new_block.id()` instead of `new_head->id` (code is in the non-fork-switch path where `new_head` is out of scope) | -| 7 | Add fork collision state fields to `impl` struct | `witness.cpp` | Done | **Deviation**: removed `_fork_collision_head_num` (dead code) and `_fork_collision_start_time` (unused). Only `fork_collision_defer_count_` and `_fork_collision_timeout_blocks` remain | -| 8 | Add `--fork-collision-timeout-blocks` CLI option | `witness.cpp` | Done | Default 21, matches plan | -| 9 | Two-level fork decision in `maybe_produce_block()` | `witness.cpp` | Done | **Deviation**: Level 2 timeout runs BEFORE the HF12 check, so pre-HF12 nodes also benefit from the timeout. The plan had Level 2 inside the HF12 branch | -| 10 | Reset `fork_collision_defer_count_` in `block_production_loop()` | `witness.cpp` | Done | Reset on `produced`, `not_synced`, `not_my_turn`. **Not reset** on `not_time_yet` (timer hasn't fired yet, count should persist) | - -### Bugs Found and Fixed During Review - -| # | Severity | Bug | Fix | -|---|----------|-----|-----| -| 1 | Critical | Pre-HF12 path deferred forever (same as original bug) — Level 2 timeout was inside the `else if (has_hardfork(HF12))` branch | Moved Level 2 timeout check before the HF12 branch so all nodes benefit | -| 2 | Low | `_fork_collision_head_num` declared but never read or written | Removed the field | -| 3 | Info | `fork_collision_defer_count_` was planned as `std::atomic` but implemented as `uint32_t` | Kept as `uint32_t` — all access is single-threaded (block production loop) | diff --git a/.qoder/plans/Witness-to-Validator_Full_Rename_6e522add.md b/.qoder/plans/Witness-to-Validator_Full_Rename_6e522add.md deleted file mode 100644 index ae25feb8ac..0000000000 --- a/.qoder/plans/Witness-to-Validator_Full_Rename_6e522add.md +++ /dev/null @@ -1,148 +0,0 @@ -# Witness-to-Validator Full Rename Plan - -Phase 1 (internal C++ methods/enums) is already done. This plan covers everything remaining. - -## Scope: 5 Protocol Operations - -| Type ID | Old Struct | New Struct | -|---------|-----------|------------| -| 6 | `witness_update_operation` | `validator_update_operation` | -| 7 | `account_witness_vote_operation` | `account_validator_vote_operation` | -| 8 | `account_witness_proxy_operation` | `account_validator_proxy_operation` | -| 30 | `shutdown_witness_operation` | `shutdown_validator_operation` | -| 42 | `witness_reward_operation` | `validator_reward_operation` | - -## Task 1: Protocol operation struct renames - -**Files:** -- `libraries/protocol/include/graphene/protocol/chain_operations.hpp` — rename 3 structs + 3 FC_REFLECT macros -- `libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp` — rename 2 structs + 2 FC_REFLECT macros + constructors -- `libraries/protocol/include/graphene/protocol/operations.hpp` — update 5 names in static_variant list (order unchanged!) -- `libraries/protocol/chain_operations.cpp` — rename 3 validate() method definitions - -Rename `witness` field to `validator` in `account_validator_vote_operation` (was `account_witness_vote_operation`). Also rename `witness` field to `validator` in `validator_reward_operation` (was `witness_reward_operation`). - -## Task 2: JSON backward-compatibility alias table - -**File:** `libraries/protocol/operation_util_impl.cpp` - -Add a `resolve_operation_name()` function that maps old JSON names to new names: -- `witness_update` → `validator_update` -- `account_witness_vote` → `account_validator_vote` -- `account_witness_proxy` → `account_validator_proxy` -- `shutdown_witness` → `shutdown_validator` -- `witness_reward` → `validator_reward` - -This must be hooked into the `from_variant` path so nodes accept both old and new JSON names from clients. - -## Task 3: Evaluator renames - -**Files:** -- `libraries/chain/include/graphene/chain/chain_evaluator.hpp` — `DEFINE_EVALUATOR(witness_update)` → `DEFINE_EVALUATOR(validator_update)`, same for vote and proxy -- `libraries/chain/chain_evaluator.cpp` — 3 method signatures -- `libraries/chain/chain_properties_evaluators.cpp` — `witness_update_evaluator::do_apply` -- `libraries/chain/database.cpp` — 3 `register_evaluator` calls + all `push_virtual_operation(witness_reward_operation(...))` → `validator_reward_operation(...)` and `push_virtual_operation(shutdown_witness_operation(...))` → `shutdown_validator_operation(...)` - -## Task 4: Chain object renames - -**File:** `libraries/chain/include/graphene/chain/witness_objects.hpp` -- `witness_object` → `validator_object` -- `witness_schedule_object` → `validator_schedule_object` -- `witness_schedule_type` → `validator_schedule_type` -- `current_shuffled_witnesses` → `current_shuffled_validators` -- `witness_index` → `validator_index` -- `witness_id_type` → `validator_id_type` -- `witness_object_type` → `validator_object_type` -- All FC_REFLECT macros - -Also rename the file itself: `witness_objects.hpp` → `validator_objects.hpp` - -**File:** `libraries/chain/include/graphene/chain/chain_objects.hpp` -- `block_post_validation_object` → `validator_confirmation_object` - -**File:** `libraries/chain/include/graphene/chain/chain_object_types.hpp` -- `witness_object_type` → `validator_object_type` -- `witness_schedule_object_type` → `validator_schedule_object_type` - -**Every file that includes `witness_objects.hpp`** must be updated to include `validator_objects.hpp` and use new type names. - -## Task 5: Database layer references - -**Files:** -- `libraries/chain/database.cpp` — all references to witness_object, witness_schedule_object, get_witness_schedule_object(), etc. -- `libraries/chain/database.hpp` — method signatures like `get_witness_schedule_object()` -- `libraries/chain/include/graphene/chain/database.hpp` — same - -Key method renames: -- `get_witness_schedule_object()` → `get_validator_schedule_object()` -- `get_scheduled_witness()` → `get_scheduled_validator()` -- `get_slot_time()`, `get_slot_at_time()` — keep (no "witness") -- `adjust_witness_votes()` → `adjust_validator_votes()` -- `adjust_proxied_witness_votes()` → `adjust_proxied_validator_votes()` - -## Task 6: API object + endpoint renames - -**File:** `libraries/api/include/graphene/api/witness_api_object.hpp` → rename to `validator_api_object.hpp` -- `witness_api_object` → `validator_api_object` - -**File:** `libraries/api/witness_api_object.cpp` — same rename + all field references - -**File:** `plugins/witness_api/plugin.cpp` — rename all 8 API endpoints + add deprecated aliases: -- `get_active_witnesses` → `get_active_validators` (+ keep old as alias) -- `get_witness_schedule` → `get_validator_schedule` (+ keep old as alias) -- `get_witnesses` → `get_validators` (+ keep old as alias) -- `get_witness_by_account` → `get_validator_by_account` (+ keep old as alias) -- `get_witnesses_by_vote` → `get_validators_by_vote` (+ keep old as alias) -- `get_witnesses_by_counted_vote` → `get_validators_by_counted_vote` (+ keep old as alias) -- `get_witness_count` → `get_validator_count` (+ keep old as alias) -- `lookup_witness_accounts` → `lookup_validator_accounts` (+ keep old as alias) - -**File:** `plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp` — update DEFINE_API_ARGS macros - -## Task 7: Wallet renames - -**Files:** -- `libraries/wallet/wallet.cpp` — rename commands, keep old as deprecated aliases -- `libraries/wallet/include/graphene/wallet/wallet.hpp` — method declarations -- `libraries/wallet/include/graphene/wallet/remote_node_api.hpp` — remote API method names - -## Task 8: Plugin directory + config renames - -This involves actual directory renaming which is outside the scope of code editing tools. Manual steps: -- `plugins/witness/` → `plugins/validator/` -- `plugins/witness_api/` → `plugins/validator_api/` -- `plugins/witness_guard/` → `plugins/validator_guard/` -- Update all CMakeLists.txt references -- Update `share/vizd/config/config_witness.ini` → `config_validator.ini` -- Update `share/vizd/config/config.ini` plugin names - -## Task 9: Update documentation - -- `.qoder/docs/witness-to-validator-migration-reference.md` — update status table to "Done" -- `.qoder/docs/op-witness.md` → rename to `op-validator.md` with new operation names -- `.qoder/docs/witness-plugin.md` → rename/rewrite -- `.qoder/docs/data-types.md` — update operation type table - -## Task 10: Build verification - -Compile the entire project to verify no broken references. - -## Execution Order - -Tasks 1-5 must be done together (they're interdependent — renaming a struct breaks all references). Recommended approach: -1. Task 1 (protocol structs) + Task 2 (alias table) together -2. Task 3 (evaluators) — immediately after, since they reference the structs -3. Task 4 (chain objects) — next, largest scope -4. Task 5 (database layer) — after chain objects -5. Task 6 (API) — after database -6. Task 7 (wallet) — after API -7. Task 8 (plugins/config) — last, requires directory renames -8. Task 9 (docs) -9. Task 10 (build verify) - -## Risk Assessment - -- **Binary wire format:** Zero risk — integer type IDs are unchanged -- **JSON compatibility:** The alias table ensures old clients can still submit transactions with old names -- **Shared memory on-disk format:** `witness_object` is serialized to shared memory by type ID, not name — but FC_REFLECT field names ARE serialized in some contexts. Need to verify if renaming FC_REFLECT fields breaks the shared memory format. If it does, a migration layer is needed. -- **Snapshot format:** Snapshots serialize objects as JSON with field names — renaming fields breaks snapshot compatibility with old snapshots diff --git a/.qoder/plans/dlt-p2p-network-redesign_91a7ca29.md b/.qoder/plans/dlt-p2p-network-redesign_91a7ca29.md deleted file mode 100644 index 5ff1e60b16..0000000000 --- a/.qoder/plans/dlt-p2p-network-redesign_91a7ca29.md +++ /dev/null @@ -1,785 +0,0 @@ -# DLT P2P Network Redesign — Analysis & Implementation Plan - -## 1. Current System Analysis - -### 1.1 Existing P2P Architecture (node.cpp — 6978 lines) -The current P2P layer is a general-purpose synopsis-based protocol from Graphene/BitShares with many layers of backwards compatibility: - -- **Hello handshake**: Exchanges node identity, chain state, DLT mode, emergency status via `user_data` fields (already extended for DLT in `generate_hello_user_data` around line 2297). -- **Synopsis sync**: `get_blockchain_synopsis()` builds exponential backoff hash list → peer returns which blocks it doesn't know → `fetch_items` requests batches → `send_sync_block_to_node_delegate` processes them. -- **Inventory gossip**: Normal operation uses `item_ids_inventory_message` to advertise new blocks/transactions. -- **Fork handling**: `fork_rejected_until` soft-ban, `unlinkable_block_strikes`, `sync_spam_strikes` counters. -- **Chain status announcements**: Type 5018 — already carries head, LIB, DLT range, emergency flags. -- **Post-validation**: `block_post_validation_message` (type 6009) lets witnesses signal block confirmation. - -### 1.2 Existing DLT Infrastructure (already built) -| Component | What exists | -|-----------|-------------| -| `dlt_block_log` | Memory-mapped rolling block log with index, `read_block_by_num()`, `head()`, `start_block_num()`, `truncate_before()` | -| `fork_db` | `fetch_block_by_number()`, `fetch_branch_from()`, `compare_fork_branches()` (vote-weighted) | -| `node_delegate` | `is_dlt_mode()`, `get_dlt_earliest_block_num()`, `is_emergency_consensus_active()`, `has_emergency_private_key()` | -| `chain_status_announcement` | Message type 5018 with head/LIB/DLT/emergency fields | -| `witness_plugin` | Minority fork detection, `resync_from_lib()`, fork collision deferral with Level 1 (vote-weight) and Level 2 (timeout) | - -### 1.3 What's Broken/Problematic in node.cpp -- 6978 lines of mostly irrelevant Graphene inheritance -- Synopsis-based sync is overengineered for DLT where blocks are contiguous -- Complex state machine (synopsis→fetch_ids→fetch_items→process) prone to ping-pong loops -- Peer discovery is overcomplicated (potential_peer_db, firewall checks, address gossip) -- Inventory advertisement adds complexity without benefit for block-only DLT -- Soft-ban/strike logic scattered across multiple locations -- Thread model (`P2P_IN_DEDICATED_THREAD`) adds overhead - ---- - -## 2. Proposed Design Analysis - -### 2.1 Core Protocol (Normal Mode) - -**Step 1: Peer registration + discovery** -- **Bootstrap**: Read seed peer endpoints from config (`p2p-seed-node`, already exists in p2p_plugin) -- **Peer discovery via "friends" exchange**: After a node transitions from sync/catchup to forward mode, it becomes eligible to participate in peer discovery. Once every 10 minutes per peer, a node may send a `dlt_peer_exchange_request` ("send me your 'our fork' friends") to any connected peer. The responder replies with `dlt_peer_exchange_reply` containing its known "our fork" peer endpoints (IP:port + node_id). If a peer asks too frequently, the responder sends `dlt_peer_exchange_rate_limited` ("you already asked, wait N seconds"). -- **Peer database**: Simple in-memory set of known peer endpoints, pruned on disconnect. No persistent potential_peer_db, no firewall checks — simpler than the old `peers.json` + `potential_peer_db` machinery. -- **Assessment**: Controlled peer discovery without the complexity of the existing address-gossip system. Rate-limiting (10 min cooldown per peer) prevents flooding while allowing the network to gradually learn about new nodes that join. - -**Step 2: Hello with DLT range + status** -- Send: `[dlt_start, dlt_end]`, head block (num + hash), LIB (num + hash), emergency status, fork status, node status (sync/forward) -- Receiver checks if initiator's head/LIB are in our dlt_block_log or fork_db → flags as "our fork" (exchange enabled, `fork_alignment=true`) or "not our fork" (`fork_alignment=false`) -- **Node status model**: The node has 2 statuses: - - **SYNC**: Catching up — send hello, try to find next continuation after our head, request blocks in ranges. When we reach the top → transition to FORWARD. - - **FORWARD**: Caught up — exchange data with "our fork" peers. If fork detected, get competing blocks and mark peer as "our" or "not our". Don't care if peer's head is above ours. -- **Assessment**: This replaces the existing synopsis approach. Better for DLT. Simplified from the previous 3-state model: - - We don't care about initiator's head being above ours — they're just ahead. - - In sync mode: just get blocks after our head. - - In forward mode: mark peer as "our" or "not our" based on chain link. - - LIB below dlt_start = "unknown" during sync (allow exchange), re-evaluate in forward mode. - - **Keep**: "fork status (not used, but visible in logs)" — valuable for debugging. - -**Step 3: Range query + get request** -- Peer asks: "do you have block X with hash H?" -- We check dlt_block_log: if known, respond with actual available range + wait for specific `get` packet -- `get` packet: block_num + prev_hash (to verify chain link) -- We send: the block + next_available_num (or 0 if none) + sync_status bool -- If sync_status says "last block", flag peer as "our fork" (exchange enabled) -- **Assessment**: This is essentially a binary-search-free sync. Good for DLT. But: - - **Improvement**: The "range query" step could be skipped. Just include the range in hello response. - - **Improvement**: The "prev_hash" check is a good anti-corruption measure. Keep it. - - **Issue**: What if we don't have the requested block? Current design says "we saying that we not have it" — need a `not_available` response type. - -**Step 4: Exchange + retranslation** -- When we receive a block/transaction from "our fork" peer → add to chain + retranslate to all "our fork" peers -- Witnesses send post-validation to accelerate LIB -- **Assessment**: Already implemented via `broadcast_block` + `broadcast_block_post_validation`. The retranslation to "our fork" peers only is a good optimization over broadcast-to-all. - -### 2.2 Fork Resolution (Fork Mode) - -**Key design**: Wait until end of schedule round, then tally vote weights per fork branch. - -**Analysis of the proposed fork resolution**: -- The existing system already has `compare_fork_branches()` in database.cpp (line 1359-1417) which does vote-weighted comparison with +10% longer-chain bonus. -- The user wants to defer fork resolution to schedule round boundaries — this is MORE conservative than the existing Level 1+Level 2 system. -- **Resolution trigger**: Use `num_scheduled_witnesses * 2` blocks (e.g., 21 × 2 = 42 blocks = 2 full rounds) as the fork resolution threshold — same as the existing emergency DLT minority fork detection in witness.cpp (line 722: `CHAIN_MAX_WITNESSES * 2`). This avoids the ambiguity of "end of schedule round" when different forks have diverged at different block heights. When 42 blocks have been produced since the fork was first detected, resolution fires. -- **Improvement needed**: None — this is a clean, proven threshold already used in production code. - -**Data structure: fork_db with `get_all_active_forks()`**: -- The fork_db already supports multiple forks at same height (`fetch_block_by_number()` returns a vector). -- Add `fork_database::get_all_active_branch_tips()` — returns distinct tip IDs for all currently tracked fork branches. -- Each branch is then resolved via the existing `compare_fork_branches()` vote-weighted comparison (database.cpp line 1359-1417). -- **Design decision confirmed**: Use fork_db as the sole fork data structure; no separate "array of arrays". - -**Issue: "Stop producing blocks if in minority fork"** -- Already implemented in witness.cpp minority fork detection (line 648-666) -- The new P2P layer needs to expose a `bool is_on_majority_fork()` query - -### 2.3 Emergency Mode - -The user's emergency design is mostly aligned with existing code: -- Emergency activates when no blocks processed for timeout period (already in `_apply_block` emergency check, database.cpp ~line 5200) -- Reset witness signing keys (already done: `w.signing_key = public_key_type()` at line 5001) -- Free slots go to `committee` account (already: hybrid schedule override at line 2733) -- Nodes with emergency key produce blocks (already: witness_plugin emergency master check) - -**Issue**: "If node in emergency got blocks with emergency witness — auto choose this fork as main head" -- The existing `compare_fork_branches()` already does this: branch with emergency committee blocks wins unconditionally (line 1395-1397) -- **Good**: Aligned with existing logic. - -**Issue**: "LIB moves faster" during emergency -- Already implemented: during emergency, LIB advances every block capped at HEAD-1 (line 5737-5782) -- **Good**: No changes needed. - -### 2.4 Anti-Spam - -"Good packet resets spam counter" — **This is a significant improvement over existing logic.** - -Current system has independent strike counters that only decrement on certain actions. The user's proposal is: -- Each peer has a `spam_strikes` counter -- Any valid block, valid transaction, or valid hello response → reset counter to 0 -- Invalid/duplicate/wrong-fork packets → increment counter -- Counter exceeds threshold → soft-ban - -**Assessment**: Much simpler and more effective than the current multi-counter system. The reset-on-good behavior naturally recovers from transient issues. - -**Recommendation**: Use a single `spam_strikes` per peer instead of the current multiple counters (`unlinkable_block_strikes`, `sync_spam_strikes`, `fetch_ids_rate_limit_strikes`). - -### 2.5 DLT Pruning - -"Remove old blocks every 1000 blocks, keep only `dlt-block-log-max-blocks` (default 100000)" - -The existing `dlt_block_log::truncate_before()` works but is expensive (copies all retained data). - -**Decision: Modify `truncate_before()` to batch-prune 10000 blocks at once instead of 1000.** This reduces copy frequency by 10x while keeping the implementation simple (no ring-buffer refactor needed). The pruning trigger checks every 10000 blocks produced — when `current_head - dlt_start > dlt-block-log-max-blocks`, prune the oldest 10000 blocks in one call. - ---- - -## 3. Design Issues & Improvements - -### 3.1 Peer Discovery via "Friends" Exchange — Design Details - -The user's proposal is: -- After catchup → forward mode, exchange "our fork" peers -- `dlt_peer_exchange_request` message: "send me your friends" -- `dlt_peer_exchange_reply` message: list of known peer endpoints (IP:port + node_id) -- Rate limit: once per 10 minutes per peer -- If asked too soon: `dlt_peer_exchange_rate_limited` ("you already asked, wait N seconds") - -**Improvements over old system**: The existing node.cpp uses: -- `potential_peer_db` (persistent JSON file `peers.json`) -- `address_request_message`/`address_message` gossip -- `firewall_check_state_data` for NAT detection -- Connection disposition tracking (last_connection_succeeded/failed) - -All of this is unnecessary for DLT where we only care about "our fork" peers. The proposed friends exchange is simpler: -- No persistence — peers are discovered through live exchange only -- No firewall checks — DLT nodes are assumed reachable (config-controlled) -- No address gossip — only intentional peer exchange requests -- Rate-limiting prevents abuse (spam counter also applies) - -**Edge cases**: -- New node with only seed peers: after catchup, sends `dlt_peer_exchange_request` to a seed. Seed responds with its known peers. Node connects to new ones. -- Peer disconnects: NOT immediately removed from `_known_peers` set. Instead, the peer is flagged as `disconnected` and reconnection is attempted with an incrementing backoff step (starting at ~30s, doubling each retry, capped at 3600s max). If the peer does not respond for 8 hours (configurable), it is permanently removed from `_known_peers`. -- Self in peer list: receiver filters out its own node_id and already-connected peers. -- All peers exhausted: periodic timer (every ~12 min) picks a random connected peer to re-request. If no new peers found, nothing happens. - -### 3.2 Transaction Propagation Design - -Transactions follow a clear lifecycle: -1. **Ingest**: Transactions enter the system via the `jsonrpc` plugin (API endpoint). -2. **Broadcast**: The node translates (retransmits) the transaction to all "our fork" peers. -3. **Mempool**: A separate in-memory transaction index (mempool) collects and deduplicates pending transactions. -4. **Witness inclusion**: When a witness creates a block and includes the transaction, the block is broadcast. -5. **Dedup on block receipt**: When we receive a block containing a transaction that is in our mempool, we remove that transaction from the mempool (we have a separate index for working with transactions). -6. **Retranslation**: If we receive a new transaction from an "our fork" peer, we retranslate it to all other "our fork" peers (excluding the sender). - -This provides natural deduplication: transactions are tracked in the mempool until they appear in a confirmed block, then pruned. - -**Transaction expiry pruning**: Transactions have an `expiration` field (`fc::time_point_sec`). Expired transactions MUST be pruned from the P2P mempool — they will never be included in a block. The P2P layer checks mempool entries periodically and removes any where `expiration < now`. - -**Transaction fork linkage (TaPoS)**: Transactions reference a specific block via `ref_block_num` + `ref_block_prefix` (TaPoS — Transactions as Proof of Stake). The chain validates this in `_apply_transaction()` (database.cpp:4547-4554) by checking against `block_summary_object`. If `ref_block_num` points to a block NOT in our fork (e.g., on a competing fork), the TaPoS check fails → the transaction is invalid for us. **Therefore**: when we switch forks or detect that a transaction's `ref_block_num` references a block not on our chain, we MUST prune that transaction from the P2P mempool. - -**Current system already has these mechanisms (but at the chain level, not P2P level)**: -- `_pending_tx` (database.hpp:504): `vector` — the witness mempool. Used during `generate_block()` to select transactions for the next block. -- `transaction_object` / `transaction_index` (transaction_object.hpp): Chainbase persistent index with `by_trx_id` (hashed, for dedup) and `by_expiration` (ordered, for cleanup). Created when a transaction is included in a block. -- `clear_expired_transactions()` (database.cpp:5944-5953): Already prunes expired `transaction_object` entries during block processing. -- TaPoS check (database.cpp:4547-4554): Verifies `ref_block_num`/`ref_block_prefix` against `block_summary_object`. Rejects transactions referencing unknown/fork blocks. - -**The new DLT P2P mempool is SEPARATE from the chain's `_pending_tx`**: The P2P mempool sits at the network layer (before chain acceptance). It needs its own expiry + fork-alignment pruning because the chain's mechanisms only apply after a transaction is accepted into `_pending_tx`. A transaction that arrives from a peer might be expired or reference a fork block — we should prune it at the P2P layer before even trying to push it to the chain. - -**Comparison with old P2P transaction flow**: The old P2P does NOT work the same way. The old flow uses: -- **Inventory gossip**: Advertises transaction IDs via `item_ids_inventory_message` → peer requests if missing. Not direct relay. -- **No separate mempool**: Chain's `accept_transaction()` handles dedup internally — no explicit P2P-level mempool index. -- **Broadcast to ALL peers**: `node->broadcast(trx_message(tx))` sends to every connected peer, not just "our fork". -- **No mempool pruning**: When a transaction appears in a block, there's no explicit removal from a P2P mempool — the chain just won't accept it again. -- **No expiry pruning at P2P level**: Expired transactions are only cleaned up by the chain's `clear_expired_transactions()`, not at the P2P layer. - -The new DLT P2P flow is simpler and more efficient: direct relay (no inventory gossip), separate P2P mempool index (explicit dedup, expiry pruning, and fork-alignment pruning), and targeted broadcast ("our fork" peers only). - -### 3.3 Initial Sync / Gap Filling - -**DLT stores only the top head block range** — older blocks are pruned. For example, if the node holds blocks [200..500], blocks [1..199] are already pruned and NOT stored. This is how DLT works: only the latest `dlt-block-log-max-blocks` range is retained. - -**Bulk get with last-block-info**: The `get` request works in bulk (request a range of blocks). The reply includes the requested blocks PLUS additional info only about the **last block in the bulk** (its hash, next_available, sync_status). This keeps replies compact. - -**Gap handling for new/recovery nodes**: -- A new or recovering node tries to fetch all needed blocks from known peers. -- If a gap is discovered (missing blocks that no connected peer has), the node MUST **reset its state** and go forward by **importing a snapshot from trusted peers**. -- There is no partial gap-filling across disconnected ranges — either the chain is contiguous from the peer's perspective, or the node resyncs from a snapshot. - -### 3.4 IMPORTANT: Multiple Simultaneous Peers -The proposal describes 1:1 exchange but not multi-peer coordination: -- What if peer A gives us block 100 and peer B gives us a different block 100? -- The fork_db handles this, but the P2P layer needs to know when to stop requesting from a peer -- **Recommendation**: Track per-peer "last good block" and prefer peers that give us chain-extending blocks - -### 3.5 IMPORTANT: "Our fork" determination and Node Sync/Forward Model - -The node operates in two primary statuses: - -**1. SYNC status** (catching up): -- We send hello to peers and try to find the next continuation after our head block. -- We request blocks in ranges after our head from connected peers. -- When we reach the top (head = no more blocks available from any peer) → transition to FORWARD status. -- During sync, we don't care about fork alignment — we just need blocks that continue our chain. - -**2. FORWARD status** (caught up, exchanging data): -- We exchange blocks and transactions with "our fork" peers. -- If a fork is detected (block doesn't link to our head), we try to get the competing blocks. -- The competing blocks may be true for our consensus or false — it's OK, we just need to **mark this peer as "our" or "not our"**. -- "Our fork" peer = their head or LIB hash is in our `dlt_block_log` or `fork_db`. -- "Not our fork" peer = their blocks don't link to anything we know. - -**We don't care if initiator's head is above ours** — they're just ahead, that's fine. Strange things happen on the network. - -**Fork status during forward mode**: -- `fork_status = 0` (normal): Peer's blocks extend our chain → "our fork", full exchange. -- `fork_status = 1` (looking_resolution): Peer's blocks create a fork → still exchange, but mark peer. Let `fork_db` + vote-weight resolution determine winner. -- `fork_status = 2` (minority): We're on the losing fork → stop producing, switch. - -**Edge case: initiator's LIB is below our dlt_start**: We can't verify their fork alignment → treat as "unknown" during sync (allow exchange to get blocks). Once in forward mode, re-evaluate after we have their blocks. - -### 3.6 Color-Coded Logging — Good Design -Existing color codes already defined (line 79-83 in node.cpp): -``` -CLOG_RED = fork blocks -CLOG_ORANGE = warnings -CLOG_GRAY = transactions -CLOG_GREEN = sync/production -CLOG_CYAN = diagnostics -``` -The user's proposal adds: -- Green: sync start, catchup, block production -- White: normal block exchange from our fork -- Red: fork block exchange -- Dark gray: transaction exchange - -**Assessment**: These map well to existing colors. Just need consistency. - -### 3.7 Security Hardening (from AI Review) - -**P0 — Mempool DoS protection**: Hard mempool size limits with eviction policy: -- `max_transactions` (default 10000) — hard cap on mempool entries -- `max_memory_bytes` (default 100MB) — hard cap on total mempool memory -- `max_tx_size` (default 64KB) — reject oversized transactions at P2P layer -- `max_expiration_headroom` (default 24h) — reject transactions with `expiration` too far in the future -- Eviction policy: when caps are hit, evict oldest-expiry transactions first -- All rejections increment sender's `spam_strikes` - -**P1 — Peer exchange poisoning protection**: Anti-sybil diversity requirements: -- `max_peers_per_subnet` (default 2 per /24) — prevent same-subnet domination -- `max_accept_from_exchange` (default 10 peers per exchange reply) — cap to prevent single-reply flooding -- `min_uptime_for_exchange` (default 600s) — peer must be connected and stable before its endpoint is shared in exchange replies -- Filter exchange replies: remove peers from same subnet if limit reached, cap reply size - -**P1 — Fork resolution hysteresis**: The 42-block window alone can create a decision race at the boundary where both forks briefly appear as winners during network partition. Add confirmation requirement: -- After the 42-block window, compute the vote-weight winner -- The winner must maintain its lead for `CONFIRMATION_BLOCKS` (6) consecutive blocks before we execute the fork switch -- If the lead flips during confirmation, reset the counter — prevents premature switching - -**P1 — Block validation ordering**: The `prev_block_id` check prevents chain link corruption, but we also need: -- `block.previous` must exist in our `fork_db` or `dlt_block_log` — reject blocks that reference unknown ancestors (prevents circular dependencies and future-reference attacks) -- Per-peer `expected_next_block` tracking — reject blocks that skip too far ahead (prevents hole-creation attacks) -- Blocks received but not yet validated: track with `pending_block_batch` timeout (30s) — if validation doesn't complete, soft-ban the peer - -**P2 — Sync-to-forward transition race**: "No more blocks available from any peer" is ambiguous during temporary disconnects. Add sync stagnation detection: -- Track `last_block_received_time` during sync -- If no new block for `SYNC_STAGNATION_SEC` (30s), re-request from all connected peers (up to 3 retries) -- If still stagnated after retries → transition to FORWARD with warning log -- Prevents getting stuck in SYNC due to transient network issues - -**P2 — Reconnection backoff amplification**: Add jitter and reset conditions: -- Add random jitter (±25% of backoff interval) to prevent synchronized reconnection storms -- Reset `reconnect_backoff_sec` to initial value (30s) when a connection stays stable for > 5 minutes — prevents persistent long backoff from transient disconnects - -**P2 — Transaction fork-awareness during sync**: Transactions received during SYNC mode reference a chain that may get reorganized during catchup. Tag them as provisional: -- `mempool_entry.is_provisional = true` for transactions received during sync -- `mempool_entry.expected_head` = our head at time of receipt -- On SYNC→FORWARD transition: revalidate all provisional entries against final chain, prune those whose TaPoS is now invalid - -**P3 — Protocol version negotiation**: Add `protocol_version` field to `dlt_hello_message` (start at 1). Peers with different major versions disable exchange. Allows future protocol upgrades without breaking compatibility. - -**P3 — Peer lifecycle state machine**: Explicit states with timeouts: -- `connecting` (5s timeout) → `handshaking` (10s timeout) → `syncing` → `active` → `disconnected` (backoff) → `banned` (duration from spam threshold) -- Each transition has a timeout — if stuck in any intermediate state, disconnect and move to `disconnected` - ---- - -## 4. Implementation Plan - -### Phase 1: New Message Types (libraries/network/include/graphene/network/) - -**Task 1.1**: Define new message types in `core_messages.hpp` (after type 5018): -``` -dlt_hello_message_type = 5100 // replaces hello for DLT peers -dlt_hello_reply_message_type = 5101 // response to dlt_hello -dlt_range_request_message_type = 5102 // "do you have block N?" -dlt_range_reply_message_type = 5103 // "yes, my range is [S..E]" -dlt_get_block_range_message_type = 5104 // "send me blocks [N..M]" (bulk fetch) -dlt_block_range_reply_message_type = 5105 // blocks [N..M] + last-block info only -dlt_get_block_message_type = 5106 // "send me block N, prev=H" (single block) -dlt_block_reply_message_type = 5107 // block + next_available + sync_status -dlt_not_available_message_type = 5108 // "I don't have that block" -dlt_fork_status_message_type = 5109 // fork resolution status -dlt_peer_exchange_request_type = 5110 // "send me your 'our fork' friends" -dlt_peer_exchange_reply_type = 5111 // list of known peer endpoints -dlt_peer_exchange_rate_limited_type = 5112 // "you already asked, wait N sec" -dlt_transaction_message_type = 5113 // broadcast new transaction to peers -``` - -**Task 1.2**: Define message structs: - -```cpp -// DLT Hello — sent on connection -struct dlt_hello_message { - uint16_t protocol_version; // start at 1; major version mismatch disables exchange - block_id_type head_block_id; - uint32_t head_block_num; - block_id_type lib_block_id; - uint32_t lib_block_num; - uint32_t dlt_earliest_block; // our dlt_block_log start - uint32_t dlt_latest_block; // our dlt_block_log end (head) - bool emergency_active; - bool has_emergency_key; - uint8_t fork_status; // 0=normal, 1=looking_resolution, 2=minority - uint8_t node_status; // 0=sync (catching up), 1=forward (caught up, exchanging) -}; - -// DLT Hello Reply — response to hello -struct dlt_hello_reply_message { - bool exchange_enabled; // "our fork" = true - bool fork_alignment; // true = peer's blocks link to ours, false = not our fork - block_id_type initiator_head_seen; // which of initiator's blocks we recognize - block_id_type initiator_lib_seen; - uint32_t our_dlt_earliest; - uint32_t our_dlt_latest; - uint8_t our_fork_status; - uint8_t our_node_status; // 0=sync, 1=forward -}; - -// Block range request (bulk fetch) -struct dlt_get_block_range_message { - uint32_t start_block_num; - uint32_t end_block_num; - block_id_type prev_block_id; // hash of block (start-1) for chain link verification -}; - -// Block range reply — reply includes all blocks + info only about the LAST block -struct dlt_block_range_reply_message { - std::vector blocks; - uint32_t last_block_next_available; // next block after last in range (0=none) - bool is_last; // true = last block we have, peer fully synced -}; - -// Transaction broadcast -struct dlt_transaction_message { - signed_transaction trx; -}; - -// Single block request (non-bulk, for individual blocks) -struct dlt_get_block_message { - uint32_t block_num; - block_id_type prev_block_id; // to verify chain link -}; - -// Single block reply -struct dlt_block_reply_message { - signed_block block; - uint32_t next_available; // 0 = no more blocks - bool is_last; // true = last block we have, peer fully synced -}; - -// Not available -struct dlt_not_available_message { - uint32_t block_num; -}; - -// Peer exchange request — "send me your 'our fork' friends" -// Rate-limited: responder ignores if asked more than once per 10 min -struct dlt_peer_exchange_request { - // empty — the request itself implies intent -}; - -// Peer exchange reply — list of known "our fork" peer endpoints -struct dlt_peer_exchange_reply { - struct peer_endpoint_info { - fc::ip::endpoint endpoint; - node_id_t node_id; - }; - std::vector peers; -}; - -// Rate-limit response — "you already asked, wait N seconds" -struct dlt_peer_exchange_rate_limited { - uint32_t wait_seconds; // how long until next allowed request -}; -``` - -### Phase 2: New DLT P2P Node Class (libraries/network/) - -**Task 2.1**: Create `dlt_p2p_node.hpp` — new class `dlt_p2p_node`: -- Peer array from config (seeds) + dynamically discovered peers -- Per-peer state: `dlt_peer_state` struct - - `node_status` (0=sync, 1=forward) - - `exchange_enabled` (our fork) - - `fork_alignment` (true = blocks link to ours) - - `peer_head_num`, `peer_head_id`, `peer_lib_num`, `peer_lib_id` - - `peer_dlt_range_start`, `peer_dlt_range_end` - - `peer_emergency_active`, `peer_has_emergency_key` - - `peer_node_status` (0=sync, 1=forward) - - `spam_strikes` (single counter, reset on good packet) - - `last_good_packet_time` - - `last_peer_exchange_request_time` (per-peer rate-limit, 600s cooldown) - - `pending_requests` (what we asked this peer for) - - `expected_next_block` (for block ordering validation) - - `pending_block_batches` (blocks received but not yet validated, with 30s timeout) - - `last_connection_duration` (for backoff reset on stable connections > 5min) - - **Peer lifecycle state machine**: - - `peer_lifecycle_state` (connecting → handshaking → syncing → active → disconnected → banned) - - Each intermediate state has a timeout (connecting=5s, handshaking=10s) - - Timeout → disconnect and move to `disconnected` - - **Reconnection tracking**: - - `connection_state` (connected / disconnected / banned) - - `disconnected_since` (fc::time_point, set on disconnect) - - `next_reconnect_attempt` (fc::time_point, incrementing backoff) - - `reconnect_backoff_sec` (starts at 30s, doubles each retry, capped at 3600s) -- `_known_peers`: in-memory set of `{fc::ip::endpoint, node_id_t}` — non-persistent. Peers are NOT immediately removed on disconnect; they are flagged `disconnected` and reconnection is attempted with backoff. A peer is permanently removed only after **8 hours of continuous non-response** (configurable via `dlt-peer-max-disconnect-hours`). -- Main loop: process incoming messages, periodic peer discovery (every ~10 min send exchange request to one peer), periodic reconnection attempts for disconnected peers. -- **Node status transitions**: - - Start in SYNC mode: send hello to peers, request blocks after our head. - - SYNC → FORWARD: when no more blocks available from any peer (reached the top), transition to FORWARD and start exchanging data + enable peer discovery. - - FORWARD → SYNC: if we detect a long fork or need to resync (rare, e.g. after snapshot import). -- No dedicated thread — use fc::asio or simple poll loop - -**Task 2.2**: Create `dlt_p2p_node.cpp` — implement: - -**Normal mode handlers**: -``` -on_dlt_hello() → check peer's chain link to ours; set fork_alignment; if our node is SYNC, request blocks after our head; if FORWARD, mark peer as "our"/"not our"; check protocol_version compatibility -on_dlt_hello_reply() → if exchange_enabled && fork_alignment, start catching up (if SYNC) or begin exchange (if FORWARD) -on_dlt_get_block_range() → bulk fetch: request blocks [N..M], reply with blocks + last-block info only -on_dlt_block_range_reply() → validate prev_hash link on first block, validate block ordering (previous must be known), push all to chain, retranslate; if last block reached and we're SYNC → transition to FORWARD -on_dlt_get_block() → read from dlt_block_log, send block_reply or not_available -on_dlt_block_reply() → validate prev_hash link, validate block ordering, push to chain, retranslate -on_dlt_transaction() → add to P2P mempool (dedup by tx_id, check expiry, check TaPoS fork alignment, check size limits), retranslate to all "our fork" peers (excl. sender) -on_dlt_peer_exchange_request() → if peer asked in last 600s, send rate_limited; else send our known "our fork" peers (filtered: min_uptime, subnet diversity, cap reply size) -on_dlt_peer_exchange_reply() → add received peer endpoints to our known-peers set (filtered: subnet diversity, cap per reply), attempt connections to new ones -on_block_added_to_chain() → remove any transactions in this block from P2P mempool (separate mempool index); if fork switch occurred, prune transactions whose `ref_block_num` is not on our fork -transition_to_forward() → when sync reaches top (no more blocks from any peer, or sync stagnation timeout): set node_status=FORWARD, start exchange, enable peer discovery; revalidate provisional mempool entries -periodic_mempool_cleanup() → prune expired transactions (`expiration < now`) and TaPoS-invalid transactions (`ref_block_num` not on our fork) from P2P mempool; enforce mempool size limits with eviction -sync_stagnation_check() → if no new block for 30s during sync, re-request from all peers (up to 3 retries); if still stagnated → transition_to_forward with warning -peer_lifecycle_timeout() → check peer lifecycle state timeouts (connecting=5s, handshaking=10s); if stuck, disconnect and move to disconnected -block_validation_timeout() → if pending_block_batch not validated within 30s, soft-ban the peer that sent it -``` - -**Peer discovery logic**: -```cpp -// Per-peer rate-limit tracking -fc::time_point _last_peer_exchange_request_time; -static constexpr uint32_t PEER_EXCHANGE_COOLDOWN_SEC = 600; // 10 minutes - -void dlt_p2p_node::on_dlt_peer_exchange_request(peer_id peer) { - auto now = fc::time_point::now(); - if (_last_peer_exchange_request_time != fc::time_point() && - now - _last_peer_exchange_request_time < fc::seconds(PEER_EXCHANGE_COOLDOWN_SEC)) { - uint32_t wait = PEER_EXCHANGE_COOLDOWN_SEC - - (now - _last_peer_exchange_request_time).count() / 1000000; - send_message(peer, dlt_peer_exchange_rate_limited{wait}); - return; - } - _last_peer_exchange_request_time = now; - - // Collect "our fork" peers (exchange_enabled=true, in FORWARD mode) - std::vector friends; - for (auto& [id, state] : _peer_states) { - if (state.exchange_enabled && state.node_status == NODE_STATUS_FORWARD) { - friends.push_back({state.endpoint, id}); - } - } - send_message(peer, dlt_peer_exchange_reply{friends}); -} - -void dlt_p2p_node::on_dlt_peer_exchange_reply(const dlt_peer_exchange_reply& reply) { - for (auto& info : reply.peers) { - if (!is_known_peer(info.node_id) && !is_connected_to(info.node_id)) { - _known_peers.insert(info); - // Attempt connection if under max_connections - if (_active_connections.size() < _max_connections) { - connect_to_peer(info.endpoint); - } - } - } -} - -// Reconnection logic for disconnected peers -void dlt_p2p_node::handle_disconnect(peer_id peer) { - auto& state = _peer_states[peer]; - state.peer_lifecycle_state = PEER_LIFECYCLE_DISCONNECTED; - state.disconnected_since = fc::time_point::now(); - - // Reset backoff to initial if the connection was stable (>5 min) - if (state.last_connection_duration > 300) { // 5 minutes stable - state.reconnect_backoff_sec = INITIAL_RECONNECT_BACKOFF_SEC; // 30s - } - - state.reconnect_backoff_sec = std::min(state.reconnect_backoff_sec * 2, MAX_RECONNECT_BACKOFF_SEC); // capped at 3600s - - // Add random jitter (±25%) to prevent synchronized reconnection storms - uint32_t jitter = (rand() % (state.reconnect_backoff_sec / 2)) - (state.reconnect_backoff_sec / 4); - state.next_reconnect_attempt = fc::time_point::now() + fc::seconds(state.reconnect_backoff_sec + jitter); - // Do NOT remove from _known_peers — peer stays known for reconnection -} - -void dlt_p2p_node::periodic_reconnect_check() { - auto now = fc::time_point::now(); - auto expire_threshold = now - fc::hours(PEER_MAX_DISCONNECT_HOURS); // 8 hours default - - for (auto it = _known_peers.begin(); it != _known_peers.end(); ) { - auto& state = _peer_states[it->node_id]; - if (state.peer_lifecycle_state == PEER_LIFECYCLE_DISCONNECTED) { - // Permanently remove if no response for 8 hours - if (state.disconnected_since < expire_threshold) { - wlog("Removing peer ${p} after ${h}h of non-response", ("p", it->node_id)("h", PEER_MAX_DISCONNECT_HOURS)); - it = _known_peers.erase(it); - _peer_states.erase(it->node_id); - continue; - } - // Attempt reconnection if backoff timer has elapsed - if (now >= state.next_reconnect_attempt && _active_connections.size() < _max_connections) { - ilog("Attempting reconnect to peer ${p} (backoff=${b}s)", ("p", it->node_id)("b", state.reconnect_backoff_sec)); - connect_to_peer(it->endpoint); - } - } - ++it; - } -} -``` - -**Fork mode handlers**: -``` -check_fork_resolution_needed() → called each block; if multiple forks exist, set fork_status=looking_resolution -resolve_fork_at_round_end() → called when schedule round completes; tally vote weights per fork branch -``` - -**Emergency mode handlers**: -``` -on_emergency_status_change() → update peer flags, adjust fork choice priority -``` - -**Task 2.3**: Anti-spam implementation: -```cpp -bool dlt_p2p_node::record_packet_result(peer_id peer, bool is_good) { - if (is_good) { - peer_state[peer].spam_strikes = 0; - peer_state[peer].last_good_packet_time = now(); - return true; - } - peer_state[peer].spam_strikes++; - if (peer_state[peer].spam_strikes >= SPAM_THRESHOLD) { - soft_ban_peer(peer, BAN_DURATION); - return false; - } - return true; -} -``` - -### Phase 3: P2P Plugin Replacement (plugins/p2p/) - -**Task 3.1**: Replace `p2p_plugin_impl` in `p2p_plugin.cpp` — swap internal implementation: -- Appbase plugin requiring `chain::plugin` (same as current) -- Replace internal `graphene::network::node` with `dlt_p2p_node` -- Configuration: keep `p2p-endpoint` (port 2001/4243), `p2p-seed-node`; add `dlt-block-log-max-blocks`, `dlt-peer-max-disconnect-hours`, `dlt-mempool-max-tx`, `dlt-mempool-max-bytes`, `dlt-mempool-max-tx-size`, `dlt-mempool-max-expiration-hours`, `dlt-peer-exchange-max-per-reply`, `dlt-peer-exchange-max-per-subnet`, `dlt-peer-exchange-min-uptime-sec` -- Remove old config: `p2p-stats-enabled`, `p2p-stats-interval`, `p2p-stale-sync-detection`, `p2p-stale-sync-timeout-seconds` (replaced by built-in DLT mechanisms) -- Public API stays identical: `broadcast_block()`, `broadcast_block_post_validation()`, `broadcast_transaction()`, `broadcast_chain_status()`, `set_block_production()`, `resync_from_lib()`, `trigger_resync()`, `get_connections_count()`, `reconnect_seeds()`, `pause_block_processing()`, `resume_block_processing()`, `get_last_network_block_time()` -- **Transaction lifecycle**: - - Transactions enter via `jsonrpc` plugin → added to P2P mempool (separate in-memory index, dedup by tx_id) - - On receipt: check expiry (`expiration < now` → discard), check TaPoS fork alignment (`ref_block_num` not on our fork → discard) - - Broadcast transaction to all "our fork" peers (excl. sender) - - On new block (witness produces): broadcast block to all "our fork" peers - - On block receipt: remove any transactions in this block from P2P mempool (using separate mempool index) - - On fork switch: prune P2P mempool transactions whose `ref_block_num` is not on our fork -- Periodic: prune P2P mempool (expired + TaPoS-invalid), prune dlt_block_log (batch-prune 10000 at a time via `truncate_before()`), update peer status, reconnect to disconnected peers (backoff 30s→…→3600s), resolve forks, remove stale peers (8h non-response) - -**Task 3.2**: Color-coded console logging: -```cpp -#define DLT_LOG_GREEN "\033[32m" // sync, production -#define DLT_LOG_WHITE "\033[37m" // normal block exchange -#define DLT_LOG_RED "\033[91m" // fork block -#define DLT_LOG_DGRAY "\033[90m" // transaction exchange -#define DLT_LOG_RESET "\033[0m" - -// Usage: -ilog(DLT_LOG_GREEN "Starting sync from peer ${p}" DLT_LOG_RESET, ("p", peer)); -ilog(DLT_LOG_WHITE "Got block #${n} from our fork peer ${p}" DLT_LOG_RESET, ("n", num)("p", peer)); -ilog(DLT_LOG_RED "FORK: Got block #${n} from ${p} — does NOT link to our head!" DLT_LOG_RESET, ("n", num)("p", peer)); -ilog(DLT_LOG_DGRAY "Got transaction ${id} from ${p}" DLT_LOG_RESET, ("id", txid)("p", peer)); -``` - -### Phase 4: Fork Resolution Implementation - -**Task 4.1**: Fork resolution trigger (confirmed: `num_scheduled_witnesses * 2`): -```cpp -// Fork resolution fires after 42 blocks (2 full rounds) since first detection. -// Same threshold as emergency DLT minority fork detection (CHAIN_MAX_WITNESSES * 2). -static constexpr uint32_t FORK_RESOLUTION_BLOCK_THRESHOLD = CHAIN_MAX_WITNESSES * 2; - -struct fork_branch_info { - block_id_type tip; - std::vector blocks; // blocks in this branch - std::set witnesses; - share_type total_vote_weight; - bool has_emergency_blocks; - uint32_t block_count; -}; - -// Called each time a block is applied: -void dlt_p2p_node::track_fork_state(const signed_block& block) { - auto competing = fork_db.fetch_block_by_number(block.block_num()); - if (competing.size() > 1) { - if (!_fork_detected) { - _fork_detected = true; - _fork_detection_block_num = block.block_num(); - } - } - - // Resolution trigger: 42 blocks (2 rounds) since fork was first detected - if (_fork_detected && block.block_num() - _fork_detection_block_num >= FORK_RESOLUTION_BLOCK_THRESHOLD) { - resolve_fork(); - _fork_detected = false; - } -} -``` - -**Task 4.2**: Fork resolution logic (uses confirmed `fork_db.get_all_active_branch_tips()` + existing `compare_fork_branches()` + hysteresis confirmation): -```cpp -struct fork_resolution_state { - block_id_type current_winner_tip; - uint32_t consecutive_blocks_as_winner = 0; - static constexpr uint32_t CONFIRMATION_BLOCKS = 6; // Must maintain lead for 6 blocks - - bool is_confirmed() const { - return consecutive_blocks_as_winner >= CONFIRMATION_BLOCKS; - } -}; - -void dlt_p2p_node::resolve_fork() { - // Get all distinct branch tips from fork_db - auto branch_tips = fork_db.get_all_active_branch_tips(); - if (branch_tips.size() < 2) return; // no fork to resolve - - fork_branch_info winner; - share_type max_weight = 0; - - for (auto& tip : branch_tips) { - auto info = compute_branch_info(tip, fork_db); - // +10% bonus to longer chain (reuses existing compare_fork_branches logic) - if (info.block_count > winner.block_count) { - info.total_vote_weight += info.total_vote_weight / 10; - } - if (info.total_vote_weight > max_weight) { - max_weight = info.total_vote_weight; - winner = info; - } - } - - // Hysteresis: winner must maintain lead for CONFIRMATION_BLOCKS before we switch - if (winner.tip == _fork_state.current_winner_tip) { - _fork_state.consecutive_blocks_as_winner++; - } else { - _fork_state.current_winner_tip = winner.tip; - _fork_state.consecutive_blocks_as_winner = 1; - } - - if (!_fork_state.is_confirmed()) { - ilog(DLT_LOG_ORANGE "Fork resolution: candidate ${t} has ${n}/${c} confirmations", - ("t", winner.tip)("n", _fork_state.consecutive_blocks_as_winner)("c", fork_resolution_state::CONFIRMATION_BLOCKS)); - return; // Not confirmed yet, wait - } - - if (our_head_is_on_branch(winner)) { - // We're on the majority fork — continue - _fork_status = FORK_STATUS_NORMAL; - ilog(DLT_LOG_GREEN "Fork resolved: we are on majority fork (weight=${w})" DLT_LOG_RESET, ("w", max_weight)); - } else { - // We're on minority fork — switch or stop producing - _fork_status = FORK_STATUS_MINORITY; - wlog(DLT_LOG_RED "We are on MINORITY fork! Stopping production, switching to majority." DLT_LOG_RESET); - switch_to_branch(winner.tip); - notify_witness_plugin_minority_fork(); - } - - // Reset hysteresis state after resolution - _fork_state = fork_resolution_state{}; -} -``` - -### Phase 5: In-Place Replacement (No Additional Port) - -The new DLT P2P system **replaces** the old `node.cpp`-based P2P — it does NOT run alongside it on a separate port. Reasons: -- Old P2P and new DLT P2P use different wire protocols (old: Graphene hello/synopsis/inventory; new: DLT hello/range/exchange types 5100-5113). They cannot communicate with each other — running both on different ports creates two isolated sub-networks. -- All witnesses can switch simultaneously (system is in emergency mode), so no gradual migration period is needed. -- The `witness` and `witness_guard` plugins have hard dependencies on `p2p_plugin` via `APPBASE_PLUGIN_REQUIRES`. Replacing the internals avoids any changes to those plugins. - -**Task 5.1**: Replace `p2p_plugin` implementation in-place: -- Keep the same plugin name `"p2p"` and class name `p2p_plugin` -- Keep the same public API: `broadcast_block()`, `broadcast_block_post_validation()`, `broadcast_transaction()`, `broadcast_chain_status()`, `set_block_production()`, `resync_from_lib()`, `trigger_resync()`, `get_connections_count()`, `reconnect_seeds()`, `pause_block_processing()`, `resume_block_processing()`, `get_last_network_block_time()` -- Replace internal `p2p_plugin_impl` (which wraps `graphene::network::node`) with a new impl that wraps `dlt_p2p_node` -- Keep the same `p2p-endpoint` config option (default port 2001 mainnet, 4243 testnet) -- Keep `p2p-seed-node` config option (seed peers) -- Add new config options: `dlt-block-log-max-blocks`, `dlt-peer-max-disconnect-hours`, `dlt-mempool-max-tx`, `dlt-mempool-max-bytes`, `dlt-mempool-max-tx-size`, `dlt-mempool-max-expiration-hours`, `dlt-peer-exchange-max-per-reply`, `dlt-peer-exchange-max-per-subnet`, `dlt-peer-exchange-min-uptime-sec` -- Remove old config options that are no longer relevant: `p2p-stats-enabled`, `p2p-stats-interval`, `p2p-stale-sync-detection`, `p2p-stale-sync-timeout-seconds` (replaced by built-in DLT mechanisms) -- `witness`, `witness_guard`, and `snapshot` plugins require **zero code changes** — they consume the same `p2p_plugin` interface - -**Task 5.2**: Remove old `node.cpp`-based code paths: -- `p2p_plugin_impl` no longer inherits from `graphene::network::node_delegate` -- The `graphene::network::node` class and `node.cpp` are no longer used by the plugin (can be kept in the library for reference but not linked into the build) -- Old message types (synopsis, inventory gossip, etc.) are no longer processed - -**Task 5.3**: Testing: -- Unit tests for message serialization -- Integration test: 3-node network with fork injection -- Emergency mode activation test -- Anti-spam threshold test -- Verify witness plugin works with replaced p2p_plugin (no API changes) - ---- - -## 5. Summary of Improvements Needed in User's Proposal - -| Issue | Proposed Fix | -|-------|-------------| -| Transaction propagation definition | CONFIRMED: jsonrpc → P2P mempool → all "our fork" peers. Dedup when block confirms transaction (separate mempool index). Expired transactions pruned from P2P mempool. TaPoS fork-alignment check: if `ref_block_num` is not on our fork → prune from P2P mempool. | -| Single-block fetch is slow for bulk sync | CONFIRMED: Bulk `get_range` with info only about last block in the bulk. | -| "End of schedule round" ambiguous during forks | CONFIRMED: Use `num_scheduled_witnesses * 2` (42 blocks = 2 rounds), same threshold as existing emergency DLT minority fork detection. | -| "Our fork" determination needs 3 states | CONFIRMED: Node has 2 statuses (sync/forward). In sync: just get blocks after our head. In forward: mark peer as "our" or "not our" based on chain link. Don't care if peer is ahead. LIB below dlt_start = "unknown" during sync, re-evaluate in forward mode. | -| Peer disconnect handling | CONFIRMED: Flag disconnected, reconnect with incrementing backoff (30s→60s→…→3600s max). Remove after 8 hours of non-response. | -| dlt_block_log pruning is expensive | CONFIRMED: Modify `truncate_before()` to batch-prune 10000 blocks at once instead of 1000. | -| DLT gap filling / initial sync | CONFIRMED: DLT stores only top head range. Gaps → reset state + import snapshot from trusted peers. | -| Dual-mode migration with additional port | CONFIRMED: No additional port needed. In-place replacement — keep same `p2p` plugin name, same `p2p-endpoint` port (2001/4243), same public API. Replace internals only (`node.cpp` → `dlt_p2p_node`). `witness`/`witness_guard`/`snapshot` plugins require zero changes. | -| Mempool DoS protection | P0: Hard size limits (max_transactions=10000, max_memory=100MB, max_tx_size=64KB), max expiration 24h, eviction by oldest-expiry | -| Peer exchange poisoning | P1: Anti-sybil: max 2 peers per /24 subnet, max 10 peers per exchange reply, min 600s uptime before sharing | -| Fork resolution race condition | P1: Add hysteresis — winner must maintain lead for 6 consecutive blocks after 42-block window before switch executes | -| Block validation ordering | P1: block.previous must be known, per-peer expected_next_block tracking, pending_block_batch 30s timeout | -| Sync-to-forward transition race | P2: Sync stagnation detection — 30s no-block timeout, 3 retries, then transition with warning | -| Reconnection backoff amplification | P2: Add jitter (±25%), reset backoff on stable connection > 5min | -| Transaction fork-awareness during sync | P2: Tag sync-mode transactions as provisional, revalidate on SYNC→FORWARD transition | -| Protocol version negotiation | P3: `protocol_version` field in hello, major version mismatch disables exchange | -| Peer lifecycle state machine | P3: connecting(5s)→handshaking(10s)→syncing→active→disconnected→banned with timeouts | - ---- - -## 6. File Structure - -``` -libraries/network/ -├── include/graphene/network/ -│ ├── dlt_p2p_messages.hpp # New message types -│ ├── dlt_p2p_node.hpp # New node class -│ └── dlt_p2p_peer_state.hpp # Per-peer state struct -├── dlt_p2p_messages.cpp # Message serialization -├── dlt_p2p_node.cpp # Core network logic -└── CMakeLists.txt # Updated - -plugins/p2p/ # In-place replacement — same plugin name, same API -├── include/graphene/plugins/p2p/ -│ └── p2p_plugin.hpp # Same header, same public API -├── p2p_plugin.cpp # Replaced impl: wraps dlt_p2p_node instead of node.cpp -└── CMakeLists.txt -``` diff --git a/.qoder/repowiki/en/content/API Reference.md b/.qoder/repowiki/en/content/API Reference.md deleted file mode 100644 index 80730cbc9a..0000000000 --- a/.qoder/repowiki/en/content/API Reference.md +++ /dev/null @@ -1,443 +0,0 @@ -# API Reference - - -**Referenced Files in This Document** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp) -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp) -- [plugin.hpp](file://plugins/committee_api/include/graphene/plugins/committee_api/committee_api.hpp) -- [plugin.hpp](file://plugins/social_network/plugin.hpp) -- [plugin.hpp](file://plugins/paid_subscription_api/plugin.hpp) -- [plugin.hpp](file://plugins/custom_protocol_api/include/graphene/plugins/custom_protocol_api/custom_protocol_api.hpp) -- [plugin.hpp](file://plugins/invite_api/plugin.hpp) -- [plugin.hpp](file://plugins/witness_api/plugin.hpp) -- [plugin.hpp](file://plugins/network_broadcast_api/plugin.hpp) -- [plugin.hpp](file://plugins/account_history/plugin.hpp) -- [plugin.cpp](file://plugins/account_history/plugin.cpp) -- [history_object.hpp](file://plugins/account_history/include/graphene/plugins/account_history/history_object.hpp) -- [plugin.hpp](file://plugins/follow/plugin.hpp) -- [plugin.hpp](file://plugins/private_message/plugin.hpp) -- [plugin.hpp](file://plugins/tags/plugin.hpp) -- [plugin.hpp](file://plugins/test_api/plugin.hpp) -- [plugin.cpp](file://plugins/operation_history/plugin.cpp) - - -## Update Summary -**Changes Made** -- Enhanced Account History API documentation with improved get_account_history method behavior -- Added detailed error handling documentation for edge cases -- Updated method parameter specifications and validation logic -- Improved documentation for account range tracking and sequence management -- Added practical examples for common API usage patterns - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the VIZ CPP Node JSON-RPC API system. It covers the HTTP and WebSocket endpoints exposed by the node, the method registration mechanism, request/response schemas, error handling, and operational guidance. It also outlines the functional categories of APIs present in the codebase and provides practical usage patterns and integration notes. - -## Project Structure -The JSON-RPC system is implemented as a plugin that registers API methods and dispatches requests. The webserver plugin exposes HTTP and WebSocket endpoints and forwards JSON-RPC requests to the JSON-RPC plugin. Other plugins register their own API methods under named namespaces. - -```mermaid -graph TB -subgraph "HTTP/WebSocket Layer" -WS["Webserver Plugin
HTTP/WS endpoints"] -end -subgraph "JSON-RPC Core" -JR["JSON-RPC Plugin
Method registry and dispatcher"] -end -subgraph "API Plugins" -DB["Database API"] -CM["Committee API"] -SN["Social Network API"] -PS["Paid Subscription API"] -CP["Custom Protocol API"] -IW["Invite API"] -WT["validator API"] -NB["Network Broadcast API"] -AH["Account History API"] -FL["Follow API"] -PM["Private Message API"] -TG["Tags API"] -TA["Test API"] -end -WS --> JR -JR --> DB -JR --> CM -JR --> SN -JR --> PS -JR --> CP -JR --> IW -JR --> WT -JR --> NB -JR --> AH -JR --> FL -JR --> PM -JR --> TG -JR --> TA -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L378-L395) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L203) - -**Section sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L254-L312) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L378-L423) - -## Core Components -- JSON-RPC Plugin: Provides the method registry, dispatch logic, and response envelope. It supports batch requests and maintains method-to-plugin reindexing for compatibility. -- Webserver Plugin: Exposes HTTP and WebSocket endpoints. Routes inbound messages to the JSON-RPC plugin and returns responses. -- API Plugins: Each plugin registers its methods under a namespace (e.g., database_api, social_network, committee_api). Methods follow a consistent signature pattern and return structured results. - -Key behaviors: -- Request envelope: Supports JSON-RPC 2.0 with id, method, and params. -- Method naming: api_name.method_name. -- Batch requests: Array of requests processed serially with ordered responses. -- Error codes: Standardized JSON-RPC error codes plus internal codes for parsing and dispatch failures. - -**Section sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L46-L54) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L215-L256) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L402-L423) - -## Architecture Overview -The runtime flow for HTTP and WebSocket requests: - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "Webserver Plugin" -participant JR as "JSON-RPC Plugin" -participant API as "Target API Plugin" -Client->>WS : "POST / or WS message" -WS->>JR : "call(json)" -JR->>JR : "Parse and validate" -JR->>API : "Dispatch api.method(args)" -API-->>JR : "Result or exception" -JR-->>WS : "JSON-RPC response" -WS-->>Client : "HTTP 200 or WS message" -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L216-L246) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L290-L336) - -## Detailed Component Analysis - -### HTTP Endpoints -- Endpoint: HTTP server configured via webserver plugin options. -- Method: POST with JSON body. -- Headers: Content-Type application/json. -- Body: Single JSON-RPC request or array of requests. -- Response: JSON object or array of JSON-RPC responses. - -Operational notes: -- The webserver plugin resolves endpoints and starts listeners for HTTP and/or WebSocket. -- For HTTP, the request body is forwarded to the JSON-RPC plugin; the response is sent back immediately. - -**Section sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L254-L312) -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L216-L246) - -### WebSocket Endpoints -- Endpoint: WebSocket server configured via webserver plugin options. -- Transport: Text frames carrying JSON-RPC messages. -- Behavior: Messages are processed asynchronously; responses are sent back on the same connection. - -**Section sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L254-L312) -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L192-L214) - -### JSON-RPC Envelope and Errors -- Envelope fields: - - jsonrpc: String "2.0". - - id: Integer or string; optional for notifications. - - method: String "api.method" or legacy "call" with params. - - params: Optional array or object depending on method. -- Responses include: - - result: Result object or array. - - error: Object with code, message, and optional data. - - id: Matches request id. - -Standard error codes: -- Parse error, invalid request, method not found, invalid params, internal error, server error, no params, parse params error, error during call. - -**Section sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L27-L32) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L46-L54) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L215-L256) - -### Method Registration and Dispatch -- Registration: Plugins register methods via a macro-based API declaration and the JSON-RPC plugin's add_api_method. -- Dispatch: The dispatcher validates method names, extracts api_name and method_name, and invokes the bound method with a msg_pack carrying parsed arguments. -- Compatibility: A method reindex maps method names to parent plugins to support legacy calls. - -```mermaid -flowchart TD -Start(["Incoming Request"]) --> Parse["Parse JSON-RPC"] -Parse --> Validate{"Valid?"} -Validate --> |No| Err["Return JSON-RPC error"] -Validate --> |Yes| Resolve["Resolve api.method"] -Resolve --> Found{"Found?"} -Found --> |No| NotFound["Return method not found"] -Found --> |Yes| Call["Invoke bound method"] -Call --> Result["Return result or error"] -Err --> End(["Done"]) -NotFound --> End -Result --> End -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L213) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L215-L256) - -**Section sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L109-L113) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L159-L178) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L342-L357) - -### Database API (Blockchain State Queries) -- Namespace: database_api -- Purpose: Read-only queries against the blockchain state. -- Typical methods (selected): - - get_dynamic_global_properties - - get_chain_properties - - get_hardfork_version - - get_block_header, get_block - - get_irreversible_block_header, get_irreversible_block - - lookup_account_names, lookup_accounts, get_account_count - - get_vesting_delegations, get_expiring_vesting_delegations - - get_transaction_hex, get_required_signatures, get_potential_signatures, verify_authority, verify_account_authority - - get_database_info - - get_proposed_transactions - - get_accounts_on_sale, get_accounts_on_auction, get_subaccounts_on_sale -- Notes: - - Many methods accept parameters; consult the API plugin header for exact signatures. - - Subscriptions: set_block_applied_callback, set_pending_transaction_callback, cancel_all_subscriptions. - -Example invocation pattern: -- HTTP: POST with {"jsonrpc":"2.0","id":1,"method":"database_api.get_dynamic_global_properties","params":[]}. -- WebSocket: Send the same JSON text frame. - -**Section sources** -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L137-L169) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L209-L226) - -### Social Network APIs (Content and Interactions) -- Namespace: social_network -- Purpose: Content discovery, discussions, and social interactions. -- Typical methods (selected): - - get_discussions_by_payout, get_post_discussions_by_payout - - get_comment_discussions_by_payout - - get_discussions_by_trending, get_discussions_by_created, get_discussions_by_votes - - get_active_votes, get_account_votes - - get_content_replies - - get_follow_counts, get_followers_by_type, get_following_by_type -- Notes: - - Methods commonly accept pagination and filtering parameters; refer to plugin header for exact signatures. - -Example invocation pattern: -- HTTP: POST with {"jsonrpc":"2.0","id":1,"method":"social_network.get_discussions_by_trending","params":[...]} - -**Section sources** -- [plugin.hpp](file://plugins/social_network/plugin.hpp) - -### Governance APIs (Committee and Proposals) -- Namespace: committee_api -- Purpose: Committee-related operations and proposals. -- Typical methods (selected): - - get_active_committee - - get_proposal - - get_proposals -- Notes: - - Consult the plugin header for exact signatures and parameters. - -Example invocation pattern: -- HTTP: POST with {"jsonrpc":"2.0","id":1,"method":"committee_api.get_active_committee","params":[]} - -**Section sources** -- [plugin.hpp](file://plugins/committee_api/include/graphene/plugins/committee_api/committee_api.hpp) - -### Custom Protocol APIs (Specialized Business Logic) -- Namespace: custom_protocol_api -- Purpose: Specialized operations defined by custom protocol extensions. -- Typical methods (selected): - - get_custom_protocol_data -- Notes: - - Consult the plugin header for exact signatures and parameters. - -Example invocation pattern: -- HTTP: POST with {"jsonrpc":"2.0","id":1,"method":"custom_protocol_api.get_custom_protocol_data","params":[]} - -**Section sources** -- [plugin.hpp](file://plugins/custom_protocol_api/include/graphene/plugins/custom_protocol_api/custom_protocol_api.hpp) - -### Account History API (Enhanced) -- Namespace: account_history -- Purpose: Retrieve account operation history with enhanced error handling and edge case management. -- Method: get_account_history -- Parameters: - - account: String - Account name to query history for - - from: Integer - Absolute sequence number, where UINT32_MAX (-1) means most recent - - limit: Integer - Maximum number of items to return (1-1000, inclusive) -- Return: Map of sequence numbers to operation objects -- Enhanced Features: - - Improved error handling for edge cases - - Better parameter validation with clear error messages - - Account range tracking to prevent searching unavailable sequences - - Enhanced documentation for method behavior - -**Updated** Enhanced get_account_history method with improved error handling and edge case management - -**Section sources** -- [plugin.hpp](file://plugins/account_history/plugin.hpp#L83-L92) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L186-L234) -- [history_object.hpp](file://plugins/account_history/include/graphene/plugins/account_history/history_object.hpp#L44-L119) - -### Additional API Categories -- Paid Subscription API: Namespace paid_subscription_api -- Invite API: Namespace invite_api -- validator API: Namespace witness_api -- Network Broadcast API: Namespace network_broadcast_api -- Follow API: Namespace follow -- Private Message API: Namespace private_message -- Tags API: Namespace tags -- Test API: Namespace test_api - -Notes: -- Each API is implemented by its respective plugin and registered with the JSON-RPC plugin. -- Invocation follows the same JSON-RPC envelope and method naming scheme. - -**Section sources** -- [plugin.hpp](file://plugins/paid_subscription_api/plugin.hpp) -- [plugin.hpp](file://plugins/invite_api/plugin.hpp) -- [plugin.hpp](file://plugins/witness_api/plugin.hpp) -- [plugin.hpp](file://plugins/network_broadcast_api/plugin.hpp) -- [plugin.hpp](file://plugins/follow/plugin.hpp) -- [plugin.hpp](file://plugins/private_message/plugin.hpp) -- [plugin.hpp](file://plugins/tags/plugin.hpp) -- [plugin.hpp](file://plugins/test_api/plugin.hpp) - -## Dependency Analysis -- The webserver plugin depends on the JSON-RPC plugin to process requests. -- The JSON-RPC plugin depends on appbase and maintains a registry of api_name.method_name to bound methods. -- API plugins depend on the JSON-RPC plugin to register their methods. - -```mermaid -graph LR -WS["webserver_plugin"] --> JR["json_rpc_plugin"] -JR --> DB["database_api"] -JR --> CM["committee_api"] -JR --> SN["social_network"] -JR --> PS["paid_subscription_api"] -JR --> CP["custom_protocol_api"] -JR --> IW["invite_api"] -JR --> WT["witness_api"] -JR --> NB["network_broadcast_api"] -JR --> AH["account_history"] -JR --> FL["follow"] -JR --> PM["private_message"] -JR --> TG["tags"] -JR --> TA["test_api"] -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L314-L327) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L378-L395) - -**Section sources** -- [plugin.hpp](file://plugins/webserver/webserver_plugin.cpp#L314-L327) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L378-L400) - -## Performance Considerations -- Thread pool sizing: Configure the webserver thread pool to match workload characteristics. -- Batch requests: Submit multiple requests in a single HTTP call to reduce overhead. -- Subscriptions: Use callbacks judiciously; they can increase memory and CPU usage. -- Rate limiting: Apply external rate limiting at the reverse proxy or load balancer level if needed. -- Account history limits: The get_account_history method enforces a maximum limit of 1000 operations to prevent excessive resource consumption. - -## Troubleshooting Guide -Common issues and resolutions: -- Invalid JSON-RPC envelope: Ensure jsonrpc equals "2.0", id is integer or string, and method is present. -- Method not found: Verify api_name.method_name exists and the plugin is loaded. -- Invalid params: Check parameter types and presence according to the method signature. -- Server errors: Inspect logs for exceptions and stack traces. -- Account history errors: Common errors include account not found in history index, from sequence out of range, and limit exceeded. - -**Updated** Enhanced error handling for account history API with specific error messages for edge cases - -Error handling behavior: -- Parsing and dispatch errors return standardized JSON-RPC error objects. -- Exceptions thrown by API methods are captured and mapped to server errors with optional data. -- Account history API provides specific error messages for parameter validation failures. - -**Section sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L290-L311) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L114-L136) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L194-L202) - -## Conclusion -The VIZ CPP Node exposes a robust JSON-RPC interface via HTTP and WebSocket, backed by a modular plugin architecture. API plugins register methods under distinct namespaces, enabling clear separation of concerns. The Account History API has been enhanced with improved error handling, better parameter validation, and enhanced documentation for edge cases. By following the documented envelopes, method naming, and error handling patterns, clients can reliably integrate with the node for blockchain state queries, social interactions, governance operations, and specialized protocols. - -## Appendices - -### Practical Usage Patterns -- HTTP GET/POST: Use POST with application/json and a JSON-RPC envelope. -- WebSocket: Send text frames with JSON-RPC envelopes; subscribe to block or transaction callbacks if needed. -- Batch: Send an array of requests; receive an array of responses in order. -- Account History: Use get_account_history with proper parameter validation and error handling. - -### Migration and Backwards Compatibility -- Legacy call: The "call" method remains supported for backward compatibility; prefer explicit "api.method" calls. -- Method reindex: Some methods are remapped to parent plugins; ensure your client handles reindexing if you rely on legacy names. -- Account History: The enhanced get_account_history method maintains backward compatibility while providing improved error handling. - -**Section sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L235-L236) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L342-L357) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L186-L234) - -### Account History API Examples -Common usage patterns for the enhanced get_account_history method: - -**Get latest operations for an account:** -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "account_history.get_account_history", - "params": ["useraccount", 4294967295, 100] -} -``` - -**Get operations from a specific sequence:** -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "account_history.get_account_history", - "params": ["useraccount", 1000, 50] -} -``` - -**Error handling examples:** -- Account not found in history index: "Account not found in history index, it may have been purged since the last ${b} blocks are stored in the history" -- From sequence out of range: "From is less than account history start sequence ${s}" or "From is greater than account history end sequence ${s}" -- Limit exceeded: "Limit of ${l} is greater than maximum allowed (1000)" - -**Section sources** -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L186-L234) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L194-L202) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Advanced Topics/Advanced Plugin Development.md b/.qoder/repowiki/en/content/Advanced Topics/Advanced Plugin Development.md deleted file mode 100644 index b787dfe91d..0000000000 --- a/.qoder/repowiki/en/content/Advanced Topics/Advanced Plugin Development.md +++ /dev/null @@ -1,354 +0,0 @@ -# Advanced Plugin Development - - -**Referenced Files in This Document** -- [plugin.md](file://documentation/plugin.md) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [testing.md](file://documentation/testing.md) -- [building.md](file://documentation/building.md) -- [newplugin.py](file://programs/util/newplugin.py) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [account_history_plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides advanced guidance for developing plugins for the VIZ C++ Node. It covers plugin architecture patterns, lifecycle management, integration with the core system, advanced development techniques (custom evaluators, database object extensions, inter-plugin communication), testing strategies, performance optimization, deployment, and troubleshooting. The content is grounded in the repository’s plugin framework, JSON-RPC API binding, evaluator system, and existing plugin implementations. - -## Project Structure -The plugin ecosystem is organized around a modular architecture: -- Core chain and protocol abstractions define operations, evaluators, and database signals. -- The JSON-RPC plugin provides a dispatch mechanism for API registration and invocation. -- Individual plugins implement domain-specific logic, register APIs, and subscribe to chain/database events. -- A scaffolding script automates boilerplate generation for new plugins. - -```mermaid -graph TB -subgraph "Core Chain" -D["database.hpp
Signals and hooks"] -E["evaluator.hpp
Base evaluator"] -CE["chain_evaluator.hpp
Concrete evaluators"] -end -subgraph "JSON-RPC Layer" -J["json_rpc/plugin.hpp
API registry and dispatcher"] -end -subgraph "Plugins" -AH["account_history/plugin.cpp/.hpp
History tracking"] -TA["test_api/test_api_plugin.cpp/.hpp
Sample API"] -WS["webserver_plugin.hpp
HTTP/WebSocket server"] -end -D --> AH -D --> TA -J --> AH -J --> TA -WS --> J -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L471-L501) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L23) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L1-L28) -- [building.md](file://documentation/building.md#L1-L200) - -## Core Components -- Application base and plugin lifecycle: Plugins derive from the application base plugin interface and implement initialize/startup/shutdown hooks. They register API factories and connect to database signals. -- JSON-RPC API binding: The JSON-RPC plugin maintains a registry of API methods and dispatches incoming requests. Plugins register their methods via a macro-driven visitor pattern. -- Chain/database integration: Plugins subscribe to database signals (e.g., applied_block, pre_apply_operation, on_applied_transaction) to react to chain events. -- Scaffolding tool: The new plugin generator produces boilerplate for plugin headers, implementation, API classes, and CMake configuration. - -Key implementation references: -- Plugin lifecycle and API registration in a typical plugin implementation. -- JSON-RPC API registration and method dispatch. -- Database signals used by plugins to observe chain activity. -- Scaffolding script for generating plugin boilerplate. - -**Section sources** -- [account_history_plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L97) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L471-L501) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L23) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [newplugin.py](file://programs/util/newplugin.py#L126-L217) - -## Architecture Overview -The plugin architecture centers on: -- A plugin base class exposing lifecycle hooks. -- A JSON-RPC plugin that binds API names to method implementations. -- A chain database that emits signals plugins can subscribe to. -- Optional webserver plugin to expose HTTP/WS endpoints. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "webserver_plugin" -participant JR as "json_rpc : : plugin" -participant PL as "MyPlugin API" -participant DB as "database" -Client->>WS : "HTTP/WS request" -WS->>JR : "Dispatch JSONRPC" -JR->>PL : "Invoke method(args)" -PL->>DB : "Read/Write chain state" -DB-->>PL : "Result" -PL-->>JR : "Return value" -JR-->>WS : "Response" -WS-->>Client : "HTTP/WS response" -``` - -**Diagram sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L185-L194) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) - -## Detailed Component Analysis - -### Plugin Lifecycle and Registration Patterns -- Lifecycle: initialize → startup → shutdown. During startup, plugins typically register API factories and connect to database signals. -- Registration: Plugins register APIs via a macro-driven visitor that iterates over declared methods and registers them with the JSON-RPC plugin. -- Example patterns: - - Connect to applied_block to react to new blocks. - - Use weak read locks when querying the database from API methods to avoid contention. - - Add plugin-specific indices to the database for efficient lookups. - -```mermaid -flowchart TD -Start(["Initialize"]) --> Startup["Startup"] -Startup --> RegAPI["Register API factory"] -RegAPI --> ConnectSignals["Connect to database signals"] -ConnectSignals --> RunLoop["Serve requests"] -RunLoop --> Shutdown["Shutdown"] -Shutdown --> End(["Exit"]) -``` - -**Diagram sources** -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L471-L501) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L185-L194) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) - -**Section sources** -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L471-L501) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L185-L194) -- [plugin.md](file://documentation/plugin.md#L11-L28) - -### JSON-RPC API Binding and Invocation -- API registration: Plugins declare API methods and use a macro to register them with the JSON-RPC plugin. The macro expands to a visitor that binds method names to callable lambdas. -- Dispatch: The JSON-RPC plugin stores method descriptors and invokes bound methods with parsed arguments. -- Argument/result types: Methods accept a single argument struct and return a single result struct; void methods use a dedicated type. - -```mermaid -sequenceDiagram -participant P as "Plugin" -participant JR as "json_rpc : : plugin" -participant C as "Caller" -P->>JR : "Register API methods" -C->>JR : "Call method with args" -JR->>P : "Invoke bound method" -P-->>JR : "Return result" -JR-->>C : "JSONRPC response" -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L121-L140) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L25-L35) - -**Section sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L27-L53) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L25-L35) - -### Database Signals and Inter-Plugin Communication -- Signals: The database emits signals for pre/post operation application, applied blocks, and transaction events. Plugins can subscribe to these signals to implement cross-cutting concerns. -- Inter-plugin coordination: Plugins can communicate indirectly by observing each other’s operations via signals and shared indices. - -```mermaid -sequenceDiagram -participant DB as "database" -participant P1 as "Plugin A" -participant P2 as "Plugin B" -DB->>P1 : "pre_apply_operation(notification)" -DB->>P2 : "pre_apply_operation(notification)" -DB->>P1 : "applied_block(block)" -DB->>P2 : "applied_block(block)" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L487-L489) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L487-L489) - -### Custom Evaluators and Database Object Extensions -- Evaluators: Operations are validated and applied by evaluators. The evaluator base defines apply/get_type, and concrete evaluators implement do_apply. -- Custom operation interpreter: The database supports registering custom operation interpreters to extend the evaluator registry for custom operations. -- Extension patterns: Plugins can add new indices to the database to support efficient queries for custom data. - -```mermaid -classDiagram -class evaluator~OperationType~ { -+apply(op) -+get_type() -} -class evaluator_impl~EvaluatorType, OperationType~ { -+apply(op) -+get_type() -+db() -} -class account_history_plugin { -+plugin_initialize() -+plugin_startup() -+plugin_shutdown() -} -evaluator_impl <|-- account_history_plugin : "uses database signals" -``` - -**Diagram sources** -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L407-L410) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L471-L501) - -**Section sources** -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L407-L410) - -### Webserver and Real-Time Event Handling -- Webserver plugin: Provides HTTP/WS endpoints and runs its own io_service thread to isolate request handling from the main application thread. -- Real-time events: Plugins can emit notifications via database signals; the webserver can expose these via WebSocket endpoints (pattern demonstrated by the webserver plugin). - -```mermaid -graph TB -WS["webserver_plugin.hpp
HTTP/WS server"] -JR["json_rpc::plugin.hpp
API registry"] -AH["account_history_plugin.cpp
applied_block listener"] -WS --> JR -AH --> JR -``` - -**Diagram sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp#L471-L501) - -**Section sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -### Asynchronous Processing and Threading Model -- Threading: The webserver plugin runs its own io_service thread to avoid blocking the main application thread. Callbacks can be posted to the HTTP thread from any thread. -- Asynchronous patterns: Plugins should avoid heavy work in signal handlers; queue tasks to background threads or use async I/O where appropriate. - -**Section sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L19-L31) - -### Plugin Testing Framework -- Unit tests: Build targets include a chain test executable. Tests are categorized by functionality (basic, block, operation, serialization, etc.). -- Runtime configuration: Test harness supports log level, report level, and selective test execution via runtime options. -- Coverage: Code coverage can be captured using lcov with Debug builds and the coverage flag. - -```mermaid -flowchart TD -Dev["Write tests"] --> Build["Build chain_test"] -Build --> Run["Run tests with options"] -Run --> Report["Generate coverage reports"] -``` - -**Diagram sources** -- [testing.md](file://documentation/testing.md#L1-L43) - -**Section sources** -- [testing.md](file://documentation/testing.md#L1-L43) - -### Deployment and Distribution -- Enabling plugins: Use configuration options to enable plugins and public APIs. Some plugins require replaying the chain when enabling/disabling. -- Public APIs: Configure which APIs are exposed publicly and protect sensitive endpoints. -- Packaging: Drop third-party plugins into the external plugins directory; CMake aggregates internal plugins and builds them automatically. - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) - -## Dependency Analysis -Plugins declare dependencies on other plugins and the chain database. The JSON-RPC plugin is often a dependency for API-enabled plugins. The application base manages plugin initialization order and dependency resolution. - -```mermaid -graph LR -JR["json_rpc::plugin.hpp"] -AH["account_history/plugin.cpp/.hpp"] -TA["test_api/test_api_plugin.cpp/.hpp"] -DB["database.hpp"] -AH --> JR -TA --> JR -AH --> DB -TA --> DB -``` - -**Diagram sources** -- [account_history_plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L61-L65) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) - -**Section sources** -- [account_history_plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L61-L65) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35) - -## Performance Considerations -- Memory management: Use weak read locks when querying the database from API methods to minimize contention. Avoid long-running operations in signal handlers. -- Caching: Maintain in-memory caches for hot paths; invalidate on applied_block or relevant signals. -- Resource utilization: Prefer streaming or paginated queries for large datasets. Limit batch sizes and use database indices added by plugins. -- Concurrency: Offload CPU-intensive tasks to background threads; use async I/O in the webserver plugin to keep the main thread responsive. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Debugging: Use the debug_node plugin to simulate chain state changes and test plugin behavior under controlled scenarios. Bind RPC to localhost and restrict public API exposure when using debug APIs. -- Logging: Adjust log levels and test report levels to gather more details during failures. -- Signal handling: Verify that plugins connect to the correct signals and handle errors gracefully without deadlocking the chain. - -**Section sources** -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) -- [testing.md](file://documentation/testing.md#L16-L23) - -## Conclusion -The VIZ C++ Node provides a robust, extensible plugin architecture. By leveraging the JSON-RPC API binding, database signals, and scaffolding tools, developers can implement advanced plugins ranging from custom APIs to deep chain integrations. Following the lifecycle patterns, performance guidelines, and testing strategies outlined here will help ensure reliable, maintainable, and high-performance plugins. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Examples Index -- Custom validator: Implement an evaluator for a custom operation and register it via the custom operation interpreter. -- Specialized API: Use the scaffolding tool to generate a plugin, add API methods, and register them with the JSON-RPC plugin. -- System integration: Subscribe to applied_block and pre_apply_operation signals to mirror chain state to external systems. - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L126-L217) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Advanced Topics/Advanced Topics.md b/.qoder/repowiki/en/content/Advanced Topics/Advanced Topics.md deleted file mode 100644 index 4ed9191704..0000000000 --- a/.qoder/repowiki/en/content/Advanced Topics/Advanced Topics.md +++ /dev/null @@ -1,293 +0,0 @@ -# Advanced Topics - - -**Referenced Files in This Document** -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [1.hf](file://libraries/chain/hardfork.d/1.hf) -- [10.hf](file://libraries/chain/hardfork.d/10.hf) -- [11.hf](file://libraries/chain/hardfork.d/11.hf) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [CMakeLists.txt](file://plugins/CMakeLists.txt) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp) -- [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides expert-level guidance on advanced topics for the VIZ CPP Node, focusing on hardfork implementation and management, database schema design and optimization, security considerations and vulnerability assessment, and advanced plugin development patterns. It synthesizes the codebase’s hardfork system, fork database, chain database, network node, plugin framework, and protocol versioning to deliver practical, code-backed advice for extending core functionality and integrating with external systems. - -## Project Structure -The VIZ CPP Node is organized around a layered architecture: -- Protocol: Versioning, operations, and core types -- Chain: Block processing, database, fork database, evaluators, and object schemas -- Network: Peer-to-peer node and message handling -- Plugins: Modular extensions exposing APIs and specialized indexes -- Programs: Executables and utilities - -```mermaid -graph TB -subgraph "Protocol" -PVersion["version.hpp"] -end -subgraph "Chain" -DB["database.hpp"] -ForkDB["fork_database.hpp"] -Objects["chain_objects.hpp"] -Eval["evaluator.hpp"] -COI["custom_operation_interpreter.hpp"] -end -subgraph "Network" -NetNode["node.hpp"] -end -subgraph "Plugins" -PList["plugins/CMakeLists.txt"] -ABK["account_by_key_plugin.cpp"] -end -PVersion --> DB -DB --> ForkDB -DB --> Objects -DB --> Eval -DB --> COI -NetNode --> DB -PList --> ABK -``` - -**Diagram sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L1-L156) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L226) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L1-L62) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L28) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L355) -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp#L1-L233) - -**Section sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L1-L156) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L226) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L1-L62) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L28) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L355) -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp#L1-L233) - -## Core Components -- Hardfork system: Managed via dedicated headers and a property object storing processed hardforks, current and next hardfork versions, and timestamps. -- Fork database: Maintains a tree of unlinked blocks with indices for ID, previous ID, and block number, enabling efficient branching and rebranching. -- Chain database: Extends a persistent object store with block validation, transaction processing, hardfork orchestration, and plugin hooks. -- Network node: Provides peer discovery, sync, and broadcast with delegate callbacks for block/tx handling. -- Plugin framework: Adds indexes and APIs, integrates with database signals, and supports custom operation interpreters. - -**Section sources** -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf#L18-L56) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L182-L304) -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -## Architecture Overview -The system orchestrates block/application logic through the chain database, which coordinates with the fork database for out-of-order block handling and with plugins for extended functionality. Network peers supply blocks and transactions, validated by the chain database against configured hardforks and checkpoints. - -```mermaid -sequenceDiagram -participant Peer as "Peer" -participant Net as "Network Node" -participant DB as "Chain Database" -participant Fork as "Fork DB" -participant HF as "Hardfork Logic" -Peer->>Net : "Block/Transaction" -Net->>DB : "handle_block()/handle_transaction()" -DB->>HF : "process_hardforks()" -HF-->>DB : "applied hardforks" -DB->>Fork : "push_block()/walk_main_branch_to_num()" -Fork-->>DB : "head/new head" -DB-->>Net : "validation result" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L78-L98) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L511-L516) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L78-L95) - -## Detailed Component Analysis - -### Hardfork Implementation and Management -- Versioning model: Uses a triple (major, hardfork, release) with dedicated hardfork version comparisons. -- Hardfork headers define constants for each hardfork, including activation time and version. -- Hardfork property object tracks processed hardforks, last hardfork, current and next hardfork versions, and next activation time. -- Orchestration: The chain database initializes hardfork arrays, processes hardforks per block, and applies migrations when thresholds are met. - -```mermaid -flowchart TD -Start(["Start"]) --> Init["Initialize hardfork arrays"] -Init --> Check["Check next hardfork time/version"] -Check --> |Threshold reached| Apply["Apply hardfork migration"] -Check --> |Not reached| Wait["Continue normal operation"] -Apply --> UpdateProps["Update hardfork property object"] -UpdateProps --> Next["Advance last_hardfork/current_hardfork_version"] -Next --> End(["End"]) -Wait --> End -``` - -**Diagram sources** -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf#L18-L56) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L511-L516) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L52-L121) - -**Section sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L1-L156) -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf#L18-L56) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) -- [10.hf](file://libraries/chain/hardfork.d/10.hf#L1-L7) -- [11.hf](file://libraries/chain/hardfork.d/11.hf#L1-L7) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L511-L516) - -### Database Schema Design and Optimization -- Object persistence: Chain database extends a persistent object store; objects are defined with multi-index containers supporting unique and composite keys. -- Index optimization: Composite indices enable efficient queries by author/permlink, account authorities, and routing tables; hashed indices accelerate lookups by ID and previous ID. -- Fork database indices: Hashed by block ID and by previous ID; ordered by block number; capped at a maximum depth to control memory growth. -- Schema evolution: New plugin indexes are added via a helper that registers multi-index types with the database. - -```mermaid -classDiagram -class Database { -+initialize_indexes() -+add_plugin_index() -} -class ChainObjects { -+escrow_index -+withdraw_vesting_route_index -+award_shares_expire_index -+block_post_validation_index -} -class ForkDB { -+push_block() -+fetch_branch_from() -+walk_main_branch_to_num() -} -Database --> ChainObjects : "manages" -Database --> ForkDB : "coordinates" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L411-L414) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L50-L201) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L100-L121) - -**Section sources** -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L226) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L411-L414) - -### Security Considerations and Vulnerability Assessment -- Cryptographic implementation: Relies on underlying protocol/crypto primitives for signatures and keys; ensure keys are handled securely and never logged. -- API authentication: Plugins expose JSON-RPC APIs; secure endpoints via reverse proxy or TLS termination and restrict access. -- Network security: The node delegates block/tx handling to a delegate; validate all inbound messages and enforce rate limits; use allowed peers and bandwidth caps. -- Vulnerability mitigation: Keep hardforks synchronized across the network; monitor block/post validation; avoid exposing internal ports publicly; sanitize logs to prevent sensitive data leakage. - -[No sources needed since this section provides general guidance] - -### Advanced Plugin Development Patterns -- Custom evaluators: Define evaluators via macros and templates; attach to operations; use database references for state reads/writes. -- Database object extensions: Add plugin-specific indexes using a registration helper; connect to pre/post operation signals to keep auxiliary structures consistent. -- Inter-plugin communication: Use database signals (pre/post apply operation, applied block) to coordinate state changes; pass data via shared objects or plugin APIs. - -```mermaid -sequenceDiagram -participant DB as "Chain Database" -participant Plug as "Plugin" -participant Sig as "Signals" -DB->>Sig : "pre_apply_operation" -Sig-->>Plug : "operation notification" -Plug->>Plug : "update auxiliary indexes" -DB->>Sig : "post_apply_operation" -Sig-->>Plug : "operation notification" -Plug->>Plug : "finalize state" -``` - -**Diagram sources** -- [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp#L158-L164) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L238-L286) - -**Section sources** -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L1-L62) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L28) -- [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp#L1-L233) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L238-L286) - -## Dependency Analysis -- Protocol versioning underpins hardfork comparisons and migrations. -- Chain database depends on fork database for block tree management and on plugin indexes for extended schemas. -- Network node depends on chain database for validation and delegates block/tx handling. -- Plugins depend on chain database signals and registration helpers. - -```mermaid -graph LR -Version["version.hpp"] --> DB["database.hpp"] -DB --> Fork["fork_database.hpp"] -DB --> Objects["chain_objects.hpp"] -DB --> Plugins["Plugins"] -Net["node.hpp"] --> DB -``` - -**Diagram sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L1-L156) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L226) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L355) - -**Section sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L1-L156) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L226) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L355) - -## Performance Considerations -- Memory management: Tune shared memory sizing and periodic checks; adjust increments and minimum free sizes to avoid frequent resizing. -- Fork database depth: The maximum depth bounds memory growth; ensure reordering windows align with operational needs. -- Index selection: Use composite indices for multi-field queries; prefer hashed indices for fast ID lookups; minimize redundant indexes. -- Scalability: Offload heavy analytics to plugins; leverage plugin indexes to reduce chain database overhead; batch operations where appropriate. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Hardfork delays: Verify next hardfork time and version; confirm processed hardforks and last hardfork counters; ensure clocks are synchronized. -- Validation failures: Inspect skip flags and validation steps; check checkpoints and block log integrity; review plugin pre/post operation handlers. -- Network issues: Confirm peer connectivity, bandwidth limits, and allowed peers; inspect propagation data for blocks/transactions. -- Plugin anomalies: Validate plugin initialization and shutdown sequences; ensure indexes are registered and signals are connected. - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L54-L73) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L186-L193) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L268-L289) -- [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp#L197-L222) - -## Conclusion -This guide outlined advanced topics for VIZ CPP Node, covering hardfork lifecycle management, database design with multi-index strategies, security best practices, and plugin development patterns. By leveraging the provided code-backed insights—particularly around hardfork headers, fork database indices, chain database orchestration, and plugin registration—you can extend core functionality safely and efficiently while maintaining performance and security. - -## Appendices -- Example references for deep dives: - - Hardfork headers: [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7), [10.hf](file://libraries/chain/hardfork.d/10.hf#L1-L7), [11.hf](file://libraries/chain/hardfork.d/11.hf#L1-L7) - - Plugin registration: [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - - Plugin example: [account_by_key_plugin.cpp](file://plugins/account_by_key/account_by_key_plugin.cpp#L1-L233) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Advanced Topics/Database Schema Design.md b/.qoder/repowiki/en/content/Advanced Topics/Database Schema Design.md deleted file mode 100644 index 7da4617ba1..0000000000 --- a/.qoder/repowiki/en/content/Advanced Topics/Database Schema Design.md +++ /dev/null @@ -1,554 +0,0 @@ -# Database Schema Design - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [index.hpp](file://libraries/chain/include/graphene/chain/index.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the database schema design and persistence model of VIZ CPP Node. It covers: -- Object persistence system and chain object types -- Database schema definitions and storage optimization strategies -- Fork database implementation, conflict resolution, and branch management -- Index management and query optimization techniques -- Object relationships and interaction patterns -- Schema evolution, versioning, migrations, and backward compatibility -- Practical examples for extending the schema with custom objects and optimizing queries -- Maintenance procedures and guidance for designing efficient data models for plugins - -## Project Structure -The database layer is built on top of ChainBase and integrates with Protocol types. The schema is defined via object classes and Boost.MultiIndex containers. Core runtime components include: -- Database lifecycle and schema initialization -- Fork database for chain branching and conflict resolution -- Index registration and plugin index hooks -- Object definitions for accounts, content, validators, proposals, invites, and auxiliary objects - -```mermaid -graph TB -subgraph "Database Layer" -DB["database.hpp/.cpp"] -IDX["index.hpp"] -FDB["fork_database.hpp/.cpp"] -end -subgraph "Schema Objects" -TYPES["chain_object_types.hpp"] -ACC["account_object.hpp"] -CON["content_object.hpp"] -WIT["witness_objects.hpp"] -PRO["proposal_object.hpp"] -INV["invite_objects.hpp"] -EXT["chain_objects.hpp"] -end -DB --> FDB -DB --> IDX -DB --> TYPES -DB --> ACC -DB --> CON -DB --> WIT -DB --> PRO -DB --> INV -DB --> EXT -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [database.cpp](file://libraries/chain/database.cpp#L206-L268) -- [index.hpp](file://libraries/chain/include/graphene/chain/index.hpp#L8-L24) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L146) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L28-L145) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L226) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [database.cpp](file://libraries/chain/database.cpp#L206-L268) -- [index.hpp](file://libraries/chain/include/graphene/chain/index.hpp#L8-L24) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) - -## Core Components -- Database lifecycle and initialization: - - Open and reindex operations, schema initialization, evaluator setup, and hardfork initialization - - Memory management and resizing during replay -- Fork database: - - Maintains a tree of unlinked and linked blocks, supports branch fetching and head updates - - Enforces maximum reordering window and invalidation flags -- Index management: - - Core and plugin indices are registered via helper functions - - MultiIndex containers define primary and composite indexes per object type - -Key responsibilities: -- [Open/reindex](file://libraries/chain/database.cpp#L206-L268) -- [Initialize schema and indexes](file://libraries/chain/database.cpp#L2992-L3005) -- [Fork database operations](file://libraries/chain/fork_database.cpp#L33-L90) -- [Index registration helpers](file://libraries/chain/include/graphene/chain/index.hpp#L8-L24) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L206-L268) -- [database.cpp](file://libraries/chain/database.cpp#L2992-L3005) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L90) -- [index.hpp](file://libraries/chain/include/graphene/chain/index.hpp#L8-L24) - -## Architecture Overview -The database architecture combines ChainBase with custom schema objects and a fork-aware block storage model. - -```mermaid -classDiagram -class database { -+open(data_dir, shared_mem_dir, ...) -+reindex(data_dir, shared_mem_dir, ...) -+initialize_indexes() -+init_schema() -+push_block(...) -+push_transaction(...) -+get_account(name) -+get_content(author, permlink) -+get_witness(owner) -+get_proposal(author, title) -} -class fork_database { -+push_block(signed_block) -+set_head(item) -+fetch_branch_from(first, second) -+walk_main_branch_to_num(n) -+is_known_block(id) -} -class account_object -class content_object -class witness_object -class proposal_object -class invite_object -database --> fork_database : "uses" -database --> account_object : "persists" -database --> content_object : "persists" -database --> witness_object : "persists" -database --> proposal_object : "persists" -database --> invite_object : "persists" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L144) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L114) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L28-L83) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L38) - -## Detailed Component Analysis - -### Object Persistence System and Chain Object Types -Object types are enumerated and mapped to persistent objects. Each object inherits from a base object class and is associated with a MultiIndex container that defines its indexes. - -- Enumerated object types: - - Dynamic global property, account, authority, validator, transaction, block summary, validator schedule, content, content type, content vote, validator vote, hardfork property, vesting routes, authorities history, recovery requests, escrow, block stats, vesting delegation, fixed delegation, delegation expiration, metadata, proposal, required approvals, committee request/vote, invite, award shares expiration, paid subscriptions, validator penalties, block post validation - -- Object identity and serialization: - - Object IDs are typed identifiers; reflection and raw packing/unpacking are supported for persistence and RPC - -Representative definitions: -- [Object types enumeration](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L146) -- [Object ID and reflection](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L113-L207) - -Storage optimization strategies: -- Shared string buffers and inter-process allocators minimize memory overhead -- Composite indexes reduce scans for common queries (e.g., by_name, by_cashout_time) - -**Section sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L146) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L113-L207) - -### Account Object Schema and Indexes -Accounts store balances, vesting shares, delegation metrics, auction/bidding state, bandwidth, and governance participation. - -Primary and composite indexes: -- by_id (unique) -- by_name (unique, lexicographic) -- by_account_on_sale/by_account_on_auction (non-unique) -- by_account_on_sale_start_time (non-unique) -- by_subaccount_on_sale (non-unique) -- by_next_vesting_withdrawal (composite: next_vesting_withdrawal + id) - -Optimization notes: -- Composite index by_next_vesting_withdrawal enables efficient batch processing of upcoming withdrawals -- Separate indexes for sale/auction flags support targeted queries for marketplace operations - -**Section sources** -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L144) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L291-L315) - -### Content Object Schema and Indexes -Content objects represent posts/comments with voting, payout, and nesting metadata. Content types and votes are separate objects with dedicated indexes. - -Indexes: -- by_id (unique) -- by_cashout_time (composite: cashout_time + id) -- by_permlink (composite: author + permlink) -- by_root (composite: root_content + id) -- by_parent (composite: parent_author + parent_permlink + id) -- Non-consensus indexes (API-heavy): - - by_last_update (composite: parent_author + last_update + id) - - by_author_last_update (composite: author + last_update + id) - -Content vote indexes: -- by_id (unique) -- by_content_voter (unique composite: content + voter) -- by_voter_content (unique composite: voter + content) -- by_voter_last_update (composite: voter + last_update + content) -- by_content_weight_voter (composite: content + weight + voter) - -Optimization notes: -- Composite indexes on author/permlink and root/content enable fast lookup of discussions and hierarchy navigation -- Weighted ordering by content_weight_voter supports leaderboards and trending calculations - -**Section sources** -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L114) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L197-L248) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L144-L184) - -### validator and Governance Objects -validator objects track scheduling, votes, signing keys, and penalties. Governance objects include votes and schedules. - -Indexes: -- witness_object: - - by_id (unique) - - by_work (non-unique) - - by_name (unique) - - by_vote_name (composite: votes + owner) - - by_counted_vote_name (composite: counted_votes + owner) - - by_schedule_time (composite: virtual_scheduled_time + id) -- witness_vote_object: - - by_id (unique) - - by_account_witness (unique composite: account + validator) - - by_witness_account (unique composite: validator + account) -- witness_schedule_object: - - by_id (unique) -- witness_penalty_expire_object: - - by_id (unique) - - by_account (non-unique) - - by_expiration (non-unique) - -Optimization notes: -- Virtual scheduled time index supports O(log N) scheduling decisions -- Composite indexes on account-validator pairs enable fast vote lookups - -**Section sources** -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L183-L219) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L224-L248) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L250-L256) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L276-L291) - -### Proposal and Authority Objects -Proposals encapsulate partially approved transactions with required/approved sets. Required approvals link accounts to proposals. - -Indexes: -- proposal_object: - - by_id (unique) - - by_account (composite: author + title) - - by_expiration (non-unique) -- required_approval_object: - - by_id (unique) - - by_account (composite: account + proposal) - -Optimization notes: -- Composite keys on author/title and account/proposal enable targeted queries for governance dashboards - -**Section sources** -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L108-L126) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L128-L140) - -### Invite Object Schema -Invite objects track creator, receiver, secret, key, balances, and status. - -Indexes: -- by_id (unique) -- by_invite_key (non-unique) -- by_status (non-unique) -- by_creator (non-unique) -- by_receiver (non-unique) - -Optimization notes: -- Multiple non-unique indexes support quick filtering by key, status, and participant roles - -**Section sources** -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L44-L66) - -### Auxiliary and Specialized Objects -Auxiliary objects include vesting routes, escrow, award shares expiration, and block post validation. - -Examples: -- Withdraw vesting routes: - - by_id (unique) - - by_withdraw_route (composite: from_account + to_account) - - by_destination (composite: to_account + id) -- Escrow: - - by_id (unique) - - by_from_id (composite: from + escrow_id) - - by_to (composite: to + id) - - by_agent (composite: agent + id) - - by_ratification_deadline (composite: is_approved + ratification_deadline + id) -- Award shares expiration: - - by_id (unique) - - by_expiration (non-unique) -- Block post validation: - - by_id (unique) - -Optimization notes: -- Composite indexes on multi-key fields enable targeted settlement/cleanup workflows - -**Section sources** -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L75-L98) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L104-L141) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L160-L172) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L192-L201) - -### Fork Database Implementation -The fork database manages a tree of blocks with: -- Linked index for canonical chain -- Unlinked index for orphaned/out-of-order blocks -- Hashed indexes by block_id and by_previous -- Ordered index by block_num - -Conflict resolution and branch management: -- push_block inserts blocks and links to previous if known -- fetch_branch_from walks both branches toward a common ancestor -- walk_main_branch_to_num and fetch_block_on_main_branch_by_number traverse canonical chain -- set_max_size prunes old blocks beyond a configurable threshold - -Invalidation and unlinkability: -- Blocks flagged invalid prevent further linking -- Attempting to push blocks outside the reordering window triggers assertions - -```mermaid -flowchart TD -Start(["Push Block"]) --> CheckHead["Has head and previous known?"] -CheckHead --> |No| InsertUnlinked["Insert into unlinked index"] -CheckHead --> |Yes| Link["Link to previous"] -Link --> InsertLinked["Insert into linked index"] -InsertLinked --> UpdateHead["Update head if higher num"] -UpdateHead --> Done(["Return head"]) -InsertUnlinked --> TryNext["Attempt to link pending blocks"] -TryNext --> Done -``` - -**Diagram sources** -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L90) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L100-L121) - -**Section sources** -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L90) - -### Index Management and Query Optimization -Index registration: -- Core indices are added via add_core_index -- Plugin indices are registered via add_plugin_index and signaled after schema initialization - -Common optimization techniques: -- Prefer composite indexes for multi-field queries (e.g., author+permlink, voter+content) -- Use non-unique indexes for status filters (e.g., by_account_on_sale, by_expiration) -- Order composite keys by selectivity and access patterns (e.g., time-based indexes with secondary sort by id) - -**Section sources** -- [index.hpp](file://libraries/chain/include/graphene/chain/index.hpp#L8-L24) -- [database.cpp](file://libraries/chain/database.cpp#L2952-L2960) - -### Object Relationship Patterns -Relationships among entities: -- Accounts own content and votes; content references authors and parents; votes reference accounts and content -- validators schedule and produce blocks; validator votes link accounts to validators -- Proposals require approvals from active/master/regular sets; required approvals link accounts to proposals -- Invites connect creators/receivers and keys to balances -- Vesting delegation links delegators to delegatees; expiration objects track time-based cleanup - -```mermaid -erDiagram -ACCOUNT ||--o{ CONTENT : "author" -ACCOUNT ||--o{ CONTENT_VOTE : "voter" -CONTENT ||--o{ CONTENT_VOTE : "content" -ACCOUNT ||--o{ WITNESS_VOTE : "account" -validator ||--o{ WITNESS_VOTE : "validator" -ACCOUNT ||--o{ VESTING_DELEGATION : "delegator" -ACCOUNT ||--o{ VESTING_DELEGATION_EXPIRATION : "delegator" -ACCOUNT ||--o{ PROPOSAL : "author" -ACCOUNT ||--o{ REQUIRED_APPROVAL : "account" -PROPOSAL ||--o{ REQUIRED_APPROVAL : "proposal" -ACCOUNT ||--o{ INVITE : "creator/receiver" -``` - -**Diagram sources** -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L144) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L121-L138) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L135-L150) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L90-L103) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L38) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L181-L228) - -## Dependency Analysis -The database depends on ChainBase for persistence primitives and integrates with Protocol types. The fork database is tightly coupled with block storage and maintains a separate block tree. - -```mermaid -graph LR -DB["database.hpp/.cpp"] --> CB["ChainBase"] -DB --> PROT["protocol/*"] -DB --> FDB["fork_database.hpp/.cpp"] -DB --> SCHEMA["schema headers (.hpp)"] -SCHEMA --> TYPES["chain_object_types.hpp"] -SCHEMA --> OBJACC["account_object.hpp"] -SCHEMA --> OBJCON["content_object.hpp"] -SCHEMA --> OBJWIT["witness_objects.hpp"] -SCHEMA --> OBJPRO["proposal_object.hpp"] -SCHEMA --> OBJINV["invite_objects.hpp"] -SCHEMA --> OBJEXT["chain_objects.hpp"] -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L50) -- [database.cpp](file://libraries/chain/database.cpp#L1-L40) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L20) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L1-L40) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L1-L20) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L1-L15) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L1-L20) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L1-L15) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L1-L15) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L20) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L50) -- [database.cpp](file://libraries/chain/database.cpp#L1-L40) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L20) - -## Performance Considerations -- Memory management: - - Shared memory sizing and incremental growth during reindex - - Periodic checks and reserved memory protection -- Index selection: - - Use composite indexes to avoid full scans on multi-column filters - - Prefer non-unique indexes for status-based filtering -- Fork pruning: - - Limit maximum reordering window to cap memory footprint -- Batch operations: - - Leverage composite ordering for efficient iteration (e.g., by_next_vesting_withdrawal) - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and diagnostics: -- Database open failures: - - Revision mismatch between chainbase and head block num - - Head block mismatch with block log -- Reindex interruptions: - - Signal guard handles OS signals during replay -- Memory pressure: - - Automatic resizing and periodic free memory checks -- Fork database errors: - - Unlinkable block exceptions when previous is unknown - - Invalid block flags prevent propagation - -Operational references: -- [Open and revision checks](file://libraries/chain/database.cpp#L242-L248) -- [Block log validation](file://libraries/chain/database.cpp#L250-L257) -- [Signal guard and reindex loop](file://libraries/chain/database.cpp#L300-L340) -- [Memory checks and resizing](file://libraries/chain/database.cpp#L396-L400) -- [Fork database unlinkable assertion](file://libraries/chain/fork_database.cpp#L59-L63) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L242-L248) -- [database.cpp](file://libraries/chain/database.cpp#L250-L257) -- [database.cpp](file://libraries/chain/database.cpp#L300-L340) -- [database.cpp](file://libraries/chain/database.cpp#L396-L400) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L59-L63) - -## Conclusion -The VIZ CPP Node database schema leverages ChainBase with carefully designed MultiIndex containers to support high-throughput consensus and API workloads. The fork database ensures robust branch handling and conflict resolution. Composite indexes and typed object IDs enable efficient queries and maintainability. The modular index registration pattern allows plugins to extend the schema safely. Proper maintenance and monitoring of memory and fork pruning are essential for sustained performance. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Schema Evolution and Migration Guidance -- Versioning and hardforks: - - Hardfork timestamps and versions are maintained in the database and processed during initialization -- Migration strategies: - - Use schema initialization to register new indices and objects - - Apply conditional logic during replay to transform legacy data into new structures -- Backward compatibility: - - Keep existing indexes and IDs stable - - Add new optional fields and non-conflicting indexes - - Avoid changing primary key semantics - -References: -- [Hardfork initialization and processing](file://libraries/chain/database.cpp#L262-L264) -- [Schema initialization hook](file://libraries/chain/database.cpp#L2992-L3005) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L262-L264) -- [database.cpp](file://libraries/chain/database.cpp#L2992-L3005) - -### Extending the Schema with Custom Objects -Steps: -- Define a new object class inheriting from the base object -- Declare MultiIndex container with desired indexes -- Register the index via add_core_index or add_plugin_index -- Reflect the object for serialization - -Example references: -- [Core index registration helper](file://libraries/chain/include/graphene/chain/index.hpp#L14-L21) -- [Object definition pattern](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L30) -- [MultiIndex container pattern](file://libraries/chain/include/graphene/chain/content_object.hpp#L197-L248) - -**Section sources** -- [index.hpp](file://libraries/chain/include/graphene/chain/index.hpp#L14-L21) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L30) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L197-L248) - -### Query Optimization Examples -- Fast lookup by author and permlink: - - Use by_permlink composite index on content -- Efficient vote ranking: - - Use by_content_weight_voter composite index on content votes -- Batch vesting withdrawals: - - Use by_next_vesting_withdrawal composite index on accounts -- Governance filtering: - - Use by_expiration on proposals and by_account on required approvals - -References: -- [Content by_permlink](file://libraries/chain/include/graphene/chain/content_object.hpp#L210-L214) -- [Content vote by_content_weight_voter](file://libraries/chain/include/graphene/chain/content_object.hpp#L172-L181) -- [Account by_next_vesting_withdrawal](file://libraries/chain/include/graphene/chain/account_object.hpp#L307-L312) -- [Proposal by_expiration](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L123-L125) -- [Required approval by_account](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L134-L139) - -**Section sources** -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L210-L214) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L172-L181) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L307-L312) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L123-L125) -- [proposal_object.hpp](file://libraries/chain/include/graphene/chain/proposal_object.hpp#L134-L139) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Advanced Topics/Hardfork Management.md b/.qoder/repowiki/en/content/Advanced Topics/Hardfork Management.md deleted file mode 100644 index 60eb37ed22..0000000000 --- a/.qoder/repowiki/en/content/Advanced Topics/Hardfork Management.md +++ /dev/null @@ -1,550 +0,0 @@ -# Hardfork Management - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [1.hf](file://libraries/chain/hardfork.d/1.hf) -- [2.hf](file://libraries/chain/hardfork.d/2.hf) -- [3.hf](file://libraries/chain/hardfork.d/3.hf) -- [4.hf](file://libraries/chain/hardfork.d/4.hf) -- [5.hf](file://libraries/chain/hardfork.d/5.hf) -- [6.hf](file://libraries/chain/hardfork.d/6.hf) -- [7.hf](file://libraries/chain/hardfork.d/7.hf) -- [8.hf](file://libraries/chain/hardfork.d/8.hf) -- [9.hf](file://libraries/chain/hardfork.d/9.hf) -- [10.hf](file://libraries/chain/hardfork.d/10.hf) -- [11.hf](file://libraries/chain/hardfork.d/11.hf) -- [12.hf](file://libraries/chain/hardfork.d/12.hf) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) - - -## Update Summary -**Changes Made** -- Added Emergency Consensus Recovery (HF12) as the latest hardfork implementation -- Updated hardfork numbering from 11 to 12 with CHAIN_HARDFORK_12_VERSION and CHAIN_HARDFORK_12_TIME configurations -- Enhanced validator scheduling logic with emergency mode activation -- Added emergency consensus recovery mechanisms and three-state safety enforcement -- Updated CHAIN_NUM_HARDFORKS from 11 to 12 in preamble -- Added comprehensive emergency mode functionality including fork switching and validator management - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the hardfork management system in the VIZ C++ Node. It covers how hardforks are defined, stored, loaded, and enforced during node runtime; how scheduled upgrades are coordinated with validator voting; how backward compatibility is maintained; and how migrations and state transitions are executed. The system now includes Emergency Consensus Recovery (HF12) as the latest hardfork implementation with sophisticated emergency mode activation and validator scheduling changes designed to recover from network consensus failures. - -## Project Structure -The hardfork system is centered around: -- A dedicated directory of hardfork definition files under libraries/chain/hardfork.d -- A database-level hardfork property object that tracks current and next hardfork versions and timestamps -- Runtime logic in the database that initializes hardfork state, evaluates hardfork boundaries, and applies version-specific behavior -- Emergency consensus recovery mechanisms for network failure scenarios - -```mermaid -graph TB -HFDir["Hardfork Definitions
libraries/chain/hardfork.d/*.hf"] -Preamble["Preamble Header
0-preamble.hf"] -HF1["HF1
1.hf"] -HF2["HF2
2.hf"] -HF3["HF3
3.hf"] -HF4["HF4
4.hf"] -HF5["HF5
5.hf"] -HF6["HF6
6.hf"] -HF7["HF7
7.hf"] -HF8["HF8
8.hf"] -HF9["HF9
9.hf"] -HF10["HF10
10.hf"] -HF11["HF11
11.hf"] -HF12["HF12
12.hf"] -DBHdr["Database Hardfork Properties
database.hpp"] -DBInit["Hardfork Init & Evaluation
database.cpp"] -Emergency["Emergency Consensus
Recovery"] -validator["Enhanced validator
Scheduling"] -Config["Configuration
Parameters"] -HFDir --> Preamble -HFDir --> HF1 -HFDir --> HF2 -HFDir --> HF3 -HFDir --> HF4 -HFDir --> HF5 -HFDir --> HF6 -HFDir --> HF7 -HFDir --> HF8 -HFDir --> HF9 -HFDir --> HF10 -HFDir --> HF11 -HFDir --> HF12 -Preamble --> DBHdr -HFDir --> DBInit -DBHdr --> DBInit -HF12 --> Emergency -HF12 --> validator -HF12 --> Config -``` - -**Diagram sources** -- [0-preamble.hf:55-56](file://libraries/chain/hardfork.d/0-preamble.hf#L55-L56) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [config.hpp:110-123](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L123) - -**Section sources** -- [0-preamble.hf:1-56](file://libraries/chain/hardfork.d/0-preamble.hf#L1-L56) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -## Core Components -- Hardfork definition files (*.hf): Define constants for each hardfork ID, timestamp, and protocol hardfork version. They are included by the preamble and compiled into the node binary. -- Hardfork property object: Tracks last processed hardfork, current hardfork version, and next hardfork version/time. Updated by runtime logic and persisted in the database. -- Database runtime: Initializes hardfork state on open/reindex, evaluates hardfork boundaries during block application, and conditionally applies behavior changes gated by has_hardfork() checks. -- **Emergency Consensus Recovery**: Advanced emergency mode activation that automatically recovers network consensus when blocks stop being produced for extended periods. - -Key responsibilities: -- Version management: Maintains arrays of hardfork times and versions used by the node, now supporting up to 12 hardforks. -- Scheduled upgrades: Watches validator votes and sets next hardfork according to majority. -- Backward compatibility: Uses has_hardfork() checks to branch behavior depending on applied hardfork level. -- Migration and state transitions: Applies changes to chain properties, validators, and runtime logic when crossing hardfork boundaries. -- **Network recovery**: Implements automatic emergency mode activation and recovery mechanisms. - -**Section sources** -- [0-preamble.hf:54-56](file://libraries/chain/hardfork.d/0-preamble.hf#L54-L56) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -## Architecture Overview -The hardfork architecture integrates definitions, runtime initialization, and evaluation during block processing, now enhanced with emergency consensus recovery capabilities. - -```mermaid -sequenceDiagram -participant Node as "Node Startup" -participant DB as "Database" -participant HFProps as "Hardfork Property Object" -participant ForkDB as "Fork DB" -participant BlockLog as "Block Log" -participant Emergency as "Emergency Mode" -Node->>DB : open(data_dir, shared_mem_dir, ...) -DB->>DB : init_schema(), initialize_indexes(), initialize_evaluators() -DB->>HFProps : load current_hardfork_version, next_hardfork, next_hardfork_time -DB->>BlockLog : open("block_log") -DB->>ForkDB : start_block(head_block) -DB->>DB : init_hardforks() (reads persisted state) -DB->>Emergency : check emergency conditions -Emergency-->>DB : activate if needed -DB-->>Node : ready to validate/apply blocks -loop Block Processing -Node->>DB : push_block(new_block) -DB->>DB : evaluate hardfork boundaries -DB->>HFProps : update next_hardfork if validator consensus changes -DB->>Emergency : monitor LIB timestamp -Emergency-->>DB : activate/deactivate emergency mode -DB-->>Node : block accepted or rejected based on hardfork rules -end -``` - -**Diagram sources** -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) -- [database.cpp:2052-2143](file://libraries/chain/database.cpp#L2052-L2143) - -## Detailed Component Analysis - -### Hardfork Directory and Definition Files -- Purpose: Provide compile-time constants for hardfork IDs, timestamps, and protocol hardfork versions. -- Organization: One *.hf file per hardfork, plus a preamble header that defines the hardfork property object schema and counts. -- **Updated**: Now includes HF12 (Emergency Consensus Recovery) as the latest hardfork. -- Example entries: - - Hardfork 1: timestamp and version for fixing a median calculation. - - Hardfork 2: timestamp and version for fixing a committee approval threshold. - - Hardfork 4: introduces major protocol changes (award operations, custom sequences). - - Hardfork 6: modifies validator penalties and vote counting. - - Hardfork 9: adjusts chain parameters for invites, subscriptions, and fees. - - Hardfork 11: emission model changes. - - **Hardfork 12: Emergency Consensus Recovery with emergency mode activation and validator scheduling changes**. - -Practical notes: -- Modify *.hf files to define a new hardfork; do not edit generated files. -- Keep timestamps realistic and coordinated with validator voting. -- **Updated CHAIN_NUM_HARDFORKS from 11 to 12 in preamble**. - -**Section sources** -- [0-preamble.hf:54-56](file://libraries/chain/hardfork.d/0-preamble.hf#L54-L56) -- [1.hf:1-7](file://libraries/chain/hardfork.d/1.hf#L1-L7) -- [2.hf:1-7](file://libraries/chain/hardfork.d/2.hf#L1-L7) -- [3.hf:1-7](file://libraries/chain/hardfork.d/3.hf#L1-L7) -- [4.hf:1-7](file://libraries/chain/hardfork.d/4.hf#L1-L7) -- [5.hf:1-7](file://libraries/chain/hardfork.d/5.hf#L1-L7) -- [6.hf:1-7](file://libraries/chain/hardfork.d/6.hf#L1-L7) -- [7.hf:1-7](file://libraries/chain/hardfork.d/7.hf#L1-L7) -- [8.hf:1-7](file://libraries/chain/hardfork.d/8.hf#L1-L7) -- [9.hf:1-7](file://libraries/chain/hardfork.d/9.hf#L1-L7) -- [10.hf:1-7](file://libraries/chain/hardfork.d/10.hf#L1-L7) -- [11.hf:1-7](file://libraries/chain/hardfork.d/11.hf#L1-L7) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -### Hardfork Property Object and Runtime State -- Schema: Contains processed hardforks vector, last hardfork ID, current hardfork version, and next hardfork version/time. -- Initialization: During open(), the node loads persisted hardfork state and ensures consistency with the chainbase revision and block log. -- Updates: During block application, the node evaluates validator consensus and updates next_hardfork accordingly. -- **Enhanced**: Now supports emergency consensus properties for HF12. - -```mermaid -classDiagram -class HardforkPropertyObject { -+id -+processed_hardforks -+last_hardfork -+current_hardfork_version -+next_hardfork -+next_hardfork_time -+emergency_consensus_active -+emergency_consensus_start_block -} -class Database { -+init_hardforks() -+process_hardforks() -+apply_hardfork(hardfork) -+has_hardfork(hardfork) bool -+_hardfork_times[] -+_hardfork_versions[] -} -Database --> HardforkPropertyObject : "reads/writes" -``` - -**Diagram sources** -- [0-preamble.hf:18-56](file://libraries/chain/hardfork.d/0-preamble.hf#L18-L56) -- [global_property_object.hpp:138-144](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L138-L144) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -**Section sources** -- [0-preamble.hf:18-56](file://libraries/chain/hardfork.d/0-preamble.hf#L18-L56) -- [global_property_object.hpp:138-144](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L138-L144) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -### Emergency Consensus Recovery (HF12) -**New Section**: HF12 introduces comprehensive emergency consensus recovery mechanisms designed to automatically recover network consensus when blocks stop being produced for extended periods. - -#### Emergency Mode Activation -- **Automatic Detection**: Monitors last irreversible block (LIB) timestamp and activates emergency mode after CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC (1 hour) without new blocks. -- **Emergency validator**: Creates and activates the CHAIN_EMERGENCY_WITNESS_ACCOUNT ("committee") with CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY to produce blocks. -- **Neutral Voting**: Emergency validator votes for the currently applied hardfork version (status quo) to avoid pushing for new hardforks. - -#### Three-State Safety Enforcement -- **Healthy Network**: Participation ≥ 33% - enforces safe defaults automatically, overriding manual config overrides. -- **Distressed Network**: Participation < 33% - honors manual config overrides to allow operators to accelerate recovery. -- **Emergency Mode**: Automatically bypasses both stale and participation checks when emergency consensus is active. - -#### Enhanced validator Scheduling -- **Hybrid Schedule**: During emergency mode, replaces unavailable validator slots with emergency validator while maintaining real validator positions. -- **Vote Weighted Fork Switching**: HF12 implements vote-weighted chain comparison during fork switching, using sum of raw votes from unique non-committee validators as primary criterion. -- **Median Computation**: Excludes emergency validator from median property computations to prevent skewing chain parameters. - -```mermaid -flowchart TD -Start(["Block Production Check"]) --> CheckHF["Check HF12 Active?"] -CheckHF --> |Yes| CheckEmergency{"Emergency Mode Active?"} -CheckHF --> |No| Legacy["Legacy Behavior"] -CheckEmergency --> |Yes| EnableProduction["Auto-enable Production
Bypass Stale & Participation Checks"] -CheckEmergency --> |No| CheckParticipation["Check Participation Rate"] -CheckParticipation --> |≥33%| Healthy["Healthy Network
Enforce Safe Defaults"] -CheckParticipation --> |<33%| Distressed["Distressed Network
Honor Manual Overrides"] -EnableProduction --> End(["Production Allowed"]) -Healthy --> End -Distressed --> End -Legacy --> End -``` - -**Diagram sources** -- [validator.cpp:354-392](file://plugins/validator/validator.cpp#L354-L392) -- [database.cpp:2052-2143](file://libraries/chain/database.cpp#L2052-L2143) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) - -**Section sources** -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [database.cpp:2052-2143](file://libraries/chain/database.cpp#L2052-L2143) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) -- [validator.cpp:354-392](file://plugins/validator/validator.cpp#L354-L392) - -### Hardfork Evaluation and Block Application -- During block validation and application, the node checks: - - Whether the current hardfork version requires applying changes. - - Whether validator consensus indicates a scheduled upgrade. - - **Emergency mode activation conditions** based on LIB timestamp monitoring. -- The node compares validator votes against configured hardfork versions/times and updates next_hardfork accordingly. -- Behavior changes are gated behind has_hardfork() checks to preserve backward compatibility. -- **Enhanced**: HF12 adds emergency consensus detection and three-state safety enforcement logic. - -```mermaid -flowchart TD -Start(["Start Block Apply"]) --> CheckHF["Check current_hardfork_version vs next_hardfork"] -CheckHF --> Consensus{"validator consensus
requires upgrade?"} -Consensus --> |Yes| UpdateNext["Update next_hardfork and time"] -Consensus --> |No| CheckEmergency["Check Emergency Mode Conditions"] -CheckEmergency --> EmergencyActive{"Emergency Mode
Active?"} -EmergencyActive --> |Yes| EmergencyLogic["Apply Emergency
Consensus Logic"] -EmergencyActive --> |No| Continue["Continue with current version"] -UpdateNext --> Continue -EmergencyLogic --> Continue -Continue --> ApplyOps["Apply operations with
has_hardfork() branches"] -ApplyOps --> End(["End Block Apply"]) -``` - -**Diagram sources** -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) -- [database.cpp:1600-1654](file://libraries/chain/database.cpp#L1600-L1654) - -**Section sources** -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) -- [database.cpp:1600-1654](file://libraries/chain/database.cpp#L1600-L1654) - -### Migration Procedures and State Transitions -- On open(): - - Initialize schema and indexes. - - Open block log and fork DB. - - Initialize hardfork state from persisted data. - - Assert chainbase revision matches head block number. - - **Initialize emergency consensus properties if present**. -- On reindex: - - Replay blocks from the block log with skip flags optimized for speed. - - Apply each block with hardfork-aware logic including emergency mode detection. - - Set revision periodically to ensure progress. - - **Process emergency consensus state transitions during replay**. - -Operational guidance: -- Before upgrading, back up the database and block log. -- Run with read-only mode initially to verify compatibility. -- Monitor logs for hardfork-related warnings or errors. -- **Monitor emergency mode activation and deactivation events**. - -**Section sources** -- [database.cpp:206-268](file://libraries/chain/database.cpp#L206-L268) -- [database.cpp:270-350](file://libraries/chain/database.cpp#L270-L350) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -### Rollback and Recovery for Failed Upgrades -- Undo sessions: The database uses undo sessions to roll back partial changes when block application fails. -- Fork switching: If a new head does not build off the current head, the node switches forks and reapplies blocks safely. -- **Enhanced fork switching**: HF12 implements vote-weighted chain comparison during fork switching, using sum of raw votes from unique non-committee validators as primary criterion. -- Pop block: Removes the head block and restores transactions to pending state. -- **Emergency mode recovery**: Automatic recovery mechanisms handle emergency consensus state transitions. - -```mermaid -sequenceDiagram -participant DB as "Database" -participant ForkDB as "Fork DB" -participant Session as "Undo Session" -participant Emergency as "Emergency Mode" -DB->>ForkDB : push_block(new_block) -alt failure -DB->>ForkDB : remove(new_block.id) -DB-->>DB : throw exception -else success -DB->>Session : start_undo_session() -DB->>Session : apply_block(...) -Session-->>DB : session.push() -end -Note over Emergency : HF12 Enhanced Logic -ForkDB->>Emergency : check emergency conditions -Emergency->>ForkDB : set_emergency_mode(flag) -``` - -**Diagram sources** -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) -- [fork_database.hpp:118-129](file://libraries/chain/include/graphene/chain/fork_database.hpp#L118-L129) - -**Section sources** -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) -- [fork_database.hpp:118-129](file://libraries/chain/include/graphene/chain/fork_database.hpp#L118-L129) - -### Implementing Custom Hardfork Logic -Steps to add a new hardfork: -1. Define the hardfork in a new *.hf file with ID, timestamp, and version. -2. **Increment CHAIN_NUM_HARDFORKS in the preamble** (now 12). -3. Gate behavior changes using has_hardfork(CHAIN_HARDFORK_N) checks in relevant code paths. -4. Optionally adjust chain parameters or validators in update_median_witness_props() or related routines. -5. **Consider emergency mode implications** for network recovery scenarios. -6. Test with a local testnet or snapshot to validate behavior before mainnet deployment. - -Examples of where to add logic: -- Operation evaluators: Wrap validation or execution in has_hardfork() branches. -- Chain properties: Adjust median properties or inflation logic based on hardfork level. -- validator scheduling: Update vote counting or penalties depending on hardfork. -- **Emergency mode handling**: Implement recovery mechanisms for network failure scenarios. - -**Section sources** -- [0-preamble.hf:54-56](file://libraries/chain/hardfork.d/0-preamble.hf#L54-L56) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.cpp:1715-1779](file://libraries/chain/database.cpp#L1715-L1779) -- [database.cpp:2341-2399](file://libraries/chain/database.cpp#L2341-L2399) - -### Adding New Operation Types -- Define the operation type in the protocol layer. -- Register an evaluator for the operation. -- Gate any new behavior behind has_hardfork() checks. -- Ensure backward compatibility by preserving old validation paths. -- **Consider emergency mode compatibility** for critical operations. - -### Modifying Existing Behavior -- Identify the affected subsystem (e.g., validator voting, inflation, chain properties, emergency mode). -- Add a new hardfork constant and timestamp. -- Update the relevant runtime logic to branch on has_hardfork(). -- **Implement emergency mode considerations** for network recovery scenarios. -- Verify with tests and a staged rollout. - -**Section sources** -- [database.cpp:1830-1891](file://libraries/chain/database.cpp#L1830-L1891) -- [database.cpp:2341-2399](file://libraries/chain/database.cpp#L2341-L2399) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -### Common Hardfork Scenarios -- Protocol changes: Introduce new operations or modify semantics (e.g., award operations, custom sequences). -- Bug fixes: Correct calculations or state transitions (e.g., median properties, vote accounting). -- Feature additions: Enable new chain parameters or fee structures (e.g., invites, subscriptions, validator penalties). -- **Network recovery**: Emergency consensus activation and recovery mechanisms for consensus failures. - -Validation procedures: -- Confirm hardfork timestamps align with validator votes. -- Verify has_hardfork() branches execute the intended logic. -- Run reindex tests to ensure deterministic replay. -- **Test emergency mode activation and recovery scenarios**. - -**Section sources** -- [1.hf:1-7](file://libraries/chain/hardfork.d/1.hf#L1-L7) -- [2.hf:1-7](file://libraries/chain/hardfork.d/2.hf#L1-L7) -- [4.hf:1-7](file://libraries/chain/hardfork.d/4.hf#L1-L7) -- [6.hf:1-7](file://libraries/chain/hardfork.d/6.hf#L1-L7) -- [9.hf:1-7](file://libraries/chain/hardfork.d/9.hf#L1-L7) -- [11.hf:1-7](file://libraries/chain/hardfork.d/11.hf#L1-L7) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -## Dependency Analysis -The hardfork system depends on: -- Hardfork definition files for compile-time constants. -- Database hardfork property object for runtime state. -- validator consensus for determining next hardfork. -- Block log and fork database for replay and fork switching. -- **Emergency consensus parameters and configurations**. - -```mermaid -graph TB -HFFiles["*.hf files"] -Preamble["0-preamble.hf"] -HF12["12.hf
Emergency Consensus"] -DBHdr["database.hpp
hardfork arrays"] -DBInit["database.cpp
init_hardforks/process_hardforks"] -HFProps["HardforkPropertyObject"] -ForkDB["Fork DB"] -BlockLog["Block Log"] -Config["config.hpp
Emergency Params"] -validator["validator.cpp
Three-State Safety"] -Emergency["Emergency Logic"] -HFFiles --> Preamble -Preamble --> DBHdr -DBHdr --> DBInit -HFProps --> DBInit -ForkDB --> DBInit -BlockLog --> DBInit -HF12 --> DBInit -Config --> DBInit -validator --> DBInit -Emergency --> DBInit -``` - -**Diagram sources** -- [0-preamble.hf:54-56](file://libraries/chain/hardfork.d/0-preamble.hf#L54-L56) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [config.hpp:110-123](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L123) -- [validator.cpp:354-392](file://plugins/validator/validator.cpp#L354-L392) - -**Section sources** -- [0-preamble.hf:54-56](file://libraries/chain/hardfork.d/0-preamble.hf#L54-L56) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) -- [database.hpp:577-578](file://libraries/chain/include/graphene/chain/database.hpp#L577-L578) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [config.hpp:110-123](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L123) - -## Performance Considerations -- Reindexing with skip flags reduces overhead by bypassing expensive validations. -- Periodic revision setting during reindex prevents excessive memory pressure. -- Auto-scaling shared memory helps avoid failures during heavy reindex workloads. -- **Emergency mode optimization**: Minimal performance impact through efficient emergency validator creation and activation. -- **Enhanced fork switching**: Vote-weighted chain comparison adds computational overhead but improves network stability. - -Recommendations: -- Configure shared memory sizing appropriately for your hardware. -- Monitor free memory and adjust thresholds to trigger resizing proactively. -- **Monitor emergency mode activation frequency** to assess network health. -- **Optimize validator scheduling algorithms** for emergency mode scenarios. - -**Section sources** -- [database.cpp:270-350](file://libraries/chain/database.cpp#L270-L350) -- [database.cpp:368-430](file://libraries/chain/database.cpp#L368-L430) -- [database.cpp:1096-1135](file://libraries/chain/database.cpp#L1096-L1135) - -## Troubleshooting Guide -Common issues and resolutions: -- Revision mismatch on open: Indicates chainbase revision does not match head block number; reindex or restore from a compatible backup. -- Chain state mismatch with block log: Requires reindex to reconcile state. -- Memory exhaustion during reindex: Increase shared memory size or tune auto-resize parameters. -- Hardfork not triggering: Verify validator consensus matches configured hardfork version/time; check has_hardfork() branches. -- **Emergency mode activation failures**: Check CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC configuration and LIB timestamp monitoring. -- **Emergency validator creation issues**: Verify CHAIN_EMERGENCY_WITNESS_ACCOUNT and CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY settings. -- **Three-state safety enforcement problems**: Review validator participation rate calculations and configuration overrides. - -Diagnostic steps: -- Review logs around open() and reindex() for explicit assertions or exceptions. -- Confirm hardfork timestamps and versions in *.hf files. -- Validate that next_hardfork updates according to validator votes. -- **Monitor emergency mode activation and deactivation events**. -- **Check fork switching behavior** during emergency mode. - -**Section sources** -- [database.cpp:206-268](file://libraries/chain/database.cpp#L206-L268) -- [database.cpp:270-350](file://libraries/chain/database.cpp#L270-L350) -- [database.cpp:1600-1654](file://libraries/chain/database.cpp#L1600-L1654) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [validator.cpp:354-392](file://plugins/validator/validator.cpp#L354-L392) - -## Conclusion -The VIZ hardfork system provides a robust, versioned mechanism for managing protocol upgrades, now enhanced with comprehensive emergency consensus recovery capabilities. By combining compile-time definitions, runtime state tracking, consensus-driven scheduling, and automatic network recovery mechanisms, it enables safe, backward-compatible upgrades even during network failure scenarios. The addition of HF12 (Emergency Consensus Recovery) significantly strengthens the system's resilience and reliability for critical network operations. - -## Appendices -- Practical checklist for upgrades: - - Freeze node binaries to the target version. - - Prepare a snapshot and backup. - - Deploy *.hf files and rebuild (remember to increment CHAIN_NUM_HARDFORKS to 12). - - Start node in read-only mode to validate. - - Monitor logs and metrics. - - Coordinate with validators to reach consensus. - - **Monitor emergency mode activation and recovery scenarios**. - - Proceed with full node operation after verification. -- **Emergency mode operational procedures**: - - Monitor CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC threshold. - - Verify emergency validator activation and block production. - - Track emergency mode duration and automatic deactivation. - - Test fork switching behavior during emergency scenarios. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Advanced Topics/Security Implementation.md b/.qoder/repowiki/en/content/Advanced Topics/Security Implementation.md deleted file mode 100644 index a8b6eb9e92..0000000000 --- a/.qoder/repowiki/en/content/Advanced Topics/Security Implementation.md +++ /dev/null @@ -1,467 +0,0 @@ -# Security Implementation - - -**Referenced Files in This Document** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp) -- [shared_authority.cpp](file://libraries/chain/shared_authority.cpp) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [plugin.hpp](file://plugins/auth_util/include/graphene/plugins/auth_util/plugin.hpp) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) -- [config.ini](file://share/vizd/config/config.ini) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive security implementation documentation for the VIZ CPP Node. It covers cryptographic security (digital signature verification, key management, secure communications), authority and multi-signature systems, API authentication and authorization, network security, vulnerability assessment, best practices for plugin development, and operational security procedures. The goal is to help operators, developers, and auditors understand how security is implemented and how to extend or maintain it safely. - -## Project Structure -Security-relevant components are distributed across: -- Protocol layer: cryptographic primitives, authorities, and signature validation logic -- Chain layer: shared-memory compatible authority representation -- Network layer: encrypted peer-to-peer transport -- Plugins: API exposure and webserver hosting -- Configuration: runtime security-related settings - -```mermaid -graph TB -subgraph "Protocol Layer" -A["authority.hpp"] -B["sign_state.hpp"] -end -subgraph "Chain Layer" -C["shared_authority.hpp"] -end -subgraph "Network Layer" -D["stcp_socket.hpp"] -E["node.hpp"] -end -subgraph "Plugins" -F["auth_util plugin.hpp/.cpp"] -G["webserver_plugin.hpp/.cpp"] -end -subgraph "Config" -H["config.ini"] -end -A --> B -C --> B -D --> E -F --> A -G --> H -``` - -**Diagram sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [plugin.hpp](file://plugins/auth_util/include/graphene/plugins/auth_util/plugin.hpp#L21-L54) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) -- [config.ini](file://share/vizd/config/config.ini) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L1-L113) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L1-L99) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L355) -- [plugin.hpp](file://plugins/auth_util/include/graphene/plugins/auth_util/plugin.hpp#L1-L60) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L1-L99) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L1-L62) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L1-L336) -- [config.ini](file://share/vizd/config/config.ini) - -## Core Components -- Digital signature verification and authority checks: - - Authority model with thresholds and weighted participants - - Signature state engine to validate authorities recursively -- Key management: - - Public key extraction from signatures - - Shared authority for shared memory compatibility -- Secure communications: - - ECDH-based per-connection AES encryption for peer transport -- API exposure: - - Webserver plugin serving JSON-RPC over HTTP/WebSocket - - Authorization via account authority and signature validation - -Key implementation references: -- Authority and classification: [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- Signature state and recursion depth: [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- Shared authority for chain storage: [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) -- Encrypted peer transport: [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- Webserver plugin endpoints: [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -## Architecture Overview -The security architecture integrates cryptographic checks, authority validation, and secure transport. At runtime: -- Clients submit transactions with signatures -- The node validates signatures against account authorities -- Transactions are propagated securely over encrypted peer links -- APIs are exposed via a configurable webserver - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "Webserver Plugin" -participant Auth as "Auth Utility Plugin" -participant Chain as "Chain Database" -participant Net as "Network Node" -Client->>WS : "JSON-RPC request" -WS->>Auth : "check_authority_signature(account, level, digest, sigs)" -Auth->>Chain : "lookup account authority" -Auth->>Auth : "derive signing keys from signatures" -Auth->>Auth : "build sign_state and check_authority()" -Auth-->>WS : "verified signing keys" -WS-->>Client : "JSON-RPC response" -Client->>Net : "broadcast signed transaction" -Net-->>Client : "propagation confirmed" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L258-L262) - -## Detailed Component Analysis - -### Digital Signature Verification and Authority System -- Authority model: - - Threshold-weighted participation for accounts - - Support for account and key authorities - - Classification enum for roles (master, active, key, regular) -- Signature state engine: - - Builds a set of provided signatures - - Recursively resolves nested authorities up to a maximum depth - - Filters unused approvals and signatures -- Shared authority: - - Compatible with shared memory allocation for chain objects - -```mermaid -classDiagram -class Authority { -+uint32_t weight_threshold -+account_auths -+key_auths -+add_authority(...) -+get_keys() -+is_impossible() -+num_auths() -+clear() -+validate() -} -class SignState { -+provided_signatures -+unused_signatures -+approved_by -+unused_approvals -+signed_by(k) -+check_authority(id) -+check_authority(au, depth) -+remove_unused_signatures() -+filter_unused_approvals() -} -class SharedAuthority { -+weight_threshold -+account_auths -+key_auths -+add_authority(...) -+get_keys() -+is_impossible() -+num_auths() -+validate() -} -SignState --> Authority : "validates against" -SharedAuthority --> Authority : "convertible to/from" -``` - -**Diagram sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp) -- [shared_authority.cpp](file://libraries/chain/shared_authority.cpp) - -### Key Management and Signature Extraction -- Extract signing keys from provided signatures using deterministic recovery -- Build a set of available keys for authority checks -- Validate authorities against account active/master/regular levels - -```mermaid -flowchart TD -Start(["Start"]) --> GetSig["Receive signatures"] -GetSig --> RecoverKeys["Recover public keys from signatures"] -RecoverKeys --> BuildSet["Build available keys set"] -BuildSet --> ResolveAuth["Resolve account authority by level"] -ResolveAuth --> CheckSS["Initialize sign_state and check_authority()"] -CheckSS --> Pass{"Authority satisfied?"} -Pass --> |Yes| ReturnOK["Return verified keys"] -Pass --> |No| ReturnErr["Fail with error"] -ReturnOK --> End(["End"]) -ReturnErr --> End -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) - -**Section sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) - -### Secure Communication Protocols (Peer Transport) -- ECDH key exchange establishes a shared secret per connection -- AES encoder/decoder streams protect message payloads -- TCP socket wrapper supports read/write buffering and flushing - -```mermaid -sequenceDiagram -participant PeerA as "Peer A" -participant PeerB as "Peer B" -participant SockA as "stcp_socket(A)" -participant SockB as "stcp_socket(B)" -PeerA->>SockA : "connect_to(remote)" -PeerB->>SockB : "accept()" -SockA->>SockB : "exchange ephemeral keys" -SockA->>SockA : "derive shared secret" -SockB->>SockB : "derive shared secret" -SockA->>SockB : "encrypted payload" -SockB->>SockA : "encrypted payload" -``` - -**Diagram sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) - -**Section sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) - -### API Authentication and Authorization -- Webserver plugin exposes JSON-RPC over HTTP and WebSocket -- Requests are dispatched to registered handlers on the application’s io_service thread -- Authorization is enforced by the auth_util plugin, which verifies signatures against account authorities - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "Webserver Plugin" -participant RPC as "JSON-RPC Handler" -participant Auth as "Auth Utility Plugin" -participant DB as "Chain Database" -Client->>WS : "HTTP/WS JSON-RPC" -WS->>RPC : "call(payload)" -RPC->>Auth : "check_authority_signature(...)" -Auth->>DB : "lookup account authorities" -Auth-->>RPC : "verification result" -RPC-->>WS : "authorized response" -WS-->>Client : "HTTP/WS response" -``` - -**Diagram sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) - -**Section sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) -- [plugin.hpp](file://plugins/auth_util/include/graphene/plugins/auth_util/plugin.hpp#L21-L54) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) - -### Network Security Measures -- Peer authentication via encrypted channels prevents passive eavesdropping -- Node maintains peer database and propagation timing metadata -- Bandwidth limits and advanced parameters can be configured for operational control - -```mermaid -graph TB -N["Node"] -P1["Peer 1"] -P2["Peer 2"] -Enc["Encrypted Channel (ECDH+AES)"] -N --- Enc --- P1 -N --- Enc --- P2 -N -.-> P1 -N -.-> P2 -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -### Vulnerability Assessment Procedures -- Common risks: - - Insufficient signature validation leading to unauthorized operations - - Weak randomness or reused nonces in cryptographic contexts - - Man-in-the-middle attacks on unencrypted RPC endpoints - - Denial-of-service via oversized payloads or excessive concurrent requests -- Penetration testing approaches: - - Validate authority bypass attempts by submitting malformed signatures - - Test recursion depth limits and resource exhaustion under heavy nested authorities - - Verify transport encryption and handshake integrity - - Stress test webserver endpoints for rate-limiting and memory exhaustion -- Audit methodology: - - Static analysis of authority thresholds and recursion bounds - - Dynamic testing of signature recovery and authority resolution paths - - Network capture analysis to confirm encrypted transport usage - - Review configuration files for exposed endpoints and weak defaults - -[No sources needed since this section provides general guidance] - -### Security Best Practices for Plugin Development -- Input validation: - - Reject malformed or oversized payloads at plugin boundaries - - Enforce strict schema validation for JSON-RPC arguments -- Secure coding patterns: - - Prefer constant-time comparisons for secrets - - Avoid storing plaintext credentials; derive keys securely -- Threat modeling: - - Enumerate trusted vs. untrusted inputs - - Model actor permissions per account authority levels -- Example patterns: - - Use the existing auth_util API to validate signatures before processing sensitive operations - - Leverage the webserver plugin’s thread pool sizing to manage resource consumption - -[No sources needed since this section provides general guidance] - -### Practical Examples for Custom Plugins -- Implementing signature verification: - - Use the auth_util API to validate signatures against account authorities before applying state changes - - Reference: [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- Exposing a secure API: - - Register JSON-RPC endpoints via the webserver plugin and enforce authorization inside the handler - - Reference: [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) -- Integrating encrypted transport: - - For inter-node communication, rely on the built-in stcp_socket for ECDH-based encryption - - Reference: [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -**Section sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -### Security Monitoring, Incident Response, and Updates -- Monitoring: - - Track peer connection counts, bandwidth usage, and propagation delays - - Monitor webserver thread pool saturation and error rates -- Incident response: - - Isolate affected endpoints, rotate keys, and re-validate authorities - - Review logs around failed signature validations and authority checks -- Updates: - - Apply hardforks and protocol upgrades that adjust authority thresholds or signature validation rules - - Update transport encryption parameters and cipher suites as needed - -[No sources needed since this section provides general guidance] - -## Dependency Analysis -The security subsystem exhibits clear separation of concerns: -- Protocol and chain layers define the authority model and signature validation -- Network layer enforces transport security -- Plugins expose APIs and orchestrate authorization - -```mermaid -graph LR -Proto["Protocol Layer
authority.hpp, sign_state.hpp"] -Chain["Chain Layer
shared_authority.hpp"] -Net["Network Layer
stcp_socket.hpp, node.hpp"] -Auth["Auth Utility Plugin
plugin.hpp/.cpp"] -WS["Webserver Plugin
webserver_plugin.hpp/.cpp"] -Proto --> Auth -Chain --> Auth -Net --> WS -WS --> Auth -``` - -**Diagram sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [plugin.hpp](file://plugins/auth_util/include/graphene/plugins/auth_util/plugin.hpp#L21-L54) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L100) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [plugin.hpp](file://plugins/auth_util/include/graphene/plugins/auth_util/plugin.hpp#L21-L54) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) - -## Performance Considerations -- Signature validation cost scales with authority depth and key count; tune recursion limits and key sets accordingly -- Webserver thread pool size impacts throughput under load; monitor queue lengths and latency -- Network bandwidth limits prevent abuse while maintaining propagation performance - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Signature validation failures: - - Confirm account authority levels and weights - - Verify signature recovery produces expected public keys - - Check recursion depth and nested authority chains -- Transport errors: - - Validate ECDH handshake completion and shared secret derivation - - Inspect TCP socket read/write buffers and flush behavior -- Webserver issues: - - Ensure endpoints resolve and ports are available - - Confirm thread pool size adequate for expected concurrency - -**Section sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L78) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L266-L312) - -## Conclusion -VIZ CPP Node implements a robust security model centered on threshold-based authorities, deterministic signature verification, and encrypted peer transport. Operators should focus on proper configuration, continuous monitoring, and disciplined plugin development practices to maintain a secure deployment. Regular audits, updates, and incident response procedures will further strengthen resilience against evolving threats. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices -- Configuration highlights for security: - - Configure webserver endpoints and thread pool size - - Ensure encrypted peer transport is enabled and properly bound - - Limit bandwidth and manage peer lists for operational safety - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Architecture Overview.md b/.qoder/repowiki/en/content/Architecture Overview/Architecture Overview.md deleted file mode 100644 index 11a1f01001..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Architecture Overview.md +++ /dev/null @@ -1,537 +0,0 @@ -# Architecture Overview - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [CMakeLists.txt](file://CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [libraries/api/include/graphene/api/chain_api_properties.hpp](file://libraries/api/include/graphene/api/chain_api_properties.hpp) -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp) -- [plugins/chain/plugin.cpp](file://plugins/chain/plugin.cpp) -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the architecture of the VIZ C++ Node system. It explains how the main vizd process orchestrates core libraries (chain, protocol, network, wallet), the plugin-based extension mechanism, and external dependencies. It also documents the event-driven observer pattern, data flow from JSON-RPC requests through plugins to database operations, and system boundaries for peer communication, API handling, and persistent state management. Cross-cutting concerns such as performance, security, and monitoring are addressed alongside architectural decisions like the use of C++ for performance, Boost.Signals2 for event handling, and layered separation of concerns. - -## Project Structure -The repository is organized around a layered design: -- Top-level build and configuration: CMake and third-party dependencies -- Libraries: chain, protocol, network, api, utilities, time, wallet -- Plugins: modular feature extensions (chain, p2p, json_rpc, database_api, etc.) -- Programs: vizd (daemon), cli_wallet, utilities -- Share: configuration templates, Dockerfiles, seed nodes - -```mermaid -graph TB -subgraph "Top-level" -CMK["CMakeLists.txt"] -LIBS_CMK["libraries/CMakeLists.txt"] -PLUG_CMK["plugins/CMakeLists.txt"] -end -subgraph "Libraries" -L_CHAIN["libraries/chain"] -L_PROTOCOL["libraries/protocol"] -L_NETWORK["libraries/network"] -L_API["libraries/api"] -L_UTIL["libraries/utilities"] -L_TIME["libraries/time"] -L_WALLET["libraries/wallet"] -end -subgraph "Plugins" -P_CHAIN["plugins/chain"] -P_JSONRPC["plugins/json_rpc"] -P_DB_API["plugins/database_api"] -P_P2P["plugins/p2p"] -P_WBS["plugins/webserver"] -P_WITNESS["plugins/validator"] -P_TAGS["plugins/tags"] -P_ACCOUNT_HISTORY["plugins/account_history"] -P_ACCOUNT_BY_KEY["plugins/account_by_key"] -P_PRIVATE_MESSAGE["plugins/private_message"] -P_AUTH_UTIL["plugins/auth_util"] -P_DEBUG["plugins/debug_node"] -P_RAW_BLOCK["plugins/raw_block"] -P_BLOCK_INFO["plugins/block_info"] -P_FOLLOW["plugins/follow"] -P_COMMITTEE_API["plugins/committee_api"] -P_INVITE_API["plugins/invite_api"] -P_PAID_SUBSCRIPTION_API["plugins/paid_subscription_api"] -P_CUSTOM_PROTOCOL_API["plugins/custom_protocol_api"] -end -subgraph "Programs" -PR_VIZD["programs/vizd"] -PR_CLI["programs/cli_wallet"] -PR_UTIL["programs/util"] -end -CMK --> LIBS_CMK -CMK --> PLUG_CMK -CMK --> PR_VIZD -CMK --> PR_CLI -CMK --> PR_UTIL -LIBS_CMK --> L_CHAIN -LIBS_CMK --> L_PROTOCOL -LIBS_CMK --> L_NETWORK -LIBS_CMK --> L_API -LIBS_CMK --> L_UTIL -LIBS_CMK --> L_TIME -LIBS_CMK --> L_WALLET -PLUG_CMK --> P_CHAIN -PLUG_CMK --> P_JSONRPC -PLUG_CMK --> P_DB_API -PLUG_CMK --> P_P2P -PLUG_CMK --> P_WBS -PLUG_CMK --> P_WITNESS -PLUG_CMK --> P_TAGS -PLUG_CMK --> P_ACCOUNT_HISTORY -PLUG_CMK --> P_ACCOUNT_BY_KEY -PLUG_CMK --> P_PRIVATE_MESSAGE -PLUG_CMK --> P_AUTH_UTIL -PLUG_CMK --> P_DEBUG -PLUG_CMK --> P_RAW_BLOCK -PLUG_CMK --> P_BLOCK_INFO -PLUG_CMK --> P_FOLLOW -PLUG_CMK --> P_COMMITTEE_API -PLUG_CMK --> P_INVITE_API -PLUG_CMK --> P_PAID_SUBSCRIPTION_API -PLUG_CMK --> P_CUSTOM_PROTOCOL_API -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -## Core Components -- vizd process: Initializes plugins, sets logging, starts the event loop, and manages lifecycle. -- Chain library: Core blockchain state machine, fork database, block/tx validation, and persistence. -- Protocol library: Operation definitions, transactions, and chain-specific types. -- Network library: Peer-to-peer networking, message handling, and synchronization. -- Wallet library: Remote node API integration and transaction construction utilities. -- Plugin system: Modular features exposed via APIs and hooks integrated into the app lifecycle. - -Key architectural anchors: -- Event-driven observer pattern via fc::signals in the chain database. -- JSON-RPC plugin routing to registered APIs. -- Plugin registration and initialization in vizd’s main entry point. - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L10-L12) -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L200) -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L127) -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L151-L200) - -## Architecture Overview -High-level system boundaries and interactions: -- Internal boundaries: vizd process hosts plugins; plugins depend on libraries; chain database persists state; network interacts with peers; API plugins expose RPC endpoints. -- External boundaries: peers (other nodes), clients (wallets, applications), and optional external systems (e.g., MongoDB plugin). - -```mermaid -graph TB -subgraph "vizd Process" -MAIN["programs/vizd/main.cpp
Initialize plugins, logging, exec loop"] -APPBASE["AppBase Framework
(external thirdparty)"] -end -subgraph "Core Libraries" -L_CHAIN["libraries/chain
database.hpp"] -L_PROTOCOL["libraries/protocol
operations.hpp"] -L_NETWORK["libraries/network
node.hpp"] -L_API["libraries/api
chain_api_properties.hpp"] -L_WALLET["libraries/wallet
wallet.hpp"] -end -subgraph "Plugins" -P_CHAIN["plugins/chain
plugin.cpp"] -P_JSONRPC["plugins/json_rpc
plugin.cpp"] -P_DB_API["plugins/database_api"] -P_P2P["plugins/p2p"] -P_WBS["plugins/webserver"] -P_WITNESS["plugins/validator"] -P_TAGS["plugins/tags"] -P_ACCOUNT_HISTORY["plugins/account_history"] -P_ACCOUNT_BY_KEY["plugins/account_by_key"] -P_PRIVATE_MESSAGE["plugins/private_message"] -P_AUTH_UTIL["plugins/auth_util"] -P_DEBUG["plugins/debug_node"] -P_RAW_BLOCK["plugins/raw_block"] -P_BLOCK_INFO["plugins/block_info"] -P_FOLLOW["plugins/follow"] -P_COMMITTEE_API["plugins/committee_api"] -P_INVITE_API["plugins/invite_api"] -P_PAID_SUBSCRIPTION_API["plugins/paid_subscription_api"] -P_CUSTOM_PROTOCOL_API["plugins/custom_protocol_api"] -end -MAIN --> APPBASE -MAIN --> P_CHAIN -MAIN --> P_JSONRPC -MAIN --> P_DB_API -MAIN --> P_P2P -MAIN --> P_WBS -MAIN --> P_WITNESS -MAIN --> P_TAGS -MAIN --> P_ACCOUNT_HISTORY -MAIN --> P_ACCOUNT_BY_KEY -MAIN --> P_PRIVATE_MESSAGE -MAIN --> P_AUTH_UTIL -MAIN --> P_DEBUG -MAIN --> P_RAW_BLOCK -MAIN --> P_BLOCK_INFO -MAIN --> P_FOLLOW -MAIN --> P_COMMITTEE_API -MAIN --> P_INVITE_API -MAIN --> P_PAID_SUBSCRIPTION_API -MAIN --> P_CUSTOM_PROTOCOL_API -P_CHAIN --> L_CHAIN -P_JSONRPC --> L_API -P_DB_API --> L_API -P_P2P --> L_NETWORK -P_WBS --> L_API -P_WITNESS --> L_CHAIN -P_TAGS --> L_CHAIN -P_ACCOUNT_HISTORY --> L_CHAIN -P_ACCOUNT_BY_KEY --> L_CHAIN -P_PRIVATE_MESSAGE --> L_CHAIN -P_AUTH_UTIL --> L_CHAIN -P_DEBUG --> L_CHAIN -P_RAW_BLOCK --> L_CHAIN -P_BLOCK_INFO --> L_CHAIN -P_FOLLOW --> L_CHAIN -P_COMMITTEE_API --> L_CHAIN -P_INVITE_API --> L_CHAIN -P_PAID_SUBSCRIPTION_API --> L_CHAIN -P_CUSTOM_PROTOCOL_API --> L_PROTOCOL -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L61-L91) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L50) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L200) -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/api/include/graphene/api/chain_api_properties.hpp](file://libraries/api/include/graphene/api/chain_api_properties.hpp#L11-L44) -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L127) -- [plugins/chain/plugin.cpp](file://plugins/chain/plugin.cpp#L169-L182) -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L151-L200) - -## Detailed Component Analysis - -### vizd Process and Plugin Registration -- The main entry point registers and initializes core plugins, sets logging configuration, and starts the event loop. -- Plugins are conditionally compiled and registered, including chain, p2p, webserver, database_api, and many others. - -```mermaid -sequenceDiagram -participant Main as "vizd main.cpp" -participant App as "AppBase Application" -participant Chain as "Chain Plugin" -participant Net as "P2P Plugin" -participant WS as "Webserver Plugin" -Main->>App : register_plugin(chain) -Main->>App : register_plugin(p2p) -Main->>App : register_plugin(webserver) -Main->>App : initialize(chain,p2p,webserver) -App-->>Main : initialized -Main->>App : startup() -App->>Chain : start() -App->>Net : start() -App->>WS : start() -Main->>App : exec() (event loop) -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L61-L91) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L117-L122) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L139-L142) - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) - -### Chain Library and Observer Pattern -- The chain database extends chainbase and emits signals for events (e.g., block accepted, transaction applied). -- Plugins subscribe to these signals to implement features like history tracking, indexing, and notifications. - -```mermaid -classDiagram -class Database { -+open(data_dir, shared_mem_dir, ...) -+push_block(...) -+push_transaction(...) -+reindex(...) -+wipe(...) -} -class ChainPlugin { -+db() Database -+accept_block(...) -+accept_transaction(...) -} -Database <.. ChainPlugin : "used by" -``` - -**Diagram sources** -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L50) -- [plugins/chain/plugin.cpp](file://plugins/chain/plugin.cpp#L169-L182) - -**Section sources** -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L10-L12) -- [plugins/chain/plugin.cpp](file://plugins/chain/plugin.cpp#L96-L167) - -### Network Library and Peer Communication -- The network node defines a delegate interface for block/transaction handling and synchronization. -- It coordinates peer connections, message propagation, and blockchain sync status callbacks. - -```mermaid -classDiagram -class NodeDelegate { -<> -+has_item(id) -+handle_block(blk_msg, sync_mode, contained_trx_ids) -+handle_transaction(trx_msg) -+handle_message(msg) -+get_block_ids(synopsis, remaining, limit) -+get_item(id) -+get_blockchain_synopsis(ref, n) -+sync_status(item_type, item_count) -+connection_count_changed(count) -+get_block_number(hash) -+get_block_time(hash) -+get_blockchain_now() -+get_head_block_id() -+estimate_last_known_fork(ts) -+error_encountered(msg, err) -} -class Node { -+set_node_delegate(NodeDelegate*) -+load_configuration(path) -+close() -} -Node ..|> NodeDelegate : "implements" -``` - -**Diagram sources** -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L60-L167) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L200) - -**Section sources** -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L200) - -### Protocol Layer and Operations -- The protocol layer defines the operation type union and related chain operations, virtual operations, and chain-specific types. -- This forms the basis for validation, signing, and evaluation in the chain library. - -```mermaid -classDiagram -class Operation { -<> -} -class Operations { -+transfer_operation -+account_update_operation -+proposal_create_operation -+...many others... -} -Operation <|-- Operations : "contains" -``` - -**Diagram sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -**Section sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) - -### API Layer and JSON-RPC -- The JSON-RPC plugin exposes a method registry and dispatch mechanism to route RPC calls to plugin APIs. -- It constructs responses and handles errors consistently. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant JSONRPC as "JSON-RPC Plugin" -participant Registry as "Registered APIs" -participant Chain as "Chain Database" -Client->>JSONRPC : {"method" : "call","params" : [api,method,args]} -JSONRPC->>Registry : find_api_method(api, method) -Registry-->>JSONRPC : api_method -JSONRPC->>Registry : invoke(api_method, args) -Registry-->>JSONRPC : result or error -JSONRPC-->>Client : {"result" : ..., "id" : ...} -``` - -**Diagram sources** -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L151-L200) -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L200) - -**Section sources** -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L151-L200) - -### Wallet Integration -- The wallet library integrates with remote node APIs and provides helpers for transaction building and signing. -- It relies on protocol types and chain constants for compatibility. - -**Section sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L127) - -## Dependency Analysis -- Build-time: CMake aggregates libraries and plugins; thirdparty components are referenced externally. -- Runtime: vizd depends on appbase; plugins depend on libraries; chain database underpins most plugins. - -```mermaid -graph LR -MAIN["vizd main.cpp"] --> APPBASE["AppBase"] -MAIN --> PCHAIN["plugins/chain"] -MAIN --> PJRPC["plugins/json_rpc"] -PCHAIN --> LCHAIN["libraries/chain"] -PJRPC --> LAPIS["libraries/api"] -PCHAIN -.-> LPROTOCOL["libraries/protocol"] -PCHAIN -.-> LNETWORK["libraries/network"] -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L61-L91) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -## Performance Considerations -- C++ chosen for performance-critical components (validation, networking, persistence). -- Shared memory database (chainbase) optimized for fast reads/writes. -- Optional MongoDB plugin for analytics/offloading; controlled via build flags. -- Logging configuration supports JSON/console/file appenders for observability. -- Compiler flags tuned per platform; optional ccache support. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Logging configuration: The main process parses logging sections from config and applies fc::configure_logging. -- Exception handling: JSON-RPC plugin wraps transport exceptions and returns structured errors. -- Chain operations: Chain plugin validates blocks/transactions and can replay or wipe the database when corrupted. - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L211-L288) -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L96-L136) -- [plugins/chain/plugin.cpp](file://plugins/chain/plugin.cpp#L134-L146) - -## Conclusion -The VIZ C++ Node employs a modular, plugin-based architecture built atop robust core libraries. The vizd process orchestrates initialization and runtime, while plugins extend functionality safely. The chain database leverages an observer pattern for event-driven features, and JSON-RPC provides a clean API boundary. System boundaries separate peer communication, client APIs, and persistent state, enabling maintainability, scalability, and targeted optimizations. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### System Context Diagram -```mermaid -graph TB -subgraph "External" -PEERS["Peers"] -CLIENTS["Wallets/Applications"] -end -subgraph "Node" -VIZD["vizd process"] -subgraph "Plugins" -P_CHAIN["chain"] -P_P2P["p2p"] -P_JSONRPC["json_rpc"] -P_DB_API["database_api"] -P_WBS["webserver"] -P_WITNESS["validator"] -P_TAGS["tags"] -P_ACCOUNT_HISTORY["account_history"] -P_ACCOUNT_BY_KEY["account_by_key"] -P_PRIVATE_MESSAGE["private_message"] -P_AUTH_UTIL["auth_util"] -P_DEBUG["debug_node"] -P_RAW_BLOCK["raw_block"] -P_BLOCK_INFO["block_info"] -P_FOLLOW["follow"] -P_COMMITTEE_API["committee_api"] -P_INVITE_API["invite_api"] -P_PAID_SUBSCRIPTION_API["paid_subscription_api"] -P_CUSTOM_PROTOCOL_API["custom_protocol_api"] -end -subgraph "Libraries" -L_CHAIN["chain"] -L_PROTOCOL["protocol"] -L_NETWORK["network"] -L_API["api"] -L_WALLET["wallet"] -end -end -PEERS <- --> P_P2P -CLIENTS <- --> P_JSONRPC -CLIENTS <- --> P_DB_API -CLIENTS <- --> P_WBS -VIZD --> P_CHAIN -VIZD --> P_P2P -VIZD --> P_JSONRPC -VIZD --> P_DB_API -VIZD --> P_WBS -VIZD --> P_WITNESS -VIZD --> P_TAGS -VIZD --> P_ACCOUNT_HISTORY -VIZD --> P_ACCOUNT_BY_KEY -VIZD --> P_PRIVATE_MESSAGE -VIZD --> P_AUTH_UTIL -VIZD --> P_DEBUG -VIZD --> P_RAW_BLOCK -VIZD --> P_BLOCK_INFO -VIZD --> P_FOLLOW -VIZD --> P_COMMITTEE_API -VIZD --> P_INVITE_API -VIZD --> P_PAID_SUBSCRIPTION_API -VIZD --> P_CUSTOM_PROTOCOL_API -P_CHAIN --> L_CHAIN -P_JSONRPC --> L_API -P_DB_API --> L_API -P_P2P --> L_NETWORK -P_WBS --> L_API -P_WITNESS --> L_CHAIN -P_TAGS --> L_CHAIN -P_ACCOUNT_HISTORY --> L_CHAIN -P_ACCOUNT_BY_KEY --> L_CHAIN -P_PRIVATE_MESSAGE --> L_CHAIN -P_AUTH_UTIL --> L_CHAIN -P_DEBUG --> L_CHAIN -P_RAW_BLOCK --> L_CHAIN -P_BLOCK_INFO --> L_CHAIN -P_FOLLOW --> L_CHAIN -P_COMMITTEE_API --> L_CHAIN -P_INVITE_API --> L_CHAIN -P_PAID_SUBSCRIPTION_API --> L_CHAIN -P_CUSTOM_PROTOCOL_API --> L_PROTOCOL -``` - -**Diagram sources** -- [README.md](file://README.md#L1-L53) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L61-L91) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L50) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L200) -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/api/include/graphene/api/chain_api_properties.hpp](file://libraries/api/include/graphene/api/chain_api_properties.hpp#L11-L44) -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L127) -- [plugins/chain/plugin.cpp](file://plugins/chain/plugin.cpp#L169-L182) -- [plugins/json_rpc/plugin.cpp](file://plugins/json_rpc/plugin.cpp#L151-L200) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Block Log Reader Module.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Block Log Reader Module.md deleted file mode 100644 index 2f0b970ed9..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Block Log Reader Module.md +++ /dev/null @@ -1,472 +0,0 @@ -# Block Log Reader Module - - -**Referenced Files in This Document** -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -The Block Log Reader Module is a critical component of the VIZ CPP Node that provides efficient access to blockchain data stored in append-only block log files. This module serves two primary purposes: traditional block log storage for full nodes and DLT (Snapshot-based) block log storage for lightweight nodes. The module implements memory-mapped file access patterns to achieve high-performance block retrieval while maintaining data integrity through robust validation mechanisms. - -The module consists of two main classes: `block_log` for standard blockchain nodes and `dlt_block_log` for snapshot-based nodes. Both classes provide thread-safe access to block data through memory-mapped files, enabling fast random access to blocks by number and sequential traversal capabilities. - -## Project Structure -The Block Log Reader Module is organized within the VIZ blockchain implementation under the chain library. The structure follows a clear separation of concerns with interface definitions in header files and implementation details in source files. - -```mermaid -graph TB -subgraph "Block Log Module Structure" -A[block_log.hpp] --> B[block_log.cpp] -C[dlt_block_log.hpp] --> D[dlt_block_log.cpp] -E[database.hpp] --> F[database.cpp] -G[test_block_log.cpp] -H[plugin.cpp] -end -subgraph "File Types" -I[.block_log files] -J[.block_log.index files] -K[.dlt_block_log files] -L[.dlt_block_log.index files] -end -B --> I -B --> J -D --> K -D --> L -``` - -**Diagram sources** -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [block_log.cpp:1-302](file://libraries/chain/block_log.cpp#L1-L302) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) -- [dlt_block_log.cpp:1-414](file://libraries/chain/dlt_block_log.cpp#L1-L414) - -**Section sources** -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) - -## Core Components -The Block Log Reader Module comprises two primary components that work together to provide comprehensive block storage and retrieval capabilities: - -### Standard Block Log (`block_log`) -The standard block log provides traditional blockchain storage with a complete append-only log of all blocks. It supports: -- Memory-mapped file access for high performance -- Random access by block number through index files -- Sequential traversal capabilities -- Automatic index reconstruction when corrupted -- Thread-safe operations with reader-writer locks - -### DLT Block Log (`dlt_block_log`) -The DLT block log is designed specifically for snapshot-based nodes that maintain only a rolling window of recent blocks. It provides: -- Offset-aware indexing for arbitrary starting block numbers -- Rolling window capability with configurable block limits -- Efficient truncation operations to manage storage -- Specialized handling for snapshot-loaded states -- Optimized for lightweight node operations - -**Section sources** -- [block_log.hpp:38-71](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) - -## Architecture Overview -The Block Log Reader Module integrates deeply with the VIZ blockchain database system, providing essential block storage and retrieval capabilities. The architecture follows a layered approach with clear separation between storage, access, and application layers. - -```mermaid -graph TB -subgraph "Application Layer" -A[Database Operations] -B[Plugin Services] -C[Debug Tools] -end -subgraph "Block Log Layer" -D[Block Log Manager] -E[DLT Block Log Manager] -F[Memory Mapped Files] -end -subgraph "Storage Layer" -G[Main Block Files] -H[Index Files] -I[DLT Block Files] -J[DLT Index Files] -end -subgraph "Validation Layer" -K[File Integrity Checks] -L[Index Reconstruction] -M[Thread Safety] -end -A --> D -A --> E -B --> D -C --> D -D --> F -E --> F -F --> G -F --> H -F --> I -F --> J -D --> K -E --> K -D --> L -E --> L -D --> M -E --> M -``` - -**Diagram sources** -- [database.cpp:229-231](file://libraries/chain/database.cpp#L229-L231) -- [database.cpp:569-580](file://libraries/chain/database.cpp#L569-L580) -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [dlt_block_log.cpp:161-209](file://libraries/chain/dlt_block_log.cpp#L161-L209) - -The architecture ensures that block data is consistently available across different operational modes, supporting both full nodes with complete block histories and lightweight nodes with rolling block windows. - -**Section sources** -- [database.hpp:567-568](file://libraries/chain/include/graphene/chain/database.hpp#L567-L568) -- [database.cpp:229-231](file://libraries/chain/database.cpp#L229-L231) - -## Detailed Component Analysis - -### Block Log Implementation Analysis -The block_log class implements a sophisticated memory-mapped file system for efficient block storage and retrieval. The implementation uses a doubly-linked list structure where each block is followed by its position in the file, enabling both forward and backward traversal. - -```mermaid -classDiagram -class BlockLog { --unique_ptr~block_log_impl~ my -+block_log() -+~block_log() -+open(fc : : path) -+close() -+is_open() bool -+append(signed_block) uint64_t -+flush() -+read_block(uint64_t) pair~signed_block,uint64_t~ -+read_block_by_num(uint32_t) optional~signed_block~ -+get_block_pos(uint32_t) uint64_t -+read_head() signed_block -+head() optional~signed_block~ -+static npos : uint64_t -} -class BlockLogImpl { --optional~signed_block~ head --block_id_type head_id --string block_path --string index_path --mapped_file block_mapped_file --mapped_file index_mapped_file --read_write_mutex mutex -+open(fc : : path) -+append(signed_block, vector~char~) uint64_t -+read_block(uint64_t, signed_block) uint64_t -+construct_index() -+get_block_pos(uint32_t) uint64_t -+read_head() signed_block -} -BlockLog --> BlockLogImpl : "owns" -``` - -**Diagram sources** -- [block_log.hpp:38-71](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [block_log.cpp:15-227](file://libraries/chain/block_log.cpp#L15-L227) - -The implementation employs several key design patterns: - -#### Memory-Mapped File Access -The module uses Boost's memory-mapped file functionality to achieve zero-copy block access. This approach eliminates the overhead of traditional file I/O operations and enables direct memory access to block data. - -#### Index-Based Random Access -The block log maintains a separate index file containing file positions for each block. This allows O(1) random access by block number through direct index calculation rather than sequential scanning. - -#### Automatic Recovery Mechanisms -The implementation includes comprehensive recovery mechanisms that automatically detect and repair inconsistencies between block files and index files during initialization. - -**Section sources** -- [block_log.cpp:105-113](file://libraries/chain/block_log.cpp#L105-L113) -- [block_log.cpp:115-132](file://libraries/chain/block_log.cpp#L115-L132) -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) - -### DLT Block Log Implementation Analysis -The dlt_block_log class extends the standard block log concept to support snapshot-based nodes with rolling window capabilities. Unlike the standard block log, it maintains an offset-aware index that can start from arbitrary block numbers. - -```mermaid -classDiagram -class DLTBlockLog { --unique_ptr~dlt_block_log_impl~ my -+dlt_block_log() -+~dlt_block_log() -+open(fc : : path) -+close() -+is_open() bool -+append(signed_block) uint64_t -+flush() -+read_block_by_num(uint32_t) optional~signed_block~ -+head() optional~signed_block~ -+start_block_num() uint32_t -+head_block_num() uint32_t -+num_blocks() uint32_t -+truncate_before(uint32_t) -+static npos : uint64_t -} -class DLTBlockLogImpl { --optional~signed_block~ head --block_id_type head_id --uint32_t _start_block_num --string block_path --string index_path --mapped_file block_mapped_file --mapped_file index_mapped_file --read_write_mutex mutex -+open(fc : : path) -+append(signed_block, vector~char~) uint64_t -+read_block(uint64_t, signed_block) uint64_t -+construct_index() -+get_block_pos(uint32_t) uint64_t -+read_head() signed_block -+truncate_before(uint32_t) -} -DLTBlockLog --> DLTBlockLogImpl : "owns" -``` - -**Diagram sources** -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) -- [dlt_block_log.cpp:18-277](file://libraries/chain/dlt_block_log.cpp#L18-L277) - -The DLT implementation introduces several specialized features: - -#### Offset-Aware Indexing -The index header stores the starting block number, enabling the calculation of index positions for any block within the rolling window. This design allows the log to maintain gaps at the beginning when blocks are truncated. - -#### Rolling Window Management -The module supports dynamic truncation of old blocks while maintaining the rolling window constraint. This is essential for lightweight nodes that need to limit storage consumption. - -#### Snapshot Mode Compatibility -The DLT block log is specifically designed to work with snapshot-based node operations where the main blockchain state is loaded from a snapshot rather than replaying the entire block history. - -**Section sources** -- [dlt_block_log.cpp:161-209](file://libraries/chain/dlt_block_log.cpp#L161-L209) -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) - -### Database Integration Analysis -The Block Log Reader Module integrates seamlessly with the VIZ database system, providing transparent access to block data regardless of the operational mode. - -```mermaid -sequenceDiagram -participant DB as Database -participant BL as BlockLog -participant DLT as DLTBlockLog -participant FS as File System -DB->>BL : open(data_dir/"block_log") -DB->>DLT : open(data_dir/"dlt_block_log") -DB->>FS : initialize file mappings -Note over DB : During normal operation -DB->>BL : read_block_by_num(block_num) -BL->>FS : read block data -FS-->>BL : block data -BL-->>DB : signed_block -Note over DB : During DLT mode -DB->>DLT : read_block_by_num(block_num) -DLT->>FS : read block data -FS-->>DLT : block data -DLT-->>DB : signed_block -Note over DB : Fallback mechanism -DB->>BL : read_block_by_num(block_num) -alt block not found -DB->>DLT : read_block_by_num(block_num) -DLT-->>DB : signed_block or none -end -``` - -**Diagram sources** -- [database.cpp:229-231](file://libraries/chain/database.cpp#L229-L231) -- [database.cpp:569-580](file://libraries/chain/database.cpp#L569-L580) -- [database.cpp:623-640](file://libraries/chain/database.cpp#L623-L640) - -The integration provides several key benefits: - -#### Transparent Fallback Mechanism -The database automatically attempts to retrieve blocks from the standard block log first, falling back to the DLT block log if the standard log doesn't contain the requested block. This design ensures compatibility across different node configurations. - -#### Mode-Specific Behavior -The module adapts its behavior based on whether the node is operating in normal mode or DLT mode, optimizing performance and resource usage accordingly. - -#### Consistent API Interface -Both block log implementations expose identical APIs, allowing the database to treat them uniformly regardless of the underlying storage mechanism. - -**Section sources** -- [database.cpp:229-231](file://libraries/chain/database.cpp#L229-L231) -- [database.cpp:569-580](file://libraries/chain/database.cpp#L569-L580) -- [database.cpp:623-640](file://libraries/chain/database.cpp#L623-L640) - -## Dependency Analysis -The Block Log Reader Module has well-defined dependencies that contribute to its modularity and maintainability. Understanding these dependencies is crucial for effective integration and troubleshooting. - -```mermaid -graph TB -subgraph "External Dependencies" -A[Boost.Iostreams] -B[Boost.Filesystem] -C[Boost.Thread] -D[FC Library] -E[Protocol Library] -end -subgraph "Internal Dependencies" -F[Chain Library] -G[Database Core] -H[Memory Management] -end -subgraph "Block Log Module" -I[block_log] -J[dlt_block_log] -end -I --> A -I --> B -I --> C -I --> D -I --> E -I --> F -I --> G -I --> H -J --> A -J --> B -J --> C -J --> D -J --> E -J --> F -J --> G -J --> H -``` - -**Diagram sources** -- [block_log.cpp:1-6](file://libraries/chain/block_log.cpp#L1-L6) -- [dlt_block_log.cpp:1-6](file://libraries/chain/dlt_block_log.cpp#L1-L6) - -The dependency structure reveals several important characteristics: - -### External Library Dependencies -The module relies on several Boost libraries for core functionality: -- **Boost.Iostreams**: Provides memory-mapped file capabilities essential for high-performance block access -- **Boost.Filesystem**: Handles file system operations including path manipulation and file existence checks -- **Boost.Thread**: Enables thread-safe operations through shared mutexes and RAII-based locking - -### Internal Library Dependencies -The module integrates with VIZ's internal libraries: -- **FC Library**: Provides fundamental data structures and utilities used throughout the blockchain implementation -- **Protocol Library**: Defines the block structure and serialization formats used for block storage and retrieval - -### Design Benefits -The dependency structure supports several design goals: -- **Modularity**: Clear separation between external and internal dependencies enables easier maintenance -- **Portability**: Minimal external dependencies reduce portability challenges -- **Performance**: Direct integration with VIZ's optimized data structures maximizes efficiency - -**Section sources** -- [block_log.cpp:1-6](file://libraries/chain/block_log.cpp#L1-L6) -- [dlt_block_log.cpp:1-6](file://libraries/chain/dlt_block_log.cpp#L1-L6) - -## Performance Considerations -The Block Log Reader Module is designed with performance as a primary concern, implementing several optimization strategies to minimize latency and maximize throughput. - -### Memory-Mapped File Performance -The use of memory-mapped files eliminates the overhead of traditional file I/O operations by allowing direct memory access to block data. This approach provides several performance benefits: - -- **Zero-Copy Access**: Blocks can be accessed directly from memory without additional copying operations -- **Predictable Latency**: Memory-mapped access provides consistent performance characteristics regardless of file size -- **Reduced System Calls**: Eliminates the overhead of frequent system calls associated with traditional file I/O - -### Index-Based Random Access -The dual-file architecture with separate data and index files enables O(1) random access to blocks by number. The index file contains pre-calculated positions for each block, eliminating the need for sequential scanning. - -### Thread-Safe Operations -The implementation uses reader-writer locks to balance concurrent access patterns: -- **Read Operations**: Multiple readers can access the block log simultaneously without blocking -- **Write Operations**: Exclusive access ensures data consistency during block appends -- **Lock Granularity**: Fine-grained locking minimizes contention in high-concurrency scenarios - -### Storage Optimization Strategies -Several strategies are employed to optimize storage usage and access patterns: - -#### Block Size Limits -The implementation enforces maximum block size limits to prevent memory allocation issues and ensure predictable performance characteristics. - -#### File Resizing Operations -Blocks are appended using efficient file resizing operations that minimize fragmentation and maintain optimal file system performance. - -#### Index Reconstruction Efficiency -When index corruption is detected, the reconstruction process is optimized to minimize downtime and ensure data integrity. - -**Section sources** -- [block_log.cpp:73-88](file://libraries/chain/block_log.cpp#L73-L88) -- [dlt_block_log.cpp:83-98](file://libraries/chain/dlt_block_log.cpp#L83-L98) - -## Troubleshooting Guide -The Block Log Reader Module includes comprehensive error handling and diagnostic capabilities to facilitate troubleshooting and maintenance operations. - -### Common Issues and Solutions - -#### Block Log Corruption Detection -The module implements automatic detection of block log corruption through multiple validation mechanisms: - -**File Size Validation**: Ensures minimum file sizes are maintained to prevent access violations -**Position Verification**: Validates block positions using embedded position markers -**Index Consistency Checks**: Verifies that index entries correspond to actual block data - -#### Recovery Procedures -When corruption is detected, the module follows systematic recovery procedures: - -**Index Reconstruction**: Automatically rebuilds corrupted index files by scanning the main data file -**File Reinitialization**: Recreates missing or damaged files with appropriate initialization -**State Validation**: Verifies recovered state against expected block log characteristics - -#### Performance Monitoring -The module provides several mechanisms for monitoring and diagnosing performance issues: - -**Operation Timing**: Logs timing information for critical operations to identify performance bottlenecks -**Memory Usage Tracking**: Monitors memory-mapped file usage to prevent excessive memory consumption -**File System Health**: Continuously monitors file system health and reports potential issues - -### Diagnostic Tools and Utilities -Several tools and utilities are available for troubleshooting block log issues: - -#### Test Harness -The module includes a comprehensive test harness that demonstrates proper usage and validates functionality across different scenarios. - -#### Debug Plugin Integration -The debug plugin provides interactive access to block log functionality, enabling developers to inspect block data and diagnose issues in real-time. - -#### Logging and Monitoring -Extensive logging is implemented throughout the module to provide detailed information about operations, errors, and performance characteristics. - -**Section sources** -- [block_log.cpp:115-132](file://libraries/chain/block_log.cpp#L115-L132) -- [dlt_block_log.cpp:125-159](file://libraries/chain/dlt_block_log.cpp#L125-L159) -- [test_block_log.cpp:1-54](file://programs/util/test_block_log.cpp#L1-L54) - -## Conclusion -The Block Log Reader Module represents a sophisticated implementation of blockchain data storage and retrieval systems. Its dual-mode architecture supports both traditional full nodes and modern snapshot-based lightweight nodes, providing flexibility while maintaining high performance standards. - -The module's design emphasizes several key principles: -- **Performance**: Memory-mapped files and index-based access enable fast block retrieval -- **Reliability**: Comprehensive error handling and automatic recovery mechanisms ensure data integrity -- **Flexibility**: Support for multiple operational modes accommodates diverse deployment scenarios -- **Maintainability**: Clean separation of concerns and modular design facilitate ongoing development and maintenance - -The implementation demonstrates best practices in systems programming, combining low-level file system operations with high-level abstractions to create a robust and efficient block storage solution. The module serves as a foundation for the broader VIZ blockchain infrastructure, enabling reliable and scalable blockchain operations across various node configurations. - -Future enhancements could focus on additional performance optimizations, expanded monitoring capabilities, and further simplification of the dual-mode architecture to reduce complexity while maintaining compatibility with existing deployments. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Block Processing and Validation.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Block Processing and Validation.md deleted file mode 100644 index 47c662e950..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Block Processing and Validation.md +++ /dev/null @@ -1,547 +0,0 @@ -# Block Processing and Validation - - -**Referenced Files in This Document** -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [node.cpp](file://libraries/network/node.cpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) - - -## Update Summary -**Changes Made** -- Enhanced logging system documentation for sync blocks with info level logging and console color formatting -- Updated sync block and normal block push logs from debug to info level for better production visibility -- Added documentation for console color formatting in block processing logs -- Improved logging visibility for production environments - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Logging System Enhancements](#logging-system-enhancements) -7. [Dependency Analysis](#dependency-analysis) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction -This document explains the Block Processing and Validation system responsible for accepting incoming blocks, validating their integrity and consensus compliance, applying state changes, and maintaining blockchain consistency. It focuses on: -- Efficient block storage and retrieval via the block log -- The block validation pipeline (header, size, merkle, validator scheduling) -- The push_block() and validate_block() orchestration -- Block summary object creation and validator participation tracking -- Block replay for synchronization and state reconstruction -- Integration with fork database, validator scheduling, and state persistence -- Block size limits, transaction ordering, and consensus enforcement -- Enhanced logging system with production-ready visibility - -## Project Structure -The block processing pipeline spans several core modules: -- Chain database: orchestrates validation, fork selection, state application, and persistence -- Fork database: manages canonical chain and forks for reorganization decisions -- Block log: persistent append-only storage enabling fast replay and random access -- Block summary and validator schedule: support consensus checks and participation metrics -- Network layer: handles block propagation and synchronization with enhanced logging - -```mermaid -graph TB -subgraph "Chain Layer" -DB["database.hpp/.cpp"] -FD["fork_database.hpp/.cpp"] -BL["block_log.hpp/.cpp"] -BS["block_summary_object.hpp"] -end -subgraph "Network Layer" -NET["node.cpp"] -P2P["p2p_plugin.cpp"] -CHAIN["chain plugin.cpp"] -end -subgraph "Protocol" -PB["signed_block (protocol)"] -TX["signed_transaction (protocol)"] -end -DB --> FD -DB --> BL -DB --> BS -DB --> PB -DB --> TX -NET --> DB -P2P --> NET -CHAIN --> DB -``` - -**Diagram sources** -- [database.hpp:36-200](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) -- [fork_database.hpp:53-122](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [block_log.hpp:38-71](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [block_summary_object.hpp:19-42](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L42) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) - -**Section sources** -- [database.hpp:36-200](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) -- [fork_database.hpp:53-122](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [block_log.hpp:38-71](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [block_summary_object.hpp:19-42](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L42) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) - -## Core Components -- Block log: Append-only, memory-mapped storage with an auxiliary index for O(1) random access by block number. Supports reading head, reading by position, and reconstructing the index if inconsistent. -- Fork database: Maintains a linked tree of candidate blocks, supports pushing blocks, fetching branches, and selecting the heaviest chain head. -- Database: Implements validate_block(), push_block(), and apply_block() to enforce consensus rules, apply state changes, and persist blocks. -- Network layer: Handles block propagation, synchronization, and enhanced logging with console color formatting. - -Key responsibilities: -- validate_block(): Validates Merkle root, block size, and optionally validator signature and schedule alignment -- push_block(): Coordinates fork selection, reorganization, and state application -- apply_block(): Applies all transactions and operations, updates dynamic properties, and creates block summaries -- block_log: Provides deterministic replay and persistence -- Enhanced logging: Provides production-ready visibility with info level logging and console color formatting - -**Section sources** -- [block_log.hpp:38-71](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [fork_database.hpp:53-122](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [database.hpp:193-196](file://libraries/chain/include/graphene/chain/database.hpp#L193-L196) -- [database.cpp:737-792](file://libraries/chain/database.cpp#L737-L792) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) - -## Architecture Overview -The block processing flow integrates validation, fork management, state application, persistence, and enhanced logging with console color formatting. - -```mermaid -sequenceDiagram -participant Net as "Network/P2P" -participant P2P as "p2p_plugin.cpp" -participant Chain as "chain plugin.cpp" -participant DB as "database.cpp" -participant FD as "fork_database.cpp" -participant BL as "block_log.cpp" -Net->>P2P : "handle_block(sync_mode, block)" -P2P->>P2P : "ilog(CLOG_WHITE ... sync/normal block)" -P2P->>Chain : "accept_block(block, sync_mode)" -Chain->>Chain : "ilog(sync_start/end messages)" -Chain->>DB : "validate_block(skip)" -DB->>DB : "_validate_block(...)" -Chain->>DB : "push_block(block, skip)" -DB->>FD : "push_block(new_block)" -alt "Fork switch required" -DB->>DB : "pop_block() until fork split" -DB->>DB : "apply_block() for each fork branch" -end -DB->>DB : "apply_block(new_block)" -DB->>BL : "append(signed_block)" -P2P->>Net : "ilog(successful/not applied)" -Net-->>Net : "result (fork_switched?)" -``` - -**Diagram sources** -- [p2p_plugin.cpp:142-174](file://plugins/p2p/p2p_plugin.cpp#L142-L174) -- [plugin.cpp:103-142](file://plugins/chain/plugin.cpp#L103-L142) -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [block_log.cpp:253-257](file://libraries/chain/block_log.cpp#L253-L257) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) - -## Detailed Component Analysis - -### Block Log: Storage, Retrieval, and Replay -The block log provides: -- Append-only persistence of signed blocks -- Random access by block number via an index file -- Head traversal and index reconstruction -- Memory-mapped IO for performance - -Implementation highlights: -- Open/close lifecycle with index consistency checks -- Index reconstruction if mismatched with head position -- Safe read with bounds and endianness-aware position reads -- Append with packed serialization and position linking - -```mermaid -flowchart TD -Start(["Open block_log"]) --> CheckFiles["Check block/index existence"] -CheckFiles --> |Both present| CompareHeads["Compare last positions"] -CompareHeads --> |Mismatch| RebuildIndex["Reconstruct index"] -CompareHeads --> |Match| Ready["Ready"] -CheckFiles --> |Index missing| RebuildIndex -CheckFiles --> |Block empty| CleanIndex["Remove and recreate index"] -RebuildIndex --> Ready -Ready --> Append["append(signed_block)"] -Append --> Flush["flush()"] -Ready --> ReadByNum["read_block_by_num(n)"] -ReadByNum --> ReadPos["get_block_pos(n)"] -ReadPos --> ReadBlock["read_block(pos)"] -ReadBlock --> Head["read_head()/head()"] -``` - -**Diagram sources** -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [block_log.cpp:195-226](file://libraries/chain/block_log.cpp#L195-L226) -- [block_log.cpp:263-299](file://libraries/chain/block_log.cpp#L263-L299) - -**Section sources** -- [block_log.hpp:38-71](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [block_log.cpp:195-226](file://libraries/chain/block_log.cpp#L195-L226) -- [block_log.cpp:263-299](file://libraries/chain/block_log.cpp#L263-L299) - -### Fork Database: Canonical Chain and Reorganization -The fork database maintains: -- Linked tree of blocks with previous-id linkage -- Heaviest chain head selection -- Fetching branches from divergent heads -- Unlinkable block caching and DFS insertion - -Key behaviors: -- push_block() inserts and links; marks invalid blocks to prevent chaining -- fetch_branch_from() returns branches to a common ancestor -- walk_main_branch_to_num() and fetch_block_on_main_branch_by_number() resolve main-chain blocks by number - -```mermaid -classDiagram -class fork_database { -+push_block(signed_block) shared_ptr -+set_head(item_ptr) void -+head() shared_ptr -+is_known_block(id) bool -+fetch_block(id) item_ptr -+fetch_branch_from(first, second) pair -+walk_main_branch_to_num(n) item_ptr -+set_max_size(s) void -} -class fork_item { -+id : block_id_type -+num : uint32_t -+invalid : bool -+data : signed_block -+prev : weak_ptr -+previous_id() block_id_type -} -fork_database --> fork_item : "manages" -``` - -**Diagram sources** -- [fork_database.hpp:53-122](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) - -**Section sources** -- [fork_database.hpp:53-122](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) - -### Database: Validation Pipeline and Block Application -The database coordinates validation and application: -- validate_block(): Enforces Merkle root, block size, and optionally validator signature and schedule alignment -- push_block(): Orchestrates fork selection and reorganization; calls apply_block() on success -- apply_block(): Applies all transactions and operations, updates dynamic properties, and creates block summaries - -```mermaid -sequenceDiagram -participant Caller as "Caller" -participant DB as "database.cpp" -participant FD as "fork_database.cpp" -participant BL as "block_log.cpp" -Caller->>DB : "validate_block(new_block, skip)" -DB->>DB : "_validate_block(...)" -DB-->>Caller : "skip flags updated" -Caller->>DB : "push_block(new_block, skip)" -DB->>FD : "push_block(new_block)" -alt "new_head differs from head" -DB->>DB : "fetch_branch_from(new_head, head)" -DB->>DB : "pop_block() until split" -DB->>DB : "apply_block() for each branch" -end -DB->>DB : "apply_block(new_block)" -DB->>BL : "append(new_block)" -DB-->>Caller : "fork_switched?" -``` - -**Diagram sources** -- [database.cpp:737-792](file://libraries/chain/database.cpp#L737-L792) -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [block_log.cpp:253-257](file://libraries/chain/block_log.cpp#L253-L257) - -Validation steps: -- Merkle root verification against transaction set -- Block size enforcement against dynamic maximum -- Optional validator signature validation -- Optional validator schedule alignment (slot correctness) - -Fork selection: -- If new head extends beyond current head, compute branches and switch if heavier -- On failure, mark block invalid and restore good fork - -State application: -- apply_block() applies all operations and transactions -- Updates dynamic global properties (participation, sizes, reserve ratios) -- Creates block summary entries for TaPOS - -**Section sources** -- [database.hpp:193-196](file://libraries/chain/include/graphene/chain/database.hpp#L193-L196) -- [database.cpp:737-792](file://libraries/chain/database.cpp#L737-L792) -- [database.cpp:847-925](file://libraries/chain/database.cpp#L847-L925) -- [database.cpp:3443-3500](file://libraries/chain/database.cpp#L3443-L3500) -- [database.cpp:3723-3748](file://libraries/chain/database.cpp#L3723-L3748) -- [database.cpp:3750-3757](file://libraries/chain/database.cpp#L3750-L3757) -- [database.cpp:3759-3873](file://libraries/chain/database.cpp#L3759-L3873) - -### Enhanced validator Account Validation and Graceful Error Handling - -**Updated** Enhanced validator account validation during block production with comprehensive pre-check mechanisms - -The system now performs preliminary verification using find_account() calls instead of relying solely on get_account() which would throw exceptions if accounts are missing. This ensures graceful handling of missing validator accounts and prevents node crashes during block production. - -Key improvements: -- Pre-check mechanism using find_account() before block generation -- Graceful error handling with critical logging for shared memory corruption detection -- Prevention of exceptions during block production when validator accounts are missing -- Comprehensive error reporting with validator metadata for debugging - -```mermaid -flowchart TD -WStart(["validator Block Production"]) --> PreCheck["Pre-check: find_account(witness_owner)"] -PreCheck --> Found{"Account found?"} -Found --> |Yes| SigCheck["Validate validator signature"] -Found --> |No| CriticalLog["Critical: Log missing account details"] -CriticalLog --> Assert["Assert with detailed error message"] -SigCheck --> GenBlock["Generate block"] -GenBlock --> PostCheck["Post-check: find_account(cwit.owner)"] -PostCheck --> PostFound{"Account found?"} -PostFound --> |Yes| ApplyBlock["Apply block and distribute rewards"] -PostFound --> |No| CriticalLog2["Critical: Log missing validator account"] -CriticalLog2 --> Assert2["Assert with replay recommendation"] -``` - -**Diagram sources** -- [database.cpp:1294-1311](file://libraries/chain/database.cpp#L1294-L1311) -- [database.cpp:2824-2837](file://libraries/chain/database.cpp#L2824-L2837) -- [database.cpp:2871-2884](file://libraries/chain/database.cpp#L2871-L2884) - -**Section sources** -- [database.cpp:1294-1311](file://libraries/chain/database.cpp#L1294-L1311) -- [database.cpp:2824-2837](file://libraries/chain/database.cpp#L2824-L2837) -- [database.cpp:2871-2884](file://libraries/chain/database.cpp#L2871-L2884) -- [database.hpp:185-187](file://libraries/chain/include/graphene/chain/database.hpp#L185-L187) - -### Block Header Validation and validator Scheduling -Header validation ensures: -- Previous block ID matches current head -- Timestamp monotonicity -- validator signature verification (optional) -- validator schedule alignment (slot correctness) - -validator participation tracking: -- Missed block counters and penalties -- Participation rate computation via recent slots -- Irreversible block updates based on validator majorities - -```mermaid -flowchart TD -HStart(["validate_block_header"]) --> PrevCheck["Verify previous == head_block_id"] -PrevCheck --> TSCheck["Verify timestamp > head_block_time"] -TSCheck --> SigCheck{"skip_witness_signature?"} -SigCheck --> |No| VerifySig["Validate block.signee against validator key"] -SigCheck --> |Yes| SkipSig["Skip signature check"] -VerifySig --> SchCheck{"skip_witness_schedule_check?"} -SkipSig --> SchCheck -SchCheck --> |No| SlotCheck["Get slot_at_time(timestamp) and verify scheduled validator"] -SchCheck --> |Yes| SkipSch["Skip schedule check"] -SlotCheck --> HEnd(["Valid"]) -SkipSch --> HEnd -``` - -**Diagram sources** -- [database.cpp:3724-3748](file://libraries/chain/database.cpp#L3724-L3748) -- [database.cpp:3759-3873](file://libraries/chain/database.cpp#L3759-L3873) - -**Section sources** -- [database.cpp:3724-3748](file://libraries/chain/database.cpp#L3724-L3748) -- [database.cpp:3759-3873](file://libraries/chain/database.cpp#L3759-L3873) - -### Block Summary Object and TaPOS -Block summary objects store minimal per-block identifiers used for TaPOS (Transaction as Proof of Stake). They enable transactions to reference recent block hashes and timestamps for validity and expiration checks. - -```mermaid -classDiagram -class block_summary_object { -+id -+block_id -} -class block_summary_index -block_summary_index --> block_summary_object : "stores" -``` - -**Diagram sources** -- [block_summary_object.hpp:19-42](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L42) - -**Section sources** -- [block_summary_object.hpp:19-42](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L42) - -### Block Replay Mechanism -Replay reconstructs chain state by iterating blocks from the block log: -- Start from genesis or configured block number -- Apply each block in sequence, updating state and dynamic properties -- Skip heavy validations during reindex to accelerate replay -- Ensure chainbase revision matches head block number - -```mermaid -flowchart TD -RStart(["reindex(from_block_num)"]) --> LoadFlags["Set skip flags for reindex"] -LoadFlags --> Loop["Iterate blocks from block_log"] -Loop --> Apply["apply_block(next_block)"] -Apply --> Next["Next block"] -Next --> |More| Loop -Next --> |Done| REnd(["Complete"]) -``` - -**Diagram sources** -- [database.cpp:270-300](file://libraries/chain/database.cpp#L270-L300) -- [database.cpp:250-257](file://libraries/chain/database.cpp#L250-L257) - -**Section sources** -- [database.cpp:270-300](file://libraries/chain/database.cpp#L270-L300) -- [database.cpp:250-257](file://libraries/chain/database.cpp#L250-L257) - -## Logging System Enhancements - -**Updated** Enhanced logging system for sync blocks with info level logging and console color formatting - -The logging system has been significantly enhanced to provide better production visibility with info level logging and console color formatting. These improvements ensure that block processing activities are visible in production environments without overwhelming debug-level verbosity. - -### Sync Block Logging with Info Level Visibility - -The network layer now provides enhanced logging for sync block operations with info level granularity: - -- **Successful sync block acceptance**: `ilog("Successfully pushed sync block ${num} (id:${id})")` - Provides confirmation when sync blocks are successfully processed -- **Sync block rejection**: `ilog("Sync block #${num} not applied (already on chain, micro-fork, or parent unknown ahead)")` - Documents why sync blocks are not applied -- **Sync mode transitions**: `ilog("\033[92m>>> Syncing Blockchain started from block #${n} (head: ${head})\033[0m")` and `ilog("\033[92mSync mode ended: received normal block #${n} (head: ${head}), sync_start_logged reset\033[0m")` - Tracks sync mode lifecycle - -These logs are upgraded from debug to info level, making them visible in production configurations without requiring debug logging to be enabled. - -### Console Color Formatting for Block Processing - -The P2P plugin implements comprehensive console color formatting for block processing visibility: - -- **Color constants**: `CLOG_WHITE`, `CLOG_GRAY`, `CLOG_RESET` define ANSI color codes for terminal output -- **Sync block notifications**: `ilog(CLOG_WHITE "Chain pushing sync block #${block_num} (head: ${head}, gap: ${gap})" CLOG_RESET)` - White text for sync blocks -- **Normal block notifications**: `ilog(CLOG_WHITE "Chain pushing normal block #${block_num} (head: ${head}, gap: ${gap})" CLOG_RESET)` - White text for normal blocks -- **Transaction processing**: `ilog(CLOG_WHITE "Got ${t} transactions on block ${b} by ${w} -- latency: ${l} ms" CLOG_RESET)` - White text for transaction counts - -The color formatting enhances readability in terminal environments, allowing operators to quickly distinguish between sync blocks, normal blocks, and transaction processing information. - -### Production-Ready Logging Strategy - -The enhanced logging system follows a production-ready strategy: - -- **Info level logging**: Critical block processing events are logged at info level for production visibility -- **Console color formatting**: Terminal output uses color codes for improved readability -- **Structured information**: Logs include block numbers, timestamps, validator information, and performance metrics -- **Minimal noise**: Debug-level verbose logging is reduced while maintaining essential operational information - -```mermaid -flowchart TD -LogStart(["Block Processing Event"]) --> CheckType{"Block Type?"} -CheckType --> |Sync Block| SyncLog["ilog(info level)"] -CheckType --> |Normal Block| NormalLog["ilog(info level)"] -SyncLog --> ColorFormat["Console Color Formatting"] -NormalLog --> ColorFormat -ColorFormat --> StructInfo["Structured Information"] -StructInfo --> ProdVisibility["Production Visibility"] -``` - -**Diagram sources** -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) - -**Section sources** -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) -- [p2p_plugin.cpp:16:19](file://plugins/p2p/p2p_plugin.cpp#L16-L19) - -## Dependency Analysis -The following diagram shows module-level dependencies among the core components involved in block processing. - -```mermaid -graph LR -DB["database.cpp"] --> FD["fork_database.cpp"] -DB --> BL["block_log.cpp"] -DB --> BS["block_summary_object.hpp"] -DB --> DH["database.hpp"] -FD --> FH["fork_database.hpp"] -BL --> BH["block_log.hpp"] -BS --> BH -NET["node.cpp"] --> DB -P2P["p2p_plugin.cpp"] --> NET -CHAIN["chain plugin.cpp"] --> DB -``` - -**Diagram sources** -- [database.hpp:3-8](file://libraries/chain/include/graphene/chain/database.hpp#L3-L8) -- [fork_database.hpp:3-18](file://libraries/chain/include/graphene/chain/fork_database.hpp#L3-L18) -- [block_log.hpp:3-9](file://libraries/chain/include/graphene/chain/block_log.hpp#L3-L9) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) - -**Section sources** -- [database.hpp:3-8](file://libraries/chain/include/graphene/chain/database.hpp#L3-L8) -- [fork_database.hpp:3-18](file://libraries/chain/include/graphene/chain/fork_database.hpp#L3-L18) -- [block_log.hpp:3-9](file://libraries/chain/include/graphene/chain/block_log.hpp#L3-L9) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) - -## Performance Considerations -- Memory-mapped IO: The block log uses memory-mapped files for efficient random access and streaming reads/writes. -- Index reconstruction: On mismatch, the index is reconstructed by scanning the entire block log; this is expensive but safe. -- Skip flags: During reindex or trusted operations, validations can be selectively skipped to improve throughput. -- Undo sessions: State changes are wrapped in undo sessions to support rollback on errors and efficient reversion during forks. -- Pending transactions: During block generation, transactions are re-applied to reflect time-dependent semantics and respect block size limits. -- **Enhanced validator validation**: Pre-check mechanisms using find_account() avoid exception overhead and improve block production reliability. -- **Enhanced logging**: Info level logging provides production visibility without debug-level overhead, while console color formatting improves terminal readability. - -## Troubleshooting Guide -Common issues and remedies: -- Block log/head mismatch: The block log automatically detects inconsistencies and reconstructs the index. If repeated failures occur, inspect logs around index construction and ensure proper shutdown procedures. -- Fork reorganization failures: If applying a fork branch fails, the system removes invalid blocks from the fork database, restores the good fork, and rethrows the error. Review the failing block and validator participation. -- Excessive memory usage during replay: The database reserves memory and resizes shared memory if allocation fails mid-replay. Monitor logs for forced resizing events. -- Invalid validator schedule: If a block's timestamp slot does not align with the scheduled validator, validation fails. Verify time synchronization and validator schedules. -- **Missing validator accounts**: Enhanced pre-check mechanisms now detect missing validator accounts gracefully, logging critical details and preventing node crashes. Such issues typically indicate shared memory corruption requiring node restart with replay. -- **Enhanced logging visibility**: Production environments can now monitor block processing through info level logs without debug configuration, while console color formatting improves terminal readability for operators. - -**Section sources** -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [database.cpp:847-925](file://libraries/chain/database.cpp#L847-L925) -- [database.cpp:804-823](file://libraries/chain/database.cpp#L804-L823) -- [database.cpp:3724-3748](file://libraries/chain/database.cpp#L3724-L3748) -- [database.cpp:1294-1311](file://libraries/chain/database.cpp#L1294-L1311) -- [node.cpp:3354-3366](file://libraries/network/node.cpp#L3354-L3366) -- [plugin.cpp:104-121](file://plugins/chain/plugin.cpp#L104-L121) -- [p2p_plugin.cpp:152-157](file://plugins/p2p/p2p_plugin.cpp#L152-L157) - -## Conclusion -The Block Processing and Validation system integrates robust storage (block log), fork management (fork database), and strict consensus validation (database) to ensure blockchain consistency. The validate_block() and push_block() methods coordinate header checks, validator scheduling, Merkle roots, and block size limits. The system supports efficient replay for synchronization and maintains validator participation metrics to enforce consensus. - -**Enhanced validator account validation** provides improved reliability during block production by performing preliminary verification using find_account() calls instead of relying on get_account() which would throw exceptions. This change ensures graceful handling of missing validator accounts and prevents node crashes, while maintaining comprehensive error reporting for debugging shared memory corruption scenarios. - -**Enhanced logging system** provides production-ready visibility with info level logging for sync blocks and normal blocks, upgraded from debug level for better production monitoring. Console color formatting improves terminal readability with white text for block processing notifications and structured information including block numbers, validator information, and performance metrics. - -Proper use of skip flags, memory mapping, and undo sessions yields strong performance and reliability, making the system resilient to various operational challenges while maintaining strict consensus enforcement. The enhanced logging system ensures that block processing activities are visible in production environments without overwhelming debug-level verbosity, supporting effective monitoring and troubleshooting of blockchain operations. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Chain Library.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Chain Library.md deleted file mode 100644 index c27426cf05..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Chain Library.md +++ /dev/null @@ -1,995 +0,0 @@ -# Chain Library - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp) -- [operation_notification.hpp](file://libraries/chain/include/graphene/chain/operation_notification.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [snapshot_plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [node.cpp](file://libraries/network/node.cpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [console_appender.cpp](file://thirdparty/fc/src/log/console_appender.cpp) -- [main.cpp](file://programs/vizd/main.cpp) - - -## Update Summary -**Changes Made** -- Enhanced blockchain synchronization logging with guard variable to prevent duplicate sync start messages -- Updated sync restart reasons to use info-level logging instead of debug-level for clearer insights -- Added block processing logs with current head block numbers and gap calculations -- Increased logging frequency from every 10,000 blocks to every 500 blocks during synchronization -- Enhanced logging system documentation with ANSI color codes for operational visibility -- Improved network node synchronization logging with info-level messages for sync restart reasons - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Snapshot Loading and DLT Mode](#snapshot-loading-and-dlt-mode) -7. [Enhanced Logging System and Operational Visibility](#enhanced-logging-system-and-operational-visibility) -8. [Dependency Analysis](#dependency-analysis) -9. [Performance Considerations](#performance-considerations) -10. [Troubleshooting Guide](#troubleshooting-guide) -11. [Conclusion](#conclusion) -12. [Appendices](#appendices) - -## Introduction -This document describes the Chain Library, the core blockchain state management system. It explains how the database persists blockchain state, validates blocks and transactions, resolves forks, and stores blocks efficiently. The library now includes enhanced snapshot loading capabilities, DLT (Data Ledger Technology) mode support, improved error handling during snapshot operations, and a comprehensive logging system with ANSI color codes for enhanced operational visibility. It also documents the data model (objects and indices), the evaluator system for operation processing, and the observer pattern used for event-driven notifications. Practical examples and performance optimization techniques are included to help developers integrate and operate the Chain Library effectively. - -## Project Structure -The Chain Library is organized around a central database class that orchestrates: -- State persistence and indexing via ChainBase -- Fork handling with a fork database -- Block log for durable storage -- DLT rolling block log for selective block retention -- Object model definitions for accounts, validators, committee requests, and more -- Evaluator registry for operation processing -- Observer signals for event-driven integrations -- Snapshot loading and state restoration capabilities -- Enhanced logging system with color-coded synchronization messages - -```mermaid -graph TB -subgraph "Chain Core" -DB["database.hpp/.cpp"] -FDB["fork_database.hpp/.cpp"] -BLK["block_log.hpp/.cpp"] -DLT["dlt_block_log.hpp/.cpp"] -end -subgraph "Object Model" -OT["chain_object_types.hpp"] -CO["chain_objects.hpp"] -AO["account_object.hpp"] -WO["witness_objects.hpp"] -CT["committee_objects.hpp"] -TO["transaction_object.hpp"] -end -subgraph "Processing" -EV["evaluator.hpp"] -CEV["chain_evaluator.hpp"] -ON["operation_notification.hpp"] -SNAP["snapshot_plugin.cpp"] -CHAINPLUG["chain plugin.cpp"] -end -subgraph "Logging System" -LOG["console_appender.cpp"] -MAIN["main.cpp"] -SYNC["node.cpp"] -validator["validator.cpp"] -end -DB --> FDB -DB --> BLK -DB --> DLT -DB --> OT -DB --> CO -DB --> AO -DB --> WO -DB --> CT -DB --> TO -DB --> EV -DB --> CEV -DB --> ON -CHAINPLUG --> SNAP -CHAINPLUG --> DB -SNAP --> LOG -SYNC --> LOG -validator --> LOG -MAIN --> LOG -``` - -**Diagram sources** -- [database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [database.cpp:206-456](file://libraries/chain/database.cpp#L206-L456) -- [fork_database.hpp:53-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [fork_database.cpp:1-245](file://libraries/chain/fork_database.cpp#L1-L245) -- [block_log.hpp:1-200](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L200) -- [block_log.cpp:230-302](file://libraries/chain/block_log.cpp#L230-L302) -- [dlt_block_log.hpp:35-75](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L75) -- [dlt_block_log.cpp:161-382](file://libraries/chain/dlt_block_log.cpp#L161-L382) -- [chain_object_types.hpp:44-146](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L146) -- [chain_objects.hpp:20-226](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L226) -- [account_object.hpp:20-143](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L143) -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [committee_objects.hpp:15-47](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L47) -- [transaction_object.hpp:19-56](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [chain_evaluator.hpp:14-79](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) -- [operation_notification.hpp:11-26](file://libraries/chain/include/graphene/chain/operation_notification.hpp#L11-L26) -- [plugin.cpp:360-432](file://plugins/chain/plugin.cpp#L360-L432) -- [snapshot_plugin.cpp:980-1200](file://plugins/snapshot/plugin.cpp#L980-L1200) -- [console_appender.cpp:132-154](file://thirdparty/fc/src/log/console_appender.cpp#L132-L154) -- [main.cpp:234-253](file://programs/vizd/main.cpp#L234-L253) - -**Section sources** -- [database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [database.cpp:206-456](file://libraries/chain/database.cpp#L206-L456) - -## Core Components -- Database: Central state manager that opens/closes the chain, replays history, pushes blocks and transactions, manages undo sessions, and emits observer signals. Now includes DLT mode support and snapshot loading capabilities. -- Fork Database: Maintains a tree of candidate blocks, supports branch resolution, and selects the longest chain. -- Block Log: Provides durable, memory-mapped storage for blocks and an index for fast random access. -- DLT Rolling Block Log: Enhanced block storage system that maintains only a rolling window of recent blocks for selective retention. -- Object Model: Defines all persistent objects (accounts, validators, committee requests, transactions, etc.) and their multi-index containers. -- Evaluator System: Registry and base classes for operation processing with a standardized interface. -- Observer Pattern: Signals for pre/post operation application, applied block, and transaction events. -- Snapshot Plugin: Handles state restoration from snapshots and manages snapshot lifecycle. -- Enhanced Logging System: Comprehensive color-coded logging with ANSI escape sequences for enhanced operational visibility and progress monitoring. - -Key responsibilities: -- Persistence: ChainBase-backed storage with configurable shared memory sizing and periodic revision alignment. -- Validation: Block and transaction validation with configurable skip flags for reindexing and specialized scenarios. -- Consensus: Fork selection and irreversible block updates. -- Eventing: Notifications for plugins and observers. -- State Restoration: Snapshot loading for rapid node startup and state recovery. -- DLT Operations: Selective block retention for compliance and archival purposes. -- Operational Visibility: Color-coded logging for synchronization progress and system status with enhanced frequency monitoring. - -**Section sources** -- [database.hpp:56-110](file://libraries/chain/include/graphene/chain/database.hpp#L56-L110) -- [database.cpp:206-350](file://libraries/chain/database.cpp#L206-L350) -- [fork_database.hpp:53-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [block_log.hpp:1-200](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L200) -- [dlt_block_log.hpp:35-75](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L75) -- [evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) - -## Architecture Overview -The Chain Library composes several subsystems with enhanced snapshot and DLT capabilities: -- database orchestrates state transitions and emits signals, now supporting DLT mode and snapshot operations -- fork_database maintains candidate chains -- block_log provides durable storage -- dlt_block_log provides selective block retention in DLT mode -- object model defines schema and indices -- evaluators apply operations atomically within undo sessions -- snapshot plugin handles state restoration and validation -- enhanced logging system provides color-coded operational visibility with improved synchronization monitoring - -```mermaid -sequenceDiagram -participant App as "Caller" -participant DB as "database" -participant FDB as "fork_database" -participant BLK as "block_log" -participant DLT as "dlt_block_log" -participant LOG as "Logging System" -participant OBS as "Observers" -App->>DB : push_block(signed_block) -DB->>FDB : push_block() -alt fork head advanced -DB->>DB : fetch_branch_from() -loop pop blocks until forked -DB->>DB : pop_block() -end -loop push new fork blocks -DB->>DB : apply_block() -DB->>OBS : notify_applied_block() -DB->>LOG : Emit colored sync progress (every 500 blocks) -end -else same fork -DB->>DB : apply_block() -end -DB->>BLK : append() -DB->>DLT : append() (if DLT mode) -DB->>OBS : notify_applied_block() -``` - -**Diagram sources** -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [block_log.cpp:253-257](file://libraries/chain/block_log.cpp#L253-L257) -- [dlt_block_log.cpp:211-230](file://libraries/chain/dlt_block_log.cpp#L211-L230) -- [console_appender.cpp:132-154](file://thirdparty/fc/src/log/console_appender.cpp#L132-L154) - -**Section sources** -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [fork_database.cpp:168-210](file://libraries/chain/fork_database.cpp#L168-L210) -- [block_log.cpp:253-257](file://libraries/chain/block_log.cpp#L253-L257) -- [dlt_block_log.cpp:211-230](file://libraries/chain/dlt_block_log.cpp#L211-L230) - -## Detailed Component Analysis - -### Database: State Management, Validation, and Events -The database class extends ChainBase and encapsulates: -- Opening/closing the database and block log -- Reindexing from block log -- Pushing blocks and transactions with configurable validation -- Undo sessions for atomic state transitions -- Observer signals for operations and blocks -- DLT mode support for selective block retention -- Snapshot loading capabilities for rapid state restoration - -Notable APIs: -- Block lifecycle: validate_block, push_block, pop_block, generate_block -- Transaction lifecycle: validate_transaction, push_transaction -- Queries: get_account, get_witness, get_content, get_escrow, get_dynamic_global_properties, get_witness_schedule_object, get_hardfork_property_object -- Fork and block log helpers: is_known_block, is_known_transaction, fetch_block_by_id, fetch_block_by_number, get_block_ids_on_fork -- DLT mode: _dlt_mode flag, _dlt_block_log_max_blocks configuration -- Observers: pre_apply_operation, post_apply_operation, applied_block, on_pending_transaction, on_applied_transaction - -Validation flags allow skipping expensive checks during reindexing or trusted operations. - -```mermaid -classDiagram -class database { -+open(data_dir, shared_mem_dir, ...) -+reindex(data_dir, shared_mem_dir, from_block_num, ...) -+open_from_snapshot(data_dir, shared_mem_dir, initial_supply, shared_file_size, flags) -+push_block(signed_block, skip) -+validate_block(signed_block, skip) -+push_transaction(signed_transaction, skip) -+validate_transaction(signed_transaction, skip) -+generate_block(...) -+pop_block() -+get_account(name) -+get_witness(name) -+get_content(author, permlink) -+get_escrow(name, id) -+get_dynamic_global_properties() -+get_witness_schedule_object() -+get_hardfork_property_object() -+is_known_block(id) -+is_known_transaction(id) -+fetch_block_by_id(id) -+fetch_block_by_number(n) -+get_block_ids_on_fork(head) -+pre_apply_operation(signal) -+post_apply_operation(signal) -+applied_block(signal) -+on_pending_transaction(signal) -+on_applied_transaction(signal) -+initialize_hardforks() -} -class fork_database -class block_log -class dlt_block_log -database --> fork_database : "uses" -database --> block_log : "uses" -database --> dlt_block_log : "uses" -``` - -**Diagram sources** -- [database.hpp:111-558](file://libraries/chain/include/graphene/chain/database.hpp#L111-L558) -- [database.cpp:206-456](file://libraries/chain/database.cpp#L206-L456) -- [fork_database.hpp:53-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [block_log.hpp:1-200](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L200) -- [dlt_block_log.hpp:35-75](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L75) - -**Section sources** -- [database.hpp:111-558](file://libraries/chain/include/graphene/chain/database.hpp#L111-L558) -- [database.cpp:206-456](file://libraries/chain/database.cpp#L206-L456) - -### Fork Database: Fork Resolution and Branch Selection -The fork database maintains a tree of candidate blocks: -- push_block inserts a block and links it to previous if possible -- fetch_branch_from computes divergent branches to resolve forks -- walk_main_branch_to_num and fetch_block_on_main_branch_by_number resolve canonical chain membership -- set_max_size prunes old blocks to bound memory growth - -```mermaid -flowchart TD -Start(["push_block"]) --> LinkCheck["Link to previous known block?"] -LinkCheck --> |No| Cache["Add to unlinked cache"] -LinkCheck --> |Yes| Insert["_push_block: insert into index"] -Insert --> UpdateHead{"New head? (higher num)"} -UpdateHead --> |Yes| SetHead["Set head"] -UpdateHead --> |No| Done(["Return head"]) -Cache --> Done -SetHead --> Done -``` - -**Diagram sources** -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) - -**Section sources** -- [fork_database.hpp:53-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L125) -- [fork_database.cpp:47-90](file://libraries/chain/fork_database.cpp#L47-L90) - -### Block Log: Efficient Storage and Retrieval -The block log provides: -- Memory-mapped files for blocks and an index -- Random access by block number via index -- Append-only writes with position tracking -- Robust startup logic to reconcile head positions and reconstruct index if needed - -```mermaid -flowchart TD -Open(["open(file)"]) --> InitFiles["Ensure block/index files exist"] -InitFiles --> MapFiles["Map files into memory"] -MapFiles --> Reconcile["Compare heads and reconstruct index if needed"] -Reconcile --> Ready(["Ready"]) -Append(["append(block)"]) --> Pack["Pack block"] -Pack --> WriteBlock["Write to block log"] -WriteBlock --> WriteIndex["Write position to index"] -WriteIndex --> UpdateHead["Update head"] -ReadNum(["read_block_by_num(n)"]) --> Pos["Get position from index"] -Pos --> ReadBlock["Read block at position"] -ReadBlock --> Verify["Verify block number"] -``` - -**Diagram sources** -- [block_log.cpp:134-194](file://libraries/chain/block_log.cpp#L134-L194) -- [block_log.cpp:195-227](file://libraries/chain/block_log.cpp#L195-L227) -- [block_log.cpp:270-285](file://libraries/chain/block_log.cpp#L270-L285) - -**Section sources** -- [block_log.hpp:1-200](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L200) -- [block_log.cpp:134-194](file://libraries/chain/block_log.cpp#L134-L194) -- [block_log.cpp:270-285](file://libraries/chain/block_log.cpp#L270-L285) - -### DLT Block Log: Selective Block Retention -The DLT block log provides: -- Memory-mapped files for blocks and an index with rolling window capability -- Configurable maximum blocks retention via _dlt_block_log_max_blocks -- Automatic truncation when exceeding configured limits -- Separate from standard block log for compliance and archival purposes -- Support for DLT mode where only irreversible blocks are retained - -```mermaid -flowchart TD -Open(["open(file)"]) --> InitFiles["Ensure dlt block/index files exist"] -InitFiles --> MapFiles["Map files into memory"] -MapFiles --> Reconcile["Reconstruct index if mismatched"] -Reconcile --> Ready(["Ready"]) -Append(["append(block)"]) --> CheckLimit{"Exceeds max blocks?"} -CheckLimit --> |Yes| Truncate["Truncate old blocks"] -CheckLimit --> |No| Continue["Continue"] -Truncate --> TruncateBefore["truncate_before(new_start)"] -TruncateBefore --> Continue -Continue --> Pack["Pack block"] -Pack --> WriteBlock["Write to dlt block log"] -WriteBlock --> WriteIndex["Write position to index"] -WriteIndex --> UpdateHead["Update head"] -``` - -**Diagram sources** -- [dlt_block_log.cpp:161-230](file://libraries/chain/dlt_block_log.cpp#L161-L230) -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) - -**Section sources** -- [dlt_block_log.hpp:35-75](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L75) -- [dlt_block_log.cpp:161-230](file://libraries/chain/dlt_block_log.cpp#L161-L230) -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) - -### Data Model: Objects and Indices -The object model defines persistent entities and their indices: -- Object types enumerate all managed object kinds -- Account, validator, committee request/vote, transaction, escrow, vesting delegation, and more -- Multi-index containers provide unique and composite keys for efficient lookups - -Representative object categories: -- Accounts: balances, vesting shares, delegation, auction metadata, bandwidth tracking -- validators: votes, virtual scheduling, signing keys, version/hardfork votes -- Committee: requests with statuses, funding, payouts -- Transactions: deduplication and expiration tracking -- Escrow and routes: multi-signature transfers and routing - -```mermaid -erDiagram -ACCOUNT { -name : string -balance : asset -vesting_shares : asset -delegated_vesting_shares : asset -received_vesting_shares : asset -energy : int16 -last_vote_time : time_point_sec -} -validator { -owner : account_name_type -votes : share_type -signing_key : public_key_type -running_version : version -hardfork_version_vote : hardfork_version -hardfork_time_vote : time_point_sec -} -COMMITTEE_REQUEST { -request_id : uint32 -creator : account_name_type -worker : account_name_type -required_amount_min : asset -required_amount_max : asset -status : uint16 -start_time : time_point_sec -end_time : time_point_sec -payout_amount : asset -remain_payout_amount : asset -} -TRANSACTION_OBJECT { -trx_id : transaction_id_type -expiration : time_point_sec -} -ESCROW { -escrow_id : uint32 -from : account_name_type -to : account_name_type -agent : account_name_type -ratification_deadline : time_point_sec -escrow_expiration : time_point_sec -token_balance : asset -pending_fee : asset -to_approved : bool -agent_approved : bool -disputed : bool -} -ACCOUNT ||--o{ WITNESS_VOTE : "votes_for" -ACCOUNT ||--o{ ESCROW : "escrows" -ACCOUNT ||--o{ COMMITTEE_VOTE : "votes" -COMMITTEE_REQUEST ||--o{ COMMITTEE_VOTE : "votes" -TRANSACTION_OBJECT ||--|| BLOCK : "referenced_in" -``` - -**Diagram sources** -- [chain_object_types.hpp:44-146](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L146) -- [account_object.hpp:20-143](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L143) -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [committee_objects.hpp:15-47](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L47) -- [transaction_object.hpp:19-56](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [chain_objects.hpp:20-141](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L141) - -**Section sources** -- [chain_object_types.hpp:44-146](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L146) -- [account_object.hpp:20-143](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L143) -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [committee_objects.hpp:15-47](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L47) -- [transaction_object.hpp:19-56](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [chain_objects.hpp:20-141](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L141) - -### Evaluator System: Operation Processing -The evaluator system provides a uniform mechanism to apply operations: -- Base evaluator interface with apply and type identification -- Evaluator implementations for each operation type -- Registry-driven dispatch to the appropriate evaluator - -```mermaid -classDiagram -class evaluator~OperationType~ { -<> -+apply(op : OperationType) void -+get_type() int -} -class evaluator_impl~EvaluatorType, OperationType~ { --_db : database -+apply(op : OperationType) void -+get_type() int -+db() database& -} -class account_create_evaluator -class transfer_evaluator -class witness_update_evaluator -evaluator_impl <|-- account_create_evaluator -evaluator_impl <|-- transfer_evaluator -evaluator_impl <|-- witness_update_evaluator -evaluator <|-- evaluator_impl -``` - -**Diagram sources** -- [evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [chain_evaluator.hpp:14-79](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) - -**Section sources** -- [evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [chain_evaluator.hpp:14-79](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) - -### Observer Pattern: Event-Driven Architecture -The database emits signals for: -- Pre/post operation application -- Applied block -- Pending/applied transactions - -Plugins and observers can subscribe to these signals to react to state changes without tight coupling. - -```mermaid -sequenceDiagram -participant DB as "database" -participant OBS as "Observers" -participant EVAL as "evaluator" -DB->>EVAL : apply_operation(op) -DB->>OBS : pre_apply_operation(note) -EVAL-->>DB : operation applied -DB->>OBS : post_apply_operation(note) -DB->>OBS : applied_block(block) -DB->>OBS : on_applied_transaction(trx) -``` - -**Diagram sources** -- [database.hpp:252-286](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [operation_notification.hpp:11-26](file://libraries/chain/include/graphene/chain/operation_notification.hpp#L11-L26) -- [database.cpp:1158-1198](file://libraries/chain/database.cpp#L1158-L1198) - -**Section sources** -- [database.hpp:252-286](file://libraries/chain/include/graphene/chain/database.hpp#L252-L286) -- [operation_notification.hpp:11-26](file://libraries/chain/include/graphene/chain/operation_notification.hpp#L11-L26) -- [database.cpp:1158-1198](file://libraries/chain/database.cpp#L1158-L1198) - -## Snapshot Loading and DLT Mode - -### Enhanced Chain Plugin Startup Sequence -The chain plugin now implements a sophisticated startup sequence that handles both normal operation and snapshot-based initialization: - -```mermaid -flowchart TD -Start(["plugin_startup()"]) --> CheckSnapshot{"--snapshot option present?"} -CheckSnapshot --> |Yes| CheckSHM["Check shared_memory.bin exists"] -CheckSHM --> |Exists & >0| NormalStartup["Skip snapshot import, use normal startup"] -CheckSHM --> |Missing/Empty| CheckFile["Check snapshot file exists"] -CheckFile --> |Exists| OpenSnapshot["Call db.open_from_snapshot()"] -CheckFile --> |Missing| NormalStartup -OpenSnapshot --> LoadState["Execute snapshot_load_callback()"] -LoadState --> ValidateState["Validate snapshot integrity"] -ValidateState --> Success{"Load success?"} -Success --> |Yes| RenameFile["Rename snapshot to .used"] -Success --> |No| Error["Log fatal error and exit"] -NormalStartup --> NormalOpen["Call db.open()"] -NormalOpen --> ReplayCheck{"Need replay?"} -ReplayCheck --> |Yes| ExecuteReplay["Execute replay_db()"] -ReplayCheck --> |No| CompleteStartup["Complete startup"] -ExecuteReplay --> CompleteStartup -CompleteStartup --> Sync["on_sync()"] -``` - -**Diagram sources** -- [plugin.cpp:364-432](file://plugins/chain/plugin.cpp#L364-L432) -- [plugin.cpp:434-491](file://plugins/chain/plugin.cpp#L434-L491) - -**Section sources** -- [plugin.cpp:364-432](file://plugins/chain/plugin.cpp#L364-L432) -- [plugin.cpp:434-491](file://plugins/chain/plugin.cpp#L434-L491) - -### Database Initialization for Snapshot Mode -The database provides a dedicated `open_from_snapshot` method that initializes the chain in DLT mode: - -**Updated** Enhanced with DLT mode flag management and improved error handling - -Key features: -- Sets `_dlt_mode = true` for DLT operations -- Wipes existing shared memory to ensure clean state -- Initializes schema and opens shared memory with proper flags -- Opens both standard and DLT block logs -- Handles genesis initialization in read-write mode - -**Section sources** -- [database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) - -### Snapshot Loading Process -The snapshot plugin implements comprehensive state restoration: - -**Updated** Enhanced with improved error handling and validation - -Process overview: -1. Parse and validate snapshot header -2. Verify chain ID compatibility and version support -3. Validate payload checksum for integrity -4. Clear genesis-created objects to prevent conflicts -5. Import objects in dependency order (singletons first, then multi-instance) -6. Set chainbase revision to match snapshot head block -7. Seed fork database with snapshot head block - -**Section sources** -- [snapshot_plugin.cpp:1000-1200](file://plugins/snapshot/plugin.cpp#L1000-L1200) - -### DLT Mode Flag Management -The database maintains two key DLT-related flags: - -**Updated** Added comprehensive DLT mode support - -- `_dlt_mode`: Boolean flag indicating snapshot-loaded state -- `_dlt_block_log_max_blocks`: Configurable limit for rolling block retention - -Behavior in DLT mode: -- Block log append operations are conditionally executed -- Irreversible blocks are selectively written to DLT block log -- Automatic truncation when exceeding configured limits -- Separate from standard block log for compliance purposes - -**Section sources** -- [database.hpp:57-64](file://libraries/chain/include/graphene/chain/database.hpp#L57-L64) -- [database.cpp:3985-4051](file://libraries/chain/database.cpp#L3985-L4051) - -### Error Handling Improvements -Enhanced error handling during snapshot operations: - -**Updated** Added comprehensive error handling and recovery mechanisms - -- Graceful fallback to normal startup when snapshot import fails -- Detailed error logging with specific failure reasons -- Automatic snapshot file renaming to prevent re-import -- Validation of snapshot integrity before import -- Proper cleanup and resource management on errors - -**Section sources** -- [plugin.cpp:391-417](file://plugins/chain/plugin.cpp#L391-L417) -- [snapshot_plugin.cpp:1014-1032](file://plugins/snapshot/plugin.cpp#L1014-L1032) - -## Enhanced Logging System and Operational Visibility - -### Enhanced Logging Infrastructure -The Chain Library now features a comprehensive logging system with ANSI color codes designed to improve operational visibility during blockchain synchronization and state management operations. - -#### Color-Coded Log Categories -The logging system uses distinct ANSI color codes for different operational contexts: - -- **Green (✓)**: Successful operations, block generation, and positive confirmations -- **Brown/Yellow (⚠)**: Warning conditions, synchronization progress, and informational messages -- **Cyan (✗)**: Error conditions and critical failures -- **Default**: Debug-level information and internal operations - -#### Synchronization Progress Messages -Blockchain synchronization messages utilize yellow color coding for enhanced visibility: - -- **Yellow ANSI Codes**: `\033[93m` for synchronization progress indicators -- **Orange ANSI Codes**: `\033[33m` for snapshot import operations -- **Green ANSI Codes**: `\033[92m` for successful block generation - -#### Enhanced Synchronization Monitoring -**Updated** The synchronization system now includes improved monitoring with: - -- **Guard Variable**: `sync_start_logged` prevents duplicate sync start messages -- **Increased Frequency**: Progress logging occurs every 500 blocks instead of every 10,000 blocks -- **Color-Coded Progress**: Yellow messages for synchronization progress and block acceptance -- **Comprehensive Coverage**: Both sync start and ongoing progress messages are color-coded - -#### Implementation Details -The logging system leverages the FC (Fast Crypto) framework's console appender with enhanced color support: - -```mermaid -flowchart TD -Start(["Block Processing"]) --> CheckSync{"Currently Syncing?"} -CheckSync --> |Yes| CheckGuard{"sync_start_logged?"} -CheckGuard --> |No| LogStart["Log Sync Start (Green)"] -LogStart --> SetGuard["Set sync_start_logged = true"] -SetGuard --> CheckFreq{"block_num() % 500 == 0?"} -CheckGuard --> |Yes| CheckFreq -CheckFreq --> |Yes| LogProgress["Log Progress (Yellow)"] -CheckFreq --> |No| Skip["Skip Logging"] -CheckSync --> |No| ResetGuard["Reset sync_start_logged = false"] -ResetGuard --> Continue["Continue Processing"] -LogProgress --> Continue -Skip --> Continue -``` - -**Diagram sources** -- [plugin.cpp:100-113](file://plugins/chain/plugin.cpp#L100-L113) -- [console_appender.cpp:132-154](file://thirdparty/fc/src/log/console_appender.cpp#L132-L154) -- [main.cpp:234-253](file://programs/vizd/main.cpp#L234-L253) - -#### Synchronization Progress Indicators -During blockchain synchronization, the system provides enhanced color-coded progress updates: - -- **Green messages** indicate synchronization start (only logged once per sync session) -- **Yellow messages** indicate synchronization progress every 500 blocks -- **Green messages** confirm successful block generation by validators -- **Orange messages** highlight snapshot import operations -- **Default messages** show internal synchronization mechanics - -#### Color Scheme Configuration -The logging system supports configurable color schemes through program options: - -- Console appenders support level-specific color assignments -- Default color mappings include debug (green), warn (brown/yellow), and error (cyan) -- Custom color configurations can be specified in logging configuration files - -**Section sources** -- [plugin.cpp:58-113](file://plugins/chain/plugin.cpp#L58-L113) -- [console_appender.cpp:132-154](file://thirdparty/fc/src/log/console_appender.cpp#L132-L154) -- [main.cpp:234-253](file://programs/vizd/main.cpp#L234-L253) -- [snapshot_plugin.cpp:1018-1032](file://plugins/snapshot/plugin.cpp#L1018-L1032) -- [validator.cpp:286](file://plugins/validator/validator.cpp#L286) - -### Enhanced Troubleshooting with Color Coding -The enhanced color-coded logging system significantly improves troubleshooting capabilities: - -#### Visual Progress Tracking -- **Green sync start messages**: Show when synchronization begins (logged once per session) -- **Yellow progress bars**: Show synchronization completion every 500 blocks -- **Green confirmations**: Highlight successful operations -- **Orange warnings**: Indicate snapshot import progress -- **Red alerts**: Signal critical errors requiring immediate attention - -#### Operational Context Clarity -Different log levels use distinct colors to provide immediate context: -- **Debug (Green)**: Low-level operational details -- **Info/Warn (Yellow/Brown)**: Synchronization progress and warnings -- **Error (Cyan)**: Critical failures and exceptions - -#### Terminal Output Enhancement -The ANSI color codes provide immediate visual feedback in terminal environments: -- Color-coded timestamps for better temporal tracking -- Distinct color schemes for different log sources -- Improved readability during high-volume logging scenarios -- Guard variable prevents log spam during sync sessions - -**Section sources** -- [node.cpp:3446-3456](file://libraries/network/node.cpp#L3446-L3456) -- [snapshot_plugin.cpp:1770-1771](file://plugins/snapshot/plugin.cpp#L1770-L1771) -- [validator.cpp:286](file://plugins/validator/validator.cpp#L286) - -### Network Node Synchronization Logging Enhancements -**Updated** The network node synchronization system now uses info-level logging for sync restart reasons, providing clearer insights into synchronization behavior: - -- **Info-level logging**: Sync restart reasons now use ilog instead of dlog for better visibility -- **Current head block numbers**: Logs include current head block numbers and gap calculations -- **Enhanced sync restart detection**: Improved logging for peer synchronization restarts -- **Block acceptance notifications**: Yellow progress messages with block numbers and producer information - -**Section sources** -- [node.cpp:2428-2444](file://libraries/network/node.cpp#L2428-L2444) -- [node.cpp:3276-3280](file://libraries/network/node.cpp#L3276-L3280) -- [database.cpp:5295-5303](file://libraries/chain/database.cpp#L5295-L5303) - -### Enhanced Logging Improvements Summary -The logging system enhancements include: - -1. **Guard Variable Protection**: `sync_start_logged` prevents duplicate sync start messages -2. **Info-Level Logging**: Sync restart reasons use ilog instead of dlog for better visibility -3. **Enhanced Progress Frequency**: Logging occurs every 500 blocks instead of every 10,000 blocks -4. **Color-Coded Notifications**: ANSI escape sequences provide visual distinction for different log types -5. **Current Head Block Numbers**: Logs include current head block numbers and gap calculations -6. **Improved Network Sync**: Better logging for peer synchronization restarts and block acceptance - -**Section sources** -- [plugin.cpp:58-121](file://plugins/chain/plugin.cpp#L58-L121) -- [node.cpp:2428-2444](file://libraries/network/node.cpp#L2428-L2444) -- [node.cpp:3276-3280](file://libraries/network/node.cpp#L3276-L3280) -- [database.cpp:5295-5303](file://libraries/chain/database.cpp#L5295-L5303) - -## Dependency Analysis -The database depends on: -- fork_database for chain selection -- block_log for durable storage -- dlt_block_log for selective block retention in DLT mode -- object model headers for schema definitions -- evaluator registry for operation application -- observer signals for event emission -- snapshot plugin for state restoration -- enhanced logging system for operational visibility - -```mermaid -graph LR -DB["database.hpp/.cpp"] --> FDB["fork_database.hpp/.cpp"] -DB --> BLK["block_log.hpp/.cpp"] -DB --> DLT["dlt_block_log.hpp/.cpp"] -DB --> OT["chain_object_types.hpp"] -DB --> CO["chain_objects.hpp"] -DB --> AO["account_object.hpp"] -DB --> WO["witness_objects.hpp"] -DB --> CT["committee_objects.hpp"] -DB --> TO["transaction_object.hpp"] -DB --> EV["evaluator.hpp"] -DB --> CEV["chain_evaluator.hpp"] -DB --> ON["operation_notification.hpp"] -CHAINPLUG["chain plugin.cpp"] --> SNAP["snapshot_plugin.cpp"] -CHAINPLUG --> DB -LOGSYS["console_appender.cpp"] --> MAIN["main.cpp"] -LOGSYS --> SYNC["node.cpp"] -LOGSYS --> validator["validator.cpp"] -``` - -**Diagram sources** -- [database.hpp:1-561](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [database.cpp:1-200](file://libraries/chain/database.cpp#L1-L200) -- [fork_database.hpp:1-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp:1-200](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L200) -- [dlt_block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L75) -- [chain_object_types.hpp:1-246](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L1-L246) -- [chain_objects.hpp:1-226](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L1-L226) -- [account_object.hpp:1-565](file://libraries/chain/include/graphene/chain/account_object.hpp#L1-L565) -- [witness_objects.hpp:1-313](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L1-L313) -- [committee_objects.hpp:1-137](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L1-L137) -- [transaction_object.hpp:1-56](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L1-L56) -- [evaluator.hpp:1-62](file://libraries/chain/include/graphene/chain/evaluator.hpp#L1-L62) -- [chain_evaluator.hpp:1-80](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L1-L80) -- [operation_notification.hpp:1-27](file://libraries/chain/include/graphene/chain/operation_notification.hpp#L1-L27) -- [plugin.cpp:1-526](file://plugins/chain/plugin.cpp#L1-L526) -- [snapshot_plugin.cpp:1-1976](file://plugins/snapshot/plugin.cpp#L1-L1976) -- [console_appender.cpp:132-154](file://thirdparty/fc/src/log/console_appender.cpp#L132-L154) -- [main.cpp:234-253](file://programs/vizd/main.cpp#L234-L253) - -**Section sources** -- [database.hpp:1-561](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [database.cpp:1-200](file://libraries/chain/database.cpp#L1-L200) - -## Performance Considerations -- Shared memory sizing and auto-resize: Configure minimum free memory thresholds and incremental growth to avoid frequent resizes during heavy load. -- Block log I/O: Memory-mapped files reduce syscall overhead; ensure adequate OS page cache and avoid fragmentation. -- Fork pruning: Limit fork cache size to bound memory usage; prune old blocks when head advances significantly. -- Validation flags: During reindexing, skip expensive checks (signatures, merkle, authority) to accelerate replay. -- Bandwidth accounting: Efficient per-account bandwidth calculations prevent excessive CPU usage on producers. -- Undo sessions: Use short-lived sessions to minimize rollback overhead; squash temporary sessions after successful application. -- DLT mode optimization: Configure `_dlt_block_log_max_blocks` appropriately to balance storage requirements and performance. -- Snapshot loading: Use snapshot mode for rapid node startup, especially for large blockchains. -- **New**: Enhanced logging performance: Color-coded logging with guard variables and increased frequency provides better operational visibility with minimal overhead while significantly improving troubleshooting efficiency. -- **New**: Synchronization monitoring: The `sync_start_logged` guard prevents duplicate messages and reduces log volume during sync sessions. -- **New**: Info-level logging for sync restarts: Using ilog instead of dlog for sync restart reasons provides clearer insights into synchronization behavior. -- **New**: Network synchronization improvements: Enhanced logging with current head block numbers and gap calculations improves monitoring accuracy. - -## Troubleshooting Guide -Common issues and remedies: -- Chain mismatch after restart: The database verifies revision against head block number; if inconsistent, a specific exception is thrown indicating the mismatch. -- Block log/head mismatch: On open, the database validates that the block log head matches the chain head; if not, a specific exception instructs reindexing. -- Memory pressure: Monitor free memory and trigger auto-resize when thresholds are met; periodically print free memory status. -- Fork collisions: When multiple blocks are produced at the same slot, warnings are logged; ensure correct fork resolution and head updates. -- Bad allocation during block push: On memory exhaustion, the system attempts to resize shared memory and retry. -- Snapshot import failures: Check shared_memory.bin existence and snapshot file accessibility; the system gracefully falls back to normal startup. -- DLT mode errors: Verify DLT block log configuration and ensure sufficient disk space for rolling window retention. -- Chain ID mismatches: Snapshot loading validates chain ID compatibility; ensure using correct snapshot for target network. -- **New**: Enhanced color-coded log interpretation: Use green messages for synchronization start (once per session), yellow messages for synchronization progress every 500 blocks, green for successful operations, orange for snapshot operations, and red for critical errors to quickly identify operational status. -- **New**: Synchronization monitoring: The guard variable prevents duplicate sync start messages and ensures consistent progress reporting every 500 blocks. -- **New**: Info-level logging improvements: Sync restart reasons now use info-level logging for better visibility and clearer insights into synchronization behavior. -- **New**: Network synchronization logging: Enhanced logging with current head block numbers and gap calculations provides better monitoring accuracy. - -**Section sources** -- [database.cpp:232-248](file://libraries/chain/database.cpp#L232-L248) -- [database.cpp:270-350](file://libraries/chain/database.cpp#L270-L350) -- [database.cpp:368-430](file://libraries/chain/database.cpp#L368-L430) -- [database.cpp:832-844](file://libraries/chain/database.cpp#L832-L844) -- [plugin.cpp:371-380](file://plugins/chain/plugin.cpp#L371-L380) -- [snapshot_plugin.cpp:1018-1020](file://plugins/snapshot/plugin.cpp#L1018-L1020) - -## Conclusion -The Chain Library provides a robust, modular framework for blockchain state management with enhanced snapshot loading capabilities and DLT mode support. Its design separates concerns across database orchestration, fork handling, durable storage, typed object models, operation processing, and event-driven observation. The addition of snapshot loading enables rapid node startup and state restoration, while DLT mode provides selective block retention for compliance and archival purposes. The enhanced logging system with ANSI color codes significantly improves operational visibility during synchronization and troubleshooting, featuring guard variables to prevent duplicate messages and increased frequency monitoring for better progress tracking. The updated info-level logging for sync restart reasons provides clearer insights into synchronization behavior with current head block numbers and gap calculations. By leveraging ChainBase for persistence, fork_database for consensus, block_log for storage, the new DLT block log for selective retention, and comprehensive color-coded logging for operational insights, it achieves high throughput, reliability, and regulatory compliance. Developers can extend functionality via evaluators and observe state changes through signals, enabling flexible plugin architectures with enhanced operational capabilities. - -## Appendices - -### Common Database Operations and Examples -- Open and replay: - - Open database and block log, initialize indexes and evaluators, then start block log and rewind undo state. - - Reindex from a specified block number with skip flags to bypass validations. - - **New**: Open from snapshot using `open_from_snapshot()` for rapid initialization. -- Push block: - - Validate block (merkle and size), push to fork database, resolve forks if needed, apply block, persist to block log, emit applied block signal. - - **Enhanced**: In DLT mode, selectively write irreversible blocks to DLT block log with color-coded progress updates every 500 blocks. -- Push transaction: - - Validate transaction size, apply within a pending session, record changes, and emit pending/applied transaction signals. -- Query state: - - Retrieve account, validator, content, escrow, dynamic global properties, validator schedule, and hardfork property objects by name or identifier. -- Fork resolution: - - Compute branches from current head to a candidate fork head, pop blocks until common ancestor, then push new fork blocks. -- **New**: Snapshot operations: - - Load snapshot state via callback during startup with orange/yellow color-coded progress indicators - - Validate snapshot integrity and chain compatibility - - Handle snapshot file lifecycle (renaming, cleanup) -- **New**: Enhanced color-coded logging: - - Green messages for synchronization start (logged once per session via guard variable) - - Yellow messages for synchronization progress every 500 blocks - - Green messages for successful block generation - - Orange messages for snapshot import operations - - Red messages for error conditions -- **New**: Network synchronization logging: - - Info-level logging for sync restart reasons with clearer insights - - Current head block numbers and gap calculations in DLT mode - - Enhanced sync progress notifications with producer information - -**Section sources** -- [database.cpp:206-350](file://libraries/chain/database.cpp#L206-L350) -- [database.cpp:800-925](file://libraries/chain/database.cpp#L800-L925) -- [database.cpp:936-970](file://libraries/chain/database.cpp#L936-L970) -- [database.hpp:136-169](file://libraries/chain/include/graphene/chain/database.hpp#L136-L169) -- [plugin.cpp:364-432](file://plugins/chain/plugin.cpp#L364-L432) -- [snapshot_plugin.cpp:1000-1200](file://plugins/snapshot/plugin.cpp#L1000-L1200) - -### Performance Optimization Techniques -- Tune shared memory growth: Set minimum free memory and increment sizes to avoid frequent resizing. -- Use skip flags during reindexing: Disable signature and authority checks to accelerate replay. -- Monitor and log memory: Periodic logs help detect approaching limits before failures. -- Keep fork cache bounded: Adjust maximum fork size to control memory footprint. -- Batch operations: Group related operations to minimize undo session overhead. -- **New**: Configure DLT mode: Set `_dlt_block_log_max_blocks` to balance storage and performance. -- **New**: Optimize snapshot loading: Use appropriate snapshot files and monitor import performance with color-coded progress indicators. -- **New**: Manage DLT storage: Regularly monitor DLT block log size and adjust retention policies. -- **New**: Enhanced logging optimization: Color-coded logging with guard variables and increased frequency provides better operational benefits with minimal overhead. -- **New**: Info-level logging optimization: Using ilog for sync restart reasons improves visibility with minimal performance impact. -- **New**: Network synchronization optimization: Enhanced logging with current head block numbers and gap calculations provides better monitoring accuracy. - -**Section sources** -- [database.cpp:368-430](file://libraries/chain/database.cpp#L368-L430) -- [fork_database.cpp:92-124](file://libraries/chain/fork_database.cpp#L92-L124) -- [database.hpp:62-64](file://libraries/chain/include/graphene/chain/database.hpp#L62-L64) -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) - -### DLT Mode Configuration -**New Section** Configuration and usage guidelines for DLT mode - -Configuration options: -- `--dlt-block-log-max-blocks`: Number of recent blocks to keep in rolling DLT block log (default: 100000) -- `_dlt_mode`: Internal flag indicating DLT operation mode -- `_dlt_block_log`: Separate block log instance for DLT operations - -Usage scenarios: -- Compliance reporting with selective block retention -- Archival purposes with configurable retention periods -- Reduced storage requirements for non-validating nodes -- Regulatory compliance with immutable block records - -**Section sources** -- [plugin.cpp:234-236](file://plugins/chain/plugin.cpp#L234-L236) -- [database.hpp:57-64](file://libraries/chain/include/graphene/chain/database.hpp#L57-L64) -- [dlt_block_log.cpp:161-230](file://libraries/chain/dlt_block_log.cpp#L161-L230) - -### Enhanced Color-Coded Logging Reference -**New Section** Comprehensive reference for enhanced color-coded logging system - -#### ANSI Color Codes Used -- **`\033[92m`** (Green): Successful operations, block generation confirmations, and synchronization start messages -- **`\033[33m`** (Orange): Snapshot import operations and P2P transfer progress -- **`\033[93m`** (Yellow): Synchronization progress, warnings, and informational messages (every 500 blocks) -- **`\033[96m`** (Cyan): Error conditions and critical failures -- **`\033[0m`** (Reset): Reset color formatting - -#### Enhanced Log Message Categories -- **Synchronization Start**: Green messages for sync start (logged once per session via `sync_start_logged` guard) -- **Synchronization Progress**: Yellow messages for block acceptance and sync progress every 500 blocks -- **Snapshot Operations**: Orange messages for import/export operations -- **Block Generation**: Green messages for successful block production -- **Error Conditions**: Cyan messages for critical failures -- **Debug Information**: Default color for low-level operational details -- **Network Sync**: Info-level messages for sync restarts and peer interactions with current head block numbers - -#### Guard Variables and Frequency Control -- **`sync_start_logged`**: Boolean guard variable to prevent duplicate sync start messages -- **Logging Frequency**: Progress logging occurs every 500 blocks instead of every 10,000 blocks for better monitoring -- **Terminal Compatibility**: ANSI color codes work in Unix/Linux terminals with proper color support -- **Windows Compatibility**: Windows terminals may require ANSI emulation for full color support -- **Fallback Behavior**: Non-color terminals automatically fall back to plain text output - -**Section sources** -- [plugin.cpp:58-113](file://plugins/chain/plugin.cpp#L58-L113) -- [snapshot_plugin.cpp:50-53](file://plugins/snapshot/plugin.cpp#L50-L53) -- [console_appender.cpp:132-154](file://thirdparty/fc/src/log/console_appender.cpp#L132-L154) -- [node.cpp:3446-3456](file://libraries/network/node.cpp#L3446-L3456) -- [validator.cpp:286](file://plugins/validator/validator.cpp#L286) - -### Network Synchronization Logging Reference -**New Section** Enhanced logging improvements for network synchronization - -#### Info-Level Logging Improvements -- **Sync Restart Reasons**: Now use ilog instead of dlog for better visibility -- **Peer Synchronization**: Enhanced logging for peer synchronization restarts -- **Block Acceptance**: Yellow progress messages with block numbers and producer information -- **Gap Calculations**: Current head block numbers and gap calculations in DLT mode - -#### Logging Categories -- **Sync Start**: Green messages with block numbers and head information -- **Sync Progress**: Yellow messages every 500 blocks with timestamp and producer -- **Sync End**: Reset messages when normal blocks are received -- **Network Events**: Info-level messages for sync restarts and peer interactions with enhanced detail - -**Section sources** -- [node.cpp:2428-2444](file://libraries/network/node.cpp#L2428-L2444) -- [node.cpp:3276-3280](file://libraries/network/node.cpp#L3276-L3280) -- [database.cpp:5295-5303](file://libraries/chain/database.cpp#L5295-L5303) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Database Management/DLT Rolling Block Log.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Database Management/DLT Rolling Block Log.md deleted file mode 100644 index 7917b065fb..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Database Management/DLT Rolling Block Log.md +++ /dev/null @@ -1,1572 +0,0 @@ -# DLT Rolling Block Log - - -**Referenced Files in This Document** -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [snapshot_plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) - - -## Update Summary -**Changes Made** -- Enhanced Windows compatibility with separate logical file size tracking to address memory-mapped file size drift after thousands of resize operations -- Implemented sophisticated mapping verification and healing mechanisms through verify_mapping() method -- Added comprehensive diagnostic capabilities including verify_continuity() and resize_count() methods -- Enhanced automatic gap recovery system with intelligent gap detection and DLT block log reset functionality -- Integrated periodic diagnostic monitoring into P2P stats task for DLT mode nodes -- Added signal-based integration with snapshot plugin for automatic fresh snapshot creation -- Enhanced gap logging and monitoring with automatic warning suppression through _dlt_gap_logged state management -- Added new reset() method for safe log clearing and reinitialization -- Enhanced diagnostic monitoring with comprehensive gap detection and integrity verification - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project_structure) -3. [Core Components](#core_components) -4. [Architecture Overview](#architecture_overview) -5. [Detailed Component Analysis](#detailed_component_analysis) -6. [Windows Compatibility and Memory Mapping Fixes](#windows-compatibility-and-memory-mapping-fixes) -7. [Crash Recovery and Atomic Operations](#crash-recovery-and-atomic-operations) -8. [Selective Retention Policies](#selective-retention-policies) -9. [Automatic Pruning Capabilities](#automatic-pruning-capabilities) -10. [Enhanced Blockchain Recovery System](#enhanced-blockchain-recovery-system) -11. [Configuration Management](#configuration-management) -12. [Dependency Analysis](#dependency-analysis) -13. [Performance Considerations](#performance-considerations) -14. [Enhanced Error Handling and Fallback Mechanisms](#enhanced-error-handling-and-fallback-mechanisms) -15. [DLT Mode Fork Database Seeding](#dlt-mode-fork-database-seeding) -16. [Enhanced Block Availability Checking](#enhanced-block-availability-checking) -17. [Stalled Sync Detection for DLT Nodes](#stalled-sync-detection-for-dlt-nodes) -18. [Enhanced Gap Handling During Synchronization](#enhanced-gap-handling-during-synchronization) -19. [DLT Block Log Accessibility Enhancement](#dlt-block-log-accessibility-enhancement) -20. [Comprehensive DLT Block Range Management System](#comprehensive-dlt-block-range-management-system) -21. [Enhanced P2P Synchronization Capabilities](#enhanced-p2p-synchronization-capabilities) -22. [Multi-Layered Fallback Mechanisms](#multi-layered-fallback-mechanisms) -23. [Enhanced DLT Block Log Reset Functionality](#enhanced-dlt-block-log-reset-functionality) -24. [Automatic Gap Recovery System](#automatic-gap-recovery-system) -25. [Enhanced Diagnostic and Monitoring Capabilities](#enhanced-diagnostic-and-monitoring-capabilities) -26. [Troubleshooting Guide](#troubleshooting-guide) -27. [Conclusion](#conclusion) - -## Introduction -This document explains the comprehensive DLT (Data Ledger Technology) Rolling Block Log implementation used by VIZ blockchain nodes to maintain a sliding window of recent irreversible blocks with selective retention policies and automatic pruning capabilities. The DLT mode provides advanced support for snapshot-based nodes, enabling efficient serving of recent blocks to P2P peers while maintaining configurable retention windows and automated cleanup mechanisms. Recent enhancements include critical Windows compatibility improvements with separate logical file size tracking, sophisticated mapping verification and healing mechanisms, enhanced diagnostic capabilities, and strengthened validation logic throughout the implementation. The latest architectural improvements introduce comprehensive Windows compatibility fixes, methods to synchronize and verify logical sizes against actual mapped sizes, healing mechanisms for file size mismatches, periodic mapping verification, improved block read/append logic using logical sizes with correctness assertions, and enhanced diagnostic capabilities through `verify_mapping()`, `verify_continuity()`, and `resize_count()` methods. - -## Project Structure -The DLT rolling block log is implemented as a standalone component with comprehensive integration into the main database system. It operates alongside the traditional block log while providing specialized functionality for snapshot-based ("DLT") nodes with selective retention and automatic pruning capabilities. - -```mermaid -graph TB -subgraph "Chain Layer" -DLT["dlt_block_log.hpp/.cpp"] -BL["block_log.cpp"] -DB["database.cpp"] -FD["fork_database.cpp"] -DH["database.hpp"] -END -subgraph "Plugins" -CP["plugins/chain/plugin.cpp"] -SP["plugins/snapshot/plugin.cpp"] -PP["plugins/p2p/p2p_plugin.cpp"] -END -CP --> DB -SP --> DB -PP --> DB -DB --> DLT -DB --> BL -DB --> FD -DLT -.-> BL -FD -.-> DB -CP --> DH -SP --> DH -PP --> DH -``` - -**Diagram sources** -- [dlt_block_log.hpp:1-89](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L89) -- [dlt_block_log.cpp:1-582](file://libraries/chain/dlt_block_log.cpp#L1-L582) -- [block_log.cpp:1-302](file://libraries/chain/block_log.cpp#L1-L302) -- [database.cpp:220-271](file://libraries/chain/database.cpp#L220-L271) -- [fork_database.cpp:1-258](file://libraries/chain/fork_database.cpp#L1-L258) -- [plugin.cpp:320-330](file://plugins/chain/plugin.cpp#L320-L330) -- [snapshot_plugin.cpp:1960-2039](file://plugins/snapshot/plugin.cpp#L1960-L2039) -- [p2p_plugin.cpp:255-286](file://plugins/p2p/p2p_plugin.cpp#L255-L286) -- [database.hpp:515-516](file://libraries/chain/include/graphene/chain/database.hpp#L515-L516) - -**Section sources** -- [dlt_block_log.hpp:1-89](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L89) -- [dlt_block_log.cpp:1-582](file://libraries/chain/dlt_block_log.cpp#L1-L582) -- [block_log.cpp:1-302](file://libraries/chain/block_log.cpp#L1-L302) -- [database.cpp:220-271](file://libraries/chain/database.cpp#L220-L271) -- [fork_database.cpp:1-258](file://libraries/chain/fork_database.cpp#L1-L258) -- [plugin.cpp:320-330](file://plugins/chain/plugin.cpp#L320-L330) -- [snapshot_plugin.cpp:1960-2039](file://plugins/snapshot/plugin.cpp#L1960-L2039) -- [p2p_plugin.cpp:255-286](file://plugins/p2p/p2p_plugin.cpp#L255-L286) -- [database.hpp:515-516](file://libraries/chain/include/graphene/chain/database.hpp#L515-L516) - -## Core Components -- **DLT Rolling Block Log API**: Provides comprehensive methods for opening/closing, appending blocks, selective reading by block number, querying head/start/end indices, intelligent truncation with retention policies, and the new reset() method for safe log clearing and reinitialization. -- **Windows-Compatible Memory-Mapped File System**: Implements sophisticated logical file size tracking separate from mapped_file.size() to handle Windows memory-mapped file size drift after thousands of resize() cycles, with automatic healing mechanisms and periodic verification. -- **Advanced Memory-Safe Implementation**: Manages sophisticated memory-mapped files for data and offset-aware index storage using std::memcpy operations instead of unsafe pointer casts, maintains head state with automatic validation, reconstructs indexes when inconsistencies are detected, and performs safe truncation with temporary files and atomic operations. -- **Integrated Database System**: Seamlessly opens both DLT rolling block log and primary block log during normal and snapshot modes, implements fallback block retrieval when primary block log is empty, coordinates DLT mode detection and operation with enhanced error handling, and includes automatic fork database seeding functionality. -- **Enhanced Fork Database Integration**: Provides sophisticated fork database management with automatic seeding from DLT block log, improved block availability checking logic, and enhanced P2P fallback mechanisms. -- **Comprehensive Chain Plugin Configuration**: Exposes runtime options for configuring maximum blocks to retain, selective retention policies, and automatic pruning thresholds with flexible parameter management. -- **Enhanced Snapshot Plugin Integration**: Provides improved block verification, checksum validation, and seamless transition to DLT mode after snapshot import with enhanced error handling and automatic snapshot creation upon DLT block log reset. -- **Enhanced P2P Fallback Mechanisms**: Implements graceful fallback from primary block log to DLT rolling block log with detailed error reporting and logging for DLT mode scenarios. -- **Stalled Sync Detection**: Implements automatic detection and recovery from stalled P2P sync for DLT nodes, with configurable timeout settings and automatic snapshot reload capabilities. -- **Enhanced Gap Handling**: Provides sophisticated gap management during synchronization between fork database and DLT block log, with automatic seeding, intelligent gap detection, and comprehensive recovery mechanisms including the new reset() method. -- **Enhanced Blockchain Recovery System**: Implements comprehensive recovery mechanisms including DLT block log replay functionality, crash recovery with atomic file operations, and enhanced error handling for corrupted states. -- **Enhanced DLT Block Log Accessibility**: Provides both const and non-const accessors for DLT block log functionality, enabling external components to modify DLT block log properties during runtime operations while maintaining read-only access for general use. -- **Comprehensive DLT Block Range Management System**: Implements precise block availability tracking with earliest_available_block_num() method, enabling sophisticated P2P synchronization with multi-layered fallback mechanisms and enhanced peer interaction handling. -- **Enhanced P2P Synchronization Capabilities**: Provides comprehensive block range validation, advertising prevention, and detailed error reporting for DLT mode operations with improved peer interaction handling. -- **Multi-Layered Fallback Mechanisms**: Implements sophisticated block retrieval chain with fork database, primary block log, and DLT block log fallback layers, providing robust error handling and graceful degradation. -- **Enhanced DLT Block Log Reset Functionality**: Provides safe log clearing and reinitialization through the new reset() method, enabling automatic recovery from synchronization gaps and improved operational flexibility. -- **Automatic Gap Recovery System**: Implements intelligent gap detection and automatic recovery mechanisms that monitor synchronization gaps between DLT block log and fork database, automatically resetting the DLT block log when gaps are detected and suppressing redundant warnings. -- **Enhanced Diagnostic and Monitoring System**: Provides comprehensive diagnostic capabilities through `verify_mapping()` method for periodic mapping consistency verification and `resize_count()` method for tracking resize operations, with detailed logging and healing mechanisms. -- **Enhanced Gap Detection and Recovery**: Provides sophisticated gap detection and automatic recovery mechanisms that monitor synchronization gaps between DLT block log and fork database, automatically resetting the DLT block log when gaps are detected and providing comprehensive gap logging and monitoring capabilities. - -**Enhanced Key Capabilities**: -- Offset-aware index layout supporting arbitrary start block numbers with intelligent retention policies -- Append-only storage with position checks ensuring sequential integrity and selective block management -- Automatic index reconstruction with conflict resolution and selective retention enforcement -- Safe truncation with temporary files, atomic swapping, and intelligent pruning based on configured limits -- Comprehensive DLT mode support with automatic fork database seeding and fallback mechanisms -- Enhanced block identification and verification during snapshot operations -- Improved error handling and validation for DLT mode operations -- Graceful fallback mechanisms with detailed logging for P2P block serving operations -- Strengthened block validation logic with comprehensive error reporting and synchronization handling -- **Windows Compatibility**: Separate logical file size tracking to handle memory-mapped file size drift after thousands of resize operations -- **Mapping Verification**: Periodic verification of logical vs. mapped file sizes with automatic healing mechanisms -- **Diagnostic Tracking**: Comprehensive resize operation counting for monitoring and debugging -- **Critical Memory Safety Improvements**: Replaced all unsafe uint64_t pointer casts with std::memcpy operations for cross-platform compatibility -- **Comprehensive Crash Recovery**: Implemented .bak file restoration mechanisms for atomic file operations during truncation -- **Enhanced Cross-Platform Compatibility**: Standardized file operations and memory-mapped file handling across platforms -- **Automatic Fork Database Seeding**: Enhanced DLT mode fork database seeding functionality that automatically seeds fork database from dlt_block_log when chain starts from fresh snapshot import -- **Improved Block Availability Checking**: Enhanced block availability checking logic with better DLT mode support and error handling -- **Stalled Sync Detection**: Automatic detection and recovery from stalled P2P sync with configurable timeouts and snapshot reload capabilities -- **Enhanced Gap Management**: Sophisticated gap handling during synchronization with automatic seeding, intelligent detection, and comprehensive recovery mechanisms -- **Enhanced Blockchain Recovery System**: New reindex_from_dlt method provides core functionality for rebuilding blockchain state from DLT rolling block log after snapshot import -- **Enhanced DLT Block Log Accessibility**: Both const and non-const accessors enable external components to modify DLT block log properties during runtime while maintaining read-only access for general use -- **Comprehensive DLT Block Range Management**: Precise block availability tracking with earliest_available_block_num() method for improved P2P synchronization -- **Enhanced P2P Synchronization**: Multi-layered fallback mechanisms with detailed error reporting and logging for DLT mode scenarios -- **Robust Fallback Chain**: Sophisticated block retrieval chain with fork database, primary block log, and DLT block log fallback layers -- **Enhanced DLT Block Log Reset**: Safe log clearing and reinitialization through reset() method with comprehensive cleanup of temporary and backup files -- **Automatic Gap Recovery**: Intelligent gap detection and automatic recovery mechanisms with automatic DLT block log reset and signal emission to snapshot plugin -- **Enhanced Gap Detection**: New verify_continuity() method provides comprehensive gap detection and integrity verification for DLT block log -- **Automatic Gap Recovery**: Enhanced gap detection and automatic recovery system with automatic DLT block log reset and signal emission to snapshot plugin -- **Enhanced P2P Integration**: Periodic integrity scanning using verify_continuity() method with comprehensive gap reporting and logging - -**Section sources** -- [dlt_block_log.hpp:35-89](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L89) -- [dlt_block_log.cpp:18-278](file://libraries/chain/dlt_block_log.cpp#L18-L278) -- [database.cpp:230-231](file://libraries/chain/database.cpp#L230-L231) -- [fork_database.cpp:24-28](file://libraries/chain/fork_database.cpp#L24-L28) -- [plugin.cpp:327-329](file://plugins/chain/plugin.cpp#L327-L329) -- [snapshot_plugin.cpp:1968-1970](file://plugins/snapshot/plugin.cpp#L1968-L1970) -- [p2p_plugin.cpp:265-272](file://plugins/p2p/p2p_plugin.cpp#L265-L272) -- [snapshot_plugin.cpp:1414-1500](file://plugins/snapshot/plugin.cpp#L1414-L1500) -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) -- [database.hpp:515-516](file://libraries/chain/include/graphene/chain/database.hpp#L515-L516) -- [database.cpp:835-858](file://libraries/chain/database.cpp#L835-L858) -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -## Architecture Overview -The DLT rolling block log operates in conjunction with the primary block log, providing comprehensive support for snapshot-based nodes with selective retention policies and automatic pruning. During normal operation, the database opens both logs and validates them. In DLT mode (after snapshot import), the primary block log remains empty while the database holds state; the DLT rolling block log serves as a fallback with intelligent retention management and enhanced block verification. The P2P layer now includes improved error handling with graceful fallback mechanisms and detailed logging for DLT mode scenarios. The enhanced accessibility model allows external components to modify DLT block log properties during runtime operations while maintaining read-only access for general use. The comprehensive DLT block range management system provides precise block availability tracking with the earliest_available_block_num() method, enabling sophisticated P2P synchronization with multi-layered fallback mechanisms. The new automatic gap recovery system provides intelligent gap detection and automatic recovery mechanisms that monitor synchronization gaps and automatically reset the DLT block log when necessary. The enhanced diagnostic system provides comprehensive monitoring through periodic mapping verification and resize operation tracking. - -```mermaid -sequenceDiagram -participant App as "Application" -participant Chain as "Chain Plugin" -participant DB as "Database" -participant SP as "Snapshot Plugin" -participant PP as "P2P Plugin" -participant DLT as "DLT Block Log" -participant BL as "Block Log" -participant FD as "Fork Database" -App->>Chain : Start node -Chain->>DB : open(data_dir, ...) -DB->>BL : open("block_log") -DB->>DLT : open("dlt_block_log") -alt Primary block log has head -DB->>DB : validate against chain state -else Empty block log (DLT mode) -DB->>DB : set_dlt_mode=true -DB->>DB : skip block log validation -DB->>FD : seed from dlt_block_log head -end -App->>DB : get_dlt_block_log() (non-const) -DB->>DLT : modify properties during runtime -App->>DB : get_dlt_block_log() (const) -DB->>DLT : read-only access for general use -App->>DB : fetch_block_by_number(n) -DB->>BL : read_block_by_num(n) -alt Found in primary log -BL-->>DB : block -else Not found -DB->>DLT : read_block_by_num(n) -alt Found in DLT log -DLT-->>DB : block (fallback) -else Not found -DB->>DB : check DLT mode -alt In DLT mode -DB->>PP : serve via P2P fallback -PP->>PP : log graceful fallback -PP-->>DB : key_not_found_exception -else Not in DLT mode -DB-->>App : block not found -end -end -DB-->>App : block -``` - -**Diagram sources** -- [database.cpp:230-268](file://libraries/chain/database.cpp#L230-L268) -- [database.cpp:560-627](file://libraries/chain/database.cpp#L560-L627) -- [block_log.cpp:238-241](file://libraries/chain/block_log.cpp#L238-241) -- [dlt_block_log.cpp:313-328](file://libraries/chain/dlt_block_log.cpp#L313-L328) -- [snapshot_plugin.cpp:1968-1970](file://plugins/snapshot/plugin.cpp#L1968-L1970) -- [p2p_plugin.cpp:259-286](file://plugins/p2p/p2p_plugin.cpp#L259-L286) -- [fork_database.cpp:24-28](file://libraries/chain/fork_database.cpp#L24-L28) -- [database.hpp:515-516](file://libraries/chain/include/graphene/chain/database.hpp#L515-L516) - -## Detailed Component Analysis - -### DLT Rolling Block Log API -The public interface defines comprehensive lifecycle, append, read, and maintenance operations with thread-safe access via read/write locks, supporting selective retention policies and automatic pruning capabilities. The new reset() method provides safe log clearing and reinitialization functionality, while the new verify_mapping(), verify_continuity(), and resize_count() methods provide enhanced diagnostic capabilities. - -```mermaid -classDiagram -class dlt_block_log { -+open(file) -+close() -+is_open() bool -+append(block) uint64_t -+flush() -+read_block_by_num(block_num) optional~signed_block~ -+head() optional~signed_block~ -+start_block_num() uint32_t -+head_block_num() uint32_t -+num_blocks() uint32_t -+truncate_before(new_start) -+reset() -+verify_mapping() bool -+verify_continuity() vector~uint32_t~ -+resize_count() uint64_t -} -``` - -**Diagram sources** -- [dlt_block_log.hpp:35-89](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L89) - -**Section sources** -- [dlt_block_log.hpp:35-89](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L89) - -### Windows-Compatible Memory-Mapped File System -The implementation now includes sophisticated Windows compatibility fixes through separate logical file size tracking. The system maintains independent logical sizes for block and index files, tracking them separately from mapped_file.size() to handle Windows memory-mapped file size drift after thousands of resize() cycles. This prevents get_block_pos() from rejecting valid block numbers due to stale mapping metadata. - -**Key Windows Compatibility Features**: -- Separate logical file size tracking for block and index files -- Independent tracking of _logical_block_size and _logical_index_size -- Automatic healing mechanisms for stale mapping detection -- Periodic verification through verify_mapping() method -- Enhanced read_block() and get_block_pos() logic using logical sizes -- Comprehensive resize operation counting for diagnostic purposes - -**Enhanced Memory Mapping Architecture**: -- Logical sizes tracked independently of mapped_file.size() -- After thousands of resize() cycles, mapped_file.size() can return stale values -- By tracking logical sizes ourselves, we avoid this Windows-specific bug -- Enhanced get_block_pos() uses logical_index_size() instead of mapped_file.size() -- Improved read_block() validates against logical_block_size() for correctness - -**Section sources** -- [dlt_block_log.cpp:31-38](file://libraries/chain/dlt_block_log.cpp#L31-L38) -- [dlt_block_log.cpp:59-66](file://libraries/chain/dlt_block_log.cpp#L59-L66) -- [dlt_block_log.cpp:119-136](file://libraries/chain/dlt_block_log.cpp#L119-L136) -- [dlt_block_log.cpp:138-155](file://libraries/chain/dlt_block_log.cpp#L138-L155) - -### Advanced Memory-Safe Implementation Details -The implementation manages sophisticated memory-mapped files with comprehensive error handling, intelligent validation, and automatic recovery mechanisms. It enforces strict position checks using std::memcpy operations instead of unsafe pointer casts, implements selective retention policies, and provides automatic pruning capabilities. - -**Key Advanced Behaviors**: -- Sophisticated memory-mapped files for zero-copy reads with comprehensive error handling using std::memcpy -- Offset-aware index with intelligent header management and selective entry tracking using safe memory operations -- Strict position validation during append operations with conflict resolution using FC_ASSERT -- Intelligent index reconstruction with selective retention enforcement using atomic memory operations -- Safe truncation using temporary files with atomic swap and comprehensive validation -- Automatic pruning based on configured retention limits with selective block management -- **Enhanced Reset Functionality**: Safe log clearing and reinitialization with comprehensive cleanup of temporary and backup files -- **Windows Compatibility**: Separate logical file size tracking to handle memory-mapped file size drift -- **Mapping Verification**: Periodic consistency checking with automatic healing mechanisms - -**Updated** Enhanced memory safety through std::memcpy operations replacing unsafe uint64_t pointer casts throughout the implementation - -```mermaid -flowchart TD -Start([Open DLT Log]) --> CheckFiles["Check block/index existence"] -CheckFiles --> HasBlocks{"Has block records?"} -HasBlocks --> |Yes| ReadHead["Read head block"] -ReadHead --> HasIndex{"Has index records?"} -HasBlocks --> |No| WipeIndex{"Index exists?"} -WipeIndex --> |Yes| ReconstructIndex["Intelligent reconstruction"] -WipeIndex --> |No| Ready["Ready"] -HasIndex --> |Yes| CompareHeads["Compare last positions"] -CompareHeads --> Match{"Positions match?"} -Match --> |Yes| Ready -Match --> |No| ReconstructIndex -HasIndex --> |No| ReconstructIndex -ReconstructIndex --> ApplyRetention["Apply retention policies"] -ApplyRetention --> Ready -``` - -**Diagram sources** -- [dlt_block_log.cpp:161-209](file://libraries/chain/dlt_block_log.cpp#L161-L209) -- [dlt_block_log.cpp:125-159](file://libraries/chain/dlt_block_log.cpp#L125-L159) - -**Section sources** -- [dlt_block_log.cpp:18-278](file://libraries/chain/dlt_block_log.cpp#L18-L278) - -### Enhanced Append Operation Flow -The append operation validates sequential positioning with intelligent conflict resolution, writes block data with trailing position markers using std::memcpy, updates the index with selective retention enforcement, and maintains head state with automatic pruning triggers. The operation now uses logical sizes for validation and tracking. - -**Updated** Memory-safe append operations using std::memcpy for all data transfers, with enhanced validation using logical sizes - -```mermaid -sequenceDiagram -participant Client as "Caller" -participant DLT as "dlt_block_log_impl" -participant BF as "Block File" -participant IF as "Index File" -Client->>DLT : append(block) -DLT->>IF : resize to expected index size -DLT->>BF : resize to current file size + packed block + 8 -DLT->>BF : write packed block + trailing position (std : : memcpy) -DLT->>IF : write index entry (std : : memcpy) -DLT->>DLT : update head/head_id -DLT->>DLT : check retention limits -alt Exceeds 2x limit -DLT->>DLT : trigger automatic pruning -end -DLT-->>Client : block position -``` - -**Diagram sources** -- [dlt_block_log.cpp:211-268](file://libraries/chain/dlt_block_log.cpp#L211-L268) - -**Section sources** -- [dlt_block_log.cpp:211-268](file://libraries/chain/dlt_block_log.cpp#L211-L268) - -### Intelligent Truncation Process -Truncation creates temporary files containing only retained blocks with selective retention enforcement, then atomically replaces the original files with comprehensive validation and automatic cleanup using .bak files for crash recovery. - -**Updated** Enhanced truncation with comprehensive crash recovery using .bak file restoration - -```mermaid -flowchart TD -TStart([Truncate Before]) --> Validate["Validate range and open state"] -Validate --> CheckRetention["Check retention policies"] -CheckRetention --> BuildTemp["Build temp files with retained blocks"] -BuildTemp --> CloseOrig["Close original files"] -CloseOrig --> Swap["Swap temp -> original (.bak backup)"] -Swap --> Reopen["Reopen with new state"] -Reopen --> Cleanup["Cleanup temporary files"] -Cleanup --> TEnd([Complete]) -``` - -**Diagram sources** -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) - -**Section sources** -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) - -### Enhanced Reset Functionality -The new reset() method provides safe log clearing and reinitialization capabilities. It closes the current log, deletes all data and index files, removes stale temporary and backup files, then reopens the log as empty. This functionality is crucial for automatic gap recovery and synchronization gap management. - -**Enhanced Reset Features**: -- Safe log clearing with comprehensive file cleanup including temporary and backup files -- Atomic file deletion and recreation process with proper error handling -- Preservation of file path and configuration while resetting internal state -- Comprehensive logging with old range information for debugging and monitoring -- Cleanup of .tmp and .bak files that may be left from interrupted operations - -**Section sources** -- [dlt_block_log.cpp:523-543](file://libraries/chain/dlt_block_log.cpp#L523-L543) - -### Enhanced Gap Detection and Recovery System -The new verify_continuity() method provides comprehensive gap detection and integrity verification for the DLT block log. This method walks the entire block range and identifies missing or unreadable blocks, returning a vector of block numbers that need attention. The database now includes sophisticated gap detection between DLT block log and fork database, with automatic recovery mechanisms that reset the DLT block log when gaps are detected. - -**Enhanced Gap Detection Features**: -- `verify_continuity()` method: Walks entire block range and identifies missing/unreadable blocks -- Returns vector of block numbers that are missing or unreadable for comprehensive gap reporting -- O(N) complexity where N = num_blocks, used sparingly (e.g., stats task) -- Integration with automatic gap recovery system for seamless gap management -- Comprehensive gap logging with automatic warning suppression to prevent redundant messages - -**Enhanced Gap Recovery System Features**: -- Automatic detection of gaps between dlt_end and fork_db_start positions -- Intelligent gap detection with configurable thresholds and automatic DLT block log reset -- Seamless continuation of block synchronization after gap recovery -- Integration with snapshot plugin for automatic fresh snapshot creation -- Enhanced logging with detailed gap information and recovery actions -- Prevention of repeated gap recovery operations for the same gap condition -- **Enhanced State Management**: Automatic suppression of redundant gap warnings through _dlt_gap_logged flag - -**Enhanced Gap Recovery Process**: -- Detection of gap between dlt_end and fork_db_start positions -- Identification of earliest available block in fork database -- Automatic reset() method invocation to clear DLT block log -- Sequential writing of available blocks from fork database -- Signal emission to snapshot plugin for fresh snapshot creation -- Continued gap monitoring and recovery as needed -- **Enhanced Warning Suppression**: Automatic logging state management to prevent redundant gap warnings - -```mermaid -flowchart TD -GapDetection["Gap Detection"] --> CheckGap{"Gap > Threshold?"} -CheckGap --> |No| ContinueSync["Continue Normal Sync"] -CheckGap --> |Yes| FindForkStart["Find Earliest Fork Block"] -FindForkStart --> ResetDLT["Call reset() method"] -ResetDLT --> WriteBlocks["Write Available Blocks"] -WriteBlocks --> EmitSignal["Emit dlt_block_log_was_reset"] -EmitSignal --> CreateSnapshot["Create Fresh Snapshot"] -CreateSnapshot --> ContinueSync -ContinueSync -``` - -**Diagram sources** -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -**Section sources** -- [dlt_block_log.cpp:576-602](file://libraries/chain/dlt_block_log.cpp#L576-L602) -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -### Enhanced Diagnostic and Monitoring Capabilities -The new diagnostic system provides comprehensive monitoring through two key methods: `verify_mapping()` for periodic mapping consistency verification and `resize_count()` for tracking resize operations. These methods enable proactive detection and healing of Windows memory-mapped file size drift issues. - -**Enhanced Diagnostic Features**: -- `verify_mapping()` method: Checks logical vs. mapped file size consistency and heals stale mappings -- `verify_continuity()` method: Walks entire block range and reports gaps for integrity verification -- `resize_count()` method: Tracks number of resize operations since log open for diagnostic purposes -- Periodic verification integrated into P2P stats task for DLT mode nodes -- Comprehensive logging with detailed information about mapping status and healing actions -- Automatic reopening of files when stale mapping is detected - -**Enhanced Mapping Verification Process**: -- Compares mapped_file.size() with tracked _logical_block_size and _logical_index_size -- Detects stale mapping after thousands of resize() cycles -- Automatically closes and reopens files to refresh mapping -- Logs detailed information about detected inconsistencies and healing actions -- Prevents get_block_pos() from rejecting valid block numbers due to stale metadata - -**Enhanced Gap Integrity Scanning**: -- Periodic verification of DLT block log integrity through verify_continuity() method -- Comprehensive gap reporting with detailed block number information -- Integration with P2P stats task for automatic gap detection and logging -- Enhanced error reporting with gap count and missing block information -- Automatic gap suppression to prevent redundant logging - -**Section sources** -- [dlt_block_log.cpp:545-579](file://libraries/chain/dlt_block_log.cpp#L545-L579) -- [dlt_block_log.cpp:576-602](file://libraries/chain/dlt_block_log.cpp#L576-L602) -- [p2p_plugin.cpp:761-765](file://plugins/p2p/p2p_plugin.cpp#L761-L765) - -### Integrated Database Operations -The database seamlessly integrates DLT block log alongside block_log.cpp, coordinating fallback retrieval, DLT mode detection, selective retention enforcement, automatic pruning with comprehensive state management and enhanced error handling. - -```mermaid -sequenceDiagram -participant DB as "Database" -participant BL as "Block Log" -participant DLT as "DLT Block Log" -DB->>BL : open("block_log") -DB->>DLT : open("dlt_block_log") -alt Head block present in BL -DB->>DB : validate chain state -else Empty BL (DLT mode) -DB->>DB : set_dlt_mode=true -DB->>DB : skip validation -DB->>DB : seed fork_db from dlt_block_log -end -DB->>DLT : read_block_by_num(n) (fallback) -DB->>DB : check retention limits -alt Exceeds 2x limit -DB->>DLT : trigger automatic pruning -end -``` - -**Diagram sources** -- [database.cpp:230-268](file://libraries/chain/database.cpp#L230-L268) -- [database.cpp:560-627](file://libraries/chain/database.cpp#L560-L627) -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -**Section sources** -- [database.cpp:230-268](file://libraries/chain/database.cpp#L230-L268) -- [database.cpp:560-627](file://libraries/chain/database.cpp#L560-L627) -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -### Enhanced Snapshot Plugin Integration -The snapshot plugin provides comprehensive integration with DLT mode operations, including improved block verification, checksum validation, and seamless transition to DLT mode after snapshot import with enhanced error handling. The plugin now listens for the dlt_block_log_was_reset signal to automatically create fresh snapshots for other DLT nodes. - -**Enhanced Snapshot Operations**: -- Improved block identification and verification during snapshot loading -- Enhanced checksum validation with comprehensive error reporting -- Seamless transition to DLT mode with proper state initialization -- Better integration with database layer for DLT mode operations -- Enhanced fallback mechanisms for block verification and serving -- Stalled sync detection for automatic recovery from stalled P2P sync -- Automatic snapshot reload capabilities with DLT mode support -- **Enhanced Signal Integration**: Listens for dlt_block_log_was_reset signal to create fresh snapshots for other DLT nodes - -**Section sources** -- [snapshot_plugin.cpp:1968-1970](file://plugins/snapshot/plugin.cpp#L1968-L1970) -- [snapshot_plugin.cpp:942-1054](file://plugins/snapshot/plugin.cpp#L942-L1054) -- [snapshot_plugin.cpp:1414-1500](file://plugins/snapshot/plugin.cpp#L1414-L1500) -- [snapshot_plugin.cpp:2790-2791](file://plugins/snapshot/plugin.cpp#L2790-L2791) -- [snapshot_plugin.cpp:3254](file://plugins/snapshot/plugin.cpp#L3254) - -### Enhanced Blockchain Recovery System -The new enhanced blockchain recovery system provides comprehensive crash recovery capabilities through DLT block log replay functionality. The reindex_from_dlt method enables rebuilding blockchain state from DLT rolling block log after snapshot import, with enhanced error handling, progress tracking, and comprehensive logging. - -**Key Recovery Features**: -- DLT block log replay functionality for crash recovery scenarios with progress tracking -- Automatic DLT mode activation during recovery operations with enhanced validation -- Enhanced fork database seeding with proper block validation and P2P synchronization -- Comprehensive error handling with detailed logging and graceful degradation mechanisms -- Selective block replay with progress tracking and memory management optimization -- Atomic operation support with temporary file management for data integrity -- Enhanced logging with percentage completion and memory usage reporting -- Graceful handling of interrupted recovery operations with automatic cleanup - -**Section sources** -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) -- [plugin.cpp:542-555](file://plugins/chain/plugin.cpp#L542-L555) - -## Windows Compatibility and Memory Mapping Fixes - -### Separate Logical File Size Tracking -The DLT block log implementation now includes sophisticated Windows compatibility fixes through separate logical file size tracking. This addresses a critical issue where Windows memory-mapped file size can become stale after thousands of resize() cycles, causing get_block_pos() to reject valid block numbers. - -**Windows Compatibility Features**: -- Separate tracking of _logical_block_size and _logical_index_size independent of mapped_file.size() -- Independent file size validation using logical sizes instead of mapped_file.size() -- Automatic healing mechanisms for stale mapping detection and recovery -- Enhanced get_block_pos() logic that uses logical_index_size() for validation -- Improved read_block() validation against logical_block_size() for correctness - -**Enhanced Memory Mapping Architecture**: -- Logical sizes tracked separately from mapped_file.size() to handle Windows drift -- After thousands of resize() cycles, mapped_file.size() can return stale values -- By tracking logical sizes ourselves, we avoid Windows-specific memory-mapped file size bugs -- Enhanced validation logic prevents rejection of valid block numbers due to stale metadata - -**Section sources** -- [dlt_block_log.cpp:31-38](file://libraries/chain/dlt_block_log.cpp#L31-L38) -- [dlt_block_log.cpp:59-66](file://libraries/chain/dlt_block_log.cpp#L59-L66) -- [dlt_block_log.cpp:119-136](file://libraries/chain/dlt_block_log.cpp#L119-L136) -- [dlt_block_log.cpp:138-155](file://libraries/chain/dlt_block_log.cpp#L138-L155) - -### Mapping Verification and Healing Mechanisms -The `verify_mapping()` method provides comprehensive periodic verification of mapping consistency and automatic healing of stale mappings. This method is automatically called from the P2P stats task for DLT mode nodes to detect and heal Windows memory-mapped file size drift issues. - -**Enhanced Mapping Verification Features**: -- Periodic verification of logical vs. mapped file size consistency -- Automatic detection and healing of stale mappings after thousands of resize operations -- Detailed logging with information about detected inconsistencies and healing actions -- Automatic reopening of files when stale mapping is detected -- Integration with P2P stats task for DLT mode nodes - -**Enhanced Healing Process**: -- Compares mapped_file.size() with tracked _logical_block_size and _logical_index_size -- Detects stale mapping after extensive file resizing operations -- Automatically closes and reopens files to refresh memory mapping -- Logs detailed information about mapping status and healing actions -- Prevents data corruption or block access issues due to stale metadata - -**Section sources** -- [dlt_block_log.cpp:74-100](file://libraries/chain/dlt_block_log.cpp#L74-L100) -- [dlt_block_log.cpp:545-574](file://libraries/chain/dlt_block_log.cpp#L545-L574) -- [p2p_plugin.cpp:761-765](file://plugins/p2p/p2p_plugin.cpp#L761-L765) - -### Enhanced Block Read/Append Logic Using Logical Sizes -The block read and append logic has been enhanced to use logical sizes instead of mapped_file.size() for improved reliability and cross-platform compatibility. This change ensures consistent behavior across different operating systems and prevents issues caused by stale memory-mapped file size metadata. - -**Enhanced Read Logic**: -- `read_block()` now validates against logical_block_size() instead of mapped_file.size() -- Enhanced position validation with detailed error reporting -- Correctness assertions using logical sizes for block boundary validation -- Improved error messages with logical size information - -**Enhanced Append Logic**: -- `append()` uses logical_index_size() for position validation -- Enhanced index entry validation with logical size tracking -- Improved error handling with detailed context information -- Better integration with resize operation tracking - -**Section sources** -- [dlt_block_log.cpp:138-155](file://libraries/chain/dlt_block_log.cpp#L138-L155) -- [dlt_block_log.cpp:304-369](file://libraries/chain/dlt_block_log.cpp#L304-L369) - -## Crash Recovery and Atomic Operations - -### Comprehensive Crash Recovery Mechanisms -The DLT block log implementation includes sophisticated crash recovery mechanisms that ensure data integrity even during unexpected shutdowns or system failures. The system automatically detects and recovers from interrupted operations using .bak file restoration. - -**Crash Recovery Features**: -- Automatic detection of interrupted truncation operations through .bak file monitoring -- Safe restoration of data from .bak files when original files are missing or corrupted -- Atomic file operations using temporary files (.tmp) and backup files (.bak) -- Comprehensive cleanup of stale temporary files during startup -- Graceful degradation when crash recovery is not possible - -**Updated** Enhanced crash recovery with .bak file restoration and atomic operation guarantees - -```mermaid -flowchart TD -Startup([Startup]) --> CheckBak["Check .bak files"] -CheckBak --> BakExists{"Bak files exist?"} -BakExists --> |Yes| CheckOriginals["Check original file status"] -CheckOriginals --> OriginalMissing{"Originals missing/empty?"} -OriginalMissing --> |Yes| RestoreFromBak["Restore from .bak files"] -OriginalMissing --> |No| CleanBak["Clean .bak files"] -BakExists --> |No| NormalStartup["Normal startup"] -RestoreFromBak --> NormalStartup -CleanBak --> NormalStartup -``` - -**Diagram sources** -- [dlt_block_log.cpp:172-202](file://libraries/chain/dlt_block_log.cpp#L172-L202) - -**Section sources** -- [dlt_block_log.cpp:172-202](file://libraries/chain/dlt_block_log.cpp#L172-L202) -- [dlt_block_log.cpp:432-444](file://libraries/chain/dlt_block_log.cpp#L432-L444) - -### Atomic File Operations -The truncation process implements atomic file operations to ensure data consistency. The system uses a three-phase approach: backup original files, write new files to temporary locations, then atomically replace originals. - -**Atomic Operation Features**: -- Backup original files to .bak before any modifications -- Write new data to .tmp files to avoid partial writes -- Atomic rename operations that are guaranteed to succeed or fail completely -- Automatic cleanup of backup files after successful operations -- Comprehensive rollback capability if operations fail - -**Section sources** -- [dlt_block_log.cpp:432-444](file://libraries/chain/dlt_block_log.cpp#L432-L444) - -## Selective Retention Policies -The DLT rolling block log implements sophisticated selective retention policies that allow fine-grained control over which blocks are maintained and when automatic pruning occurs. These policies ensure optimal disk usage while maintaining serviceability for P2P peers. - -**Retention Policy Features**: -- Configurable maximum block retention with runtime parameter control -- Intelligent pruning threshold management (2x limit vs. configured retention) -- Selective block preservation based on last irreversible block (LIB) boundaries -- Automatic cleanup of obsolete blocks while preserving serviceable ranges -- Flexible retention window adjustment for different operational requirements - -**Section sources** -- [plugin.cpp:327-329](file://plugins/chain/plugin.cpp#L327-L329) -- [database.cpp:4005-4036](file://libraries/chain/database.cpp#L4005-L4036) -- [database.cpp:4170-4172](file://libraries/chain/database.cpp#L4170-L4172) -- [database.cpp:4392-4394](file://libraries/chain/database.cpp#L4392-L4394) - -## Automatic Pruning Capabilities -The DLT rolling block log provides comprehensive automatic pruning capabilities that maintain optimal performance and disk usage through intelligent block lifecycle management and selective cleanup operations. - -**Pruning Mechanism Features**: -- Automatic pruning triggered when block count exceeds 2x configured retention limit -- Intelligent block range calculation based on head block number and retention policy -- Atomic file replacement with temporary file management for data integrity -- Comprehensive validation and cleanup of temporary files after successful pruning -- Selective pruning that preserves serviceable blocks while removing obsolete data - -**Section sources** -- [database.cpp:4043-4047](file://libraries/chain/database.cpp#L4043-L4047) -- [database.cpp:4189-4192](file://libraries/chain/database.cpp#L4189-L4192) -- [database.cpp:4419-4421](file://libraries/chain/database.cpp#L4419-L4421) - -## Enhanced Blockchain Recovery System -The new enhanced blockchain recovery system provides comprehensive crash recovery capabilities through DLT block log replay functionality. The reindex_from_dlt method enables rebuilding blockchain state from DLT rolling block log after snapshot import, with enhanced error handling, progress tracking, and comprehensive logging. - -**Enhanced Recovery System Features**: -- DLT block log replay functionality with progress tracking and percentage completion reporting -- Automatic DLT mode activation during recovery operations with enhanced validation logic -- Enhanced fork database seeding with proper block validation and P2P synchronization support -- Comprehensive error handling with detailed logging and graceful degradation mechanisms -- Selective block replay with progress tracking, memory management optimization, and checkpointing -- Atomic operation support with temporary file management for data integrity during recovery -- Enhanced logging with memory usage reporting and performance metrics -- Graceful handling of interrupted recovery operations with automatic cleanup and state restoration - -**Section sources** -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) -- [plugin.cpp:542-555](file://plugins/chain/plugin.cpp#L542-L555) - -## Configuration Management -The chain plugin provides comprehensive runtime configuration management for DLT rolling block log operations, allowing flexible control over retention policies, pruning thresholds, and operational parameters. - -**Configuration Parameters**: -- `dlt-block-log-max-blocks`: Maximum number of recent blocks to keep in the rolling DLT block log (default: 100,000) -- Runtime parameter validation and enforcement -- Integration with database state management for seamless operation -- Support for disabling DLT block log functionality (0 = disabled) - -**Section sources** -- [plugin.cpp:233-236](file://plugins/chain/plugin.cpp#L233-L236) -- [plugin.cpp:326-329](file://plugins/chain/plugin.cpp#L326-L329) - -## Dependency Analysis -The DLT rolling block log implementation has comprehensive dependencies across multiple system components, providing robust integration with the blockchain infrastructure while maintaining separation of concerns. - -**Core Dependencies**: -- dlt_block_log.hpp/cpp depends on: - - Protocol block definitions for signed blocks with comprehensive serialization - - Boost iostreams for advanced memory-mapped file access with error handling - - Boost filesystem for sophisticated file operations and cleanup - - FC library for comprehensive assertions, data streams, and logging -- database.cpp integrates DLT block log with comprehensive fallback mechanisms and state management -- plugin.cpp configures DLT rolling block log with runtime parameter management and validation -- snapshot_plugin.cpp provides enhanced integration with DLT mode operations and block verification -- p2p_plugin.cpp implements enhanced error handling and graceful fallback mechanisms for DLT mode scenarios -- fork_database.cpp provides enhanced fork database management with automatic seeding capabilities - -```mermaid -graph LR -DLT_H["dlt_block_log.hpp"] --> DLT_CPP["dlt_block_log.cpp"] -DLT_CPP --> BL_CPP["block_log.cpp"] -DLT_CPP --> DB_CPP["database.cpp"] -DLT_CPP --> FD_CPP["fork_database.cpp"] -CP_CPP["plugins/chain/plugin.cpp"] --> DB_CPP -SP_CPP["plugins/snapshot/plugin.cpp"] --> DB_CPP -PP_CPP["plugins/p2p/p2p_plugin.cpp"] --> DB_CPP -DB_CPP --> DH_HPP["database.hpp"] -``` - -**Diagram sources** -- [dlt_block_log.hpp:1-10](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L10) -- [dlt_block_log.cpp:1-7](file://libraries/chain/dlt_block_log.cpp#L1-L7) -- [block_log.cpp:1-6](file://libraries/chain/block_log.cpp#L1-L6) -- [database.cpp:1-10](file://libraries/chain/database.cpp#L1-L10) -- [fork_database.cpp:1-6](file://libraries/chain/fork_database.cpp#L1-L6) -- [plugin.cpp:1-10](file://plugins/chain/plugin.cpp#L1-L10) -- [snapshot_plugin.cpp:1960-2039](file://plugins/snapshot/plugin.cpp#L1960-L2039) -- [p2p_plugin.cpp:1-10](file://plugins/p2p/p2p_plugin.cpp#L1-L10) - -**Section sources** -- [dlt_block_log.hpp:1-10](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L10) -- [dlt_block_log.cpp:1-7](file://libraries/chain/dlt_block_log.cpp#L1-L7) -- [block_log.cpp:1-6](file://libraries/chain/block_log.cpp#L1-L6) -- [database.cpp:1-10](file://libraries/chain/database.cpp#L1-L10) -- [fork_database.cpp:1-6](file://libraries/chain/fork_database.cpp#L1-L6) -- [plugin.cpp:1-10](file://plugins/chain/plugin.cpp#L1-L10) -- [snapshot_plugin.cpp:1960-2039](file://plugins/snapshot/plugin.cpp#L1960-L2039) -- [p2p_plugin.cpp:1-10](file://plugins/p2p/p2p_plugin.cpp#L1-L10) - -## Performance Considerations -The DLT rolling block log implementation provides optimized performance characteristics through advanced memory management, intelligent caching strategies, and efficient I/O operations designed for high-throughput blockchain operations. - -**Performance Optimizations**: -- Sophisticated memory-mapped files enabling zero-copy reads with comprehensive error handling -- Offset-aware index allowing O(1) lookup performance with intelligent caching -- Batched write operations with selective retention enforcement for optimal throughput -- Intelligent truncation scheduling during low-traffic periods to minimize latency impact -- Configurable retention limits preventing excessive disk usage and rebuild overhead -- Automatic pruning reduces fragmentation and maintains optimal file system performance -- **Enhanced Memory Safety**: std::memcpy operations provide predictable performance across platforms -- **Improved Reliability**: Crash recovery mechanisms eliminate data corruption risks -- **Enhanced Recovery Performance**: DLT block log replay provides faster recovery than full blockchain reindex -- **Optimized Recovery Operations**: Progress tracking and memory management improve recovery performance -- **Enhanced Runtime Access**: Non-const accessor methods enable efficient runtime property modifications -- **Comprehensive Range Management**: earliest_available_block_num() method provides precise block availability tracking -- **Enhanced P2P Performance**: Multi-layered fallback mechanisms reduce error rates and improve synchronization -- **Enhanced Reset Performance**: Efficient log clearing and reinitialization with minimal overhead -- **Automatic Gap Recovery**: Intelligent gap detection and recovery mechanisms improve synchronization reliability -- **Windows Compatibility**: Separate logical file size tracking prevents performance issues on Windows systems -- **Enhanced Diagnostic Performance**: verify_mapping() method provides efficient periodic verification without blocking operations -- **Improved Memory Mapping**: Logical size tracking reduces memory-mapped file size drift issues and improves reliability -- **Enhanced Gap Detection Performance**: verify_continuity() method provides efficient gap detection with minimal overhead -- **Automatic Gap Recovery Performance**: Seamless gap recovery without manual intervention improves operational efficiency - -## Enhanced Error Handling and Fallback Mechanisms - -### Improved DLT Mode Detection and Validation -The database now includes enhanced DLT mode detection with improved validation logic. When the primary block log is empty but the database has state (loaded from snapshot), the system sets DLT mode and skips block log validation with comprehensive logging and graceful fallback mechanisms. - -**Enhanced DLT Mode Features**: -- Improved detection logic when block_log.read_block_by_num(head_block_num()) fails -- Enhanced logging with detailed information about DLT mode activation -- Graceful fallback mechanisms that prevent crashes when block data is unavailable -- Better integration between database and DLT block log for seamless operation -- Comprehensive error reporting for DLT mode initialization and operation - -```mermaid -flowchart TD -OpenDB["Open Database"] --> CheckBL["Check block_log head"] -CheckBL --> HasHead{"Head block present?"} -HasHead --> |Yes| ValidateState["Validate chain state"] -HasHead --> |No| SetDLT["Set DLT mode"] -SetDLT --> SkipValidation["Skip block log validation"] -SkipValidation --> SeedForkDB["Seed fork_db from dlt_block_log"] -SeedForkDB --> LogDLT["Log DLT mode activation"] -LogDLT --> Ready["Ready for DLT operations"] -``` - -**Diagram sources** -- [database.cpp:250-271](file://libraries/chain/database.cpp#L250-L271) -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -**Section sources** -- [database.cpp:259-268](file://libraries/chain/database.cpp#L259-L268) -- [database.cpp:262-267](file://libraries/chain/database.cpp#L262-L267) -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -### Enhanced P2P Fallback Implementation -The P2P plugin now includes significantly enhanced error handling specifically designed for DLT mode scenarios. When serving blocks to peers in DLT mode, the system gracefully handles cases where block data may not be available for certain ranges, providing detailed logging and appropriate error responses with comprehensive error reporting. - -**Enhanced P2P Error Handling Features**: -- Graceful fallback from primary block log to DLT rolling block log with detailed logging -- Specialized error handling for DLT mode where block data may not be available for early blocks -- Improved error reporting with specific messages for DLT mode block availability issues -- Proper exception handling with fc::key_not_found_exception for unavailable blocks in DLT mode -- Enhanced debugging information for troubleshooting DLT mode block serving issues -- Detailed logging with "Block ${id} not available in DLT mode (no block data for this range)" messages -- Graceful fallback mechanism that logs detailed information about why blocks are unavailable - -```mermaid -flowchart TD -GetItem["get_item(id)"] --> CheckType{"Is block message?"} -CheckType --> |Yes| FetchFromDB["Fetch from database"] -FetchFromDB --> Found{"Block found?"} -Found --> |Yes| ReturnBlock["Return block"] -Found --> |No| CheckDLTMode{"In DLT mode?"} -CheckDLTMode --> |Yes| LogFallback["Log graceful fallback with detailed message"] -LogFallback --> ThrowException["Throw key_not_found_exception"] -CheckDLTMode --> |No| LogError["Log detailed error with block ID"] -LogError --> ReturnEmpty["Return empty response"] -Found --> |No| CheckDLTMode -``` - -**Diagram sources** -- [p2p_plugin.cpp:259-286](file://plugins/p2p/p2p_plugin.cpp#L259-L286) - -**Section sources** -- [p2p_plugin.cpp:265-272](file://plugins/p2p/p2p_plugin.cpp#L265-L272) - -### Enhanced Database Fallback Logic -The database layer now includes improved fallback mechanisms with better error handling and logging for DLT mode operations. The fallback logic provides more detailed information about why blocks may not be available and handles edge cases more gracefully with comprehensive error reporting. - -**Enhanced Database Fallback Features**: -- Improved logging for DLT mode fallback scenarios with detailed error messages -- Better error reporting when blocks are not found in either log -- Enhanced validation and error handling for DLT mode operations -- More informative error messages for debugging DLT mode issues -- Graceful handling of edge cases in block serving operations -- Comprehensive fallback chain: fork_db → block_log → dlt_block_log → error - -**Section sources** -- [database.cpp:576-580](file://libraries/chain/database.cpp#L576-L580) -- [database.cpp:609-613](file://libraries/chain/database.cpp#L609-L613) - -### Enhanced Storage-Related Error Reporting -The system now provides comprehensive error reporting for storage-related issues in DLT mode, including detailed logging of block availability problems and graceful degradation mechanisms. - -**Enhanced Storage Error Handling**: -- Detailed logging of block availability issues in DLT mode -- Graceful fallback mechanisms that prevent crashes when blocks are unavailable -- Comprehensive error messages that help operators diagnose storage issues -- Proper exception handling that maintains system stability -- Enhanced debugging information for troubleshooting DLT mode block serving - -**Section sources** -- [database.cpp:599-621](file://libraries/chain/database.cpp#L599-L621) -- [database.cpp:623-640](file://libraries/chain/database.cpp#L623-L640) - -### Strengthened Block Validation Logic -The DLT block log implementation now includes enhanced validation logic with comprehensive error checking and reporting. The validation ensures data integrity and provides detailed feedback when inconsistencies are detected. - -**Enhanced Validation Features**: -- Improved position validation during append operations with conflict resolution -- Enhanced index reconstruction with selective retention enforcement -- Better error reporting for validation failures and recovery operations -- Comprehensive logging of validation results and corrective actions -- Strengthened block verification with detailed error messages - -**Section sources** -- [dlt_block_log.cpp:241-249](file://libraries/chain/dlt_block_log.cpp#L241-L249) -- [dlt_block_log.cpp:320-325](file://libraries/chain/dlt_block_log.cpp#L320-L325) - -### Enhanced Blockchain Recovery Error Handling -The enhanced blockchain recovery system includes comprehensive error handling with detailed logging and graceful degradation mechanisms. The system provides informative error messages and continues operation when recovery is not possible. - -**Enhanced Recovery Error Handling Features**: -- Detailed logging of recovery operations with progress tracking and percentage completion -- Graceful fallback when DLT block log replay fails with comprehensive error reporting -- Continued operation with snapshot state when recovery is not possible -- Enhanced debugging information for troubleshooting recovery issues -- Automatic cleanup and state restoration for interrupted recovery operations -- Memory usage reporting and performance metrics during recovery operations - -**Section sources** -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) -- [plugin.cpp:542-555](file://plugins/chain/plugin.cpp#L542-L555) - -## DLT Mode Fork Database Seeding - -### Automatic Fork Database Seeding Functionality -The DLT mode now includes sophisticated automatic fork database seeding functionality that enhances P2P synchronization capabilities. When the database detects DLT mode (empty block log with existing chain state), it attempts to seed the fork database from the DLT block log head, enabling immediate P2P synchronization. - -**Enhanced Fork Database Seeding Features**: -- Automatic detection of DLT mode conditions during database initialization -- Intelligent validation of DLT block log head against current chain state -- Conditional seeding of fork database when DLT block log covers the head block -- Graceful fallback to minimal fork database entry when DLT block log is incomplete -- Comprehensive logging of seeding operations and their outcomes -- Enhanced P2P synchronization capabilities through proper fork database state - -```mermaid -flowchart TD -DLTMode["DLT Mode Detected"] --> CheckHead["Check DLT Block Log Head"] -CheckHead --> ValidHead{"Head Block Valid?"} -ValidHead --> |Yes| CheckMatch["Check Block ID Match"] -CheckMatch --> |Match| SeedForkDB["Seed Fork Database from DLT Head"] -CheckMatch --> |No| MinimalEntry["Create Minimal Fork Entry"] -ValidHead --> |No| MinimalEntry -SeedForkDB --> LogSuccess["Log Successful Seeding"] -MinimalEntry --> LogFallback["Log Fallback Behavior"] -LogSuccess --> Ready["P2P Sync Ready"] -LogFallback --> Ready -``` - -**Diagram sources** -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -**Section sources** -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -### Enhanced Fork Database Integration -The fork database now integrates more closely with DLT mode operations, providing automatic seeding capabilities and improved block availability checking logic. The fork database can be seeded from DLT block log data or created as minimal entries for P2P synchronization. - -**Enhanced Fork Database Features**: -- Automatic seeding from DLT block log head when available -- Minimal fork database entries for P2P synchronization in DLT mode -- Improved block availability checking with DLT mode awareness -- Enhanced error handling for fork database operations -- Better integration with DLT block log for seamless operation - -**Section sources** -- [fork_database.cpp:24-28](file://libraries/chain/fork_database.cpp#L24-L28) -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) - -## Enhanced Block Availability Checking - -### Improved DLT Mode Block Availability Logic -The block availability checking logic has been significantly enhanced to provide better support for DLT mode operations. The system now uses block_summary objects as hints and verifies block availability against the preferred chain, with special handling for DLT mode scenarios. - -**Enhanced Block Availability Features**: -- DLT mode-aware block availability checking with improved logic -- Block summary object verification for faster block existence checks -- Preferred chain verification to ensure blocks are part of the main chain -- Enhanced error handling for DLT mode block availability issues -- Improved fallback mechanisms for block retrieval operations - -```mermaid -flowchart TD -CheckAvailability["Check Block Availability"] --> CheckDLTMode{"In DLT Mode?"} -CheckDLTMode --> |No| NormalCheck["Standard Block Summary Check"] -CheckDLTMode --> |Yes| DLTCheck["DLT Mode Block Summary Check"] -NormalCheck --> VerifyBlock["Verify Block Exists"] -DLTCheck --> VerifySummary["Verify Block Summary"] -VerifySummary --> VerifyChain["Verify Preferred Chain"] -VerifyChain --> FinalResult["Return Availability Result"] -VerifyBlock --> FinalResult -``` - -**Diagram sources** -- [database.cpp:560-595](file://libraries/chain/database.cpp#L560-L595) - -**Section sources** -- [database.cpp:560-595](file://libraries/chain/database.cpp#L560-L595) - -### Enhanced Block Retrieval Chain -The block retrieval chain has been improved to provide better fallback mechanisms and error handling. The system now follows a more sophisticated chain of fallbacks: fork_db → block_log → DLT block log → error, with enhanced validation at each step. - -**Enhanced Block Retrieval Features**: -- Improved block retrieval chain with better error handling -- Enhanced validation of block IDs at each stage -- Better integration between fork database and DLT block log -- Improved error reporting for block retrieval failures -- Enhanced fallback mechanisms for different block sources - -**Section sources** -- [database.cpp:656-697](file://libraries/chain/database.cpp#L656-L697) - -## Stalled Sync Detection for DLT Nodes - -### Automatic Stalled Sync Detection and Recovery -The snapshot plugin now includes sophisticated stalled sync detection capabilities specifically designed for DLT nodes. This feature automatically monitors P2P sync progress and can detect when the node becomes stalled, triggering automatic recovery mechanisms. - -**Stalled Sync Detection Features**: -- Configurable timeout settings for detecting stalled P2P sync -- Automatic detection of no block reception for extended periods -- Integration with trusted peer network for automatic snapshot reload -- Graceful recovery through snapshot reload without manual intervention -- Comprehensive logging of stalled sync events and recovery actions -- Automatic restart of sync detection after recovery - -```mermaid -flowchart TD -StartSync["Start P2P Sync"] --> Monitor["Monitor Block Reception"] -Monitor --> ReceiveBlock["Receive Block"] -ReceiveBlock --> UpdateTimer["Update Last Block Time"] -UpdateTimer --> Monitor -Monitor --> Stalled{"Stalled Timeout Reached?"} -Stalled --> |No| Monitor -Stalled --> |Yes| CheckPeers["Query Trusted Peers for Newer Snapshot"] -CheckPeers --> NewSnapshot{"Newer Snapshot Available?"} -NewSnapshot --> |No| ContinueSync["Continue P2P Sync"] -NewSnapshot --> |Yes| ReloadSnapshot["Reload Snapshot and Enable DLT Mode"] -ReloadSnapshot --> RestartDetection["Restart Stalled Sync Detection"] -ContinueSync --> Monitor -``` - -**Diagram sources** -- [snapshot_plugin.cpp:1435-1500](file://plugins/snapshot/plugin.cpp#L1435-L1500) -- [snapshot_plugin.cpp:2790-2791](file://plugins/snapshot/plugin.cpp#L2790-L2791) - -**Section sources** -- [snapshot_plugin.cpp:1414-1500](file://plugins/snapshot/plugin.cpp#L1414-L1500) -- [snapshot_plugin.cpp:2790-2791](file://plugins/snapshot/plugin.cpp#L2790-L2791) - -### Enhanced Configuration Options -The stalled sync detection system provides comprehensive configuration options for operators to tune the behavior according to their network conditions and requirements. - -**Configuration Options**: -- `enable-stalled-sync-detection`: Enable/disable stalled sync detection (default: false) -- `stalled-sync-timeout-minutes`: Timeout period before considering sync stalled (default: 5 minutes) -- Integration with trusted peer network for automatic snapshot discovery -- Automatic snapshot reload without manual intervention -- Comprehensive logging and monitoring capabilities - -**Section sources** -- [snapshot_plugin.cpp:2691-2696](file://plugins/snapshot/plugin.cpp#L2691-L2696) -- [snapshot_plugin.cpp:2863-2866](file://plugins/snapshot/plugin.cpp#L2863-L2866) - -## Enhanced Gap Handling During Synchronization - -### Enhanced Gap Management in DLT Mode -The DLT rolling block log now includes sophisticated gap handling capabilities that manage the synchronization gap between the fork database and DLT block log. This enhancement ensures smooth operation during the critical period when the DLT block log is catching up to the fork database. - -**Enhanced Gap Handling Features**: -- Automatic detection of gaps between fork database and DLT block log -- Intelligent logging of gap status with detailed information about progress -- Graceful handling of missing blocks in DLT block log during gap periods -- Automatic seeding of DLT block log from fork database as blocks become available -- Enhanced logging with gap filling progress and completion notifications -- Prevention of repeated logging for the same gap status -- **Enhanced Gap Recovery**: Automatic gap detection and recovery with DLT block log reset functionality - -```mermaid -flowchart TD -StartGap["Start Gap Handling"] --> CheckGap{"Gap Exists?"} -CheckGap --> |No| Complete["Gap Filled - Complete"] -CheckGap --> |Yes| LogGap["Log Gap Status"] -LogGap --> CheckForkDB["Check Fork DB for Next Block"] -CheckForkDB --> BlockAvailable{"Block Available?"} -BlockAvailable --> |Yes| AppendBlock["Append Block to DLT Log"] -AppendBlock --> UpdateProgress["Update Gap Progress"] -UpdateProgress --> CheckGap -BlockAvailable --> |No| CheckGapReset{"Need DLT Reset?"} -CheckGapReset --> |Yes| ResetDLT["Reset DLT Block Log"] -ResetDLT --> LogReset["Log Reset Action"] -LogReset --> CheckGap -CheckGapReset --> |No| LogSkip["Log Gap Skip"] -LogSkip --> WaitRetry["Wait and Retry Later"] -WaitRetry --> CheckGap -Complete -``` - -**Diagram sources** -- [database.cpp:4581-4608](file://libraries/chain/database.cpp#L4581-L4608) -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -**Section sources** -- [database.cpp:4581-4608](file://libraries/chain/database.cpp#L4581-L4608) -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -### Enhanced Gap Logging and Monitoring -The gap handling system provides comprehensive logging and monitoring capabilities to track the progress of gap filling operations. This includes detailed information about gap status, progress indicators, and completion notifications. - -**Enhanced Gap Logging Features**: -- Detailed logging of gap status with block numbers and progress indicators -- Information about DLT head block, LIB (Last Irreversible Block), and gap size -- Prevention of repeated logging for the same gap status -- Notification when gap begins to fill and when it completes -- Enhanced debugging information for troubleshooting gap handling issues -- **Enhanced Gap Recovery Logging**: Comprehensive logging of automatic gap recovery actions and DLT block log reset operations - -**Section sources** -- [database.cpp:4581-4608](file://libraries/chain/database.cpp#L4581-L4608) -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -### Automatic Gap Recovery System -The automatic gap recovery system provides sophisticated gap detection and automatic recovery mechanisms that monitor synchronization gaps between DLT block log and fork database. When gaps are detected, the system automatically resets the DLT block log and aligns it with the fork database. - -**Enhanced Gap Recovery System Features**: -- Intelligent gap detection between DLT block log end and fork database start positions -- Automatic DLT block log reset when gaps exceed acceptable thresholds -- Seamless continuation of block synchronization after reset -- Integration with snapshot plugin for automatic fresh snapshot creation -- Comprehensive logging of gap recovery actions and outcomes -- Prevention of repeated gap recovery operations for the same gap -- **Enhanced State Management**: Automatic suppression of redundant gap warnings through _dlt_gap_logged flag - -**Enhanced Gap Recovery Process**: -- Detection of gap between dlt_end and fork_db_start positions -- Identification of earliest available block in fork database -- Automatic reset() method invocation to clear DLT block log -- Sequential writing of available blocks from fork database -- Signal emission to snapshot plugin for fresh snapshot creation -- Continued gap monitoring and recovery as needed -- **Enhanced Warning Suppression**: Automatic logging state management to prevent redundant gap warnings - -```mermaid -flowchart TD -GapDetection["Gap Detection"] --> CheckGap{"Gap > Threshold?"} -CheckGap --> |No| ContinueSync["Continue Normal Sync"] -CheckGap --> |Yes| FindForkStart["Find Earliest Fork Block"] -FindForkStart --> ResetDLT["Call reset() method"] -ResetDLT --> WriteBlocks["Write Available Blocks"] -WriteBlocks --> EmitSignal["Emit dlt_block_log_was_reset"] -EmitSignal --> CreateSnapshot["Create Fresh Snapshot"] -CreateSnapshot --> ContinueSync -ContinueSync -``` - -**Diagram sources** -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -**Section sources** -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -### Signal-Based Integration with Snapshot Plugin -The automatic gap recovery system integrates with the snapshot plugin through the dlt_block_log_was_reset signal. When the DLT block log is reset due to gap recovery, the signal is emitted, prompting the snapshot plugin to create a fresh snapshot for other DLT nodes. - -**Enhanced Signal Integration Features**: -- Automatic emission of dlt_block_log_was_reset signal upon DLT block log reset -- Snapshot plugin listening for reset signal to create fresh snapshots -- Seamless coordination between gap recovery and snapshot creation -- Enhanced bootstrap capability for other DLT nodes -- Comprehensive logging of signal-based integration actions - -**Section sources** -- [database.hpp:332-338](file://libraries/chain/include/graphene/chain/database.hpp#L332-L338) -- [snapshot_plugin.cpp:3254](file://plugins/snapshot/plugin.cpp#L3254) - -### Automatic State Management for Gap Warnings -The automatic gap recovery system includes intelligent state management to suppress redundant gap warnings. A boolean flag _dlt_gap_logged is used to track whether a gap warning has already been logged, preventing repeated logging for the same gap condition. - -**Enhanced State Management Features**: -- _dlt_gap_logged boolean flag for gap warning suppression -- Automatic setting of flag when gap warning is first logged -- Reset of flag when gap begins to fill with new blocks -- Re-enabling of gap logging when gaps reappear -- Prevention of redundant gap warnings during recovery operations -- Enhanced debugging information for gap state management - -**Section sources** -- [database.cpp:5482-5499](file://libraries/chain/database.cpp#L5482-L5499) - -## Enhanced DLT Block Log Reset Functionality - -### Safe Log Clearing and Reinitialization -The new reset() method provides comprehensive safe log clearing and reinitialization capabilities. When called, it closes the current DLT block log, deletes all data and index files, removes stale temporary and backup files, then reopens the log as empty. This functionality is essential for automatic gap recovery and synchronization gap management. - -**Enhanced Reset Method Features**: -- Safe log clearing with comprehensive file cleanup including .tmp and .bak files -- Atomic file deletion and recreation process with proper error handling -- Preservation of file path and configuration while resetting internal state -- Comprehensive logging with old range information for debugging and monitoring -- Thread-safe operation with proper locking mechanisms - -**Enhanced Reset Process**: -- Close current DLT block log with proper cleanup -- Delete all data files (block_path, block_path + ".tmp", block_path + ".bak") -- Delete all index files (index_path, index_path + ".tmp", index_path + ".bak") -- Reopen DLT block log with original path -- Log completion with old range information - -**Section sources** -- [dlt_block_log.cpp:523-543](file://libraries/chain/dlt_block_log.cpp#L523-L543) - -### Automatic Gap Recovery Integration -The reset() method is automatically triggered during gap recovery operations when synchronization gaps are detected between the DLT block log and fork database. This ensures that the DLT block log is properly aligned with the fork database for continued synchronization. - -**Enhanced Gap Recovery Integration Features**: -- Automatic detection of gaps between dlt_end and fork_db_start positions -- Triggering of reset() method when gaps exceed acceptable thresholds -- Seamless continuation of block synchronization after reset -- Integration with snapshot plugin for automatic fresh snapshot creation -- Comprehensive logging of gap recovery actions and outcomes -- Prevention of repeated gap recovery operations for the same gap - -**Section sources** -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -## Automatic Gap Recovery System - -### Intelligent Gap Detection and Recovery -The automatic gap recovery system provides sophisticated gap detection and automatic recovery mechanisms that monitor synchronization gaps between DLT block log and fork database. When gaps are detected, the system automatically resets the DLT block log and aligns it with the fork database. - -**Enhanced Gap Recovery System Features**: -- Intelligent gap detection between DLT block log end and fork database start positions -- Automatic DLT block log reset when gaps exceed acceptable thresholds -- Seamless continuation of block synchronization after reset -- Integration with snapshot plugin for automatic fresh snapshot creation -- Comprehensive logging of gap recovery actions and outcomes -- Prevention of repeated gap recovery operations for the same gap -- **Enhanced Warning Suppression**: Automatic suppression of redundant gap warnings through _dlt_gap_logged state management - -**Enhanced Gap Recovery Process**: -- Detection of gap between dlt_end and fork_db_start positions -- Identification of earliest available block in fork database -- Automatic reset() method invocation to clear DLT block log -- Sequential writing of available blocks from fork database -- Signal emission to snapshot plugin for fresh snapshot creation -- Continued gap monitoring and recovery as needed -- **Enhanced State Management**: Automatic logging state management to prevent redundant gap warnings - -```mermaid -flowchart TD -GapDetection["Gap Detection"] --> CheckGap{"Gap > Threshold?"} -CheckGap --> |No| ContinueSync["Continue Normal Sync"] -CheckGap --> |Yes| FindForkStart["Find Earliest Fork Block"] -FindForkStart --> ResetDLT["Call reset() method"] -ResetDLT --> WriteBlocks["Write Available Blocks"] -WriteBlocks --> EmitSignal["Emit dlt_block_log_was_reset"] -EmitSignal --> CreateSnapshot["Create Fresh Snapshot"] -CreateSnapshot --> ContinueSync -ContinueSync -``` - -**Diagram sources** -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -**Section sources** -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) - -### Signal-Based Integration with Snapshot Plugin -The automatic gap recovery system integrates with the snapshot plugin through the dlt_block_log_was_reset signal. When the DLT block log is reset due to gap recovery, the signal is emitted, prompting the snapshot plugin to create a fresh snapshot for other DLT nodes. - -**Enhanced Signal Integration Features**: -- Automatic emission of dlt_block_log_was_reset signal upon DLT block log reset -- Snapshot plugin listening for reset signal to create fresh snapshots -- Seamless coordination between gap recovery and snapshot creation -- Enhanced bootstrap capability for other DLT nodes -- Comprehensive logging of signal-based integration actions - -**Section sources** -- [database.hpp:332-338](file://libraries/chain/include/graphene/chain/database.hpp#L332-L338) -- [snapshot_plugin.cpp:3254](file://plugins/snapshot/plugin.cpp#L3254) - -## DLT Block Log Accessibility Enhancement - -### Enhanced DLT Block Log Accessor Methods -The DLT block log accessibility has been significantly enhanced with the introduction of both const and non-const accessor methods. This enhancement enables external components to modify DLT block log properties during runtime operations while maintaining read-only access for general use. - -**Enhanced Accessor Methods**: -- `const dlt_block_log &get_dlt_block_log() const` - Provides read-only access for general use -- `dlt_block_log &get_dlt_block_log()` - Provides mutable access for external components to modify DLT block log properties during runtime operations - -**Enhanced Runtime Property Modification Capabilities**: -- External components can modify DLT block log properties during runtime operations -- Maintains thread safety through proper locking mechanisms -- Enables dynamic configuration of DLT block log behavior based on operational requirements -- Supports runtime adjustments to retention policies and pruning thresholds -- Allows external components to optimize DLT block log performance based on current workload - -**Integration with External Components**: -- Chain plugin can access DLT block log for recovery operations with mutable access -- Snapshot plugin can modify DLT block log properties during snapshot import -- P2P plugin can optimize DLT block log access patterns for peer synchronization -- Database layer can dynamically adjust DLT block log configuration based on memory usage - -**Section sources** -- [database.hpp:515-516](file://libraries/chain/include/graphene/chain/database.hpp#L515-L516) -- [plugin.cpp:627-627](file://plugins/chain/plugin.cpp#L627-L627) -- [snapshot_plugin.cpp:1473-1476](file://plugins/snapshot/plugin.cpp#L1473-L1476) - -### Enhanced External Component Integration -The enhanced accessibility model provides comprehensive integration capabilities for external components that need to interact with the DLT block log during runtime operations. - -**Enhanced External Component Integration Features**: -- Chain plugin can access DLT block log for recovery operations with mutable access -- Snapshot plugin can modify DLT block log properties during snapshot import -- P2P plugin can optimize DLT block log access patterns for peer synchronization -- Database layer can dynamically adjust DLT block log configuration based on operational requirements -- Enhanced error handling and validation for external component modifications -- Thread-safe access patterns that prevent race conditions during runtime modifications - -**Section sources** -- [plugin.cpp:626-632](file://plugins/chain/plugin.cpp#L626-L632) -- [snapshot_plugin.cpp:1472-1477](file://plugins/snapshot/plugin.cpp#L1472-L1477) - -## Comprehensive DLT Block Range Management System - -### Earliest Available Block Number Method -The comprehensive DLT block range management system introduces the earliest_available_block_num() method, which provides precise block availability tracking for DLT mode operations. This method determines the lowest block number for which the node can serve full block data from block_log, DLT block log, or fork database. - -**Enhanced Earliest Available Block Number Features**: -- Determines the lowest block number that can be served across all available sources -- In non-DLT mode, returns 1 (block_log always starts from block 1) -- In DLT mode, returns the minimum of head_block_num() and DLT block log start_block_num() -- Prevents advertising blocks that cannot be served to P2P peers -- Enables sophisticated P2P synchronization with accurate block range information - -**Enhanced DLT Mode Logic**: -- DLT mode: blocks come from dlt_block_log and fork_db -- After snapshot import, dlt_block_log may have only the head block -- earliest = head_block_num() initially -- Check dlt_block_log range: if dlt_start > 0 && dlt_start < earliest, earliest = dlt_start -- fork_db blocks are typically at/above head, so they don't lower the floor - -```mermaid -flowchart TD -StartEA["earliest_available_block_num()"] --> CheckMode{"Non-DLT Mode?"} -CheckMode --> |Yes| CheckBL["Check block_log head"] -CheckBL --> HasHead{"Has block_log head?"} -HasHead --> |Yes| ReturnOne["Return 1"] -HasHead --> |No| ReturnHead["Return head_block_num()"] -CheckMode --> |No| InitEarliest["Initialize earliest = head_block_num()"] -InitEarliest --> CheckDLT["Check dlt_block_log start_block_num()"] -CheckDLT --> ValidDLT{"dlt_start > 0 && dlt_start < earliest?"} -ValidDLT --> |Yes| UpdateEarliest["Update earliest = dlt_start"] -ValidDLT --> |No| SkipUpdate["Skip update"] -UpdateEarliest --> ReturnEarliest["Return earliest"] -SkipUpdate --> ReturnEarliest -``` - -**Diagram sources** -- [database.cpp:835-858](file://libraries/chain/database.cpp#L835-L858) - -**Section sources** -- [database.cpp:835-858](file://libraries/chain/database.cpp#L835-L858) - -### Enhanced Block Range Validation -The comprehensive DLT block range management system provides sophisticated block range validation for P2P synchronization. The system prevents advertising blocks that fall outside the available range, reducing error rates and improving peer interaction quality. - -**Enhanced Validation Features**: -- P2P layer uses earliest_available_block_num() to clamp block requests -- Prevents "You are missing a sync item you claim to have" errors -- Provides detailed logging of block range limitations -- Enables graceful handling of DLT mode block availability constraints -- Comprehensive error reporting with block ID and range information - -**Section sources** -- [p2p_plugin.cpp:294-302](file://plugins/p2p/p2p_plugin.cpp#L294-L302) -- [p2p_plugin.cpp:317-323](file://plugins/p2p/p2p_plugin.cpp#L317-L323) - -## Enhanced P2P Synchronization Capabilities - -### Multi-Layered Fallback Mechanisms -The enhanced P2P synchronization capabilities implement sophisticated multi-layered fallback mechanisms that provide robust error handling and graceful degradation. The system follows a comprehensive fallback chain: fork database → primary block log → DLT block log → error, with detailed logging at each stage. - -**Enhanced Fallback Chain Features**: -- Fork database → block_log → dlt_block_log → error progression -- Detailed logging for each fallback stage with block ID and availability information -- Enhanced error reporting with comprehensive context for troubleshooting -- Graceful degradation when blocks are not available in any source -- Improved peer interaction handling with reduced error rates - -**Enhanced Block Serving Logic**: -- get_block_by_id(): fork_db → block_log → dlt_block_log → error -- get_block_by_number(): fork_db → block_log → dlt_block_log → error -- Detailed logging with block ID, availability range, and DLT block log boundaries -- Proper exception handling with fc::key_not_found_exception for unavailable blocks - -```mermaid -flowchart TD -GetBlock["get_block_by_id()"] --> CheckForkDB["Check fork_db"] -CheckForkDB --> FoundFork{"Found in fork_db?"} -FoundFork --> |Yes| ReturnFork["Return fork_db block"] -FoundFork --> |No| CheckBlockLog["Check block_log"] -CheckBlockLog --> FoundBL{"Found in block_log?"} -FoundBL --> |Yes| ReturnBL["Return block_log block"] -FoundBL --> |No| CheckDLT["Check dlt_block_log"] -CheckDLT --> FoundDLT{"Found in dlt_block_log?"} -FoundDLT --> |Yes| ReturnDLT["Return dlt_block_log block"] -FoundDLT --> |No| ThrowError["Throw key_not_found_exception"] -``` - -**Diagram sources** -- [database.cpp:860-882](file://libraries/chain/database.cpp#L860-L882) - -**Section sources** -- [database.cpp:860-882](file://libraries/chain/database.cpp#L860-L882) -- [database.cpp:884-901](file://libraries/chain/database.cpp#L884-L901) - -### Enhanced Peer Interaction Handling -The enhanced P2P synchronization capabilities provide comprehensive peer interaction handling with detailed logging and error reporting. The system prevents common P2P errors and provides informative feedback for troubleshooting. - -**Enhanced Peer Interaction Features**: -- Detailed logging of block serving operations with block ID and range information -- Graceful fallback mechanisms that prevent peer disconnections -- Comprehensive error reporting with block availability context -- Enhanced debugging information for troubleshooting DLT mode issues -- Improved peer satisfaction through reduced error rates and better error handling - -**Section sources** -- [p2p_plugin.cpp:330-364](file://plugins/p2p/p2p_plugin.cpp#L330-L364) -- [p2p_plugin.cpp:370-489](file://plugins/p2p/p2p_plugin.cpp#L370-L489) - -## Multi-Layered Fallback Mechanisms - -### Sophisticated Block Retrieval Chain -The multi-layered fallback mechanisms implement a sophisticated block retrieval chain that provides robust error handling and graceful degradation. The system follows a comprehensive fallback progression with detailed validation and logging at each stage. - -**Enhanced Fallback Chain Implementation**: -- fork_database::fetch_block() for block ID lookups -- block_log::read_block_by_num() for primary block log retrieval -- dlt_block_log::read_block_by_num() for DLT block log fallback -- Comprehensive validation of block IDs and content at each stage -- Detailed logging with block ID, availability range, and error context - -**Enhanced Error Handling**: -- Detailed error reporting for each fallback stage -- Graceful degradation when blocks are not available -- Comprehensive logging with block ID and range information -- Proper exception handling with fc::key_not_found_exception -- Enhanced debugging information for troubleshooting - -**Section sources** -- [database.cpp:860-882](file://libraries/chain/database.cpp#L860-L882) -- [database.cpp:884-901](file://libraries/chain/database.cpp#L884-L901) - -### Enhanced Block Availability Tracking -The multi-layered fallback mechanisms provide comprehensive block availability tracking that enables precise determination of which blocks can be served from each source. This tracking system prevents serving blocks that are not available and provides detailed logging for troubleshooting. - -**Enhanced Availability Tracking Features**: -- Precise tracking of block availability across all sources -- Detailed logging of block serving operations with source information -- Enhanced error reporting with comprehensive context -- Graceful handling of unavailable blocks with proper fallback -- Improved peer interaction through accurate availability information - -**Section sources** -- [database.cpp:860-882](file://libraries/chain/database.cpp#L860-L882) -- [database.cpp:884-901](file://libraries/chain/database.cpp#L884-L901) - -## Enhanced Diagnostic and Monitoring Capabilities - -### Comprehensive Diagnostic System -The enhanced diagnostic system provides comprehensive monitoring and analysis capabilities through the new `verify_mapping()`, `verify_continuity()`, and `resize_count()` methods. These methods enable proactive detection and healing of Windows memory-mapped file size drift issues, along with detailed tracking of resize operations for performance monitoring. - -**Enhanced Diagnostic Features**: -- `verify_mapping()` method: Periodic verification of logical vs. mapped file size consistency -- `verify_continuity()` method: Walks entire block range and reports gaps for integrity verification -- `resize_count()` method: Tracking of resize operations since log open for diagnostic purposes -- Integration with P2P stats task for automatic periodic verification in DLT mode -- Comprehensive logging with detailed information about mapping status and healing actions -- Automatic reopening of files when stale mapping is detected - -**Enhanced Mapping Verification Process**: -- Compares mapped_file.size() with tracked _logical_block_size and _logical_index_size -- Detects stale mapping after thousands of resize() cycles -- Automatically closes and reopens files to refresh mapping -- Logs detailed information about detected inconsistencies and healing actions -- Prevents get_block_pos() from rejecting valid block numbers due to stale metadata - -**Enhanced Gap Integrity Scanning**: -- Periodic verification of DLT block log integrity through verify_continuity() method -- Comprehensive gap reporting with detailed block number information -- Integration with P2P stats task for automatic gap detection and logging -- Enhanced error reporting with gap count and missing block information -- Automatic gap suppression to prevent redundant logging - -**Enhanced Resize Tracking**: -- `_resize_count` field tracks number of resize operations performed -- Used for performance monitoring and debugging -- Integrated into P2P stats logging for DLT mode nodes -- Helps identify potential memory-mapped file size drift issues - -**Section sources** -- [dlt_block_log.cpp:545-579](file://libraries/chain/dlt_block_log.cpp#L545-L579) -- [dlt_block_log.cpp:576-602](file://libraries/chain/dlt_block_log.cpp#L576-L602) -- [p2p_plugin.cpp:757-765](file://plugins/p2p/p2p_plugin.cpp#L757-L765) - -### Periodic Monitoring Integration -The diagnostic system is integrated into the P2P stats task for DLT mode nodes, providing automatic periodic monitoring without manual intervention. This ensures continuous monitoring of mapping consistency and resize operations. - -**Enhanced Monitoring Integration Features**: -- Automatic periodic verification in DLT mode through P2P stats task -- Integration with existing P2P monitoring infrastructure -- Minimal performance impact through scheduled execution -- Comprehensive logging with detailed diagnostic information -- Automatic healing of detected mapping inconsistencies - -**Section sources** -- [p2p_plugin.cpp:757-765](file://plugins/p2p/p2p_plugin.cpp#L757-L765) - -## Troubleshooting Guide -Comprehensive troubleshooting guidance for DLT-specific scenarios, retention policy issues, automatic pruning failures, enhanced blockchain recovery problems, configuration issues, DLT block range management problems, enhanced P2P synchronization issues, multi-layered fallback mechanism failures, the new DLT block log accessibility enhancement, enhanced DLT block log reset functionality, automatic gap recovery system issues, enhanced diagnostic and monitoring capabilities, Windows compatibility issues, mapping verification problems, gap detection and recovery issues, and systematic diagnostic approaches and enhanced error reporting. - -**Common DLT Mode Issues**: -- Index mismatch detection and automatic reconstruction with selective retention enforcement -- Empty block log in DLT mode validation and fallback mechanism verification -- Truncation failures with temporary file cleanup and atomic operation validation -- Retention policy violations with selective block preservation and pruning triggers -- Configuration parameter validation and runtime parameter enforcement -- Enhanced block verification failures during snapshot operations -- P2P fallback errors in DLT mode with detailed logging and error reporting -- Graceful fallback mechanism failures with proper exception handling -- Storage-related issues with comprehensive error messages and logging -- Enhanced synchronization issues with detailed logging capabilities -- **Windows Compatibility Issues**: Memory-mapped file size drift after thousands of resize operations -- **Mapping Verification Problems**: Issues with verify_mapping() method periodic verification -- **Gap Detection Issues**: Problems with verify_continuity() method gap detection and integrity verification -- **Resize Tracking Issues**: Problems with resize_count() method diagnostic tracking -- **Memory Safety Issues**: Unsafe pointer cast errors resolved through std::memcpy operations -- **Crash Recovery Problems**: .bak file restoration failures and atomic operation issues -- **Cross-Platform Compatibility**: Platform-specific file operation problems -- **Fork Database Seeding Failures**: Issues with automatic DLT mode fork database seeding -- **Enhanced Block Availability Problems**: DLT mode block availability checking failures -- **Improved Error Handling**: Better error reporting and logging throughout the system -- **Stalled Sync Detection Issues**: Timeout configuration problems and recovery failures -- **Snapshot Reload Failures**: Automatic snapshot reload mechanism issues -- **Gap Handling Problems**: Issues with DLT mode gap management and synchronization -- **Enhanced Blockchain Recovery Issues**: DLT block log replay failures and recovery mechanism problems -- **Recovery Progress Tracking**: Missing progress indicators and percentage completion reporting -- **DLT Block Log Accessibility Issues**: Problems with const/non-const accessor method usage -- **Runtime Property Modification Failures**: Issues with external components modifying DLT block log properties -- **DLT Block Range Management Issues**: Problems with earliest_available_block_num() method usage -- **Enhanced P2P Synchronization Problems**: Issues with multi-layered fallback mechanisms and block serving -- **Multi-Layered Fallback Failures**: Problems with comprehensive fallback chain implementation -- **Enhanced DLT Block Log Reset Issues**: Problems with safe log clearing and reinitialization functionality -- **Automatic Gap Recovery Failures**: Issues with intelligent gap detection and automatic recovery mechanisms -- **Signal Integration Problems**: Issues with dlt_block_log_was_reset signal emission and snapshot plugin integration -- **Gap Warning Suppression Issues**: Problems with _dlt_gap_logged state management and redundant warning prevention -- **Diagnostic System Issues**: Problems with verify_mapping(), verify_continuity(), and resize_count() method usage and monitoring - -**Section sources** -- [dlt_block_log.cpp:161-209](file://libraries/chain/dlt_block_log.cpp#L161-L209) -- [dlt_block_log.cpp:356-411](file://libraries/chain/dlt_block_log.cpp#L356-L411) -- [database.cpp:259-268](file://libraries/chain/database.cpp#L259-L268) -- [p2p_plugin.cpp:265-272](file://plugins/p2p/p2p_plugin.cpp#L265-L272) -- [database.cpp:266-292](file://libraries/chain/database.cpp#L266-L292) -- [database.cpp:560-595](file://libraries/chain/database.cpp#L560-L595) -- [snapshot_plugin.cpp:1435-1500](file://plugins/snapshot/plugin.cpp#L1435-L1500) -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) -- [database.hpp:515-516](file://libraries/chain/include/graphene/chain/database.hpp#L515-L516) -- [database.cpp:835-858](file://libraries/chain/database.cpp#L835-L858) -- [dlt_block_log.cpp:523-543](file://libraries/chain/dlt_block_log.cpp#L523-L543) -- [database.cpp:4910-5150](file://libraries/chain/database.cpp#L4910-L5150) -- [database.cpp:5482-5499](file://libraries/chain/database.cpp#L5482-L5499) -- [dlt_block_log.cpp:545-579](file://libraries/chain/dlt_block_log.cpp#L545-L579) -- [dlt_block_log.cpp:576-602](file://libraries/chain/dlt_block_log.cpp#L576-L602) -- [p2p_plugin.cpp:757-765](file://plugins/p2p/p2p_plugin.cpp#L757-L765) - -## Conclusion -The DLT Rolling Block Log provides a comprehensive, offset-aware append-only storage mechanism specifically designed for snapshot-based nodes with advanced selective retention policies and automatic pruning capabilities. Recent enhancements include critical Windows compatibility improvements with separate logical file size tracking, sophisticated mapping verification and healing mechanisms, enhanced diagnostic capabilities, and strengthened validation logic with comprehensive error reporting. The most significant enhancement is the comprehensive DLT block range management system with the earliest_available_block_num() method, which provides precise block availability tracking and enables sophisticated P2P synchronization with multi-layered fallback mechanisms. The enhanced P2P synchronization capabilities now provide robust error handling with detailed logging and graceful fallback mechanisms for DLT mode scenarios, while the multi-layered fallback mechanisms ensure reliable block retrieval across fork database, primary block log, and DLT block log sources. The enhanced accessibility model provides comprehensive integration capabilities for external components that need to interact with the DLT block log during runtime operations. The chain plugin can now modify DLT block log properties during recovery operations, the snapshot plugin can adjust DLT block log behavior during snapshot import, and the P2P plugin can optimize DLT block log access patterns for peer synchronization. This enhancement maintains thread safety through proper locking mechanisms while enabling dynamic configuration of DLT block log behavior based on operational requirements. The enhanced P2P fallback mechanisms now provide graceful handling of DLT mode scenarios where block data may not be available for certain ranges, with detailed logging and appropriate error responses including specific messages like "Block ${id} not available in DLT mode (no block data for this range)". The most notable recent enhancement is the sophisticated gap handling during synchronization between fork database and DLT block log. This system automatically manages the critical period when the DLT block log is catching up to the fork database, with intelligent logging, automatic seeding, and graceful handling of missing blocks. The gap handling system prevents repeated logging for the same gap status and provides detailed progress notifications, ensuring smooth operation during the synchronization process. The new enhanced blockchain recovery system represents a major advancement in DLT node reliability and operational efficiency. The reindex_from_dlt method provides core functionality for rebuilding blockchain state from DLT rolling block log after snapshot import, with comprehensive error handling, progress tracking, enhanced fork database seeding, and detailed logging capabilities. This system enables rapid recovery from corrupted states while maintaining data integrity and operational continuity, with enhanced progress reporting and memory management optimization. Its sophisticated integration with the database ensures seamless fallback when the primary block log is empty, while configurable limits, intelligent retention enforcement, and automatic cleanup mechanisms help manage disk usage efficiently. The implementation leverages advanced memory-mapped files, strict position validation using std::memcpy operations, and comprehensive error handling to deliver reliable performance and data integrity for modern blockchain operations. The improved error handling and fallback mechanisms ensure that DLT mode operations are robust, well-documented, and provide excellent user experience for both operators and P2P peers with comprehensive logging and graceful degradation capabilities. The critical memory safety improvements eliminate undefined behavior risks, while the crash recovery mechanisms ensure data integrity even during unexpected system failures. The enhanced fork database seeding and block availability checking logic provide comprehensive support for DLT mode operations, making the system more reliable and user-friendly for snapshot-based node operations. The stalled sync detection feature further enhances the system's resilience and operational efficiency by automatically handling network connectivity issues without manual intervention. The enhanced gap handling capabilities and new blockchain recovery system represent significant improvements in DLT mode synchronization reliability, user experience, and operational efficiency. The latest accessibility enhancement completes the comprehensive DLT block log functionality by enabling external components to modify DLT block log properties during runtime operations while maintaining read-only access for general use, providing a robust foundation for advanced DLT node operations. The comprehensive DLT block range management system with the earliest_available_block_num() method, enhanced P2P synchronization capabilities with multi-layered fallback mechanisms, and sophisticated peer interaction handling represent the most significant advancement in DLT mode support and P2P synchronization reliability. The new reset() method and automatic gap recovery system provide enhanced operational flexibility and improved synchronization reliability, making the DLT rolling block log a cornerstone component of the VIZ blockchain's advanced node capabilities. The enhanced diagnostic system with verify_mapping(), verify_continuity(), and resize_count() methods provides comprehensive monitoring and proactive issue detection, ensuring optimal performance and reliability in production environments. The Windows compatibility fixes and mapping verification mechanisms address critical cross-platform issues, making the system more robust and reliable across different operating systems and deployment scenarios. The new verify_continuity() method and automatic gap recovery system represent the most significant advancement in DLT block log integrity verification and gap management, providing comprehensive protection against data corruption and synchronization issues while maintaining optimal performance and reliability. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Database Management/Database Management.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Database Management/Database Management.md deleted file mode 100644 index 30a886b208..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Database Management/Database Management.md +++ /dev/null @@ -1,1788 +0,0 @@ -# Database Management - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [chainbase.hpp](file://thirdparty/chainbase/include/chainbase/chainbase.hpp) -- [chainbase.cpp](file://thirdparty/chainbase/src/chainbase.cpp) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp) -- [plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [db_with.hpp](file://libraries/chain/include/graphene/chain/db_with.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [exception.hpp](file://thirdparty/fc/include/fc/exception/exception.hpp) -- [exception.cpp](file://thirdparty/fc/src/exception.cpp) -- [exceptions.hpp](file://libraries/protocol/include/graphene/protocol/exceptions.hpp) -- [stacktrace.cpp](file://thirdparty/fc/src/stacktrace.cpp) -- [config.ini](file://share/vizd/config/config.ini) - - -## Update Summary -**Changes Made** -- Enhanced DLT block log gap detection and recovery with automatic gap recovery mechanisms -- Improved error reporting throughout gap detection and recovery workflow with intelligent warning suppression -- Added _dlt_gap_logged flag mechanism for intelligent warning suppression during gap recovery -- Enhanced logging throughout DLT block log operations for better diagnostics and troubleshooting -- Improved gap recovery logic with better error handling and recovery strategies - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Emergency Consensus Implementation](#emergency-consensus-implementation) -7. [Dependency Analysis](#dependency-analysis) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction -This document describes the Database Management system that serves as the core state persistence layer for the VIZ blockchain. It covers the database class lifecycle, initialization and cleanup, validation steps, session management, memory allocation strategies, shared memory configuration, checkpoints for fast synchronization, block log integration, observer pattern usage, DLT mode detection and conditional operations, enhanced block fetching logic with DLT mode awareness, the new `_dlt_gap_logged` flag mechanism for intelligent warning suppression, comprehensive operation guard implementation for concurrent access protection, dual operation guard patterns for validator scheduling safety, enhanced P2P plugin block validation with operation guard protection, and practical examples of database operations and performance optimization. - -**Updated** - Enhanced with sophisticated exception handling mechanisms that preserve derived exception types during rethrow operations, comprehensive fork database management with improved diagnostic capabilities, and enhanced early rejection logic for blocks far ahead with unknown parents. The system now includes comprehensive database crash debugging capabilities with debug_crash logging throughout critical code paths, debug-block-production configuration option for detailed block production logging, and enhanced diagnostic visibility into database operations. The system also features stacktrace crash handlers for improved crash diagnostics and extensive debug logging markers (DEBUG_CRASH) throughout database and validator production code. - -## Project Structure -The database subsystem is implemented primarily in the chain library with enhanced support for operation guards, concurrent access protection, and comprehensive crash debugging: -- Core database interface and declarations: libraries/chain/include/graphene/chain/database.hpp -- Implementation of database operations with enhanced DLT mode support, emergency consensus, operation guards, concurrent access protection, and comprehensive debug logging: libraries/chain/database.cpp -- Chainbase integration with operation_guard RAII pattern and resize barrier mechanisms: thirdparty/chainbase/include/chainbase/chainbase.hpp and thirdparty/chainbase/src/chainbase.cpp -- Block log abstraction: libraries/chain/include/graphene/chain/block_log.hpp and libraries/chain/block_log.cpp -- DLT block log for rolling window storage: libraries/chain/include/graphene/chain/dlt_block_log.hpp and libraries/chain/dlt_block_log.cpp -- Fork database for reversible blocks: libraries/chain/include/graphene/chain/fork_database.hpp and libraries/chain/fork_database.cpp -- Database exceptions including unlinkable_block_exception: libraries/chain/include/graphene/chain/database_exceptions.hpp -- Snapshot plugin integration: plugins/snapshot/plugin.cpp for DLT mode initialization -- Postponed transaction processing: libraries/chain/include/graphene/chain/db_with.hpp for transaction queue management -- Validator Plugin integration with dual operation guard patterns and debug logging: plugins/validator/validator.cpp and plugins/validator/include/graphene/plugins/validator/validator.hpp -- Protocol configuration: libraries/protocol/include/graphene/protocol/config.hpp for emergency consensus constants -- Network layer integration: libraries/network/node.cpp for peer connectivity management -- P2P plugin integration with operation guard protection: plugins/p2p/p2p_plugin.cpp for enhanced exception handling and concurrent access safety -- Enhanced exception handling infrastructure: thirdparty/fc/include/fc/exception/exception.hpp and thirdparty/fc/src/exception.cpp -- Protocol exceptions with dynamic rethrow support: libraries/protocol/include/graphene/protocol/exceptions.hpp -- Stacktrace crash handlers for improved diagnostics: thirdparty/fc/src/stacktrace.cpp - -```mermaid -graph TB -subgraph "Chain Library" -DBH["database.hpp"] -DBCPP["database.cpp"] -BLH["block_log.hpp"] -BLCPP["block_log.cpp"] -DLTH["dlt_block_log.hpp"] -DLTCPP["dlt_block_log.cpp"] -FDH["fork_database.hpp"] -FDCPP["fork_database.cpp"] -DEH["database_exceptions.hpp"] -DBWH["db_with.hpp"] -ENDH["emergency_consensus_constants"] -ENDH2["exception handling infrastructure"] -STH["stacktrace crash handlers"] -end -subgraph "Chainbase Integration" -CBH["chainbase.hpp"] -CBCPP["chainbase.cpp"] -end -subgraph "Plugins" -SNAPH["snapshot/plugin.cpp"] -validator["validator/validator.cpp"] -WITNESSH["validator.hpp"] -P2PH["p2p/p2p_plugin.cpp"] -end -subgraph "Network Layer" -NODE["node.cpp"] -EXC["exceptions.hpp"] -end -subgraph "Exception System" -EXCEPTIONH["fc/exception.hpp"] -EXCEPTIONCPP["fc/exception.cpp"] -PROTOEX["protocol/exceptions.hpp"] -end -DBH --> DBCPP -DBCPP --> BLH -DBCPP --> BLCPP -DBCPP --> DLTH -DBCPP --> DLTCPP -DBCPP --> FDH -DBCPP --> FDCPP -DBCPP --> DEH -DBCPP --> DBWH -DBCPP --> ENDH -DBCPP --> CBH -DBCPP --> CBCPP -DBCPP --> EXCEPTIONH -DBCPP --> EXCEPTIONCPP -DBCPP --> PROTOEX -DBCPP --> STH -SNAPH --> DBH -validator --> DBH -WITNESSH --> validator -P2PH --> DBH -NODE --> DBH -EXC --> NODE -``` - -**Diagram sources** -- [database.hpp:1-670](file://libraries/chain/include/graphene/chain/database.hpp#L1-L670) -- [database.cpp:1-6760](file://libraries/chain/database.cpp#L1-L6760) -- [chainbase.hpp:1078-1120](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1120) -- [chainbase.cpp:1-200](file://thirdparty/chainbase/src/chainbase.cpp#L1-L200) -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [block_log.cpp:1-302](file://libraries/chain/block_log.cpp#L1-L302) -- [dlt_block_log.hpp:1-80](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L80) -- [dlt_block_log.cpp:1-476](file://libraries/chain/dlt_block_log.cpp#L1-L476) -- [fork_database.hpp:1-144](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L144) -- [fork_database.cpp:1-278](file://libraries/chain/fork_database.cpp#L1-L278) -- [database_exceptions.hpp:1-136](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L1-L136) -- [db_with.hpp:1-154](file://libraries/chain/include/graphene/chain/db_with.hpp#L1-L154) -- [plugin.cpp:1180-1379](file://plugins/snapshot/plugin.cpp#L1180-L1379) -- [validator.cpp:270-469](file://plugins/validator/validator.cpp#L270-L469) -- [validator.hpp:38-73](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L38-L73) -- [config.hpp:111-118](file://libraries/protocol/include/graphene/protocol/config.hpp#L111-L118) -- [node.cpp:3185-3384](file://libraries/network/node.cpp#L3185-L3384) -- [exceptions.hpp:27-48](file://libraries/network/include/graphene/network/exceptions.hpp#L27-L48) -- [p2p_plugin.cpp:225-424](file://plugins/p2p/p2p_plugin.cpp#L225-L424) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) -- [exceptions.hpp:21-46](file://libraries/protocol/include/graphene/protocol/exceptions.hpp#L21-L46) -- [stacktrace.cpp:1-78](file://thirdparty/fc/src/stacktrace.cpp#L1-L78) - -**Section sources** -- [database.hpp:1-670](file://libraries/chain/include/graphene/chain/database.hpp#L1-L670) -- [database.cpp:1-6760](file://libraries/chain/database.cpp#L1-L6760) -- [chainbase.hpp:1078-1120](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1120) -- [chainbase.cpp:1-200](file://thirdparty/chainbase/src/chainbase.cpp#L1-L200) -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [block_log.cpp:1-302](file://libraries/chain/block_log.cpp#L1-L302) -- [dlt_block_log.hpp:1-80](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L80) -- [dlt_block_log.cpp:1-476](file://libraries/chain/dlt_block_log.cpp#L1-L476) -- [fork_database.hpp:1-144](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L144) -- [fork_database.cpp:1-278](file://libraries/chain/fork_database.cpp#L1-L278) -- [database_exceptions.hpp:1-136](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L1-L136) -- [db_with.hpp:1-154](file://libraries/chain/include/graphene/chain/db_with.hpp#L1-L154) -- [plugin.cpp:1180-1379](file://plugins/snapshot/plugin.cpp#L1180-L1379) -- [validator.cpp:270-469](file://plugins/validator/validator.cpp#L270-L469) -- [validator.hpp:38-73](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L38-L73) -- [config.hpp:111-118](file://libraries/protocol/include/graphene/protocol/config.hpp#L111-L118) -- [node.cpp:3185-3384](file://libraries/network/node.cpp#L3185-L3384) -- [exceptions.hpp:27-48](file://libraries/network/include/graphene/network/exceptions.hpp#L27-L48) -- [p2p_plugin.cpp:225-424](file://plugins/p2p/p2p_plugin.cpp#L225-L424) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) -- [exceptions.hpp:21-46](file://libraries/protocol/include/graphene/protocol/exceptions.hpp#L21-L46) -- [stacktrace.cpp:1-78](file://thirdparty/fc/src/stacktrace.cpp#L1-L78) - -## Core Components -- database class: Public interface for blockchain state management, block and transaction processing, checkpoints, and event notifications with enhanced DLT mode support, emergency consensus implementation, operation guard integration, improved error handling, and comprehensive debug logging capabilities. -- chainbase integration: Provides persistent object storage and undo sessions with enhanced memory management, operation_guard RAII pattern, and resize barrier mechanisms for concurrent access protection. -- block_log: Append-only block storage with random-access indexing. -- dlt_block_log: Rolling window block storage specifically designed for DLT (snapshot-based) nodes. -- fork_database: Maintains reversible blocks and supports fork selection and switching with emergency mode support and enhanced unlinkable block detection. -- signal_guard: Enhanced signal handling for graceful restart sequence management. -- **Enhanced Exception Handling**: Sophisticated exception preservation during rethrow operations using fc::exception_factory and dynamic_rethrow_exception for proper derived type restoration. -- **Enhanced Fork Database Management**: Comprehensive diagnostic capabilities with detailed logging for fork recovery operations, improved unlinkable block exception handling, and enhanced fork switching logic. -- **Enhanced Early Rejection Logic**: Intelligent block validation with gap-based decision system (≤100 gap deferred to fork_db, >100 gap rejected) for blocks far ahead with unknown parents. -- **Enhanced Fork Database Exception Prevention**: Proper unlinkable_block_exception throwing for dead fork detection and improved fork switching logic with deterministic tie-breaking. -- **Enhanced Memory Management**: Comprehensive logging system for shared memory allocation with detailed free memory and maximum memory state reporting, plus deferred resize operations. -- **Enhanced P2P Synchronization**: Improved unlinkable block classification with soft-banning for dead forks and sync restart prevention for far-ahead blocks. -- **Enhanced Operation Guard System**: Comprehensive concurrent access protection using operation_guard RAII pattern, dual operation guard patterns for validator scheduling safety, and resize barrier mechanisms. -- **Enhanced Multi-Layered Block Retrieval**: Systematic fallback mechanisms that check fork database when primary block log fails to locate required data, ensuring consistent behavior across different block logging configurations. -- **Enhanced Last Irreversible Block Advancement**: Enhanced logic that falls back to fork database when block log lacks required data, maintaining data consistency and availability. -- **Enhanced Emergency Consensus**: Automatic recovery system with comprehensive logging and safety checks for network stall detection and recovery. -- **Enhanced Shared Memory Corruption Detection**: New shared_memory_corruption_exception type for structured error handling during validator account validation and block processing. -- **Enhanced Auto-Recovery System**: Integrated automatic recovery from snapshot for shared memory corruption scenarios with comprehensive error handling and node restart procedures. -- **Enhanced Crash Debugging Capabilities**: Comprehensive debug_crash logging throughout critical code paths for improved crash diagnostics and troubleshooting. -- **Enhanced Block Production Debugging**: debug-block-production configuration option for detailed block production logging and monitoring. -- **Enhanced Stacktrace Crash Handlers**: Improved crash diagnostics with stacktrace generation and signal handling for fatal errors. -- **Enhanced DLT Gap Recovery**: Intelligent gap detection and recovery mechanisms with automatic gap recovery and warning suppression using _dlt_gap_logged flag. - -Key responsibilities: -- Lifecycle: open(), open_from_snapshot(), reindex(), close(), wipe() with improved error handling -- Validation: validate_block(), validate_transaction(), with configurable skip flags -- Operations: push_block(), push_transaction(), generate_block() with enhanced memory pressure handling and concurrent access protection -- DLT Mode: Conditional block log operations, rolling window management, snapshot-aware initialization -- Observers: signals for pre/post operation, applied block, pending/applied transactions -- Persistence: integrates with block_log and dlt_block_log for different operational modes -- Enhanced Block Fetching: DLT mode-aware block retrieval with proper validation logic and fallback mechanisms -- Enhanced Exception Preservation: Proper derived exception type restoration during rethrow operations -- Enhanced Fork Recovery: Comprehensive logging and error recovery for fork switching operations -- Enhanced Early Rejection: Intelligent block rejection for far-ahead blocks with unknown parents using gap-based decision system -- Enhanced Fork Database Exception Prevention: Comprehensive mechanisms to prevent fork database exceptions through early rejection and proper dead fork detection -- Enhanced Memory Management: Detailed logging of memory states before and after resizing operations for administrator visibility -- Enhanced P2P Protection: Operation guard integration in P2P plugin for safe concurrent access during block validation and validator key retrieval -- Enhanced validator Scheduling Safety: Dual operation guard patterns in validator scheduling calculations to ensure thread safety during slot determination and validator validation -- Enhanced Shared Memory Validation: Graceful error handling for validator account validation with structured exception reporting -- Enhanced Auto-Recovery Integration: Seamless integration with Validator Plugin for automatic recovery from shared memory corruption -- **Enhanced Crash Debugging**: Comprehensive debug_crash logging markers throughout database operations for improved troubleshooting -- **Enhanced Block Production Monitoring**: debug-block-production configuration option for detailed block production logging and monitoring -- **Enhanced Stacktrace Generation**: Automatic stacktrace generation for crash diagnostics and improved debugging experience -- **Enhanced DLT Gap Detection**: Intelligent gap detection in DLT block log with automatic recovery and warning suppression -- **Enhanced Gap Recovery Logging**: Comprehensive logging throughout gap detection and recovery workflow for better diagnostics - -**Section sources** -- [database.hpp:61-115](file://libraries/chain/include/graphene/chain/database.hpp#L61-L115) -- [database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) -- [chainbase.hpp:1078-1120](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1120) -- [block_log.hpp:38-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L75) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) -- [fork_database.hpp:53-144](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L144) -- [database.cpp:929-984](file://libraries/chain/database.cpp#L929-L984) -- [db_with.hpp:33-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L33-L100) -- [config.hpp:111-118](file://libraries/protocol/include/graphene/protocol/config.hpp#L111-L118) -- [chainbase.cpp:225-279](file://thirdparty/chainbase/src/chainbase.cpp#L225-L279) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) -- [exceptions.hpp:21-46](file://libraries/protocol/include/graphene/protocol/exceptions.hpp#L21-L46) -- [stacktrace.cpp:72-78](file://thirdparty/fc/src/stacktrace.cpp#L72-L78) - -## Architecture Overview -The database composes four primary subsystems with enhanced DLT mode support, emergency consensus implementation, operation guard integration, improved error handling, and comprehensive crash debugging capabilities: -- Chainbase: Persistent object database with undo/redo capabilities, operation_guard RAII pattern, and resize barrier mechanisms for concurrent access protection -- Fork database: Holds recent blocks for fork resolution with emergency mode support and enhanced unlinkable block detection -- Block log: Immutable, append-only block storage with index -- DLT block log: Rolling window block storage for DLT (snapshot-based) nodes -- Signal guard: Enhanced signal handling for graceful restart sequences -- **Enhanced Exception Handling Infrastructure**: Sophisticated exception preservation during rethrow operations using fc::exception_factory and dynamic_rethrow_exception for proper derived type restoration -- **Enhanced Fork Database Management**: Comprehensive diagnostic capabilities with detailed logging for fork recovery operations, improved unlinkable block exception handling, and enhanced fork switching logic -- **Enhanced Early Rejection Logic**: Intelligent block validation with gap-based decision system (≤100 gap deferred to fork_db, >100 gap rejected) for blocks far ahead with unknown parents -- **Enhanced Fork Database Exception Prevention**: Proper unlinkable_block_exception throwing for dead fork detection and improved fork switching logic with deterministic tie-breaking -- **Enhanced Memory Management**: Comprehensive logging system for shared memory allocation with detailed state reporting, plus deferred resize operations -- **Enhanced P2P Synchronization**: Improved unlinkable block classification with soft-banning for dead forks and sync restart prevention for far-ahead blocks -- **Enhanced Operation Guard System**: Comprehensive concurrent access protection using operation_guard RAII pattern, dual operation guard patterns for validator scheduling safety, and resize barrier mechanisms -- **Enhanced Multi-Layered Block Retrieval**: Systematic fallback mechanisms that check fork database when primary block log fails to locate required data, ensuring consistent behavior across different block logging configurations -- **Enhanced Last Irreversible Block Advancement**: Enhanced logic that falls back to fork database when block log lacks required data, maintaining data consistency and availability -- **Enhanced Emergency Consensus**: Automatic recovery system with comprehensive logging and safety checks for network stall detection and recovery -- **Enhanced Shared Memory Corruption Detection**: New shared_memory_corruption_exception type for structured error reporting during critical validation failures -- **Enhanced Auto-Recovery Integration**: Seamless integration with Validator Plugin for automatic recovery from shared memory corruption scenarios -- **Enhanced Crash Debugging System**: Comprehensive debug_crash logging throughout critical code paths for improved crash diagnostics and troubleshooting -- **Enhanced Block Production Monitoring**: debug-block-production configuration option for detailed block production logging and monitoring -- **Enhanced Stacktrace Crash Handlers**: Automatic stacktrace generation for crash diagnostics and improved debugging experience -- **Enhanced DLT Gap Recovery System**: Intelligent gap detection and automatic recovery mechanisms with warning suppression for improved diagnostics - -```mermaid -classDiagram -class database { -+open(data_dir, shared_mem_dir, ...) -+open_from_snapshot(data_dir, shared_mem_dir, initial_supply, shared_file_size, chainbase_flags) -+reindex(data_dir, shared_mem_dir, from_block_num, ...) -+close(rewind=true) -+push_block(block, skip_flags) -+push_transaction(trx, skip_flags) -+validate_block(block, skip_flags) -+validate_transaction(trx, skip_flags) -+set_dlt_mode(enabled) -+add_checkpoints(map) -+get_block_log() -+signals : pre_apply_operation, post_apply_operation, applied_block, on_pending_transaction, on_applied_transaction -+_dlt_mode : bool -+_dlt_block_log_max_blocks : uint32_t -+_dlt_gap_logged : bool -+_debug_block_production : bool -+signal_guard : enhanced error handling -+_maybe_warn_multiple_production(height) -+CHIAN_PENDING_TRANSACTION_EXECUTION_LIMIT : time limit constant -+emergency_consensus_activation : automatic recovery system -+hybrid_witness_scheduling : dynamic validator replacement -+lib_monitoring : timestamp analysis -+check_free_memory(skip_print, current_block_num, immediate_resize) -+set_min_free_shared_memory_size(value) -+set_inc_shared_memory_size(value) -+set_block_num_check_free_size(value) -+apply_pending_resize() : deferred memory resize -+_pending_resize : bool -+_pending_resize_target : size_t -+push_block(block, skip) : enhanced error handling -+apply_pending_resize() : thread-safe memory management -+enhanced_early_rejection_logic : gap-based decision system -+enhanced_fork_exception_prevention : comprehensive exception prevention -+make_operation_guard() : concurrent access protection -+begin_resize_barrier() : resize safety -+end_resize_barrier() : resize safety -+find_block_id_for_num(block_num) : enhanced multi-layered retrieval -+fetch_block_by_id(id) : enhanced multi-layered retrieval -+fetch_block_by_number(num) : enhanced multi-layered retrieval -+update_last_irreversible_block(skip) : enhanced fallback logic -+dynamic_rethrow_exception() : enhanced exception preservation -+shared_memory_corruption_detection : structured error handling -+auto_recovery_integration : seamless recovery procedures -+install_stacktrace_crash_handler() : crash diagnostics -+enhanced_dlt_gap_recovery : automatic gap recovery with warning suppression -} -class chainbase { -+free_memory() : size_t -+max_memory() : size_t -+reserved_memory() : size_t -+set_reserved_memory(value) -+resize(new_size) -+operation_guard : concurrent access protection -+begin_resize_barrier() : resize safety -+end_resize_barrier() : resize safety -} -class operation_guard { -+operation_guard(database& db) -+~operation_guard() -+release() : manual guard release -+operation_guard(operation_guard&& other) -} -class block_log { -+open(path) -+close() -+append(block) -+read_block_by_num(num) -+flush() -+head() -} -class dlt_block_log { -+open(path) -+close() -+append(block) -+read_block_by_num(num) -+truncate_before(new_start) -+head() -+num_blocks() -} -class fork_database { -+push_block(block) -+start_block(block) -+set_head(item) -+fetch_branch_from(first, second) -+set_max_size(n) -+set_emergency_mode(active) -+is_known_block(id) -+fetch_block_by_number(num) -+fetch_block_on_main_branch_by_number(num) -+is_emergency_mode() : emergency consensus mode flag -+remove_blocks_by_number(num) -+remove_blocks_by_number(num) -} -class signal_guard { -+setup() -+restore() -+get_is_interrupted() -+throw_exception() -} -class pending_transactions_restorer { -+pending_transactions_restorer(db, skip, pending) -+~pending_transactions_restorer() -+apply_trxs : bool -+applied_txs : uint32_t -+postponed_txs : uint32_t -} -class unlinkable_block_exception { -+inherits from chain_exception -+thrown for dead fork detection -+enhanced logging for fork recovery -} -class shared_memory_corruption_exception { -+inherits from chain_exception -+thrown for critical validation failures -+triggers automatic recovery procedures -+structured error reporting -} -class enhanced_early_rejection_logic { -+gap_based_decision_system(gap) -+defer_small_gaps_to_fork_db(gap) -+reject_large_gaps_immediately(gap) -+prevent_fork_db_exceptions(block) -+avoid_sync_restart_loops(block) -} -class enhanced_fork_exception_prevention { -+detect_dead_forks_at_or_below_head(block) -+prevent_fork_db_exceptions(block) -+classify_and_handle_unlinkable_blocks(block) -} -class exception_factory { -+rethrow(exception) : preserves derived types -+register_exception() : registers exception builders -} -class crash_debug_system { -+debug_crash_logging : comprehensive debug markers -+debug_block_production : detailed production logging -+stacktrace_crash_handlers : crash diagnostics -} -class dlt_gap_recovery_system { -+_dlt_gap_logged : bool flag for warning suppression -+detect_gap_in_dlt_log() : intelligent gap detection -+automatic_gap_recovery() : automatic recovery mechanisms -+suppress_repeated_warnings() : warning suppression logic -+enhanced_logging_for_recovery() : comprehensive recovery logging -} -database --> block_log : "uses (normal mode)" -database --> dlt_block_log : "uses (DLT mode)" -database --> fork_database : "uses with enhanced error handling" -database --> signal_guard : "enhanced restart handling" -database --> pending_transactions_restorer : "manages postponed tx" -database --> chainbase : "enhanced memory management with operation guards" -database --> unlinkable_block_exception : "enhanced fork handling" -database --> shared_memory_corruption_exception : "structured corruption detection" -database --> enhanced_early_rejection_logic : "gap-based decision system" -database --> enhanced_fork_exception_prevention : "comprehensive exception prevention" -database --> exception_factory : "enhanced exception preservation" -database --> crash_debug_system : "comprehensive crash debugging" -database --> dlt_gap_recovery_system : "automatic gap recovery with warning suppression" -chainbase --> operation_guard : "RAII concurrent access protection" -``` - -**Diagram sources** -- [database.hpp:61-115](file://libraries/chain/include/graphene/chain/database.hpp#L61-L115) -- [database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) -- [chainbase.hpp:1078-1120](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1120) -- [block_log.hpp:38-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L75) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) -- [fork_database.hpp:53-144](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L144) -- [database.cpp:94-184](file://libraries/chain/database.cpp#L94-L184) -- [db_with.hpp:33-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L33-L100) -- [chainbase.cpp:225-279](file://thirdparty/chainbase/src/chainbase.cpp#L225-L279) -- [database_exceptions.hpp:83](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L83) -- [database_exceptions.hpp:122](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L122) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [stacktrace.cpp:72-78](file://thirdparty/fc/src/stacktrace.cpp#L72-L78) - -## Detailed Component Analysis - -### Database Lifecycle: Constructor, Destructor, and Methods -- Constructor and destructor: Initialize internal implementation and ensure pending transactions are cleared on destruction. -- open(): Initializes schema, opens shared memory, initializes indexes and evaluators, loads genesis if needed, opens both block_log and dlt_block_log, rewinds undo state, verifies chain consistency, and initializes hardfork state. **Enhanced** with DLT mode detection and graceful error handling. -- open_from_snapshot(): **Enhanced** - Sets DLT mode flag to true, wipes shared memory for clean state, initializes schema and chainbase, opens both block_log and dlt_block_log, and logs snapshot import progress. -- reindex(): **Enhanced** - Uses signal_guard for graceful restart handling, reads blocks sequentially from the block log with improved error propagation, applies them with aggressive skip flags to accelerate replay, periodically sets revision, checks free memory, and updates fork database head. -- close(): Clears pending transactions, flushes and closes chainbase, closes both block_log and dlt_block_log, resets fork database. -- wipe(): Closes database, wipes shared memory file, optionally removes both block_log and dlt_block_log. - -```mermaid -sequenceDiagram -participant App as "Application" -participant DB as "database" -participant SG as "signal_guard" -participant CB as "chainbase" -participant BL as "block_log" -participant DLT as "dlt_block_log" -App->>DB : open_from_snapshot(data_dir, shared_mem_dir, ...) -DB->>DB : _dlt_mode = true -DB->>CB : wipe(shared_mem_dir) -DB->>DB : init_schema() -DB->>CB : open(shared_mem_dir, flags, size) -DB->>DB : initialize_indexes() -DB->>DB : initialize_evaluators() -DB->>DB : with_strong_write_lock(init_genesis) -DB->>BL : open(data_dir/"block_log") -DB->>DLT : open(data_dir/"dlt_block_log") -App->>DB : reindex(data_dir, shared_mem_dir, from_block_num, ...) -DB->>SG : setup() -DB->>DB : with_strong_write_lock() -loop for each block -DB->>BL : read_block_by_num(block_num) -DB->>DB : apply_block(block, skip_flags) -DB->>DB : check_free_memory(...) -DB->>DB : signal_guard : get_is_interrupted()? -alt interrupted -DB->>SG : restore() -DB->>App : appbase : app().quit() -end -end -DB->>DLT : start_block(head) -``` - -**Diagram sources** -- [database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) -- [database.cpp:330-410](file://libraries/chain/database.cpp#L330-L410) -- [database.cpp:134-184](file://libraries/chain/database.cpp#L134-L184) - -**Section sources** -- [database.hpp:61-115](file://libraries/chain/include/graphene/chain/database.hpp#L61-L115) -- [database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) -- [database.cpp:503-519](file://libraries/chain/database.cpp#L503-L519) -- [database.cpp:330-410](file://libraries/chain/database.cpp#L330-L410) -- [database.cpp:134-184](file://libraries/chain/database.cpp#L134-L184) - -### Enhanced Exception Handling Infrastructure -**Updated** - The database now includes sophisticated exception handling infrastructure that preserves derived exception types during rethrow operations: - -- **Exception Factory Registration**: The fc::exception_factory system registers exception builders for proper type restoration during rethrow operations. -- **Dynamic Rethrow Support**: Enhanced dynamic_rethrow_exception() method that preserves derived exception types using exception_factory::rethrow(). -- **Protocol Exception Integration**: Protocol exceptions include dynamic_rethrow_exception() implementations that check code() values before rethrowing to ensure proper type restoration. -- **Enhanced Exception Propagation**: The database uses FC_LOG_AND_RETHROW macros that preserve exception types during logging and rethrow operations. -- **Unlinkable Block Exception Enhancement**: The unlinkable_block_exception now includes comprehensive logging for fork recovery operations with detailed block information and head block context. -- **Shared Memory Corruption Exception**: New shared_memory_corruption_exception type provides structured error handling for critical validation failures with detailed logging and automatic recovery integration. - -```mermaid -flowchart TD -Start(["Exception Occurs"]) --> Capture["Catch fc::exception"] -Capture --> Log["Log exception details"] -Log --> CheckType{"Exception type registered?"} -CheckType --> |Yes| Factory["exception_factory::rethrow()"] -Factory --> ReThrow["Rethrow as derived type"] -CheckType --> |No| DirectThrow["Direct throw (preserve type)"] -DirectThrow --> ReThrow -ReThrow --> Propagate["Propagate to caller"] -``` - -**Diagram sources** -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exceptions.hpp:21-46](file://libraries/protocol/include/graphene/protocol/exceptions.hpp#L21-L46) - -**Section sources** -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exceptions.hpp:21-46](file://libraries/protocol/include/graphene/protocol/exceptions.hpp#L21-L46) - -### Enhanced Fork Database Management with Diagnostic Capabilities -**Updated** - The fork database now includes comprehensive diagnostic capabilities and enhanced logging for fork recovery operations: - -- **Enhanced Unlinkable Block Logging**: Improved logging of fork database linking failures with detailed block information, head block context, and parent-child relationships. -- **Comprehensive Fork Recovery Logging**: Detailed logging for fork switching operations including branch comparison results, exception handling during fork recovery, and state restoration procedures. -- **Enhanced Error Recovery**: Systematic error recovery procedures during fork switching with proper state restoration and fork database cleanup. -- **Improved Fork Switching Logic**: Enhanced fork comparison logic with deterministic tie-breaking and comprehensive error handling for invalid fork scenarios. -- **Dead Fork Detection**: Proper identification and removal of stale competing blocks from fork database to prevent memory bloat and improve performance. - -```mermaid -flowchart TD -Start(["Fork Switch Attempt"]) --> CheckHead{"Head in fork_db?"} -CheckHead --> |No| Reject["Reject block (cannot switch)"] -CheckHead --> |Yes| CompareBranches["Compare fork branches"] -CompareBranches --> ComputeWeight["Compute branch weights"] -ComputeWeight --> DecideSwitch{"Should switch forks?"} -DecideSwitch --> |Yes| CheckEmergency{"Emergency mode?"} -DecideSwitch --> |No| KeepCurrent["Keep current fork"] -CheckEmergency --> |No| SwitchForks["Perform fork switch"] -CheckEmergency --> |Yes| CheckTie{"Tie at same height?"} -CheckTie --> |No| SwitchForks -CheckTie --> |Yes| TieBreak["Use deterministic hash tie-breaking"] -TieBreak --> SwitchForks -SwitchForks --> HandleExceptions["Handle exceptions during fork recovery"] -HandleExceptions --> LogRecovery["Log fork recovery operations"] -LogRecovery --> Cleanup["Clean up fork database"] -Cleanup --> UpdateHead["Update fork_db head"] -KeepCurrent --> End(["Complete"]) -UpdateHead --> End -``` - -**Diagram sources** -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) -- [database.cpp:1440-1500](file://libraries/chain/database.cpp#L1440-L1500) - -**Section sources** -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) -- [database.cpp:1440-1500](file://libraries/chain/database.cpp#L1440-L1500) -- [database_exceptions.hpp:83](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L83) - -### Enhanced Early Rejection Logic for Blocks Far Ahead with Unknown Parents -**New** - The database now includes sophisticated early rejection logic that prevents fork database exceptions and sync restart loops during snapshot imports: - -- **Gap-Based Decision System**: The `_push_block()` method implements a gap-based decision system that rejects blocks based on the gap between block number and head block number. -- **Small Gap Deferral**: Blocks with gaps ≤ 100 are deferred to fork_db unlinked index for automatic chain linking when parent blocks arrive. -- **Large Gap Rejection**: Blocks with gaps > 100 are immediately rejected to prevent memory bloat from dead-fork blocks. -- **Prevent Fork Database Exceptions**: Eliminates unnecessary fork database operations for blocks that would cause unlinkable_block_exception. -- **Avoid Sync Restart Loops**: Prevents P2P sync restart loops that would stall synchronization during snapshot imports. -- **Intelligent Parent Validation**: The system checks if the block's parent is known in the fork database before attempting fork database operations. -- **Safe First Block Acceptance**: The system always allows blocks whose previous equals the head block ID to ensure sync progress continues. - -```mermaid -flowchart TD -Start(["_push_block(new_block)"]) --> CheckAtOrBelow{"new_block.block_num() <= head_block_num()?"} -CheckAtOrBelow --> |Yes| CheckExisting{"existing_id == new_block.id()?"} -CheckExisting --> |Yes| IgnoreBlock["Ignore block (already on chain)"] -CheckExisting --> |No| CheckParent{"new_block.previous != block_id_type() && !_fork_db.is_known_block(new_block.previous)?"} -CheckParent --> |Yes| RejectDeadFork["Reject dead fork block"] -CheckParent --> |No| FallThrough["Fall through to normal logic"] -CheckAtOrBelow --> |No| CheckFarAhead{"new_block.block_num() > head_block_num() && new_block.previous != block_id_type() && !_fork_db.is_known_block(new_block.previous)?"} -CheckFarAhead --> |Yes| CheckGap{"gap = new_block.block_num() - head_block_num()"} -CheckGap --> |gap > 100| RejectLargeGap["Reject large gap (>100) immediately"] -CheckGap --> |gap <= 100| DeferSmallGap["Defer small gap (<=100) to fork_db"] -CheckFarAhead --> |No| CheckForkDB["Proceed to fork_db.push_block()"] -IgnoreBlock --> ReturnFalse["return false"] -RejectDeadFork --> ThrowException["Throw unlinkable_block_exception"] -FallThrough --> CheckForkDB -RejectLargeGap --> ReturnFalse -DeferSmallGap --> LogDefer["Log deferral to fork_db unlinked index"] -CheckForkDB --> ForkDBPush["fork_db.push_block(new_block)"] -ForkDBPush --> ReturnResult["return result"] -``` - -**Diagram sources** -- [database.cpp:1216-1286](file://libraries/chain/database.cpp#L1216-L1286) -- [database.cpp:1360-1380](file://libraries/chain/database.cpp#L1360-L1380) - -**Section sources** -- [database.cpp:1216-1286](file://libraries/chain/database.cpp#L1216-L1286) -- [database.cpp:1360-1380](file://libraries/chain/database.cpp#L1360-L1380) - -### Enhanced Fork Database Exception Prevention Mechanisms -**New** - The database now includes comprehensive mechanisms to prevent fork database exceptions through intelligent early rejection and proper dead fork detection: - -- **Dead Fork Detection at or Below Head**: Blocks at or below the head but on different forks whose parents are not in the fork database are immediately rejected with unlinkable_block_exception, enabling P2P layer to soft-ban the offending peer. -- **Gap-Based Large Gap Rejection**: Blocks far ahead of the head with gaps > 100 are silently rejected to prevent fork database operations and sync restart loops. -- **Proper Exception Classification**: The system distinguishes between dead fork blocks (at/below head) and far-ahead blocks that slipped past early rejection for proper P2P handling. -- **Enhanced Error Propagation**: Proper unlinkable_block_exception throwing ensures downstream components can classify and handle different types of unlinkable blocks appropriately. - -```mermaid -flowchart TD -Start(["Enhanced Fork Exception Prevention"]) --> CheckDeadFork{"Block at or below head on different fork?"} -CheckDeadFork --> |Yes| CheckParentKnown{"Parent in fork_db?"} -CheckParentKnown --> |No| ThrowDeadFork["Throw unlinkable_block_exception (dead fork)"] -CheckParentKnown --> |Yes| NormalLogic["Fall through to normal logic"] -CheckDeadFork --> |No| CheckFarAhead{"Block far ahead with unknown parent?"} -CheckFarAhead --> |Yes| CheckGap{"gap > 100?"} -CheckGap --> |Yes| RejectLargeGap["Reject large gap immediately"] -CheckGap --> |No| DeferSmallGap["Defer small gap to fork_db"] -CheckFarAhead --> |No| CheckForkDB["Proceed to fork_db.push_block()"] -ThrowDeadFork --> Classify["P2P soft-bans peer (dead fork)"] -RejectLargeGap --> PreventLoop["Prevent sync restart loops"] -DeferSmallGap --> LogDefer["Log deferral to fork_db"] -NormalLogic --> CheckForkDB -CheckForkDB --> ForkDBOps["Fork DB operations"] -ForkDBOps --> EnhancedHandling["Enhanced error handling"] -``` - -**Diagram sources** -- [database.cpp:1216-1286](file://libraries/chain/database.cpp#L1216-L1286) -- [p2p_plugin.cpp:175-192](file://plugins/p2p/p2p_plugin.cpp#L175-L192) -- [node.cpp:3192-3211](file://libraries/network/node.cpp#L3192-L3211) - -**Section sources** -- [database.cpp:1216-1286](file://libraries/chain/database.cpp#L1216-L1286) -- [p2p_plugin.cpp:175-192](file://plugins/p2p/p2p_plugin.cpp#L175-L192) -- [node.cpp:3192-3211](file://libraries/network/node.cpp#L3192-L3211) - -### Enhanced P2P Synchronization with Early Rejection Integration -**New** - The P2P synchronization system now integrates with the early rejection logic to prevent sync restart loops: - -- **Unlinkable Block Classification**: The P2P layer distinguishes between dead fork blocks (at or below head) and far-ahead blocks that slipped past early rejection. -- **Dead Fork Handling**: At-or-below-head blocks from dead forks trigger soft-banning to prevent continued transmission of stale blocks. -- **Far-Ahead Block Handling**: Far-ahead blocks trigger sync restart instead of soft-banning to allow sequential block fetching. -- **Deferred Resize Integration**: The P2P layer handles deferred resize scenarios by restarting sync to re-fetch missed blocks after memory operations complete. - -```mermaid -flowchart TD -Start(["P2P Block Processing"]) --> TryPush["Try push_block()"] -TryPush --> Success{"Client accepted?"} -Success --> |Yes| UpdatePeers["Update peer lists"] -Success --> |No| CheckException{"Exception type?"} -CheckException --> |unlinkable_block_exception| Classify["Classify unlinkable block"] -CheckException --> |other| HandleOther["Handle other exceptions"] -Classify --> CheckNum{"peer_block_num <= our_head?"} -CheckNum --> |Yes| SoftBan["Soft-ban peer (dead fork)"] -CheckNum --> |No| RestartSync["Restart sync (far-ahead)"] -SoftBan --> UpdatePeers -RestartSync --> UpdatePeers -HandleOther --> UpdatePeers -UpdatePeers --> End(["Complete"]) -``` - -**Diagram sources** -- [node.cpp:3185-3384](file://libraries/network/node.cpp#L3185-L3384) -- [p2p_plugin.cpp:181-196](file://plugins/p2p/p2p_plugin.cpp#L181-L196) - -**Section sources** -- [node.cpp:3185-3384](file://libraries/network/node.cpp#L3185-L3384) -- [p2p_plugin.cpp:181-196](file://plugins/p2p/p2p_plugin.cpp#L181-L196) - -### Enhanced Operation Guard Implementation for Concurrent Access Protection -**New** - The database now features comprehensive operation guard implementation for concurrent access protection: - -- **Operation Guard RAII Pattern**: The `operation_guard` class provides automatic concurrent access protection using RAII pattern, ensuring proper cleanup when guards go out of scope. -- **Dual Operation Guard Patterns**: Systematic implementation of dual operation guards in validator scheduling calculations to prevent race conditions during complex slot determination operations. -- **Resize Barrier Integration**: Operation guards participate in resize barrier mechanisms, blocking during memory resizing operations to prevent stale pointer issues. -- **P2P Plugin Protection**: Operation guard integration in P2P plugin for safe concurrent access during block validation and validator key retrieval operations. -- **validator Scheduling Safety**: Dual operation guard patterns ensure thread safety during validator scheduling calculations, protecting lockless reads from concurrent memory resizing. -- **Concurrent Resize Safety**: Enhanced resize barrier mechanisms that pause all database operations during memory resizing, preventing data corruption and stale pointer issues. - -```mermaid -flowchart TD -Start(["Operation Guard Usage"]) --> CheckCritical{"Critical Section?"} -CheckCritical --> |Yes| CreateGuard["auto op_guard = make_operation_guard()"] -CheckCritical --> |No| NormalOp["Normal Operation"] -CreateGuard --> ExecuteOp["Execute Critical Operation"] -ExecuteOp --> ReleaseGuard["op_guard.release() (optional)"] -ReleaseGuard --> End(["Complete"]) -NormalOp --> End -``` - -**Diagram sources** -- [database.cpp:1556-1588](file://libraries/chain/database.cpp#L1556-L1588) -- [database.cpp:1593-1594](file://libraries/chain/database.cpp#L1593-L1594) -- [validator.cpp:271-300](file://plugins/validator/validator.cpp#L271-L300) -- [validator.cpp:506-507](file://plugins/validator/validator.cpp#L506-L507) -- [p2p_plugin.cpp:232-243](file://plugins/p2p/p2p_plugin.cpp#L232-L243) - -**Section sources** -- [chainbase.hpp:1078-1120](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1120) -- [database.cpp:1556-1588](file://libraries/chain/database.cpp#L1556-L1588) -- [database.cpp:1593-1594](file://libraries/chain/database.cpp#L1593-L1594) -- [validator.cpp:271-300](file://plugins/validator/validator.cpp#L271-L300) -- [validator.cpp:506-507](file://plugins/validator/validator.cpp#L506-L507) -- [p2p_plugin.cpp:232-243](file://plugins/p2p/p2p_plugin.cpp#L232-L243) - -### Enhanced Memory Allocation Strategies and Shared Memory Configuration -**Updated** - The memory management system now includes comprehensive logging capabilities for shared memory allocation and a new deferred resize mechanism: - -- **Auto-resize with Detailed Logging**: When free memory drops below a configured threshold, the system increases shared memory size and logs detailed state information including free memory, maximum memory, and reserved memory before and after resizing operations. -- **Enhanced Free Memory Monitoring**: Periodic checks at configured block intervals log free memory and trigger resizing if needed, with comprehensive state reporting for administrator visibility. -- **Reserved Memory Management**: Prevents fragmentation by reserving a portion of available memory and provides detailed logging of reserved memory states. -- **Configuration Knobs**: Minimum free memory threshold, increment size, and block interval for checks with enhanced monitoring capabilities. -- **Comprehensive Memory State Reporting**: The `_resize` function now logs detailed information about memory states before and after resizing operations, providing administrators with crucial information about memory usage patterns during blockchain operation. -- **Deferred Memory Resize**: The new `_pending_resize` and `_pending_resize_target` fields store resize requests until a safe point when no read locks are held, preventing race conditions and stale pointer issues. -- **Thread-Safe Memory Management**: The `apply_pending_resize()` method acquires its own write lock, waiting for all readers to finish before performing memory operations, ensuring thread safety during high-load scenarios. -- **Enhanced Error Handling**: Graceful handling of boost::interprocess::bad_alloc exceptions by returning false instead of throwing, preserving peer connectivity and logging validator slot-misses. - -```mermaid -flowchart TD -Entry(["check_free_memory(block_num)"]) --> ModCheck["block_num % _block_num_check_free_memory == 0?"] -ModCheck --> |No| Exit(["Return"]) -ModCheck --> |Yes| Compute["Compute reserved and free memory"] -Compute --> Compare{"free_mem < min_free_shared_memory_size?"} -Compare --> |No| Exit -Compare --> |Yes| Resize["_resize(block_num, immediate_resize)"] -Resize --> Exit -``` - -**Diagram sources** -- [database.cpp:639-673](file://libraries/chain/database.cpp#L639-L673) -- [database.cpp:562-605](file://libraries/chain/database.cpp#L562-L605) - -**Section sources** -- [database.cpp:562-605](file://libraries/chain/database.cpp#L562-L605) -- [database.cpp:639-673](file://libraries/chain/database.cpp#L639-L673) -- [database.cpp:412-422](file://libraries/chain/database.cpp#L412-L422) -- [database.cpp:454-482](file://libraries/chain/database.cpp#L454-L482) -- [chainbase.cpp:225-279](file://thirdparty/chainbase/src/chainbase.cpp#L225-L279) - -### Enhanced Memory Management Logging System -**New** - The database now includes comprehensive memory management logging capabilities: - -- **Detailed Resize Logging**: The `_resize` function logs comprehensive information including block number, new memory size, free memory before resizing, and maximum memory before resizing. -- **Post-Resize State Reporting**: After memory resizing, the system logs the current free memory and reserved memory states to provide administrators with immediate feedback on memory allocation changes. -- **Memory State Consistency**: The system ensures that memory state reporting accounts for reserved memory and provides accurate free memory calculations. -- **Administrator Visibility**: Enhanced logging provides administrators with detailed insights into memory usage patterns and helps identify potential memory pressure situations before they impact system performance. -- **Deferred Resize Logging**: The `apply_pending_resize()` method logs detailed information about deferred resize operations, including target memory size and completion status. - -```mermaid -flowchart TD -Start(["_resize(current_block_num, immediate)"]) --> CheckConfig{"_inc_shared_memory_size != 0?"} -CheckConfig --> |No| LogError["elog('Auto-scaling not configured!')"] -CheckConfig --> |Yes| GetStates["Get max_mem and free_mem_before"] -GetStates --> CalculateNew["Calculate new_max = max_mem + _inc_shared_memory_size"] -CalculateNew --> CheckImmediate{"immediate?"} -CheckImmediate --> |No| LogDeferred["wlog deferred resize info"] -LogDeferred --> SetFlags["_pending_resize = true, _pending_resize_target = new_max"] -SetFlags --> ReturnTrue["return true"] -CheckImmediate --> |Yes| LogResize["wlog immediate resize info"] -LogResize --> ResizeFile["resize(new_max)"] -ResizeFile --> GetPostState["Get free_mem and reserved_mem"] -GetPostState --> LogState["wlog free and reserved memory states"] -LogState --> UpdatePrinted["Update _last_free_gb_printed"] -UpdatePrinted --> ReturnTrue -``` - -**Diagram sources** -- [database.cpp:562-605](file://libraries/chain/database.cpp#L562-L605) - -**Section sources** -- [database.cpp:562-605](file://libraries/chain/database.cpp#L562-L605) - -### Enhanced Memory Management Configuration Options -**New** - The database now provides enhanced configuration options for memory management: - -- **set_min_free_shared_memory_size(value)**: Configures the minimum free memory threshold that triggers automatic resizing operations. -- **set_inc_shared_memory_size(value)**: Sets the increment size for shared memory file expansion when the minimum free memory threshold is exceeded. -- **set_block_num_check_free_size(value)**: Configures the block interval for checking free memory and triggering resize operations. -- **Enhanced Memory Monitoring**: The system provides comprehensive monitoring of memory states with detailed logging and reporting capabilities. -- **apply_pending_resize()**: New method that applies deferred memory resize operations at safe points when no read locks are held. - -**Section sources** -- [database.hpp:148-164](file://libraries/chain/include/graphene/chain/database.hpp#L148-L164) -- [database.cpp:546-556](file://libraries/chain/database.cpp#L546-L556) - -### Enhanced Memory Management Fields -**New** - The database now includes two new fields for deferred memory management: - -- **_pending_resize**: A boolean flag indicating whether a memory resize operation is pending and should be applied at the next safe point. -- **_pending_resize_target**: Stores the target memory size for deferred resize operations, allowing the system to apply the resize when thread safety permits. - -These fields enable the deferred resize mechanism to work seamlessly with the existing memory management system while ensuring thread safety during high-load scenarios. - -**Section sources** -- [database.hpp:631-632](file://libraries/chain/include/graphene/chain/database.hpp#L631-L632) - -### Enhanced Memory Management Usage in Block Processing -**New** - The deferred memory resize mechanism is integrated into the block processing pipeline: - -- **push_block()**: Calls `apply_pending_resize()` at the beginning of block processing, before acquiring the main write lock, ensuring memory operations don't interfere with concurrent read operations. -- **generate_block()**: Calls `apply_pending_resize()` before lockless reads, preventing stale pointer issues when memory is resized during block generation. -- **Exception Handling**: When memory exhaustion occurs, the system schedules a deferred resize and lets the exception propagate, preserving peer connectivity and logging validator slot-misses. - -**Updated** - Enhanced error handling for shared memory exhaustion: - -- **Graceful Exception Handling**: The push_block() method now catches boost::interprocess::bad_alloc exceptions and handles them gracefully. -- **Peer Connectivity Preservation**: Instead of throwing exceptions that would disconnect peers, the system returns false and schedules a deferred resize. -- **Memory State Preservation**: The system preserves memory state by setting reserved memory to current free memory before scheduling resize. -- **Automatic Recovery**: The next push_block() call will apply the deferred resize safely, allowing the missed block to be re-received during normal sync. - -**Enhanced Error Handling for Memory Allocation Failures** - The push_block() function now includes sophisticated error handling for boost::interprocess::bad_alloc exceptions: - -- **Exception Detection**: The system detects boost::interprocess::bad_alloc exceptions by searching for the specific error message pattern "boost::interprocess::bad_alloc". -- **Graceful Degradation**: Instead of throwing the exception and potentially disconnecting peers, the system schedules a deferred resize and returns false to indicate the block was not applied. -- **State Preservation**: The system preserves memory state by setting reserved memory to current free memory level before scheduling the resize. -- **Peer Connectivity**: This approach prevents P2P layer disconnections and maintains validator slot-miss logging while preserving node connectivity. -- **Automatic Recovery**: The next push_block() call will apply the deferred resize safely, allowing the missed block to be re-received during normal sync. - -```mermaid -flowchart TD -Start(["push_block(new_block)"]) --> ApplyResize["apply_pending_resize()"] -ApplyResize --> AcquireLock["with_strong_write_lock()"] -AcquireLock --> TryBlock["_push_block(new_block, skip)"] -TryBlock --> CheckMemory["check_free_memory(false, new_block.block_num())"] -CheckMemory --> Success["Return result"] -TryBlock --> Exception{"Memory exception?"} -Exception --> |Yes| CheckBadAlloc{"boost::interprocess::bad_alloc?"} -CheckBadAlloc --> |No| Rethrow["throw e"] -CheckBadAlloc --> |Yes| ScheduleResize["set_reserved_memory(free_memory())"] -ScheduleResize --> SetPending["_resize(new_block.block_num())"] -SetPending --> ReturnFalse["result = false"] -Exception --> |No| Success -``` - -**Diagram sources** -- [database.cpp:1106-1145](file://libraries/chain/database.cpp#L1106-L1145) -- [database.cpp:1460-1470](file://libraries/chain/database.cpp#L1460-L1470) - -**Section sources** -- [database.cpp:1106-1145](file://libraries/chain/database.cpp#L1106-L1145) -- [database.cpp:1460-1470](file://libraries/chain/database.cpp#L1460-L1470) - -### Enhanced Fork Database Handling with Unlinkable Block Exception -**Updated** - The fork database now includes enhanced error handling for proper dead fork detection: - -- **Proper Exception Throwing**: The fork_database::push_block() method now properly throws unlinkable_block_exception when blocks fail to link, enabling better dead fork detection. -- **Enhanced Logging**: Improved logging of fork database linking failures with detailed block information and head block context. -- **Unlinked Block Caching**: Previously unlinked blocks are cached in _unlinked_index for later processing when their parents become available. -- **Improved Fork Switching**: The database's fork switching logic now properly handles unlinkable_block_exception to prevent processing blocks from dead forks. -- **Deterministic Tie-Breaking**: During emergency consensus mode, blocks with identical heights are selected deterministically using block_id hash comparison. - -```mermaid -flowchart TD -Start(["fork_database::push_block(block)"]) --> TryPush["_push_block(item)"] -TryPush --> Success{"Link successful?"} -Success --> |Yes| CheckEmergency{"Emergency mode?"} -CheckEmergency --> |No| ReturnHead["Return _head"] -CheckEmergency --> |Yes| CheckHeight{"Same height as head?"} -CheckHeight --> |No| ReturnHead -CheckHeight --> |Yes| CheckHash{"item.id < _head->id?"} -CheckHash --> |Yes| SetHead["Set _head = item"] -CheckHash --> |No| KeepHead["Keep current head"] -Success --> |No| CacheUnlinked["Cache in _unlinked_index"] -CacheUnlinked --> ThrowException["Throw unlinkable_block_exception"] -ThrowException --> Propagate["Propagate to caller"] -ReturnHead --> Propagate -SetHead --> Propagate -KeepHead --> Propagate -``` - -**Diagram sources** -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) - -**Section sources** -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) -- [database_exceptions.hpp:83](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L83) - -### Enhanced Fork Switching Logic with Dead Fork Detection and Deterministic Tie-Breaking -**Updated** - The database's fork switching logic now includes improved dead fork detection and emergency consensus tie-breaking: - -- **Dead Fork Detection**: When attempting to switch forks, the system checks if the current head block exists in the fork database before proceeding. -- **Proper Exception Handling**: If the head block is not in the fork database, the system removes the candidate block and throws unlinkable_block_exception. -- **Enhanced Branch Comparison**: Improved fork comparison logic with proper handling of emergency consensus mode tie-breaking using deterministic hash-based selection. -- **Safe Fork Switching**: The system ensures fork switching only occurs when both chains are valid and linked to the current state. -- **Emergency Consensus Tie-Breaking**: During emergency mode, identical-height blocks are selected deterministically by comparing block_id hashes. - -```mermaid -flowchart TD -Start(["Fork Switch Decision"]) --> CheckHead{"Head in fork_db?"} -CheckHead --> |No| RejectFork["Remove candidate block"] -RejectFork --> ThrowException["Throw unlinkable_block_exception"] -CheckHead --> |Yes| CompareBranches["Compare fork branches"] -CompareBranches --> ComputeWeight["Compute branch weights"] -ComputeWeight --> DecideSwitch{"Should switch forks?"} -DecideSwitch --> |Yes| CheckEmergency{"Emergency mode?"} -DecideSwitch --> |No| KeepCurrent["Keep current fork"] -CheckEmergency --> |No| SwitchForks["Perform fork switch"] -CheckEmergency --> |Yes| CheckTie{"Tie at same height?"} -CheckTie --> |No| SwitchForks -CheckTie --> |Yes| TieBreak["Use deterministic hash tie-breaking"] -TieBreak --> SwitchForks -SwitchForks --> UpdateHead["Update fork_db head"] -KeepCurrent --> End(["Complete"]) -UpdateHead --> End -``` - -**Diagram sources** -- [database.cpp:1295-1377](file://libraries/chain/database.cpp#L1295-L1377) - -**Section sources** -- [database.cpp:1295-1377](file://libraries/chain/database.cpp#L1295-L1377) - -### Enhanced Multi-Layered Block Retrieval System -**New** - The database now implements comprehensive multi-layered block retrieval with systematic fallback mechanisms: - -- **Hierarchical Retrieval Strategy**: The `find_block_id_for_num()`, `fetch_block_by_id()`, and `fetch_block_by_number()` methods implement a three-tiered retrieval system: - 1. Primary: Check fork database for current/main branch blocks - 2. Secondary: Check block log for irreversible blocks - 3. Tertiary: Check DLT block log as fallback in DLT mode - 4. Final: Query fork database for any available blocks - -- **DLT Mode Awareness**: In DLT mode, the system prioritizes DLT block log as secondary storage while maintaining fallback to block log and fork database. -- **Consistent Behavior**: This ensures that block retrieval works consistently regardless of which storage layer contains the requested data. -- **Enhanced Fault Tolerance**: Multiple fallback points prevent single points of failure and improve system reliability. - -```mermaid -flowchart TD -Start(["Block Retrieval Request"]) --> CheckForkDB["Check fork database (primary)"] -CheckForkDB --> FoundFork{"Found in fork_db?"} -FoundFork --> |Yes| ReturnFork["Return from fork_db"] -FoundFork --> |No| CheckBlockLog["Check block_log (secondary)"] -CheckBlockLog --> FoundBlockLog{"Found in block_log?"} -FoundBlockLog --> |Yes| ReturnBlockLog["Return from block_log"] -FoundBlockLog --> |No| CheckDLT{"DLT mode enabled?"} -CheckDLT --> |Yes| CheckDLTLog["Check dlt_block_log (fallback)"] -CheckDLTLog --> FoundDLT{"Found in dlt_block_log?"} -FoundDLT --> |Yes| ReturnDLT["Return from dlt_block_log"] -FoundDLT --> |No| CheckFinalFork["Check fork_db for any blocks"] -CheckFinalFork --> FoundAny{"Found in fork_db?"} -FoundAny --> |Yes| ReturnAny["Return from fork_db"] -FoundAny --> |No| ReturnNull["Return null"] -``` - -**Diagram sources** -- [database.cpp:789-827](file://libraries/chain/database.cpp#L789-L827) -- [database.cpp:860-882](file://libraries/chain/database.cpp#L860-L882) -- [database.cpp:884-901](file://libraries/chain/database.cpp#L884-L901) - -**Section sources** -- [database.cpp:789-827](file://libraries/chain/database.cpp#L789-L827) -- [database.cpp:860-882](file://libraries/chain/database.cpp#L860-L882) -- [database.cpp:884-901](file://libraries/chain/database.cpp#L884-L901) - -### Enhanced Last Irreversible Block Advancement Logic -**New** - The `update_last_irreversible_block()` method now includes comprehensive fallback mechanisms: - -- **Primary Block Log Check**: First attempts to retrieve the irreversible block from the primary block log -- **Fork Database Fallback**: If block log retrieval fails, checks the fork database for the same block number -- **Consistent ID Assignment**: Uses the fork database block data when available to maintain consistency -- **Enhanced Error Handling**: Prevents crashes when blocks are missing from block log while ensuring proper LIB advancement - -```mermaid -flowchart TD -Start(["update_last_irreversible_block()"]) --> CheckMode{"Normal mode or DLT mode?"} -CheckMode --> |Normal Mode| CheckBlockLog["Read LIB from block_log"] -CheckBlockLog --> CheckValid{"Block valid?"} -CheckValid --> |Yes| SetLIB["Set LIB ID from block_log"] -CheckValid --> |No| CheckForkDB["Check fork_db for LIB block"] -CheckForkDB --> CheckForkValid{"Fork block valid?"} -CheckForkValid --> |Yes| SetLIBFork["Set LIB ID from fork_db"] -CheckForkValid --> |No| ClearLIB["Clear LIB fields"] -CheckMode --> |DLT Mode| CheckDLTLog["Read LIB from dlt_block_log"] -CheckDLTLog --> CheckDLTValid{"DLT block valid?"} -CheckDLTValid --> |Yes| SetLIBDLT["Set LIB ID from dlt_block_log"] -CheckDLTValid --> |No| CheckForkDBDLT["Check fork_db for LIB block"] -CheckForkDBDLT --> CheckForkValidDLT{"Fork block valid?"} -CheckForkValidDLT --> |Yes| SetLIBForkDLT["Set LIB ID from fork_db"] -CheckForkValidDLT --> |No| ClearLIB -SetLIB --> UpdateFields["Update LIB reference fields"] -SetLIBFork --> UpdateFields -SetLIBDLT --> UpdateFields -SetLIBForkDLT --> UpdateFields -ClearLIB --> End(["Complete"]) -UpdateFields --> End -``` - -**Diagram sources** -- [database.cpp:5452-5482](file://libraries/chain/database.cpp#L5452-L5482) -- [database.cpp:5467-5480](file://libraries/chain/database.cpp#L5467-L5480) - -**Section sources** -- [database.cpp:5452-5482](file://libraries/chain/database.cpp#L5452-L5482) -- [database.cpp:5467-5480](file://libraries/chain/database.cpp#L5467-L5480) - -### Enhanced Block Number Collision Detection and Logging -**New** - The database now features sophisticated collision detection with rate-limiting and scenario differentiation: - -- **Same-Parent vs Different-Parent Detection**: The system differentiates between same-parent double production (colliding blocks from the same parent) and different-parent fork scenarios (divergent chain tips). -- **Rate-Limited Warnings**: Uses a static counter and timestamp to suppress repeated warnings at the same block height to avoid log spam during sustained fork conditions. -- **Timestamp Delta Analysis**: Calculates time differences between colliding blocks to help diagnose timing issues. -- **validator Information Logging**: Logs validator names and timestamps for all colliding blocks to aid in forensic analysis. -- **Parent Block ID Tracking**: Records previous block IDs to help analyze fork topology and collision origins. - -```mermaid -flowchart TD -Start(["Block Height Collision"]) --> FetchBlocks["fetch_block_by_number(height)"] -FetchBlocks --> CheckSize{"blocks.size() > 1?"} -CheckSize --> |No| Return["No collision"] -CheckSize --> |Yes| ExtractInfo["Extract validator, timestamp, previous_id"] -ExtractInfo --> SameParent{"all previous_ids identical?"} -SameParent --> |Yes| DoubleProd["Same Parent - Possible Double Production"] -SameParent --> |No| ForkScenario["Different Parents - Fork Scenario"] -DoubleProd --> RateLimit["Check rate limit (5s window)"] -ForkScenario --> RateLimit -RateLimit --> ShouldLog{"Should log warning?"} -ShouldLog --> |No| Return -ShouldLog --> |Yes| LogWarning["Log collision with scenario differentiation"] -LogWarning --> UpdateState["Update last_warned_height/time"] -UpdateState --> LogParents["Log previous block IDs for topology analysis"] -LogParents --> Return -``` - -**Diagram sources** -- [database.cpp:1147-1202](file://libraries/chain/database.cpp#L1147-L1202) - -**Section sources** -- [database.cpp:1147-1202](file://libraries/chain/database.cpp#L1147-L1202) - -### Enhanced Postponed Transaction Processing -**New** - The database now implements intelligent transaction queuing with time-based execution limits: - -- **Time-Based Execution Limits**: Uses `CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT` constant to control processing time per batch. -- **Automatic Queue Management**: When execution time exceeds the limit, transactions are automatically postponed to the next processing cycle. -- **Smart Recovery**: The `pending_transactions_restorer` class handles recovery after fork switches, attempting to reapply transactions within time limits. -- **Progressive Application**: Processes transactions in batches, applying as many as possible within the time limit, with postponed transactions moved to the pending queue. -- **Diagnostic Logging**: Logs the number of applied and postponed transactions to monitor system performance. - -```mermaid -flowchart TD -Start(["Transaction Processing"]) --> CheckTime["Check time elapsed < CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT"] -CheckTime --> |Within Limit| ApplyTx["Apply transaction immediately"] -CheckTime --> |Exceeded Limit| Postpone["Add to postponed queue"] -ApplyTx --> NextTx["Next transaction"] -Postpone --> NextTx -NextTx --> MoreTx{"More transactions?"} -MoreTx --> |Yes| CheckTime -MoreTx --> |No| Complete["Complete processing"] -Complete --> Recovery["pending_transactions_restorer recovery"] -Recovery --> BatchApply["Batch apply within time limit"] -BatchApply --> Finalize["Finalize processing"] -``` - -**Diagram sources** -- [db_with.hpp:33-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L33-L100) - -**Section sources** -- [db_with.hpp:33-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L33-L100) - -### Validation Steps Enumeration and Use Cases -Validation flags control which checks are performed during block and transaction validation: -- skip_nothing: Perform all validations -- skip_witness_signature: Skip validator signature verification (used during reindex) -- skip_transaction_signatures: Skip transaction signatures (used by non-validator nodes) -- skip_transaction_dupe_check: Skip duplicate transaction checks -- skip_fork_db: Skip fork database checks -- skip_block_size_check: Allow oversized blocks when generating locally -- skip_tapos_check: Skip TaPoS and expiration checks -- skip_authority_check: Skip authority checks -- skip_merkle_check: Skip Merkle root verification -- skip_undo_history_check: Skip undo history bounds -- skip_witness_schedule_check: Skip validator schedule validation -- skip_validate_operations: Skip operation validation -- skip_undo_block: Skip undo db on reindex -- skip_block_log: Skip writing to block log (used in DLT mode) -- skip_apply_transaction: Skip applying transaction -- skip_database_locking: Skip database locking - -Typical usage: -- Reindex uses a combination of flags to accelerate replay -- Block generation may skip certain checks for local blocks -- Validation-only nodes may skip expensive checks -- DLT mode uses skip_block_log to avoid normal block log operations - -**Section sources** -- [database.hpp:79-96](file://libraries/chain/include/graphene/chain/database.hpp#L79-L96) -- [database.cpp:340-350](file://libraries/chain/database.cpp#L340-L350) -- [database.cpp:4346-4366](file://libraries/chain/database.cpp#L4346-L4366) - -### Session Management and Undo Semantics -- Pending transaction session: A temporary undo session is created when pushing the first transaction after applying a block; successful transactions merge into the pending block session. -- Block application session: A strong write lock wraps block application; a temporary undo session is used per transaction; upon success, the session is pushed. -- Undo history: Enforced with bounds; last irreversible block advancement commits revisions and writes to appropriate block log based on DLT mode. - -```mermaid -flowchart TD -Start(["push_transaction(trx, skip)"]) --> CheckSession["Check _pending_tx_session.valid()"] -CheckSession --> |No| NewSession["start_undo_session() -> _pending_tx_session"] -CheckSession --> |Yes| UseExisting["Use existing _pending_tx_session"] -NewSession --> TempSession["start_undo_session() -> temp_session"] -UseExisting --> TempSession -TempSession --> ApplyTx["_apply_transaction(trx, skip)"] -ApplyTx --> Success{"Apply success?"} -Success --> |Yes| Merge["temp_session.squash()"] -Merge --> CheckDLT{"_dlt_mode?"} -CheckDLT --> |No| WriteNormal["Write to block_log"] -CheckDLT --> |Yes| WriteDLT["Write to dlt_block_log"] -WriteNormal --> Notify["notify_on_pending_transaction(trx)"] -WriteDLT --> Notify -Success --> |No| Discard["temp_session destructor discards changes"] -Notify --> End(["Return"]) -Discard --> End -``` - -**Diagram sources** -- [database.cpp:948-970](file://libraries/chain/database.cpp#L948-L970) -- [database.cpp:3652-3711](file://libraries/chain/database.cpp#L3652-L3711) - -**Section sources** -- [database.cpp:948-970](file://libraries/chain/database.cpp#L948-L970) -- [database.cpp:3652-3711](file://libraries/chain/database.cpp#L3652-L3711) - -### Checkpoint System for Fast Synchronization -- Checkpoints: A map of block number to expected block ID is maintained; when a checkpoint matches, the system skips expensive validations and authority checks for subsequent blocks until the last checkpoint. -- before_last_checkpoint(): Determines whether the current head is before the last checkpoint to decide whether to enforce stricter checks. - -```mermaid -flowchart TD -Start(["apply_block(block, skip)"]) --> HasCheckpoints{"_checkpoints.size() > 0?"} -HasCheckpoints --> |No| Apply["_apply_block(..., skip)"] -HasCheckpoints --> |Yes| Match{"Checkpoint present for block_num?"} -Match --> |Yes| Tighten["Set skip flags for strict checks"] -Tighten --> Apply -Match --> |No| Apply -``` - -**Diagram sources** -- [database.cpp:3444-3499](file://libraries/chain/database.cpp#L3444-L3499) - -**Section sources** -- [database.hpp:218-224](file://libraries/chain/include/graphene/chain/database.hpp#L218-L224) -- [database.cpp:3444-3499](file://libraries/chain/database.cpp#L3444-L3499) - -### Block Log Integration and Last Irreversible Block Advancement -**Enhanced** - The block log integration now includes improved gap handling for DLT mode: - -- Block log: Append-only storage with a secondary index enabling O(1) random access by block number. -- DLT Block Log: Rolling window storage for DLT mode nodes, maintaining a configurable number of recent blocks. -- IRV advancement: When sufficient validator validations are collected, the system advances last irreversible block, commits the revision, writes blocks to appropriate log based on DLT mode, and updates dynamic global properties with reference fields. -- **Enhanced Gap Logging**: Improved logging for DLT block log gaps during block processing to help diagnose synchronization issues with contextual information. - -```mermaid -sequenceDiagram -participant DB as "database" -participant FD as "fork_database" -participant BL as "block_log" -participant DLT as "dlt_block_log" -participant DGP as "dynamic_global_property_object" -DB->>DB : check_block_post_validation_chain() -alt Enough validations -DB->>DGP : last_irreversible_block_num++ -DB->>DB : commit(last_irreversible_block_num) -alt Normal mode -DB->>BL : append(block) (if not skipping) -else DLT mode with rolling window -DB->>DLT : append(block) -DB->>DLT : flush() -DB->>DLT : truncate_before() if needed -end -DB->>DGP : update last_irreversible_block_id/ref fields -DB->>FD : set_max_size(head - LRI + 1) -end -``` - -**Diagram sources** -- [database.cpp:3986-4039](file://libraries/chain/database.cpp#L3986-L4039) -- [database.cpp:4144-4175](file://libraries/chain/database.cpp#L4144-L4175) -- [database.cpp:4346-4366](file://libraries/chain/database.cpp#L4346-L4366) - -**Section sources** -- [block_log.hpp:38-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L75) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) -- [database.cpp:3986-4039](file://libraries/chain/database.cpp#L3986-L4039) -- [database.cpp:4144-4175](file://libraries/chain/database.cpp#L4144-L4175) -- [database.cpp:4346-4366](file://libraries/chain/database.cpp#L4346-L4366) - -### Observer Pattern Implementation -The database exposes signals for event-driven state changes: -- pre_apply_operation: Emitted before applying an operation -- post_apply_operation: Emitted after applying an operation -- applied_block: Emitted after a block is applied and committed -- on_pending_transaction: Emitted when a transaction is added to the pending state -- on_applied_transaction: Emitted when a transaction is applied to the chain state - -These signals are used by plugins to react to blockchain events without tight coupling. - -**Section sources** -- [database.hpp:284-307](file://libraries/chain/include/graphene/chain/database.hpp#L284-L307) -- [database.cpp:1158-1198](file://libraries/chain/database.cpp#L1158-L1198) -- [database.cpp:3652-3655](file://libraries/chain/database.cpp#L3652-L3655) - -### Enhanced Crash Debugging Capabilities -**New** - The database now includes comprehensive crash debugging capabilities with debug_crash logging throughout critical code paths: - -- **Comprehensive Debug Logging**: Extensive debug_crash logging markers (DEBUG_CRASH) throughout database operations including push_block, update_witness_schedule, schedule normal build, hybrid override, process_funds, notify_applied_block, and notify_changed_objects. -- **Block Production Monitoring**: The debug-block-production configuration option enables detailed block production logging and monitoring for troubleshooting production issues. -- **Stacktrace Crash Handlers**: Enhanced stacktrace crash handlers provide automatic stacktrace generation for crash diagnostics, improving debugging experience for fatal errors. -- **Enhanced Diagnostic Visibility**: Debug logging throughout critical code paths provides comprehensive visibility into database operations for improved troubleshooting and performance analysis. - -The debug_crash logging system includes markers for: -- push_block operations with validator information -- validator schedule updates and emergency consensus handling -- block production scheduling and execution -- fund processing and block notification cycles -- LIB advancement and fork database operations - -**Section sources** -- [database.cpp:1890-1892](file://libraries/chain/database.cpp#L1890-L1892) -- [database.cpp:2281-2283](file://libraries/chain/database.cpp#L2281-L2283) -- [database.cpp:2466-2467](file://libraries/chain/database.cpp#L2466-L2467) -- [database.cpp:2526-2527](file://libraries/chain/database.cpp#L2526-L2527) -- [database.cpp:2536-2537](file://libraries/chain/database.cpp#L2536-L2537) -- [database.cpp:4536-4537](file://libraries/chain/database.cpp#L4536-L4537) -- [database.cpp:4538-4539](file://libraries/chain/database.cpp#L4538-L4539) -- [database.cpp:4544-4545](file://libraries/chain/database.cpp#L4544-L4545) -- [database.cpp:4567-4568](file://libraries/chain/database.cpp#L4567-L4568) -- [database.cpp:4569-4570](file://libraries/chain/database.cpp#L4569-L4570) -- [database.cpp:4571-4572](file://libraries/chain/database.cpp#L4571-L4572) -- [database.cpp:4573-4574](file://libraries/chain/database.cpp#L4573-L4574) -- [database.cpp:5530-5531](file://libraries/chain/database.cpp#L5530-L5531) -- [database.cpp:5543-5544](file://libraries/chain/database.cpp#L5543-L5544) -- [database.cpp:5677-5678](file://libraries/chain/database.cpp#L5677-L5678) -- [database.cpp:5680-5681](file://libraries/chain/database.cpp#L5680-L5681) - -### Enhanced Block Production Debugging -**New** - The debug-block-production configuration option provides detailed block production logging and monitoring: - -- **Configuration Option**: The debug-block-production option is available in the Validator Plugin configuration with default value false. -- **Runtime Control**: The option can be enabled/disabled at runtime through command-line configuration. -- **Production Loop Monitoring**: Comprehensive logging for block production loop including entry/exit points, maybe_produce_block results, and scheduling operations. -- **validator Production Tracking**: Detailed logging for validator production scheduling, slot determination, and block generation processes. -- **Emergency Consensus Monitoring**: Enhanced logging for emergency consensus mode operations including validator schedule overrides and hybrid production scenarios. - -**Section sources** -- [validator.cpp:159-160](file://plugins/validator/validator.cpp#L159-L160) -- [validator.cpp:228-233](file://plugins/validator/validator.cpp#L228-L233) -- [validator.cpp:338-340](file://plugins/validator/validator.cpp#L338-L340) -- [validator.cpp:356-357](file://plugins/validator/validator.cpp#L356-L357) -- [validator.cpp:403-405](file://plugins/validator/validator.cpp#L403-L405) -- [validator.cpp:411-412](file://plugins/validator/validator.cpp#L411-L412) -- [validator.cpp:416-417](file://plugins/validator/validator.cpp#L416-L417) -- [validator.cpp:418-419](file://plugins/validator/validator.cpp#L418-L419) - -### Enhanced Stacktrace Crash Handlers -**New** - The stacktrace crash handlers provide improved crash diagnostics and debugging experience: - -- **Signal Handler Integration**: Enhanced stacktrace crash handlers integrate with standard signal handlers for SIGSEGV, SIGABRT, SIGFPE, and SIGILL. -- **Automatic Stacktrace Generation**: On fatal errors, the system generates detailed stacktrace information including demangled function names and line numbers. -- **Crash Diagnostics**: Comprehensive logging of fatal error signals with stacktrace information for improved debugging and troubleshooting. -- **Integration with Crash Debugging**: Works in conjunction with debug_crash logging to provide complete crash diagnostics and troubleshooting information. - -**Section sources** -- [stacktrace.cpp:48-78](file://thirdparty/fc/src/stacktrace.cpp#L48-L78) - -### Enhanced DLT Gap Recovery System -**New** - The database now includes comprehensive DLT gap recovery mechanisms with intelligent warning suppression: - -- **Gap Detection**: The system intelligently detects gaps between DLT block log end and fork database start during LIB advancement and block processing. -- **Automatic Gap Recovery**: When gaps are detected, the system automatically resets the DLT block log and rebuilds it from the fork database to ensure continuity. -- **Warning Suppression**: The `_dlt_gap_logged` flag mechanism prevents repeated warnings during gap recovery by suppressing repeated "block not in fork_db" messages until the gap is filled. -- **Enhanced Logging**: Comprehensive logging throughout the gap detection and recovery workflow provides detailed diagnostics for troubleshooting. -- **Gap Recovery Completion**: When gaps are filled, the system resets the `_dlt_gap_logged` flag to allow future warnings if gaps reoccur. - -```mermaid -flowchart TD -Start(["DLT Gap Detection"]) --> CheckGap{"Gap detected in DLT log?"} -CheckGap --> |No| NormalOperation["Normal operation"] -CheckGap --> |Yes| CheckRecoverable{"Recoverable gap?"} -CheckRecoverable --> |Yes| ResetDLT["Reset DLT block log from fork_db"] -ResetDLT --> RebuildDLT["Rebuild DLT log from fork_db start"] -RebuildDLT --> SuppressWarning["Set _dlt_gap_logged = true"] -SuppressWarning --> LogRecovery["Log gap recovery"] -LogRecovery --> FlushDLT["Flush DLT block log"] -FlushDLT --> ClearFlag["Clear _dlt_gap_logged on completion"] -ClearFlag --> NormalOperation -CheckRecoverable --> |No| LogNoRecover["Log no recoverable range found"] -LogNoRecover --> SuppressWarning -``` - -**Diagram sources** -- [database.cpp:5092-5105](file://libraries/chain/database.cpp#L5092-L5105) -- [database.cpp:5283-5297](file://libraries/chain/database.cpp#L5283-L5297) -- [database.cpp:5616-5635](file://libraries/chain/database.cpp#L5616-L5635) - -**Section sources** -- [database.cpp:5092-5105](file://libraries/chain/database.cpp#L5092-L5105) -- [database.cpp:5283-5297](file://libraries/chain/database.cpp#L5283-L5297) -- [database.cpp:5616-5635](file://libraries/chain/database.cpp#L5616-L5635) - -### Examples of Database Operations and Queries -- Open database and initialize: open(data_dir, shared_mem_dir, initial_supply, shared_file_size, chainbase_flags) -- **Open from snapshot**: open_from_snapshot(data_dir, shared_mem_dir, initial_supply, shared_file_size, chainbase_flags) - **Enhanced** -- Rebuild state from history: reindex(data_dir, shared_mem_dir, from_block_num, shared_file_size) - **Enhanced with signal handling** -- Push a block: push_block(signed_block, skip_flags) - **Enhanced with shared memory error handling, gap-based early rejection logic, operation guard protection, and comprehensive debug logging** -- Push a transaction: push_transaction(signed_transaction, skip_flags) -- Validate a block: validate_block(signed_block, skip_flags) -- Validate a transaction: validate_transaction(signed_transaction, skip_flags) -- **Set DLT mode**: set_dlt_mode(true/false) - **Enhanced with proper setter implementation** -- **Enhanced Exception Handling**: Sophisticated exception preservation during rethrow operations using fc::exception_factory and dynamic_rethrow_exception for proper derived type restoration -- **Enhanced Fork Database Management**: Comprehensive diagnostic capabilities with detailed logging for fork recovery operations, improved unlinkable block exception handling, and enhanced fork switching logic -- **Enhanced Early Rejection Logic**: Gap-based decision system (≤100 gap deferred to fork_db, >100 gap rejected) for intelligent block rejection of far-ahead blocks with unknown parents to prevent unnecessary fork database operations and sync restart loops -- **Enhanced Fork Database Exception Prevention**: Comprehensive mechanisms to prevent fork database exceptions through early rejection and proper dead fork detection -- **Enhanced Memory Management**: Comprehensive logging of memory states before and after resizing operations for administrator visibility -- **Enhanced P2P Protection**: Operation guard integration in P2P plugin for safe concurrent access during block validation and validator key retrieval -- **Enhanced validator Scheduling Safety**: Dual operation guard patterns in validator scheduling calculations to ensure thread safety during slot determination and validator validation -- **Enhanced Multi-Layered Block Retrieval**: Hierarchical block fetching with systematic fallback mechanisms that check fork database when primary block log fails to locate required data -- **Enhanced Last Irreversible Block Advancement**: Improved logic that falls back to fork database when block log lacks required data, maintaining data consistency -- **Enhanced Collision Detection**: Sophisticated logging for block number collisions with scenario differentiation and rate-limiting -- **Postponed Transaction Processing**: Automatic transaction queuing with time-based execution limits and smart recovery -- **Enhanced Emergency Consensus**: Automatic recovery system with comprehensive logging and safety checks for network stall detection and recovery -- **Enhanced Shared Memory Corruption Detection**: New shared_memory_corruption_exception type for structured error reporting during critical validation failures -- **Enhanced Auto-Recovery Integration**: Seamless integration with Validator Plugin for automatic recovery from shared memory corruption scenarios -- **Enhanced Crash Debugging**: Comprehensive debug_crash logging throughout critical code paths for improved crash diagnostics and troubleshooting -- **Enhanced Block Production Monitoring**: debug-block-production configuration option for detailed block production logging and monitoring -- **Enhanced Stacktrace Crash Handlers**: Automatic stacktrace generation for crash diagnostics and improved debugging experience -- **Enhanced DLT Gap Recovery**: Intelligent gap detection and automatic recovery with warning suppression using _dlt_gap_logged flag for improved diagnostics and reduced log noise - -**Section sources** -- [database.hpp:93-141](file://libraries/chain/include/graphene/chain/database.hpp#L93-L141) -- [database.cpp:458-584](file://libraries/chain/database.cpp#L458-L584) - -## Emergency Consensus Implementation - -**New** - The database now includes comprehensive emergency consensus implementation for automatic network recovery during extended periods without block production. - -### Emergency Consensus Activation Criteria -The emergency consensus system activates automatically when the network experiences extended downtime: - -- **LIB Timestamp Analysis**: System continuously monitors the last irreversible block (LIB) timestamp to detect network stalls -- **Timeout Threshold**: If no blocks are produced for more than CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC seconds (default: 3600 seconds = 1 hour), emergency mode is triggered -- **Safety Checks**: Emergency activation is skipped if LIB timestamp cannot be determined (e.g., after snapshot restore when block_log is empty) to prevent false activations -- **Guard Against Deadlocks**: Prevents emergency mode activation that would cause node deadlocks by ensuring proper LIB availability - -```mermaid -flowchart TD -Start(["Block Processing"]) --> CheckHF{"Has Hardfork 12?"} -CheckHF --> |No| Normal["Normal Operation"] -CheckHF --> |Yes| CheckActive{"Emergency Active?"} -CheckActive --> |Yes| Normal -CheckActive --> |No| CheckLIB{"LIB > 0?"} -CheckLIB --> |No| Skip["Skip Check (No LIB)"] -CheckLIB --> |Yes| FetchLIB["Fetch LIB Block"] -FetchLIB --> Valid{"LIB Block Valid?"} -Valid --> |No| Skip -Valid --> |Yes| CalcTime["Calculate Time Since LIB"] -CalcTime --> CheckTimeout{"Time >= Timeout?"} -CheckTimeout --> |No| Normal -CheckTimeout --> |Yes| Activate["Activate Emergency Mode"] -Activate --> CreateWitness["Create/Update Emergency validator"] -CreateWitness --> ResetPenalties["Reset validator Penalties"] -ResetPenalties --> OverrideSchedule["Override validator Schedule"] -OverrideSchedule --> NotifyFork["Notify Fork DB"] -NotifyFork --> LogActivation["Log Emergency Activation"] -LogActivation --> Normal -``` - -**Diagram sources** -- [database.cpp:4334-4463](file://libraries/chain/database.cpp#L4334-L4463) -- [config.hpp:111-118](file://libraries/protocol/include/graphene/protocol/config.hpp#L111-L118) - -**Section sources** -- [database.cpp:4334-4463](file://libraries/chain/database.cpp#L4334-L4463) -- [config.hpp:111-118](file://libraries/protocol/include/graphene/protocol/config.hpp#L111-L118) - -### Hybrid validator Scheduling System -During emergency mode, the system implements a hybrid validator scheduling approach: - -- **Real validator Priority**: Real validators maintain their scheduled slots during normal operation -- **Committee Replacement**: When real validators are unavailable (offline, shutdown, or missing signing keys), committee members automatically replace their slots -- **Full Coverage**: Emergency schedule expands to cover all CHAIN_MAX_WITNESSES slots, ensuring continuous block production -- **Dynamic Adjustment**: Schedule updates dynamically based on real validator availability and network conditions - -```mermaid -flowchart TD -Start(["Schedule Update"]) --> CheckEmergency{"Emergency Active?"} -CheckEmergency --> |No| NormalSchedule["Normal Schedule Update"] -CheckEmergency --> |Yes| IterateSlots["Iterate All Schedule Slots"] -IterateSlots --> CheckSlot{"Slot Available?"} -CheckSlot --> |Yes| KeepReal["Keep Real validator"] -CheckSlot --> |No| ReplaceWithCommittee["Replace with Emergency validator"] -ReplaceWithCommittee --> ExpandSchedule["Expand to Full Schedule"] -ExpandSchedule --> SyncProps["Sync Props with Latest Median"] -SyncProps --> CheckExit{"LIB > Start Block?"} -CheckExit --> |Yes| Deactivate["Deactivate Emergency Mode"] -CheckExit --> |No| Continue["Continue Emergency Mode"] -Deactivate --> NotifyFork["Notify Fork DB"] -NotifyFork --> LogDeactivation["Log Deactivation"] -LogDeactivation --> NormalSchedule -``` - -**Diagram sources** -- [database.cpp:2047-2144](file://libraries/chain/database.cpp#L2047-L2144) - -**Section sources** -- [database.cpp:2047-2144](file://libraries/chain/database.cpp#L2047-L2144) - -### Emergency validator Object Management -The system creates and manages a dedicated emergency validator object: - -- **Emergency validator Account**: Uses CHAIN_EMERGENCY_WITNESS_ACCOUNT (committee account) for emergency operations -- **Public Key Management**: Assigns CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY for block signing during emergencies -- **Properties Synchronization**: Copies current median chain properties to prevent skewing median computations -- **Version Management**: Maintains current binary version and hardfork voting alignment -- **Penalty Reset**: Emergency validator operates independently of normal penalty systems - -```mermaid -flowchart TD -Start(["Emergency Activation"]) --> CheckWitness{"Emergency validator Exists?"} -CheckWitness --> |No| CreateWitness["Create Emergency validator"] -CheckWitness --> |Yes| UpdateWitness["Update Existing validator"] -CreateWitness --> SetKey["Set Emergency Public Key"] -SetKey --> SyncProps["Copy Median Properties"] -SyncProps --> SyncVersion["Sync Version and Votes"] -UpdateWitness --> SetKey -SetKey --> SyncVersion -SyncVersion --> ResetPenalties["Reset Penalties"] -ResetPenalties --> RemoveExpire["Remove Penalty Expires"] -RemoveExpire --> OverrideSchedule["Override Schedule"] -OverrideSchedule --> NotifyFork["Notify Fork DB"] -NotifyFork --> LogCreate["Log validator Creation"] -LogCreate --> Continue["Continue Operation"] -``` - -**Diagram sources** -- [database.cpp:4378-4416](file://libraries/chain/database.cpp#L4378-L4416) - -**Section sources** -- [database.cpp:4378-4416](file://libraries/chain/database.cpp#L4378-L4416) - -### Emergency Mode Deactivation -Emergency mode automatically deactivates when network recovery is detected: - -- **LIB Progress Monitoring**: System continuously monitors last irreversible block advancement -- **Exit Condition**: When last_irreversible_block_num > emergency_consensus_start_block, emergency mode terminates -- **Graceful Transition**: Fork database is notified of emergency mode termination -- **Logging**: Comprehensive logging of emergency period duration and recovery metrics - -```mermaid -flowchart TD -Start(["LIB Monitoring"]) --> CheckEmergency{"Emergency Active?"} -CheckEmergency --> |No| Wait["Wait for LIB"] -CheckEmergency --> |Yes| CheckLIB["Check LIB Advancement"] -CheckLIB --> Compare{"LIB > Start Block?"} -Compare --> |No| Wait -Compare --> |Yes| Deactivate["Deactivate Emergency Mode"] -Deactivate --> UpdateDGP["Set emergency_consensus_active = false"] -UpdateDGP --> NotifyFork["Notify Fork DB"] -NotifyFork --> LogDeactivation["Log Deactivation"] -LogDeactivation --> Wait -``` - -**Diagram sources** -- [database.cpp:2125-2142](file://libraries/chain/database.cpp#L2125-L2142) - -**Section sources** -- [database.cpp:2125-2142](file://libraries/chain/database.cpp#L2125-L2142) - -### validator Penalty Handling During Emergencies -Emergency mode includes special handling for validator penalties: - -- **Offline validator Protection**: During emergency mode, penalties for offline validators are not applied -- **Hybrid Schedule Impact**: Committee members filling slots still count as "missed" blocks for normal penalty calculations -- **Recovery Prevention**: Prevents offline validators from accumulating penalties that could lead to permanent shutdown -- **Network Recovery**: Ensures offline validators can recover and resume participation after emergency mode ends - -```mermaid -flowchart TD -Start(["validator Missed Blocks"]) --> CheckEmergency{"Emergency Active?"} -CheckEmergency --> |No| ApplyPenalties["Apply Normal Penalties"] -CheckEmergency --> |Yes| CheckOffline{"Is Offline validator?"} -CheckOffline --> |No| ApplyPenalties -CheckOffline --> |Yes| CheckProducer{"Is Producer?"} -CheckProducer --> |Yes| ApplyPenalties -CheckProducer --> |No| SkipPenalties["Skip Penalties (Emergency)"] -SkipPenalties --> ResetRun["Reset Current Run"] -ResetRun --> Continue["Continue Processing"] -ApplyPenalties --> Continue -``` - -**Diagram sources** -- [database.cpp:4220-4230](file://libraries/chain/database.cpp#L4220-L4230) - -**Section sources** -- [database.cpp:4220-4230](file://libraries/chain/database.cpp#L4220-L4230) - -### LIB Monitoring and Safety Mechanisms -The emergency consensus system includes comprehensive LIB monitoring: - -- **Continuous Timestamp Analysis**: Monitors block timestamps to detect network stalls -- **Safety Guardrails**: Prevents false emergency activations by verifying LIB availability -- **Genesis Time Protection**: Avoids false activations by falling back to genesis_time considerations -- **Network Recovery Detection**: Monitors LIB advancement to determine when emergency mode should end - -### Enhanced Error Logging Throughout Consensus Process -**New** - The emergency consensus implementation includes comprehensive error logging and critical error handling: - -- **Critical Error Logging**: All emergency consensus activation and deactivation events are logged with detailed context including block numbers, timestamps, and validator information -- **Safety Check Logging**: Extensive logging of safety checks to prevent false activations and deadlocks -- **Transition Logging**: Detailed logging of emergency mode entry and exit conditions -- **validator Management Logging**: Comprehensive logging of emergency validator creation, updates, and penalty management -- **Schedule Override Logging**: Detailed logging of validator schedule overrides and hybrid scheduling decisions -- **LIB Monitoring Logging**: Continuous logging of LIB timestamp analysis and recovery detection -- **Error Recovery Logging**: Logging of error recovery mechanisms and fallback procedures - -The enhanced error logging system ensures that operators have comprehensive visibility into emergency consensus operations and can effectively troubleshoot any issues that arise during emergency mode activation or deactivation. - -### Enhanced Shared Memory Corruption Detection and Auto-Recovery -**New** - The database now includes comprehensive shared memory corruption detection and automatic recovery mechanisms: - -- **Structured Exception Handling**: New shared_memory_corruption_exception type replaces direct assertion failures with structured exception handling for critical validation failures. -- **Enhanced validator Validation**: Graceful error handling for validator account validation with detailed logging and structured exception reporting when validator accounts are missing from database. -- **Auto-Recovery Integration**: Seamless integration with Validator Plugin for automatic recovery from shared memory corruption scenarios. -- **Plugin-Level Recovery**: Comprehensive auto-recovery system in plugin.cpp that can automatically recover from snapshots when corruption is detected. -- **Structured Error Reporting**: Detailed logging of corruption detection events with comprehensive context including validator information, signing keys, and memory state. - -```mermaid -flowchart TD -Start(["Shared Memory Corruption Detection"]) --> DetectCorruption["Detect Missing validator Account"] -DetectCorruption --> LogCritical["Log Critical Error Details"] -LogCritical --> ThrowException["Throw shared_memory_corruption_exception"] -ThrowException --> CatchInWitness["Catch in Validator Plugin"] -CatchInWitness --> AttemptRecovery["Attempt Auto-Recovery"] -AttemptRecovery --> FindSnapshot["Find Latest Snapshot"] -FindSnapshot --> CloseDatabase["Close Corrupted Database"] -CloseDatabase --> LoadSnapshot["Load Snapshot State"] -LoadSnapshot --> ResumeOperation["Resume Node Operation"] -ResumeOperation --> End(["Complete"]) -``` - -**Diagram sources** -- [database.cpp:1680-1693](file://libraries/chain/database.cpp#L1680-L1693) -- [database.cpp:3224-3236](file://libraries/chain/database.cpp#L3224-L3236) -- [database.cpp:3272-3284](file://libraries/chain/database.cpp#L3272-L3284) -- [validator.cpp:738-742](file://plugins/validator/validator.cpp#L738-L742) -- [plugin.cpp:760-770](file://plugins/chain/plugin.cpp#L760-L770) - -**Section sources** -- [database.cpp:1680-1693](file://libraries/chain/database.cpp#L1680-L1693) -- [database.cpp:3224-3236](file://libraries/chain/database.cpp#L3224-L3236) -- [database.cpp:3272-3284](file://libraries/chain/database.cpp#L3272-L3284) -- [validator.cpp:738-742](file://plugins/validator/validator.cpp#L738-L742) -- [plugin.cpp:760-770](file://plugins/chain/plugin.cpp#L760-L770) - -## Dependency Analysis -The database depends on: -- chainbase for persistent storage and undo sessions with enhanced memory management, operation_guard RAII pattern, and resize barrier mechanisms -- block_log for immutable block storage and random access -- dlt_block_log for rolling window storage in DLT mode -- fork_database for reversible blocks and fork resolution with emergency mode support and enhanced unlinkable block detection -- protocol types and evaluators for operation processing -- signal_guard for enhanced error handling during restart sequences -- snapshot plugin for DLT mode initialization -- **Enhanced Exception Handling Infrastructure**: Sophisticated exception preservation during rethrow operations using fc::exception_factory and dynamic_rethrow_exception for proper derived type restoration -- **Enhanced Fork Database Management**: Comprehensive diagnostic capabilities with detailed logging for fork recovery operations, improved unlinkable block exception handling, and enhanced fork switching logic -- **Enhanced Early Rejection Logic**: Gap-based decision system (≤100 gap deferred to fork_db, >100 gap rejected) for intelligent block validation with early rejection strategies for blocks far ahead with unknown parents -- **Enhanced Fork Database Exception Prevention**: Comprehensive mechanisms to prevent fork database exceptions through early rejection and proper dead fork detection -- **Enhanced Memory Management**: Comprehensive logging system for shared memory allocation with detailed state reporting, plus deferred resize operations -- **Enhanced P2P Plugin Protection**: Operation guard integration in P2P plugin for safe concurrent access during block validation and validator key retrieval -- **Enhanced Operation Guard System**: Comprehensive concurrent access protection using operation_guard RAII pattern, dual operation guard patterns for validator scheduling safety, and resize barrier mechanisms -- **Enhanced Multi-Layered Block Retrieval**: Systematic fallback mechanisms for critical block data retrieval across multiple storage layers -- **Enhanced Last Irreversible Block Advancement**: Enhanced fallback logic for LIB advancement when primary storage fails -- **Enhanced Emergency Consensus**: Automatic recovery system with comprehensive logging and safety checks for network stall detection and recovery -- **Enhanced Shared Memory Corruption Detection**: New shared_memory_corruption_exception type for structured error handling and automatic recovery integration -- **Enhanced Auto-Recovery System**: Comprehensive auto-recovery from snapshot for shared memory corruption scenarios with seamless plugin integration -- **Enhanced Crash Debugging System**: Comprehensive debug_crash logging throughout critical code paths for improved crash diagnostics and troubleshooting -- **Enhanced Block Production Monitoring**: debug-block-production configuration option for detailed block production logging and monitoring -- **Enhanced Stacktrace Crash Handlers**: Automatic stacktrace generation for crash diagnostics and improved debugging experience -- **Enhanced DLT Gap Recovery System**: Intelligent gap detection and automatic recovery mechanisms with warning suppression using _dlt_gap_logged flag - -```mermaid -graph LR -DB["database.cpp"] --> CB["chainbase (external)"] -DB --> BL["block_log.hpp/.cpp"] -DB --> DLT["dlt_block_log.hpp/.cpp"] -DB --> FD["fork_database.hpp/.cpp"] -DB --> SG["signal_guard (enhanced)"] -DB --> PT["protocol types"] -DB --> EV["evaluators"] -DB --> SNAP["snapshot plugin"] -DB --> EXINF["exception handling infrastructure"] -DB --> FDMGMT["enhanced fork database management"] -DB --> EARLY["enhanced early rejection logic"] -DB --> MEMMGT["enhanced memory management"] -DB --> P2PSEC["enhanced P2P plugin protection"] -DB --> OPGUARD["enhanced operation guard system"] -DB --> MULTILAYER["enhanced multi-layered block retrieval"] -DB --> LIBADVANCE["enhanced last irreversible block advancement"] -DB --> EMER["enhanced emergency consensus"] -DB --> CORRUPTION["enhanced shared memory corruption detection"] -DB --> AUTORECOVERY["enhanced auto-recovery system"] -DB --> CRASHDEBUG["enhanced crash debugging system"] -DB --> BLOCKPROD["enhanced block production monitoring"] -DB --> STACKTRACE["enhanced stacktrace crash handlers"] -DB --> DLTGAP["enhanced DLT gap recovery system"] -``` - -**Diagram sources** -- [database.hpp:1-10](file://libraries/chain/include/graphene/chain/database.hpp#L1-L10) -- [database.cpp:1-30](file://libraries/chain/database.cpp#L1-L30) -- [database.cpp:94-184](file://libraries/chain/database.cpp#L94-L184) -- [chainbase.cpp:225-279](file://thirdparty/chainbase/src/chainbase.cpp#L225-L279) -- [database_exceptions.hpp:83](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L83) -- [database_exceptions.hpp:122](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L122) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) - -**Section sources** -- [database.hpp:1-10](file://libraries/chain/include/graphene/chain/database.hpp#L1-L10) -- [database.cpp:1-30](file://libraries/chain/database.cpp#L1-L30) -- [database.cpp:94-184](file://libraries/chain/database.cpp#L94-L184) -- [chainbase.cpp:225-279](file://thirdparty/chainbase/src/chainbase.cpp#L225-L279) -- [database_exceptions.hpp:83](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L83) -- [database_exceptions.hpp:122](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L122) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) - -## Performance Considerations -- Use skip flags during reindex to bypass expensive validations and improve replay speed. -- Configure shared memory sizing and thresholds to avoid frequent resizing and fragmentation. -- Monitor free memory and adjust increments to keep latency predictable. -- Use checkpoints to reduce validation overhead for recent blocks. -- Tune flush intervals to balance durability and throughput. -- **DLT Mode Optimization**: Use rolling window DLT block log to reduce storage requirements for snapshot-based nodes. -- **Conditional Operations**: Leverage DLT mode to skip unnecessary block log operations while maintaining required functionality. -- **Enhanced Exception Type Preservation**: Graceful fallback mechanisms prevent performance degradation during restart sequences. -- **Multi-layered Fetching**: Hierarchical block retrieval minimizes lookup overhead and improves response times. -- **Enhanced Early Rejection Efficiency**: The new gap-based early rejection logic eliminates unnecessary fork database operations for far-ahead blocks with unknown parents, significantly reducing processing overhead and preventing sync restart loops. -- **Enhanced Fork Database Exception Prevention**: Comprehensive early rejection mechanisms prevent fork database exceptions before they occur, eliminating the need for exception handling and improving overall system efficiency. -- **Enhanced Fork Database Performance**: Proper unlinkable_block_exception handling reduces processing overhead by eliminating dead fork blocks from consideration. -- **Enhanced Fork Switching**: Enhanced fork switching logic with proper dead fork detection and deterministic tie-breaking prevents unnecessary processing and improves fork resolution performance. -- **Enhanced Memory Management**: Comprehensive logging provides administrators with detailed visibility into memory usage patterns, enabling proactive capacity planning and performance optimization. -- **Enhanced Error Handling**: Graceful handling of shared memory exhaustion prevents peer disconnections and maintains network connectivity during memory pressure situations. -- **Enhanced Operation Guard Performance**: The operation guard RAII pattern provides automatic concurrent access protection with minimal overhead, ensuring thread safety without significant performance impact. -- **Enhanced Dual Guard Patterns**: Systematic implementation of dual operation guards in validator scheduling provides comprehensive thread safety with optimized performance characteristics. -- **Enhanced P2P Concurrent Safety**: Operation guard protection in P2P plugin ensures safe concurrent access during block validation with minimal performance overhead. -- **Enhanced Resize Barrier Efficiency**: Enhanced resize barrier mechanisms provide comprehensive concurrent access protection during memory resizing with optimized performance characteristics. -- **Enhanced Multi-Layered Block Retrieval**: Systematic fallback mechanisms eliminate single points of failure and improve system reliability without significant performance impact. -- **Enhanced Last Irreversible Block Advancement**: Enhanced fallback logic maintains data consistency and availability without affecting block processing performance. -- **Enhanced Emergency Consensus Logging**: Comprehensive logging provides operators with detailed visibility into emergency operations without impacting performance. -- **Enhanced Safety Check Optimization**: Emergency consensus safety checks are optimized to minimize performance impact while ensuring network stability. -- **Enhanced Deferred Memory Resize Efficiency**: The new deferred resize mechanism prevents race conditions and stale pointer issues during high-load scenarios, improving overall system reliability and performance. -- **Enhanced Thread-Safe Memory Operations**: Proper lock management during memory resize operations ensures data consistency and prevents performance degradation from thread contention. -- **Enhanced P2P Sync Restart Prevention**: The enhanced early rejection logic prevents sync restart loops during snapshot imports, improving synchronization performance and reducing network overhead. -- **Enhanced Shared Memory Corruption Detection**: Structured exception handling provides detailed error context without significant performance impact during critical validation failures. -- **Enhanced Auto-Recovery Performance**: Seamless integration with Validator Plugin enables rapid recovery from shared memory corruption without significant downtime. -- **Enhanced Crash Debugging Overhead**: The debug_crash logging system adds minimal overhead during normal operation while providing comprehensive debugging capabilities when enabled. -- **Enhanced Block Production Monitoring**: The debug-block-production option provides detailed monitoring capabilities with minimal performance impact. -- **Enhanced Stacktrace Performance**: Stacktrace crash handlers add minimal overhead and provide significant debugging benefits for crash diagnostics. -- **Enhanced DLT Gap Recovery Performance**: Intelligent gap detection and automatic recovery mechanisms provide improved diagnostics with minimal performance impact during DLT mode operations. - -## Troubleshooting Guide -Common issues and remedies: -- Memory exhaustion during block production or reindex: Increase shared file size and tune minimum free memory threshold. -- Chain mismatch between block log and database: Run reindex to rebuild state from block log. -- Excessive undo history: Ensure last irreversible block advances to prune history. -- Signal-related errors: Verify signal handlers and ensure proper exception propagation. -- **Enhanced Exception Type Preservation**: If derived exception types are not being preserved during rethrow operations, verify that fc::exception_factory is properly registering exception builders and that dynamic_rethrow_exception() is being called correctly. -- **Enhanced Fork Database Diagnostics**: Monitor fork recovery logs to identify issues with fork switching operations, including branch comparison failures and exception handling during recovery. -- **Enhanced Early Rejection Issues**: If blocks are being incorrectly rejected, verify the gap calculation logic and ensure that the early rejection criteria are appropriate for the current network conditions. -- **Enhanced Fork Database Exception Prevention**: Monitor unlinkable_block_exception handling to ensure dead fork blocks are properly detected and excluded from processing. -- **Enhanced Memory Management Issues**: Monitor enhanced memory logging to identify potential memory pressure situations and optimize configuration settings. -- **Enhanced Memory Resize Failures**: Check that memory resize operations are completing successfully and review detailed logging for resize operations. -- **Enhanced P2P Sync Restart Loops**: Verify that the enhanced early rejection logic is working correctly to prevent sync restart loops during snapshot imports and normal operation. -- **Enhanced Emergency Consensus Logging**: Verify that critical error logs are being generated and that emergency mode activation/deactivation events are properly recorded. -- **Enhanced Safety Check Failures**: Monitor emergency consensus safety checks to ensure they're functioning correctly and preventing false activations. -- **Enhanced validator Management Problems**: Verify that emergency validator objects are being created and updated correctly during emergency mode activation. -- **Enhanced Deferred Memory Resize Issues**: Monitor the new deferred resize mechanism to ensure it's properly deferring operations until safe points and applying them correctly. -- **Enhanced Thread Safety Problems**: Verify that memory resize operations are not causing race conditions or stale pointer issues during concurrent access. -- **Enhanced Performance Degradation**: Check if the deferred memory resize mechanism is causing unexpected delays or if memory operations are blocking other threads. -- **Enhanced Shared Memory Exhaustion**: Monitor boost::interprocess::bad_alloc exceptions and verify that deferred resize scheduling is working correctly to prevent peer disconnections. -- **Enhanced Peer Connectivity Issues**: Verify that memory pressure handling is preserving peer connections and not causing network instability. -- **Enhanced Fork Switching Problems**: Verify that fork switching logic properly handles unlinkable_block_exception and prevents processing of invalid forks. -- **Enhanced Dead Fork Detection**: Check that the enhanced fork database properly throws unlinkable_block_exception for blocks from dead forks to prevent wasted processing resources. -- **Enhanced Emergency Consensus Tie-Breaking**: Verify that deterministic hash-based tie-breaking is working correctly during emergency mode to ensure consistent block selection across all nodes. -- **Enhanced Operation Guard Issues**: Monitor operation guard functionality to ensure concurrent access protection is working correctly during high-load scenarios. -- **Enhanced Dual Guard Pattern Problems**: Verify that dual operation guards are properly protecting validator scheduling calculations and preventing race conditions. -- **Enhanced P2P Concurrent Access Issues**: Check that operation guard protection is working correctly in P2P plugin for safe concurrent access during block validation. -- **Enhanced Resize Barrier Failures**: Monitor resize barrier mechanisms to ensure they're properly pausing all database operations during memory resizing. -- **Enhanced Concurrent Resize Safety**: Verify that resize barrier mechanisms are preventing stale pointer issues and data corruption during memory operations. -- **Enhanced Multi-Layered Block Retrieval Issues**: Monitor the new fallback mechanisms to ensure they're properly checking fork database when primary block log fails to locate required data. -- **Enhanced Last Irreversible Block Advancement Problems**: Verify that the enhanced fallback logic is working correctly when block log lacks required data. -- **Enhanced Emergency Mode Activation**: Monitor emergency consensus activation logs and verify LIB timestamp analysis is working correctly. -- **Enhanced Hybrid Schedule Issues**: Verify that emergency validator is properly replacing unavailable validators during network recovery. -- **Enhanced Emergency Mode Deactivation**: Check that LIB advancement is properly detected to trigger emergency mode termination. -- **Enhanced validator Penalty Problems**: During emergency mode, verify that offline validator penalties are properly bypassed to prevent network recovery issues. -- **Enhanced Shared Memory Corruption Detection**: Monitor shared_memory_corruption_exception logging to ensure critical validation failures are properly reported. -- **Enhanced Auto-Recovery Integration**: Verify that Validator Plugin auto-recovery is properly integrated with plugin-level recovery system for seamless corruption handling. -- **Enhanced Auto-Recovery Performance**: Monitor auto-recovery performance to ensure rapid recovery from shared memory corruption without significant downtime. -- **Enhanced Crash Debugging Issues**: Verify that debug_crash logging is working correctly and providing comprehensive debugging information when enabled. -- **Enhanced Block Production Monitoring Problems**: Check that debug-block-production option is properly configured and providing detailed block production logging. -- **Enhanced Stacktrace Crash Handler Issues**: Verify that stacktrace crash handlers are properly installed and generating stacktrace information for crash diagnostics. -- **Enhanced DLT Gap Recovery Issues**: Monitor the new _dlt_gap_logged flag mechanism to ensure gap detection and recovery is working correctly and warning suppression is preventing log spam. -- **Enhanced Gap Recovery Logging**: Verify that comprehensive logging throughout the gap detection and recovery workflow is providing adequate diagnostics for troubleshooting. - -**Section sources** -- [database.cpp:789-827](file://libraries/chain/database.cpp#L789-L827) -- [database.cpp:270-279](file://libraries/chain/database.cpp#L270-L279) -- [database.cpp:492-501](file://libraries/chain/database.cpp#L492-L501) -- [database.cpp:1147-1202](file://libraries/chain/database.cpp#L1147-L1202) -- [db_with.hpp:33-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L33-L100) -- [database.cpp:4334-4463](file://libraries/chain/database.cpp#L4334-L4463) -- [database.cpp:2047-2144](file://libraries/chain/database.cpp#L2047-L2144) -- [database.cpp:454-482](file://libraries/chain/database.cpp#L454-L482) -- [database.cpp:1106-1145](file://libraries/chain/database.cpp#L1106-L1145) -- [database.cpp:1460-1470](file://libraries/chain/database.cpp#L1460-L1470) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) -- [database.cpp:1295-1377](file://libraries/chain/database.cpp#L1295-L1377) -- [database.cpp:1216-1286](file://libraries/chain/database.cpp#L1216-L1286) -- [node.cpp:3185-3384](file://libraries/network/node.cpp#L3185-L3384) -- [p2p_plugin.cpp:181-196](file://plugins/p2p/p2p_plugin.cpp#L181-L196) -- [chainbase.hpp:1078-1120](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1120) -- [exception.cpp:166-186](file://thirdparty/fc/src/exception.cpp#L166-L186) -- [exception.hpp:177-215](file://thirdparty/fc/include/fc/exception/exception.hpp#L177-L215) -- [stacktrace.cpp:72-78](file://thirdparty/fc/src/stacktrace.cpp#L72-L78) -- [database.cpp:5092-5105](file://libraries/chain/database.cpp#L5092-L5105) -- [database.cpp:5283-5297](file://libraries/chain/database.cpp#L5283-L5297) -- [database.cpp:5616-5635](file://libraries/chain/database.cpp#L5616-L5635) - -## Conclusion -The Database Management system provides a robust, event-driven, and efficient state persistence layer for the VIZ blockchain with enhanced DLT mode support, emergency consensus implementation, operation guard integration, improved error handling, and comprehensive crash debugging capabilities. It integrates chainbase for persistent storage with comprehensive concurrent access protection, fork_database for reversible blocks, block_log for immutable history, and dlt_block_log for rolling window storage in DLT mode. Through configurable validation flags, checkpointing, memory management, DLT mode detection with proper setter implementation, enhanced block fetching logic with DLT mode awareness, improved gap logging, the new `_dlt_gap_logged` flag mechanism for intelligent warning suppression, comprehensive operation guard implementation for concurrent access protection, dual operation guard patterns for validator scheduling safety, enhanced P2P plugin block validation with operation guard protection, systematic implementation of resize barrier mechanisms, comprehensive debug_crash logging throughout critical code paths, debug-block-production configuration option for detailed block production monitoring, and enhanced stacktrace crash handlers for improved crash diagnostics, it supports fast synchronization, reliable block processing, conditional block log operations, and extensibility via observer signals. - -**Updated** - The system now includes comprehensive database robustness improvements including refined gap-based decision system for unlinkable blocks, enhanced exception handling infrastructure that preserves derived exception types during rethrow operations, comprehensive fork database management with improved diagnostic capabilities and enhanced logging for fork recovery operations, and sophisticated early rejection logic for blocks far ahead with unknown parents. The enhanced fork management logic with improved early rejection mechanisms provides better handling of blocks far ahead with unknown parents, significantly improving the efficiency of the synchronization process and reducing the likelihood of encountering unlinkable blocks that would require peer soft-banning or sync restarts. The enhanced multi-layered block retrieval system represents a significant advancement in database reliability and fault tolerance. The improved last irreversible block advancement logic demonstrates the system's commitment to data consistency and availability. The enhanced exception handling infrastructure ensures that derived exception types are properly preserved during rethrow operations, improving debugging and troubleshooting capabilities. The enhanced memory management system provides comprehensive logging capabilities that offer administrators detailed visibility into memory usage patterns during blockchain operation, while the deferred shared memory resize mechanism significantly improves efficiency during high-load scenarios by preventing race conditions and stale pointer issues through proper thread synchronization and lock management. - -The enhanced exception handling infrastructure represents a fundamental improvement in error handling robustness throughout the database management system. The fc::exception_factory system with proper exception builder registration ensures that derived exception types are preserved during rethrow operations, while the enhanced dynamic_rethrow_exception() method provides reliable type restoration. The protocol exceptions now include comprehensive dynamic_rethrow_exception() implementations that check code() values before rethrowing to ensure proper type preservation. This enhancement significantly improves the debugging experience and makes it easier to identify and resolve issues in the database management system. - -**Enhanced Fork Database Management** - The comprehensive diagnostic capabilities and enhanced logging for fork recovery operations represent a major advancement in fork database reliability and maintainability. The detailed logging for fork switching operations, including branch comparison results, exception handling during fork recovery, and state restoration procedures, provides operators with comprehensive visibility into fork resolution activities. The improved fork switching logic with deterministic tie-breaking and comprehensive error handling for invalid fork scenarios ensures that the system can handle complex fork scenarios reliably. The dead fork detection and pruning mechanisms prevent memory bloat and improve system performance by removing stale competing blocks from the fork database. - -**Enhanced Early Rejection Logic** - The new gap-based decision system represents a significant improvement in synchronization reliability and performance. By intelligently rejecting blocks with gaps > 100 immediately while deferring blocks with gaps ≤ 100 to the fork database, the system prevents unnecessary processing overhead and eliminates the possibility of fork database exceptions that could trigger sync restart loops. This enhancement is particularly beneficial during snapshot imports where the fork database may only contain the head block, preventing the common scenario where P2P peers send blocks that are far ahead and would otherwise cause continuous sync restarts. The early rejection strategy ensures that only blocks with known parents are processed through the fork database, significantly improving the efficiency of the synchronization process and reducing the likelihood of encountering unlinkable blocks that would require peer soft-banning or sync restarts. - -**Enhanced Fork Database Exception Prevention** - The comprehensive exception prevention mechanisms represent a major advancement in fork database reliability. The system now includes multiple layers of protection against fork database exceptions, starting with the intelligent gap-based early rejection of blocks with unknown parents and extending to proper classification and handling of different types of unlinkable blocks. The dead fork detection at or below head ensures that stale fork blocks are properly identified and rejected, while the gap-based large gap rejection prevents unnecessary fork database operations that could trigger sync restart loops. This multi-layered approach to exception prevention significantly improves the robustness of the fork database and reduces the likelihood of synchronization issues caused by malformed or malicious blocks. The enhanced error propagation and classification mechanisms ensure that downstream components can properly handle different types of unlinkable blocks, leading to more efficient and reliable network synchronization. - -**Enhanced Memory Management** - The enhanced memory management system now provides comprehensive logging capabilities that offer administrators detailed visibility into memory usage patterns during blockchain operation. The new deferred shared memory resize mechanism significantly improves efficiency during high-load scenarios by preventing race conditions and stale pointer issues through proper thread synchronization and lock management. The enhanced `_resize` function logs detailed information about free memory, maximum memory, and reserved memory states before and after resizing operations, enabling proactive capacity planning and performance optimization. The improved error detection capabilities in shared memory allocation provides administrators with crucial information about memory usage patterns, helping prevent memory-related issues before they impact system performance. The comprehensive emergency consensus logging system ensures that operators have complete visibility into critical error conditions and recovery procedures. These enhancements make the database management system more transparent, manageable, and suitable for production environments where memory resource optimization and comprehensive error diagnostics are critical. - -**Enhanced P2P Synchronization** - The enhanced P2P synchronization system with improved unlinkable block classification and sync restart prevention represents a significant improvement in network reliability and performance. The P2P layer now properly distinguishes between dead fork blocks (at or below head) and far-ahead blocks that slipped past early rejection, enabling appropriate handling for each scenario. The dead fork handling with soft-banning prevents continued transmission of stale blocks, while the far-ahead block handling with sync restart prevention allows sequential block fetching without causing network stalls. The integration with deferred resize scenarios ensures that P2P operations can continue smoothly even when memory pressure occurs during block processing. - -**Enhanced Operation Guard Implementation** - The comprehensive operation guard system represents a fundamental improvement in concurrent access protection throughout the database management system. The systematic implementation of operation_guard RAII pattern provides automatic concurrent access protection across all critical sections, while the dual operation guard patterns in validator scheduling calculations ensure thread safety during complex slot determination operations. The integration of operation guards in P2P plugin block validation protects validator key retrieval operations from concurrent memory modifications, and the comprehensive resize barrier mechanisms prevent data corruption during memory resizing operations. These enhancements ensure that the database management system can handle high-load scenarios safely and reliably while maintaining data consistency and preventing race conditions that could lead to system instability or data corruption. - -**Enhanced Multi-Layered Block Retrieval System** - The new multi-layered block retrieval system represents a significant advancement in database reliability and fault tolerance. By implementing systematic fallback mechanisms that check fork database when primary block log fails to locate required data, the database ensures consistent behavior across different block logging configurations. This approach eliminates single points of failure and provides improved resilience against storage layer issues, network interruptions, and other operational challenges. The hierarchical retrieval strategy prioritizes current/main branch blocks in the fork database, then checks the primary block log for irreversible blocks, and finally uses the DLT block log as a fallback in DLT mode. This ensures that block retrieval works reliably regardless of which storage layer contains the requested data, providing improved fault tolerance and system reliability. - -**Enhanced Last Irreversible Block Advancement Logic** - The improved LIB advancement logic demonstrates the system's commitment to data consistency and availability. The comprehensive fallback mechanisms ensure that critical blockchain state information remains accessible even when primary storage layers are compromised, preventing system stalls and maintaining network integrity. The systematic approach to LIB advancement maintains consistency between different storage layers while ensuring that the system can continue operating even when individual storage components are temporarily unavailable or inconsistent. - -**Enhanced Emergency Consensus** - The enhanced emergency consensus implementation provides comprehensive logging and safety checks for network stall detection and recovery. The automatic recovery system with detailed logging and safety mechanisms ensures that operators have complete visibility into emergency operations without impacting performance. The enhanced safety check optimization prevents false activations while ensuring network stability, making the system more reliable and suitable for production environments where network reliability is critical. - -**Enhanced Shared Memory Corruption Detection** - The introduction of the new shared_memory_corruption_exception type represents a significant advancement in error handling robustness. This structured exception handling approach replaces direct assertion failures with comprehensive error reporting, enabling better debugging and troubleshooting capabilities. The enhanced validator account validation with graceful error handling during block acceptance and generation processes ensures that critical validation failures are properly detected and reported with detailed context information. - -**Enhanced Auto-Recovery Integration** - The seamless integration with Validator Plugin for automatic recovery from shared memory corruption scenarios represents a major improvement in system reliability and uptime. The comprehensive auto-recovery system in plugin.cpp provides rapid recovery from corruption scenarios with minimal downtime, while the structured error reporting ensures that operators have complete visibility into recovery operations. This integration makes the database management system more resilient to critical hardware and software failures, improving overall system reliability and operator confidence. - -**Enhanced Crash Debugging Capabilities** - The comprehensive crash debugging system with debug_crash logging throughout critical code paths represents a significant advancement in troubleshooting and diagnostics capabilities. The extensive debug markers (DEBUG_CRASH) provide detailed visibility into database operations, while the debug-block-production configuration option enables comprehensive monitoring of block production processes. The enhanced stacktrace crash handlers provide automatic crash diagnostics with detailed stacktrace information, significantly improving the debugging experience and reducing troubleshooting time for critical system issues. - -**Enhanced Block Production Monitoring** - The debug-block-production configuration option provides operators with detailed visibility into block production processes, enabling comprehensive monitoring and troubleshooting of production-related issues. The integration with Validator Plugin ensures that block production logging is comprehensive and actionable, while the detailed logging markers throughout the production pipeline provides granular visibility into production scheduling, slot determination, and block generation processes. - -**Enhanced Stacktrace Crash Handlers** - The enhanced stacktrace crash handlers provide automatic crash diagnostics with detailed stacktrace information, significantly improving the debugging experience for fatal errors. The integration with the crash debugging system ensures that operators have complete visibility into system crashes and can quickly identify and resolve critical issues through comprehensive stacktrace analysis and crash diagnostics. - -**Enhanced DLT Gap Recovery System** - The new DLT gap recovery system represents a significant advancement in DLT mode reliability and diagnostics. The intelligent gap detection and automatic recovery mechanisms with warning suppression using the `_dlt_gap_logged` flag provide improved diagnostics with minimal performance impact during DLT mode operations. The comprehensive logging throughout the gap detection and recovery workflow ensures that operators have complete visibility into DLT block log operations and can effectively troubleshoot gap-related issues without log spam or performance degradation. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Fork Resolution and Consensus.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Fork Resolution and Consensus.md deleted file mode 100644 index 32c5020a11..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Fork Resolution and Consensus.md +++ /dev/null @@ -1,1482 +0,0 @@ -# Fork Resolution and Consensus - - -**Referenced Files in This Document** -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [12.hf](file://libraries/chain/hardfork.d/12.hf) - - -## Update Summary -**Changes Made** -- Enhanced fork database with new diagnostic accessors for monitoring block storage statistics -- Added comprehensive storage health metrics including linked/unlinked index sizes and block number ranges -- Integrated diagnostic accessors into P2P monitoring system for real-time block storage analytics -- Improved fork database monitoring capabilities with min/max block number tracking -- Enhanced storage health metrics for better system observability and troubleshooting - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Emergency Consensus Recovery System](#emergency-consensus-recovery-system) -7. [Two-Level Fork Collision Resolution](#two-level-fork-collision-resolution) -8. [Vote-Weighted Fork Comparison Algorithm](#vote-weighted-fork-comparison-algorithm) -9. [Automatic Stale Fork Pruning System](#automatic-stale-fork-pruning-system) -10. [Enhanced Fork Database Diagnostic Accessors](#enhanced-fork-database-diagnostic-accessors) -11. [Storage Health Monitoring and Analytics](#storage-health-monitoring-and-analytics) -12. [Dependency Analysis](#dependency-analysis) -13. [Performance Considerations](#performance-considerations) -14. [Troubleshooting Guide](#troubleshooting-guide) -15. [Conclusion](#conclusion) -16. [Appendices](#appendices) - -## Introduction -This document explains the Fork Resolution and Consensus system that maintains blockchain integrity and handles network partitions. The system has been significantly enhanced with sophisticated gap-based early rejection logic, comprehensive duplicate detection, improved block validation mechanisms, enhanced error handling that prevents infinite synchronization loops, and **NEW**: comprehensive diagnostic accessors for monitoring fork database storage statistics. The fork_database implementation now supports intelligent block rejection, comprehensive duplicate prevention, sophisticated tie-breaking mechanisms for emergency consensus scenarios, advanced fork collision resolution with HF12 logic, and **NEW**: real-time monitoring of linked/unlinked index sizes, minimum/maximum block numbers, and comprehensive storage health metrics. - -## Project Structure -The fork resolution and consensus logic spans several core files with enhanced early rejection, validation, **NEW**: diagnostic monitoring capabilities, and comprehensive storage health metrics: -- fork_database.hpp/cpp: In-memory fork chain storage, branch selection, common ancestor detection, duplicate detection, emergency mode tie-breaking, automatic stale fork pruning, gap-based early rejection, and **NEW**: diagnostic accessors for storage statistics -- database.hpp/cpp: Blockchain database integration, block pushing with early rejection logic, chain reorganization, DLT mode management, sophisticated block validation, vote-weighted fork comparison, and **NEW**: fork database access for diagnostic monitoring -- block_log.hpp: Append-only persistence of blocks for recovery and irreversible state -- dlt_block_log.hpp/cpp: Separate rolling block log for DLT nodes to serve recent irreversible blocks to P2P peers -- validator.cpp: validator scheduling integration with emergency mode awareness, fork collision handling, two-level fork collision resolution, stuck-head timeout mechanism, and automatic chain linking -- p2p_plugin.cpp: **NEW**: Real-time monitoring of fork database storage statistics with comprehensive analytics -- config.hpp: Emergency consensus configuration constants including timeout settings and emergency validator parameters -- 12.hf: Hardfork configuration defining HF12 parameters and activation time - -```mermaid -graph TB -subgraph "Chain Layer" -FD["fork_database.hpp/.cpp"] -DBH["database.hpp"] -DBC["database.cpp"] -BLH["block_log.hpp"] -DLTH["dlt_block_log.hpp/.cpp"] -DIAG["Diagnostic Accessors"] -PMON["P2P Monitoring"] -END["Gap-Based Early Rejection"] -END2["Duplicate Detection"] -END3["Block Validation"] -END4["Exception Handling"] -END5["HF12 Fork Comparison"] -END6["Stuck-Head Timeout"] -END7["Automatic Chain Linking"] -END8["Stale Fork Pruning"] -end -FD --> DBC -DBH --> DBC -BLH --> DBC -DLTH --> DBC -DBC --> FD -DBC --> DIAG -DBC --> PMON -DBC --> END -DBC --> END2 -DBC --> END3 -DBC --> END4 -DBC --> END5 -DBC --> END6 -DBC --> END7 -DBC --> END8 -FD --> DIAG -FD --> END4 -FD --> END5 -FD --> END6 -FD --> END7 -FD --> END8 -PMON --> DIAG -``` - -**Diagram sources** -- [fork_database.hpp:128-150](file://libraries/chain/include/graphene/chain/fork_database.hpp#L128-L150) -- [fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) -- [database.cpp:1204-1270](file://libraries/chain/database.cpp#L1204-L1270) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) -- [validator.cpp:521-544](file://plugins/validator/validator.cpp#L521-L544) - -**Section sources** -- [fork_database.hpp:1-168](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L168) -- [fork_database.cpp:1-278](file://libraries/chain/fork_database.cpp#L1-L278) -- [database.hpp:1-200](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [database.cpp:1-6669](file://libraries/chain/database.cpp#L1-L6669) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) -- [dlt_block_log.cpp:1-454](file://libraries/chain/dlt_block_log.cpp#L1-L454) -- [validator.cpp:1-697](file://plugins/validator/validator.cpp#L1-L697) -- [p2p_plugin.cpp:735-771](file://plugins/p2p/p2p_plugin.cpp#L735-L771) -- [config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -## Core Components -- fork_database: Maintains a multi-indexed collection of fork items with enhanced out-of-order block caching, comprehensive duplicate detection, sophisticated tie-breaking mechanisms, emergency mode integration, automatic stale fork pruning capabilities, gap-based early rejection logic, and **NEW**: comprehensive diagnostic accessors for storage statistics monitoring -- database: Integrates fork resolution into block application with sophisticated early rejection logic, comprehensive block validation, performs chain reorganization when a better fork emerges, manages DLT mode for snapshot-based nodes, implements emergency consensus mode activation/deactivation, provides vote-weighted fork comparison for HF12, and **NEW**: exposes fork database access for diagnostic monitoring -- block_log: Provides persistent storage for blocks, enabling recovery and serving as the source of irreversible blocks -- dlt_block_log: Separate rolling block log for DLT nodes that maintains a sliding window of recent irreversible blocks for P2P synchronization -- validator: Integrates validator scheduling with emergency mode awareness, handles fork collisions through two-level resolution system, manages stuck-head timeout mechanism, implements HF12 fork collision resolution, and provides automatic chain linking when parent blocks arrive -- **NEW**: P2P monitoring system: Real-time monitoring of fork database storage statistics including linked/unlinked index sizes, minimum/maximum block numbers, and comprehensive storage health metrics -- emergency consensus: Implements timeout-based emergency mode activation, hybrid validator scheduling, and deterministic tie-breaking mechanisms -- compare_fork_branches: New HF12 function that performs vote-weighted fork comparison with +10% bonus for longer chains -- remove_blocks_by_number: New function that removes all blocks at a specific height to prevent memory bloat from dead-fork blocks - -Key responsibilities: -- Track reversible blocks in memory (fork DB) with enhanced caching for out-of-order blocks, comprehensive duplicate detection, emergency mode tie-breaking, automatic stale fork pruning, and gap-based early rejection -- Implement sophisticated early rejection logic to prevent unnecessary fork database operations and infinite synchronization loops -- Detect and select the best chain by comparing heads with improved validation, emergency mode awareness, and HF12 vote-weighted comparison -- Reorganize the chain when a higher fork becomes active with better error recovery, emergency mode integration, and fork collision resolution -- Manage DLT mode for snapshot-based nodes with automatic fork database seeding capabilities -- Implement emergency consensus mode activation based on timeout thresholds -- Provide hybrid validator scheduling during emergency periods with deterministic tie-breaking -- Persist irreversible blocks to both block_log and dlt_block_log with enhanced reliability and emergency mode awareness -- Serve recent blocks to P2P peers through dlt_block_log for faster synchronization -- Handle emergency validator account creation and key management for consensus recovery -- Distinguish between different types of invalid blocks and handle them appropriately to prevent system degradation -- **New**: Provide comprehensive diagnostic accessors for monitoring fork database storage statistics including linked/unlinked index sizes and block number ranges -- **New**: Integrate diagnostic accessors into P2P monitoring system for real-time storage analytics -- **New**: Enable automatic chain linking when parent blocks arrive via _push_next() mechanism -- **New**: Enhance duplicate detection and prevention throughout the system -- **New**: Implement separate handling paths for linear extensions vs actual fork switches during chain reorganization -- **New**: Add detailed debug logging prefixes (FORK-SWITCH-POP, FORK-RECOVER-POP) for better traceability - -**Section sources** -- [fork_database.hpp:53-168](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L168) -- [fork_database.cpp:33-92](file://libraries/chain/fork_database.cpp#L33-L92) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [dlt_block_log.hpp:13-33](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L13-L33) -- [validator.cpp:521-544](file://plugins/validator/validator.cpp#L521-L544) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -## Architecture Overview -The fork resolution pipeline integrates with block application and persistence with enhanced early rejection, sophisticated block validation, DLT mode support, automatic seeding mechanisms, emergency consensus recovery, advanced fork collision resolution, automatic chain linking, and **NEW**: comprehensive diagnostic monitoring: - -```mermaid -sequenceDiagram -participant Net as "Network" -participant DB as "database.cpp" -participant FDB as "fork_database.cpp" -participant WIT as "validator.cpp" -participant BL as "block_log.hpp" -participant DLTL as "dlt_block_log.cpp" -participant MON as "P2P Monitoring" -Net->>DB : "push_block(new_block)" -DB->>DB : "Early rejection checks" -DB->>DB : "Validate block types and conditions" -DB->>FDB : "push_block(new_block)" -alt "Block already known" -FDB-->>DB : "Ignore duplicate (duplicate detection)" -else "Unlinkable block" -FDB-->>DB : "Cache in unlinked_index" -else "Valid block" -FDB-->>DB : "Insert and _push_next" -DB->>DB : "Check emergency consensus timeout" -alt "Emergency mode activated" -DB->>WIT : "Override validator schedule to emergency validator" -DB->>FDB : "set_emergency_mode(true)" -DB->>DB : "Skip LIB advancement during emergency" -else "Normal mode" -DB->>DB : "Check new_head vs head_block_id()" -alt "Need fork switch" -DB->>DB : "HF12 : compare_fork_branches()" -DB->>FDB : "fetch_branch_from(new_head.id, head_block_id())" -FDB-->>DB : "branches" -DB->>DB : "pop blocks until common ancestor" -DB->>DB : "apply blocks from new fork" -end -end -DB->>BL : "persist irreversible blocks" -DB->>DLTL : "append to dlt_block_log (if enabled)" -DB->>MON : "collect diagnostic metrics" -MON->>MON : "analyze fork storage statistics" -``` - -**Diagram sources** -- [database.cpp:1204-1270](file://libraries/chain/database.cpp#L1204-L1270) -- [fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) -- [validator.cpp:521-544](file://plugins/validator/validator.cpp#L521-L544) -- [dlt_block_log.cpp:336-340](file://libraries/chain/dlt_block_log.cpp#L336-L340) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -## Detailed Component Analysis - -### Enhanced Gap-Based Early Rejection Logic and Block Validation -**Updated** The database now implements sophisticated early rejection logic that intelligently validates different types of blocks to prevent unnecessary processing and infinite synchronization loops. The system now includes a 100-block gap threshold to prevent memory bloat from dead-fork blocks. - -The early rejection logic includes: - -1. **Already Applied Block Detection**: If a block is at or before the current head and matches the existing block ID, it's ignored to prevent duplicate processing -2. **Different Fork Detection**: Blocks that are at or before the head but on different forks are silently rejected if their parent is not in the fork database -3. **Far Ahead Block Rejection**: Blocks that are far ahead of the current head with unknown parents are silently rejected to prevent P2P sync restart loops -4. **Parent Unknown Detection**: Blocks with unknown parents are rejected to prevent fork database overflow and sync disruption -5. **Gap-Based Rejection**: For blocks with gaps > 100 blocks, immediate rejection prevents memory bloat from dead-fork chains - -```mermaid -flowchart TD -Start(["Block Processing Request"]) --> CheckHead["Check if block_num <= head_block_num()"] -CheckHead --> IsBeforeHead{"Block at or before head?"} -IsBeforeHead --> |Yes| CheckExisting["Check existing block ID"] -CheckExisting --> MatchExisting{"ID matches existing?"} -MatchExisting --> |Yes| IgnoreBlock["Ignore block (already applied)"] -MatchExisting --> |No| CheckParent["Check parent in fork_db"] -CheckParent --> ParentUnknown{"Parent unknown?"} -ParentUnknown --> |Yes| RejectFork["Reject different fork block"] -ParentUnknown --> |No| AllowProcess["Allow processing"] -IsBeforeHead --> |No| CheckFarAhead["Check far ahead blocks"] -CheckFarAhead --> FarAhead{"Block far ahead?"} -FarAhead --> |Yes| CheckParentUnknown["Check parent unknown"] -CheckParentUnknown --> ParentUnknown2{"Parent unknown?"} -ParentUnknown2 --> |Yes| CheckGap["Calculate gap size"] -CheckGap --> GapTooBig{"Gap > 100 blocks?"} -GapTooBig --> |Yes| RejectFarAhead["Reject far ahead block (memory protection)"] -GapTooBig --> |No| DeferBlock["Defer to fork_db unlinked_index"] -ParentUnknown2 --> |No| AllowProcess -FarAhead --> |No| AllowProcess -AllowProcess --> ProcessBlock["Process block normally"] -IgnoreBlock --> End(["Complete"]) -RejectFork --> End -RejectFarAhead --> End -DeferBlock --> End -ProcessBlock --> End -``` - -**Diagram sources** -- [database.cpp:1300-1399](file://libraries/chain/database.cpp#L1300-L1399) - -**Section sources** -- [database.cpp:1300-1399](file://libraries/chain/database.cpp#L1300-L1399) - -### Enhanced Duplicate Block Detection and Prevention -**New Section** The fork database now includes comprehensive duplicate block detection to prevent redundant processing and improve P2P synchronization reliability. - -Duplicate detection mechanisms: -- Pre-insertion ID check against existing blocks in the index -- Prevention of duplicate processing during snapshot imports and P2P re-transmissions -- Efficient early rejection of already-applied blocks through database-level validation -- Comprehensive duplicate handling in emergency mode scenarios - -```mermaid -flowchart TD -Start(["Block Insertion Request"]) --> CheckDup["Check existing ID in index"] -CheckDup --> IsDup{"ID exists?"} -IsDup --> |Yes| Return["Return existing head (no-op)"] -IsDup --> |No| ValidatePrev["Validate previous block linkage"] -ValidatePrev --> LinkOK{"Link valid?"} -LinkOK --> |No| CacheUnlink["Cache in unlinked_index"] -LinkOK --> |Yes| InsertBlock["Insert into _index"] -InsertBlock --> PushNext["_push_next(new_item)"] -PushNext --> UpdateHead["Update _head if higher"] -Return --> End(["Complete"]) -CacheUnlink --> End -UpdateHead --> End -``` - -**Diagram sources** -- [fork_database.cpp:48-84](file://libraries/chain/fork_database.cpp#L48-L84) - -**Section sources** -- [fork_database.cpp:48-55](file://libraries/chain/fork_database.cpp#L48-L55) - -### Enhanced Fork Database with Improved Error Handling -**Updated** The fork database now includes comprehensive error handling, sophisticated tie-breaking mechanisms for emergency consensus scenarios, automatic stale fork pruning capabilities, enhanced gap-based early rejection logic, and **NEW**: comprehensive diagnostic accessors for storage statistics monitoring. - -The fork database supports: -- Pushing a block and linking it to the previous block with duplicate prevention -- Tracking the current head with enhanced validation and emergency mode tie-breaking -- Fetching branches from two heads to a common ancestor -- Walking the main branch to a given block number -- Removing blocks and limiting fork depth -- **New**: Iterative processing of cached unlinked blocks via `_push_next` -- **New**: Duplicate detection to prevent redundant processing during snapshot imports -- **New**: Enhanced error handling for unlinkable blocks with comprehensive logging -- **New**: Emergency mode tie-breaking with deterministic hash-based resolution for consensus stability -- **New**: Automatic stale fork pruning through `remove_blocks_by_number()` function -- **New**: Enhanced pruning system with `set_max_size()` that cleans both linked and unlinked indices -- **New**: Gap-based early rejection logic integrated with automatic chain linking -- **New**: Diagnostic accessors for monitoring storage statistics including linked/unlinked sizes and block number ranges - -```mermaid -classDiagram -class fork_database { -+push_block(b) -+set_head(h) -+head() -+pop_block() -+is_known_block(id) -+fetch_block(id) -+fetch_block_by_number(n) -+fetch_branch_from(first, second) -+walk_main_branch_to_num(block_num) -+fetch_block_on_main_branch_by_number(block_num) -+set_max_size(s) -+reset() -+start_block(b) -+remove(b) -+remove_blocks_by_number(num) -+set_emergency_mode(active) -+is_emergency_mode() -+linked_size() size_t -+unlinked_size() size_t -+linked_min_block_num() uint32_t -+linked_max_block_num() uint32_t -+unlinked_min_block_num() uint32_t -+unlinked_max_block_num() uint32_t --_push_block(item) --_push_next(new_item) --_emergency_consensus_active --_max_size --_index --_unlinked_index --_head -} -class fork_item { -+num -+invalid -+id -+data -+prev -+previous_id() -} -fork_database --> fork_item : "stores" -``` - -**Diagram sources** -- [fork_database.hpp:20-168](file://libraries/chain/include/graphene/chain/fork_database.hpp#L20-L168) -- [fork_database.cpp:33-278](file://libraries/chain/fork_database.cpp#L33-L278) - -Implementation highlights: -- **Enhanced duplicate detection**: Blocks are checked against existing IDs before insertion to prevent duplicate processing -- **Improved linking validation**: Ensures each new block's previous ID exists in the index and is not marked invalid -- **Robust unlinked block caching**: Cached blocks are processed iteratively when their parent appears via `_push_next` -- **Maximum fork depth enforcement**: Prevents unbounded growth; older blocks are pruned with enhanced cleanup -- **Better error handling**: Comprehensive exception handling for unlinkable blocks with logging -- **Automatic seeding support**: Works seamlessly with DLT mode to enable immediate P2P synchronization -- **Emergency mode tie-breaking**: During emergency mode, deterministic hash-based tie-breaking ensures consensus stability when multiple emergency producers compete at the same height -- **Enhanced exception management**: Sophisticated handling of different types of block validation failures -- **Automatic stale fork pruning**: New `remove_blocks_by_number()` function clears stale competing blocks from dead forks -- **Enhanced pruning system**: `set_max_size()` now cleans both `_index` and `_unlinked_index` for optimal memory management -- **Gap-based early rejection**: Integrated with automatic chain linking to prevent memory bloat while maintaining network efficiency -- **NEW**: **Diagnostic accessors**: Comprehensive monitoring capabilities for fork database storage statistics - -**Section sources** -- [fork_database.hpp:111-168](file://libraries/chain/include/graphene/chain/fork_database.hpp#L111-L168) -- [fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) - -### Branch Selection and Common Ancestor Detection -Branch selection relies on walking both branches backward until a common ancestor is found. The method returns two vectors representing the branches from each head to the common ancestor. - -```mermaid -flowchart TD -Start(["Start"]) --> GetHeads["Get first and second heads"] -GetHeads --> WalkLoop1["While first.head.block_num > second.head.block_num:
append first to branch1
advance first.prev"] -WalkLoop1 --> WalkLoop2["While second.head.block_num > first.head.block_num:
append second to branch2
advance second.prev"] -WalkLoop2 --> FindCommon["While first.head.previous != second.head.previous:
append both to respective branches
advance both"] -FindCommon --> DoneCommon["If both valid:
append both to branches"] -DoneCommon --> Return(["Return branches"]) -``` - -**Diagram sources** -- [fork_database.cpp:189-231](file://libraries/chain/fork_database.cpp#L189-L231) - -**Section sources** -- [fork_database.cpp:189-231](file://libraries/chain/fork_database.cpp#L189-L231) - -### Enhanced Chain Reorganization Process -**Updated** The chain reorganization process now includes improved early rejection logic, better error handling, DLT mode awareness, emergency consensus integration, HF12 fork comparison capabilities, automatic chain linking for enhanced P2P synchronization reliability, and **NEW**: comprehensive diagnostic monitoring for storage statistics. - -When a new head is higher and does not build off the current head, the database: -- Performs sophisticated early rejection checks to prevent unnecessary fork switches -- **New**: Uses HF12 logic with `compare_fork_branches()` for vote-weighted fork comparison -- **New**: Applies +10% bonus to longer chain in vote-weighted comparison -- **New**: Falls back to simple longest-chain rule for pre-HF12 compatibility -- Computes branches to the common ancestor with enhanced validation -- Pops blocks until reaching the common ancestor with improved error recovery -- Applies blocks from the new fork in reverse order with comprehensive exception handling -- Handles exceptions by invalidating the problematic fork and restoring the good fork with enhanced logging -- **New**: Works seamlessly with DLT mode to maintain fork database consistency -- **New**: Skips LIB advancement during emergency mode to prevent premature irreversibility -- **New**: Implements vote-weighted chain comparison for HF12 and above for more robust consensus -- **New**: Integrates automatic chain linking via _push_next() when parent blocks arrive -- **New**: Implements separate handling paths for linear extensions vs actual fork switches -- **New**: Adds detailed debug logging prefixes (FORK-SWITCH-POP, FORK-RECOVER-POP) for better traceability -- **New**: Monitors storage statistics during chain reorganization for performance optimization - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant FDB as "fork_database.cpp" -DB->>DB : "Early rejection checks" -DB->>FDB : "push_block(new_block)" -FDB-->>DB : "new_head" -DB->>DB : "if new_head higher and differs" -DB->>DB : "Validate current head in fork_db" -DB->>DB : "HF12 : compare_fork_branches()" -DB->>FDB : "fetch_branch_from(new_head.id, head_block_id())" -FDB-->>DB : "branches" -DB->>DB : "pop blocks until common ancestor" -loop "for each block in new fork" -DB->>DB : "apply_block(block)" -DB->>DB : "Handle exceptions with rollback" -end -DB-->>DB : "return true (switched)" -``` - -**Diagram sources** -- [database.cpp:1037-1177](file://libraries/chain/database.cpp#L1037-L1177) -- [fork_database.cpp:189-231](file://libraries/chain/fork_database.cpp#L189-L231) - -**Section sources** -- [database.cpp:1037-1177](file://libraries/chain/database.cpp#L1037-L1177) - -### Enhanced Chain Reorganization with Separate Handling Paths -**New Section** The chain reorganization process now implements separate handling paths for linear extensions versus actual fork switches, providing more efficient processing and better error recovery. - -When a new head is higher and does not build off the current head, the database: -- **Linear Extension Path**: When `branches.second` is empty (block extends directly from current head) - - No fork switching required - - Some blocks from `branches.first` may have been already applied - - Reset fork database to match current database head - - No pop operations needed -- **Actual Fork Switch Path**: When `branches.second` is not empty (divergent fork) - - Pop blocks from current fork until common ancestor - - Apply blocks from new fork in reverse order - - Comprehensive exception handling with rollback - - Detailed debug logging with FORK-SWITCH-POP prefix - -```mermaid -flowchart TD -Start(["Fork Switch Decision"]) --> CheckBranches{"branches.second empty?"} -CheckBranches --> |Yes| LinearExt["Linear Extension Path"] -CheckBranches --> |No| ActualFork["Actual Fork Switch Path"] -LinearExt --> ResetFork["Reset fork_db to match DB head"] -ResetFork --> Complete["Complete without pop operations"] -ActualFork --> PopBlocks["Pop blocks until common ancestor"] -PopBlocks --> ApplyBlocks["Apply blocks from new fork"] -ApplyBlocks --> CheckExceptions{"Exception occurred?"} -CheckExceptions --> |Yes| Rollback["Rollback and restore original fork"] -CheckExceptions --> |No| UpdateHead["Update fork_db head"] -Rollback --> Complete -UpdateHead --> Complete -``` - -**Diagram sources** -- [database.cpp:1420-1510](file://libraries/chain/database.cpp#L1420-L1510) - -**Section sources** -- [database.cpp:1420-1510](file://libraries/chain/database.cpp#L1420-L1510) - -### Enhanced Debug Logging with Prefixes -**New Section** The system now includes detailed debug logging prefixes for better traceability and debugging of fork resolution processes. - -Debug logging prefixes: -- **FORK-SWITCH-POP**: Logs when popping blocks during actual fork switching -- **FORK-RECOVER-POP**: Logs when popping blocks during fork recovery after exceptions -- **FORK-SWITCH-APPLY**: Logs when applying blocks from new fork during fork switch -- **FORK-RECOVER-APPLY**: Logs when re-applying blocks to restore original fork - -These prefixes help developers and operators quickly identify the type of fork resolution operation being performed and troubleshoot issues more effectively. - -**Section sources** -- [database.cpp:1432-1498](file://libraries/chain/database.cpp#L1432-L1498) - -### DLT Mode Integration and Automatic Seeding -**New Section** The database now supports DLT (Data Ledger Technology) mode for snapshot-based nodes, with automatic seeding of the fork database to enable immediate P2P synchronization. - -DLT mode features: -- **Automatic seeding**: When a snapshot is imported, the fork database is automatically seeded from either the DLT block log or chain state -- **Dual block logging**: Maintains both regular block_log and DLT block_log for different use cases -- **Gap handling**: Manages gaps between DLT block log and fork database during initial synchronization -- **Rolling window**: DLT block log maintains a sliding window of recent blocks for P2P peers - -```mermaid -flowchart TD -Start(["DLT Mode Detection"]) --> CheckLog["Check block_log head"] -CheckLog --> HasHead{"Has head block?"} -HasHead --> |Yes| NormalMode["Normal mode: use block_log head"] -HasHead --> |No| DLTMode["DLT mode detected"] -DLTMode --> SeedFromDLT["Try to seed from DLT block_log"] -SeedFromDLT --> SeedSuccess{"Seed successful?"} -SeedSuccess --> |Yes| ImmediateSync["Enable immediate P2P sync"] -SeedSuccess --> |No| MinimalSeed["Create minimal fork_item"] -MinimalSeed --> WaitFirstBlock["Wait for first block to apply"] -ImmediateSync --> End(["Ready for P2P sync"]) -WaitFirstBlock --> End -``` - -**Diagram sources** -- [database.cpp:259-294](file://libraries/chain/database.cpp#L259-L294) - -**Section sources** -- [database.cpp:259-294](file://libraries/chain/database.cpp#L259-L294) -- [database.hpp:57-78](file://libraries/chain/include/graphene/chain/database.hpp#L57-L78) - -### Enhanced Irreversible Block Determination and Persistence -**Updated** Irreversible blocks are determined by consensus thresholds and persisted to both block_log and dlt_block_log with enhanced reliability, DLT mode awareness, emergency consensus integration, and **NEW**: comprehensive diagnostic monitoring for storage statistics. - -The database updates last irreversible block (LIB) and writes blocks to logs when they become irreversible: -- **DLT mode awareness**: Skips block_log writes in DLT mode while still maintaining dlt_block_log -- **Dual persistence**: Writes to both block_log and dlt_block_log for comprehensive coverage -- **Gap logging**: Suppresses repeated warnings about missing blocks in fork database during initial synchronization -- **Rolling window management**: Automatically truncates DLT block log when it exceeds configured limits -- **Emergency mode integration**: Skips LIB advancement during emergency mode to prevent premature irreversibility -- **Enhanced validation**: Sophisticated block validation prevents invalid blocks from becoming irreversible -- **Stale fork pruning**: Automatically prunes stale competing blocks from dead forks at each height -- **Storage monitoring**: Collects and reports fork database storage statistics for performance optimization - -```mermaid -flowchart TD -Start(["After block application"]) --> CheckLIB["Check consensus threshold for LIB"] -CheckLIB --> CheckEmergency{"Emergency mode active?"} -CheckEmergency --> |Yes| SkipLIB["Skip LIB advancement"] -CheckEmergency --> |No| UpdateLIB["Update last_irreversible_block_num/id/ref fields"] -UpdateLIB --> CheckDLT{"DLT mode enabled?"} -CheckDLT --> |Yes| SkipBlockLog["Skip block_log write"] -CheckDLT --> |No| WriteBlockLog["Write to block_log"] -WriteBlockLog --> WriteDLT["Write to dlt_block_log"] -SkipBlockLog --> WriteDLT -WriteDLT --> CheckDLTWindow["Check DLT window size"] -CheckDLTWindow --> Truncate{"Exceeds limit?"} -Truncate --> |Yes| TruncateDLT["Truncate DLT block log"] -Truncate --> |No| PruneStale["Prune stale competing blocks"] -TruncateDLT --> PruneStale -PruneStale --> CollectStats["Collect fork storage statistics"] -CollectStats --> End(["Ready for recovery"]) -SkipLIB --> End -``` - -**Diagram sources** -- [database.cpp:4444-4533](file://libraries/chain/database.cpp#L4444-L4533) -- [dlt_block_log.cpp:336-340](file://libraries/chain/dlt_block_log.cpp#L336-L340) - -**Section sources** -- [database.cpp:4444-4533](file://libraries/chain/database.cpp#L4444-L4533) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) - -### Enhanced P2P Fallback Mechanisms -**New Section** The P2P system now includes strengthened fallback mechanisms to handle network partitions and improve synchronization reliability, with **NEW**: comprehensive diagnostic monitoring for storage statistics. - -P2P fallback features: -- **Enhanced error handling**: Better propagation of unlinkable block exceptions to network layer -- **Improved peer management**: More robust handling of disconnected peers and connection failures -- **Faster synchronization**: Automatic seeding enables immediate P2P sync for DLT nodes -- **Better network partition handling**: Enhanced mechanisms to recover from network splits -- **Intelligent block rejection**: Prevents infinite sync restart loops through early rejection logic -- **Gap-based protection**: 100-block threshold prevents memory bloat from dead-fork chains -- **Storage monitoring**: Real-time monitoring of fork database storage statistics for performance optimization - -```mermaid -sequenceDiagram -participant Peer as "P2P Peer" -participant P2P as "p2p_plugin.cpp" -participant DB as "database.cpp" -Peer->>P2P : "handle_block(block)" -P2P->>DB : "accept_block(block)" -alt "Block valid" -DB-->>P2P : "true" -P2P-->>Peer : "acknowledge" -else "Unlinkable block" -DB-->>P2P : "unlinkable_block_exception" -P2P-->>Peer : "propagate exception" -P2P->>P2P : "enhanced error handling" -P2P-->>Peer : "fallback to alternative sync" -P2P->>P2P : "collect fork storage metrics" -end -``` - -**Diagram sources** -- [p2p_plugin.cpp:118-164](file://plugins/p2p/p2p_plugin.cpp#L118-L164) - -**Section sources** -- [p2p_plugin.cpp:118-164](file://plugins/p2p/p2p_plugin.cpp#L118-L164) - -### Enhanced API Methods for Fork Detection, Chain Validation, and Recovery -**Updated** Enhanced with improved duplicate detection, DLT mode support, automatic seeding capabilities, emergency mode integration, sophisticated block validation, HF12 fork comparison capabilities, gap-based early rejection, automatic stale fork pruning, **NEW**: comprehensive diagnostic accessors, and automatic chain linking. - -- Fork detection and branch retrieval: - - get_block_ids_on_fork(head_of_fork): Returns ordered list of block IDs from the fork head back to the common ancestor - - fetch_branch_from(first, second): Returns two branches leading to a common ancestor - - **New**: compare_fork_branches(branch_a_tip, branch_b_tip): Vote-weighted fork comparison for HF12 -- Chain validation: - - validate_block(new_block, skip): Validates block Merkle root and size with enhanced error handling -- State recovery: - - open(): Initializes database and starts fork DB at head block with DLT mode awareness - - reindex(): Replays blocks and restarts fork DB at the new head - - find_block_id_for_num(block_num)/get_block_id_for_num(block_num): Resolves block ID across block log, fork DB, and TAPOS buffer with enhanced duplicate handling -- **New**: DLT mode management: - - set_dlt_mode(enabled): Enables/disables DLT mode for snapshot-based nodes - - open_from_snapshot(): Optimized initialization for snapshot-based nodes with automatic seeding -- **New**: Emergency mode management: - - set_emergency_mode(active): Activates or deactivates emergency consensus mode - - is_emergency_mode(): Checks current emergency consensus mode status -- **New**: Enhanced block validation: - - Sophisticated early rejection logic prevents infinite synchronization loops - - Intelligent duplicate detection prevents redundant processing - - Comprehensive exception handling for different block validation failures - - Gap-based rejection with 100-block threshold prevents memory bloat -- **New**: Stale fork management: - - remove_blocks_by_number(num): Removes all blocks at specific height to prune dead forks - - Enhanced pruning system with automatic cleanup of stale competing blocks -- **New**: Automatic chain linking: - - _push_next(): Iterative processing of cached unlinked blocks when parents arrive - - Gap-based protection prevents memory bloat from dead-fork chains -- **New**: Separate handling paths: - - Linear extension vs actual fork switch processing for improved efficiency - - Detailed debug logging with FORK-SWITCH-POP and FORK-RECOVER-POP prefixes -- **New**: Comprehensive diagnostic accessors: - - linked_size(): Returns number of blocks in linked index - - unlinked_size(): Returns number of blocks in unlinked index - - linked_min_block_num(): Returns minimum block number in linked index - - linked_max_block_num(): Returns maximum block number in linked index - - unlinked_min_block_num(): Returns minimum block number in unlinked index - - unlinked_max_block_num(): Returns maximum block number in unlinked index - -**Section sources** -- [database.hpp:115-128](file://libraries/chain/include/graphene/chain/database.hpp#L115-L128) -- [database.cpp:561-580](file://libraries/chain/database.cpp#L561-L580) -- [database.cpp:738-792](file://libraries/chain/database.cpp#L738-L792) -- [database.cpp:206-230](file://libraries/chain/database.cpp#L206-L230) -- [database.cpp:476-515](file://libraries/chain/database.cpp#L476-L515) -- [fork_database.hpp:111-168](file://libraries/chain/include/graphene/chain/fork_database.hpp#L111-L168) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) - -### Examples of Enhanced Fork Scenarios and Resolution Processes -**Updated** Enhanced with improved out-of-order block handling, duplicate detection, DLT mode integration, automatic seeding capabilities, emergency consensus recovery, sophisticated early rejection logic, advanced fork collision resolution, automatic chain linking, stale fork pruning, separate handling paths, detailed debug logging, and **NEW**: comprehensive diagnostic monitoring. - -- Scenario A: Out-of-order arrival of blocks with improved caching and automatic linking - - Behavior: New blocks are inserted into the unlinked cache and later inserted when their parent appears via `_push_next`, which automatically links the entire chain - - Mechanism: Enhanced `_push_next` iteratively processes pending blocks whose parent now exists, with comprehensive error handling and automatic chain completion -- Scenario B: Network partition resolves with a longer chain and improved early rejection - - Behavior: The database performs sophisticated early rejection checks, detects a higher head, computes branches, pops blocks, and applies the new fork - - Mechanism: Enhanced early rejection logic prevents unnecessary fork switches and improves P2P synchronization reliability -- Scenario C: Invalid block on a fork with improved error handling - - Behavior: The fork is invalidated and removed; the database restores the good fork and throws the exception with enhanced logging - - Mechanism: Comprehensive exception handling with rollback to previous state and improved error reporting -- **New Scenario D**: Fresh snapshot import with automatic seeding - - Behavior: DLT mode is detected, fork database is automatically seeded from DLT block log or chain state, enabling immediate P2P synchronization - - Mechanism: Enhanced DLT mode detection and automatic seeding prevents P2P synchronization delays -- **New Scenario E**: DLT block log gap handling - - Behavior: When DLT block log falls behind fork database, the system logs warnings once and continues operation - - Mechanism: Gap logging suppression prevents log flooding while maintaining operational awareness -- **New Scenario F**: Emergency consensus mode activation - - Behavior: When no blocks are produced for CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC seconds, emergency mode activates with deterministic tie-breaking - - Mechanism: Emergency validator takes over all validator slots, emergency mode flag is set, and deterministic hash-based tie-breaking ensures consensus stability -- **New Scenario G**: Emergency consensus mode deactivation - - Behavior: When LIB advances past emergency_consensus_start_block, emergency mode is deactivated and normal validator scheduling resumes - - Mechanism: Emergency mode flag is cleared and fork database is notified of emergency mode termination -- **New Scenario H**: Sophisticated early rejection in action - - Behavior: Database rejects blocks that are already applied, on different forks, or far ahead with unknown parents to prevent infinite sync loops - - Mechanism: Intelligent block validation prevents unnecessary processing and system degradation -- **New Scenario I**: Duplicate block prevention - - Behavior: Database and fork database work together to detect and prevent duplicate block processing - - Mechanism: Comprehensive duplicate detection prevents redundant CPU usage and improves synchronization reliability -- **New Scenario J**: HF12 fork collision resolution - - Behavior: When competing blocks appear at the same height, database uses vote-weighted comparison to determine the stronger fork - - Mechanism: `compare_fork_branches()` calculates total vote weight per validator, applies +10% bonus to longer chain, and resolves ties deterministically -- **New Scenario K**: Stuck-head timeout mechanism - - Behavior: After 21 consecutive deferrals (one full validator round), database removes stale competing blocks and produces on the canonical chain - - Mechanism: `fork_collision_defer_count_` tracks deferral attempts, `remove_blocks_by_number()` clears stale blocks, and timeout ensures network progress -- **New Scenario L**: Automatic stale fork pruning - - Behavior: Database periodically removes stale competing blocks from dead forks to prevent memory bloat and improve performance - - Mechanism: `remove_blocks_by_number()` clears all blocks at specific heights, combined with `set_max_size()` pruning for optimal memory usage -- **New Scenario M**: Gap-based early rejection protection - - Behavior: Database rejects blocks with gaps > 100 blocks to prevent memory bloat from dead-fork chains - - Mechanism: 100-block threshold prevents accumulation of stale blocks while allowing normal out-of-order processing -- **New Scenario N**: Automatic chain linking when parent arrives - - Behavior: Database caches unlinkable blocks and automatically links them when their parents arrive via _push_next() - - Mechanism: Iterative processing of cached blocks prevents memory bloat and maintains network efficiency -- **New Scenario O**: Linear extension vs fork switch distinction - - Behavior: Database distinguishes between linear extensions (no fork) and actual fork switches (divergent chains) with separate handling paths - - Mechanism: When `branches.second` is empty, database resets fork_db without pop operations; when not empty, database performs full fork switch with detailed logging -- **New Scenario P**: Detailed debug logging with prefixes - - Behavior: Database logs detailed information about fork resolution operations with FORK-SWITCH-POP and FORK-RECOVER-POP prefixes - - Mechanism: Enhanced logging helps developers trace fork resolution steps and identify issues more quickly -- **New Scenario Q**: Comprehensive diagnostic monitoring in action - - Behavior: P2P monitoring system collects and analyzes fork database storage statistics in real-time - - Mechanism: linked_size(), unlinked_size(), and block number range metrics provide insights into fork database health and performance -- **New Scenario R**: Storage health optimization - - Behavior: Database uses diagnostic metrics to optimize fork database performance and prevent memory bloat - - Mechanism: Storage statistics inform pruning decisions and capacity planning for optimal system performance - -**Section sources** -- [fork_database.cpp:92-103](file://libraries/chain/fork_database.cpp#L92-L103) -- [database.cpp:1075-1087](file://libraries/chain/database.cpp#L1075-L1087) -- [database.cpp:259-294](file://libraries/chain/database.cpp#L259-L294) -- [database.cpp:4581-4594](file://libraries/chain/database.cpp#L4581-L4594) -- [database.cpp:1300-1399](file://libraries/chain/database.cpp#L1300-L1399) -- [database.cpp:2125-2142](file://libraries/chain/database.cpp#L2125-L2142) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [validator.cpp:597-612](file://plugins/validator/validator.cpp#L597-L612) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -## Emergency Consensus Recovery System - -### Emergency Consensus Mode Activation -The emergency consensus mode activates automatically when no blocks are produced for more than CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC seconds (1 hour by default). This mechanism ensures blockchain continuity during extended network partitions or validator failures. - -Emergency mode activation process: -- **Timeout detection**: The database checks if seconds_since_LIB >= CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC -- **Mode activation**: Sets emergency_consensus_active = true and records emergency_consensus_start_block -- **Emergency validator setup**: Creates or updates emergency validator account with CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY -- **Penalty reset**: Resets all validator penalties and re-enables shut-down validators -- **Schedule override**: Overrides validator schedule so all slots are filled by emergency validator -- **Fork database notification**: Sets emergency mode flag in fork database for deterministic tie-breaking - -```mermaid -flowchart TD -Start(["Block Application"]) --> CheckTimeout["Check LIB timestamp vs block timestamp"] -CheckTimeout --> TimeoutExceeded{"seconds_since_LIB >= TIMEOUT?"} -TimeoutExceeded --> |No| NormalOperation["Continue normal operation"] -TimeoutExceeded --> |Yes| ActivateEmergency["Activate Emergency Mode"] -ActivateEmergency --> CreateWitness["Create/Update Emergency validator"] -CreateWitness --> ResetPenalties["Reset All validator Penalties"] -ResetPenalties --> OverrideSchedule["Override validator Schedule"] -OverrideSchedule --> NotifyForkDB["Notify Fork Database"] -NotifyForkDB --> LogActivation["Log Emergency Mode Activation"] -LogActivation --> End(["Emergency Mode Active"]) -NormalOperation --> End -``` - -**Diagram sources** -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) - -### Hybrid validator Scheduling During Emergency -During emergency mode, the validator scheduling system operates differently to ensure consensus stability: -- **All slots filled by emergency validator**: All CHAIN_MAX_WITNESSES slots are assigned to CHAIN_EMERGENCY_WITNESS_ACCOUNT -- **Deterministic tie-breaking**: Emergency mode uses hash-based tie-breaking for consensus stability -- **Skip LIB advancement**: Post-validation chain does not advance LIB during emergency mode -- **Committee exclusion**: Committee validator is excluded from hardfork vote tally and median computation during emergency - -```mermaid -flowchart TD -EmergencyMode["Emergency Mode Active"] --> OverrideSchedule["Override Schedule: All Slots -> Emergency validator"] -OverrideSchedule --> DeterministicTie["Deterministic Hash-Based Tie-Breaking"] -DeterministicTie --> SkipLIB["Skip LIB Advancement"] -SkipLIB --> CommitteeExclusion["Exclude Committee from Hardfork Votes"] -CommitteeExclusion --> EmergencyWitness["Emergency validator Produces All Blocks"] -EmergencyWitness --> ExitCondition["Check Exit Condition: LIB > Start Block"] -ExitCondition --> |True| DeactivateEmergency["Deactivate Emergency Mode"] -ExitCondition --> |False| ContinueEmergency["Continue Emergency Mode"] -DeactivateEmergency --> NotifyForkDB["Notify Fork Database"] -NotifyForkDB --> NormalOperation["Resume Normal Operation"] -``` - -**Diagram sources** -- [database.cpp:4420-4438](file://libraries/chain/database.cpp#L4420-L4438) -- [database.cpp:4444-4450](file://libraries/chain/database.cpp#L4444-L4450) - -### Emergency validator Account Management -The emergency validator account serves as the single producer during emergency consensus mode: -- **Account name**: CHAIN_EMERGENCY_WITNESS_ACCOUNT (defaults to "committee") -- **Signing key**: CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY (deterministic emergency key) -- **Properties**: Copies median chain properties to avoid skewing median computations -- **Hardfork votes**: Votes for currently applied hardfork version to maintain status quo -- **Penalty management**: Emergency validator participates in penalty reset process - -Emergency validator lifecycle: -- **Creation**: Created automatically during emergency mode activation if not exists -- **Updates**: Key and properties updated during emergency mode reactivation -- **Participation**: Produces blocks for CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS consecutive blocks -- **Cleanup**: Penalties removed and normal operations resume after exit condition met - -**Section sources** -- [config.hpp:114-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L114-L124) -- [database.cpp:4360-4398](file://libraries/chain/database.cpp#L4360-L4398) -- [database.cpp:4400-4419](file://libraries/chain/database.cpp#L4400-L4419) - -### Emergency Mode Tie-Breaking Mechanisms -During emergency consensus mode, deterministic hash-based tie-breaking ensures consensus stability when multiple emergency producers compete at the same height: -- **Hash comparison**: When two blocks compete at the same height, compare block_id hashes -- **Lower hash preferred**: The block with the lower block_id hash becomes the head -- **Consensus stability**: Eliminates fork divergence caused by P2P arrival order differences -- **Deterministic resolution**: All nodes converge on the same block regardless of network topology - -Tie-breaking algorithm: -- **Same height competition**: Only applies when emergency mode is active and blocks compete at identical heights -- **Hash-based decision**: Compare item->id < _head->id for tie-breaking decision -- **Immediate head update**: If tie-breaker selects new head, update _head immediately -- **No fork switching**: Tie-breaking occurs within emergency mode without full fork reorganization - -**Section sources** -- [fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) -- [validator.cpp:521-526](file://plugins/validator/validator.cpp#L521-L526) - -### Emergency Exit Conditions and Recovery -Emergency consensus mode deactivates automatically when: -- **LIB advancement**: Last Irreversible Block number exceeds emergency_consensus_start_block -- **Normal validator rejoin**: Regular validators resume production after emergency period -- **Manual intervention**: System administrator can manually deactivate emergency mode - -Emergency exit process: -- **LIB check**: Monitor LIB advancement past emergency start block -- **Mode deactivation**: Set emergency_consensus_active = false -- **Fork database notification**: Clear emergency mode flag in fork database -- **Normal operation resume**: Resume normal validator scheduling and LIB advancement -- **Penalty restoration**: Restore normal penalty calculations for emergency period - -**Section sources** -- [database.cpp:2125-2142](file://libraries/chain/database.cpp#L2125-L2142) -- [database.cpp:4428-4430](file://libraries/chain/database.cpp#L4428-L4430) - -## Two-Level Fork Collision Resolution - -### Overview -The two-level fork collision resolution system provides robust handling of competing blocks at the same height, combining immediate vote-weighted comparison with timeout-based fallback mechanisms. This system ensures network progress while maintaining consensus integrity. - -### Level 1: Vote-Weighted Comparison (HF12) -When HF12 is active and competing blocks exist at the same height, the system performs immediate vote-weighted comparison: - -1. **Comparison**: `compare_fork_branches()` calculates total vote weight per validator for both forks -2. **Bonus Application**: Longer chain receives +10% bonus to vote weight -3. **Decision Making**: - - If one fork has significantly more weight: produce on stronger fork - - If comparison is inconclusive: defer to timeout mechanism - - If tied: produce on current fork, continue monitoring - -### Level 2: Stuck-Head Timeout -If the network remains stuck with competing blocks for extended periods: - -1. **Timeout Detection**: After 21 consecutive deferrals (one full validator round) -2. **Action**: Remove all stale competing blocks from the dead fork -3. **Resolution**: Produce on the canonical chain with confirmed majority support -4. **Prevention**: Ensures network doesn't stall indefinitely due to fork collisions - -```mermaid -flowchart TD -Start(["Fork Collision Detected"]) --> CheckHF12{"HF12 Active?"} -CheckHF12 --> |Yes| VoteWeight["Level 1: Vote-Weighted Comparison"] -CheckHF12 --> |No| TimeoutOnly["Level 2: Timeout Only"] -VoteWeight --> Compare["compare_fork_branches()"] -Compare --> Stronger{"Stronger Fork?"} -Stronger --> |Yes| ProduceStrong["Produce on Stronger Fork"] -Stronger --> |No| Tie{"Tied or Unknown?"} -Tie --> |Yes| Defer["Defer to Timeout"] -Tie --> |No| ProduceWeak["Produce on Current Fork"] -Defer --> TimeoutCheck["Check Timeout Counter"] -TimeoutOnly --> TimeoutCheck -TimeoutCheck --> TimeoutExceeded{"Timeout Exceeded (21 deferrals)?"} -TimeoutExceeded --> |Yes| RemoveStale["Remove Stale Competing Blocks"] -TimeoutExceeded --> |No| DeferMore["Continue Deferring"] -RemoveStale --> ProduceCanonical["Produce on Canonical Chain"] -ProduceStrong --> End(["Resolved"]) -ProduceWeak --> End -ProduceCanonical --> End -DeferMore --> End -``` - -**Diagram sources** -- [validator.cpp:565-656](file://plugins/validator/validator.cpp#L565-L656) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) - -**Section sources** -- [validator.cpp:565-656](file://plugins/validator/validator.cpp#L565-L656) -- [validator.cpp:121](file://plugins/validator/validator.cpp#L121) - -## Vote-Weighted Fork Comparison Algorithm - -### compare_fork_branches() Function -The `compare_fork_branches()` function implements HF12's vote-weighted fork comparison system: - -#### Algorithm Steps: -1. **Validation**: Ensure both fork tips exist in fork database -2. **Branch Extraction**: Use `fetch_branch_from()` to get branches to common ancestor -3. **Weight Calculation**: Compute total vote weight per validator for each branch -4. **Bonus Application**: Apply +10% bonus to longer chain -5. **Comparison**: Determine stronger fork or tie - -#### Weight Calculation Details: -- **Per-validator Weight**: Sum of vote weights for each unique validator -- **Emergency validator Exclusion**: Emergency validator votes are excluded from calculation -- **Unique validator Counting**: Each validator contributes only once per branch - -#### Bonus System: -- **Longer Chain Advantage**: +10% bonus applied to the fork with more blocks -- **Consensus Signal**: Reflects stronger network support and production continuity -- **Fairness**: Prevents chains from stalling due to minor vote differences - -```mermaid -flowchart TD -Start(["compare_fork_branches()"]) --> Validate["Validate Fork Tips in Fork DB"] -Validate --> Extract["Extract Branches to Common Ancestor"] -Extract --> CalcA["Calculate Weight for Branch A"] -CalcA --> CalcB["Calculate Weight for Branch B"] -CalcB --> BonusA{"Branch A Longer?"} -CalcB --> BonusB{"Branch B Longer?"} -BonusA --> |Yes| ApplyBonusA["+10% Bonus to Branch A"] -BonusB --> |Yes| ApplyBonusB["+10% Bonus to Branch B"] -ApplyBonusA --> Compare["Compare Weights"] -ApplyBonusB --> Compare -Compare --> Result{"Result"} -Result --> |A Stronger| Return1["Return 1"] -Result --> |B Stronger| ReturnNeg1["Return -1"] -Result --> |Tied| Return0["Return 0"] -``` - -**Diagram sources** -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) - -**Section sources** -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) - -## Automatic Stale Fork Pruning System - -### Purpose -The automatic stale fork pruning system prevents memory bloat and improves performance by removing stale competing blocks from dead forks. This system complements the two-level fork collision resolution by providing proactive cleanup. - -### Implementation -The pruning system consists of two main components: - -#### 1. `remove_blocks_by_number()` Function -- **Targeted Removal**: Removes all blocks at a specific height from the fork database -- **Dead Fork Cleanup**: Clears stale competing blocks that will never become canonical -- **Memory Optimization**: Prevents accumulation of unused fork data - -#### 2. Enhanced Pruning with `set_max_size()` -- **Depth Control**: Limits fork database to configurable maximum depth -- **Automatic Cleanup**: Removes oldest blocks when size limit is exceeded -- **Dual Index Cleaning**: Cleans both linked and unlinked indices for optimal memory usage - -### Pruning Trigger Conditions -- **HF12 Fork Collision**: When stuck-head timeout exceeds threshold, stale blocks are removed -- **Size Limit Exceeded**: When fork database exceeds configured maximum size -- **Network Recovery**: After fork resolution, stale competing blocks are cleaned up -- **Gap-Based Protection**: Prevents memory bloat from dead-fork chains with 100-block threshold -- **Post-Application Cleanup**: Automatic pruning of stale competing blocks at each height - -```mermaid -flowchart TD -Start(["Fork Collision Detected"]) --> CheckTimeout{"Timeout Exceeded?"} -CheckTimeout --> |Yes| RemoveByHeight["remove_blocks_by_number(height)"] -CheckTimeout --> |No| CheckSize{"Max Size Exceeded?"} -RemoveByHeight --> CleanUp["Clean Up Memory"] -CheckSize --> |Yes| PruneOld["Prune Oldest Blocks"] -CheckSize --> |No| Monitor["Monitor Fork Health"] -PruneOld --> CleanUp -Monitor --> End(["Continue Operation"]) -CleanUp --> End -``` - -**Diagram sources** -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) -- [fork_database.cpp:114-146](file://libraries/chain/fork_database.cpp#L114-L146) - -**Section sources** -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) -- [fork_database.cpp:114-146](file://libraries/chain/fork_database.cpp#L114-L146) - -## Enhanced Fork Database Diagnostic Accessors - -### Overview -The fork database now includes comprehensive diagnostic accessors that provide real-time monitoring of storage statistics and health metrics. These accessors enable operators to track fork database performance, identify potential issues, and optimize system resources. - -### Diagnostic Accessor Functions - -#### Storage Size Metrics -- **linked_size()**: Returns the number of blocks currently stored in the linked index -- **unlinked_size()**: Returns the number of blocks currently stored in the unlinked index - -#### Block Number Range Metrics -- **linked_min_block_num()**: Returns the minimum block number in the linked index (0 if empty) -- **linked_max_block_num()**: Returns the maximum block number in the linked index (0 if empty) -- **unlinked_min_block_num()**: Returns the minimum block number in the unlinked index (0 if empty) -- **unlinked_max_block_num()**: Returns the maximum block number in the unlinked index (0 if empty) - -### Implementation Details -The diagnostic accessors provide O(1) access to fork database statistics by leveraging the underlying multi-index container structure: - -```mermaid -classDiagram -class fork_database { -+linked_size() size_t -+unlinked_size() size_t -+linked_min_block_num() uint32_t -+linked_max_block_num() uint32_t -+unlinked_min_block_num() uint32_t -+unlinked_max_block_num() uint32_t --private _index : fork_multi_index_type --private _unlinked_index : fork_multi_index_type -} -``` - -**Diagram sources** -- [fork_database.hpp:128-150](file://libraries/chain/include/graphene/chain/fork_database.hpp#L128-L150) - -### Usage in P2P Monitoring -The diagnostic accessors are integrated into the P2P monitoring system for comprehensive storage analytics: - -```mermaid -sequenceDiagram -participant P2P as "P2P Plugin" -participant DB as "Database" -participant FDB as "Fork Database" -P2P->>DB : "get_fork_db()" -DB-->>P2P : "fork_database reference" -P2P->>FDB : "linked_size()" -FDB-->>P2P : "size_t count" -P2P->>FDB : "unlinked_size()" -FDB-->>P2P : "size_t count" -P2P->>FDB : "linked_min_block_num()" -FDB-->>P2P : "uint32_t min" -P2P->>FDB : "linked_max_block_num()" -FDB-->>P2P : "uint32_t max" -P2P->>FDB : "unlinked_min_block_num()" -FDB-->>P2P : "uint32_t min" -P2P->>FDB : "unlinked_max_block_num()" -FDB-->>P2P : "uint32_t max" -P2P->>P2P : "analyze storage metrics" -``` - -**Diagram sources** -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -### Storage Statistics Collection -The P2P monitoring system collects comprehensive storage statistics for real-time analysis: - -- **Fork Database Head**: Current head block number -- **Last Irreversible Block (LIB)**: Most recent irreversible block number -- **Earliest Available Block**: Minimum block number available for P2P serving -- **DLT Block Log Range**: [start..end] range of blocks in DLT log -- **Block Log End**: End position of regular block log -- **Linked Index Statistics**: Size and [min..max] block number range -- **Unlinked Index Statistics**: Size and [min..max] block number range -- **DLT Mode Status**: Current DLT mode activation state -- **DLT Resize Count**: Number of DLT log resizes - -**Section sources** -- [fork_database.hpp:128-150](file://libraries/chain/include/graphene/chain/fork_database.hpp#L128-L150) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -## Storage Health Monitoring and Analytics - -### Real-Time Monitoring System -The P2P plugin implements a comprehensive monitoring system that collects and analyzes fork database storage statistics in real-time: - -#### Monitoring Components -- **Block Storage Metrics**: Linked and unlinked index sizes, block number ranges -- **DLT Coverage Analysis**: Gap detection between DLT block log and fork database -- **Performance Indicators**: DLT mode status, resize counts, and synchronization health -- **Alert Generation**: Automatic detection of potential storage issues - -#### Gap Detection and Analysis -The system monitors gaps between DLT block log and fork database to ensure complete P2P serving capability: - -```mermaid -flowchart TD -Start(["Storage Analysis"]) --> CheckDLT{"DLT Mode Enabled?"} -CheckDLT --> |Yes| CheckRanges["Check DLT and Fork Ranges"] -CheckDLT --> |No| BasicAnalysis["Basic Storage Analysis"] -CheckRanges --> GapDetection["Detect Coverage Gaps"] -GapDetection --> GapExists{"Gap Detected?"} -GapExists --> |Yes| Alert["Generate Gap Alert"] -GapExists --> |No| Normal["Normal Operation"] -Alert --> LogGap["Log DLT Coverage Gap"] -LogGap --> End(["Complete"]) -Normal --> End -BasicAnalysis --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:762-770](file://plugins/p2p/p2p_plugin.cpp#L762-L770) - -#### Storage Health Indicators -The monitoring system tracks key indicators of fork database health: - -- **Linked Index Utilization**: Percentage of blocks successfully linked to main chain -- **Unlinked Index Growth**: Rate of unlinked block accumulation indicating network issues -- **Block Number Distribution**: Evenness of block number distribution across indices -- **DLT Coverage Completeness**: Percentage of blocks available for P2P serving -- **Storage Capacity Planning**: Predictive analysis of storage requirements - -### Performance Optimization Opportunities -The diagnostic accessors enable several optimization opportunities: - -#### Memory Management -- **Dynamic Capacity Adjustment**: Use linked/unlinked size ratios to adjust fork database capacity -- **Early Warning Systems**: Monitor storage metrics to prevent memory exhaustion -- **Resource Allocation**: Optimize shared memory allocation based on storage patterns - -#### Network Synchronization -- **Out-of-Order Block Handling**: Analyze unlinked index growth to optimize synchronization -- **Peer Selection**: Use storage metrics to select optimal synchronization peers -- **Bandwidth Optimization**: Adjust P2P bandwidth based on storage utilization patterns - -#### Operational Insights -- **Capacity Planning**: Use historical storage metrics for infrastructure planning -- **Performance Tuning**: Adjust fork database parameters based on observed patterns -- **Issue Detection**: Identify potential problems before they impact system performance - -**Section sources** -- [p2p_plugin.cpp:739-771](file://plugins/p2p/p2p_plugin.cpp#L739-L771) - -## Dependency Analysis -**Updated** The fork resolution system now includes DLT mode dependencies, automatic seeding capabilities, comprehensive emergency consensus integration, sophisticated early rejection logic, HF12 fork comparison capabilities, advanced fork collision resolution systems, gap-based early rejection protection, automatic chain linking features, separate handling paths, detailed debug logging, and **NEW**: comprehensive diagnostic monitoring system. - -The fork resolution system depends on: -- fork_database for in-memory fork chain management with enhanced caching, duplicate detection, emergency mode tie-breaking, comprehensive error handling, automatic stale fork pruning, HF12 fork comparison, gap-based early rejection, and **NEW**: diagnostic accessors for storage statistics -- database for integrating fork resolution into block application, DLT mode management, automatic seeding, emergency consensus mode activation/deactivation with sophisticated early rejection logic, block validation, and HF12 vote-weighted fork comparison -- block_log for persistence of irreversible blocks in normal mode -- **New**: dlt_block_log for DLT mode persistence and P2P synchronization support -- **New**: Validator Plugin for emergency mode awareness, fork collision handling, two-level fork collision resolution, stuck-head timeout mechanism, HF12 fork comparison integration, and automatic chain linking -- **New**: emergency consensus configuration for timeout thresholds and emergency validator parameters -- **New**: compare_fork_branches function for HF12 vote-weighted fork comparison -- **New**: Enhanced exception handling for different types of block validation failures -- **New**: Automatic stale fork pruning system with remove_blocks_by_number() function -- **New**: Gap-based early rejection logic with 100-block threshold for memory protection -- **New**: Automatic chain linking system via _push_next() for efficient out-of-order block processing -- **New**: Separate handling paths for linear extensions vs actual fork switches during chain reorganization -- **New**: Detailed debug logging prefixes (FORK-SWITCH-POP, FORK-RECOVER-POP) for better traceability -- **New**: Comprehensive diagnostic monitoring system with real-time storage analytics - -```mermaid -graph LR -FDB["fork_database.cpp"] --> DBCPP["database.cpp"] -DBCPP --> BLH["block_log.hpp"] -DBCPP --> DLTH["dlt_block_log.hpp"] -DBCPP --> WIT["validator.cpp"] -DBH["database.hpp"] --> DBCPP -DLTH --> DBCPP -WIT --> DBCPP -CONFIG["config.hpp"] --> DBCPP -HF12["12.hf"] --> DBCPP -CMP["compare_fork_branches()"] --> DBCPP -PRUNE["remove_blocks_by_number()"] --> FDB -GAP["Gap-Based Rejection"] --> DBCPP -LINK["Automatic Chain Linking"] --> FDB -SEPARATE["Separate Handling Paths"] --> DBCPP -DEBUG["Debug Logging Prefixes"] --> DBCPP -DIAG["Diagnostic Accessors"] --> FDB -MON["P2P Monitoring"] --> DIAG -``` - -**Diagram sources** -- [fork_database.cpp:1-278](file://libraries/chain/fork_database.cpp#L1-L278) -- [database.cpp:1-6669](file://libraries/chain/database.cpp#L1-L6669) -- [dlt_block_log.cpp:1-454](file://libraries/chain/dlt_block_log.cpp#L1-L454) -- [validator.cpp:1-697](file://plugins/validator/validator.cpp#L1-L697) -- [config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -**Section sources** -- [fork_database.cpp:1-278](file://libraries/chain/fork_database.cpp#L1-L278) -- [database.cpp:1-6669](file://libraries/chain/database.cpp#L1-L6669) -- [dlt_block_log.cpp:1-454](file://libraries/chain/dlt_block_log.cpp#L1-L454) -- [validator.cpp:1-697](file://plugins/validator/validator.cpp#L1-L697) -- [config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -## Performance Considerations -**Updated** Enhanced with improved caching, duplicate detection, DLT mode integration, automatic seeding mechanisms, emergency consensus recovery optimizations, sophisticated early rejection logic, HF12 fork comparison capabilities, advanced fork collision resolution systems, gap-based early rejection protection, automatic chain linking features, separate handling paths, detailed debug logging, and **NEW**: comprehensive diagnostic monitoring system. - -- Maximum fork depth: The fork database limits the maximum number of blocks that may be skipped in an out-of-order push, preventing excessive memory usage with enhanced cleanup -- Multi-index containers: Efficient lookups by block ID and previous ID minimize traversal costs with improved indexing -- **Enhanced caching**: Improved unlinked block caching with iterative processing via `_push_next` reduces memory pressure and improves P2P synchronization -- **Duplicate prevention**: Comprehensive duplicate detection prevents redundant processing and reduces CPU overhead -- **Early rejection optimization**: Sophisticated early rejection logic prevents unnecessary fork database operations and improves overall system performance -- **Enhanced error handling**: Comprehensive exception handling with specific error types prevents system degradation and improves reliability -- **Pruning**: set_max_size prunes old blocks from both linked and unlinked indices to keep memory bounded with enhanced cleanup -- **Reorganization cost**: Reorganizing across deep forks requires popping and re-applying blocks; keeping forks shallow improves responsiveness with enhanced error recovery -- **Persistence overhead**: Writing to the block log is required for irreversible blocks; batching and flushing strategies can mitigate latency -- **DLT mode optimization**: Automatic seeding eliminates synchronization delays for snapshot-based nodes, improving overall network health -- **Gap handling**: DLT block log gap logging suppression prevents performance impact from excessive warning messages -- **Emergency mode efficiency**: Emergency mode uses optimized tie-breaking with minimal computational overhead while ensuring consensus stability -- **Hybrid scheduling**: Emergency validator scheduling minimizes complexity compared to full validator rotation during emergency periods -- **Penalty management**: Emergency penalty reset avoids complex penalty calculations during emergency mode, reducing computational load -- **Sophisticated validation**: Early rejection logic prevents unnecessary processing and reduces system load during network partitions -- **HF12 fork comparison**: Vote-weighted comparison adds computational overhead but provides more robust consensus decisions -- **Two-level collision resolution**: Additional logic for fork collision handling adds minimal overhead while providing significant reliability improvements -- **Automatic pruning**: Stale fork pruning prevents memory bloat and maintains optimal performance under fork collision conditions -- **Stuck-head timeout**: 21-block timeout provides reasonable balance between network stability and production efficiency -- **Gap-based protection**: 100-block threshold prevents memory bloat from dead-fork chains while maintaining network efficiency -- **Automatic chain linking**: _push_next() mechanism prevents memory bloat and maintains optimal performance under out-of-order block conditions -- **Separate handling paths**: Linear extension vs fork switch processing improves efficiency by avoiding unnecessary operations -- **Debug logging overhead**: Detailed debug logging prefixes add minimal overhead while providing significant debugging benefits -- **Diagnostic monitoring overhead**: Real-time storage analytics add minimal overhead while providing significant operational insights -- **Storage optimization**: Diagnostic metrics enable proactive optimization of fork database performance and resource utilization - -## Troubleshooting Guide -**Updated** Enhanced with improved error handling, duplicate detection, DLT mode support, automatic seeding capabilities, comprehensive emergency consensus troubleshooting, sophisticated early rejection logic, HF12 fork comparison troubleshooting, advanced fork collision resolution guidance, gap-based early rejection troubleshooting, automatic chain linking guidance, separate handling paths, detailed debug logging, and **NEW**: comprehensive diagnostic monitoring troubleshooting. - -Common issues and remedies: -- **Unlinkable block errors**: Occur when a block does not link to a known chain; the fork DB logs and caches the block for later insertion when its parent arrives with enhanced logging and processing via _push_next() -- **Invalid fork handling**: When reorganization fails, the database removes the problematic fork, restores the good fork, and rethrows the exception with comprehensive error recovery -- **Memory pressure**: Adjust shared memory sizing and monitor free memory; the database resizes shared memory when necessary with enhanced monitoring -- **Recovery mismatches**: During open/reindex, the database asserts chain state consistency with the block log and resets the fork DB accordingly with improved validation -- **Duplicate block processing**: The fork DB now prevents duplicate block processing, reducing CPU overhead and improving synchronization reliability -- **Early rejection failures**: Enhanced early rejection logic helps prevent unnecessary fork database operations and improves overall system performance -- **DLT mode issues**: When DLT mode is enabled, verify that DLT block log is properly configured and that automatic seeding is working correctly -- **P2P synchronization delays**: Check that automatic seeding is functioning and that fork database is properly seeded from DLT block log -- **Gap logging**: Monitor DLT block log gaps and adjust configuration if gaps persist beyond acceptable limits -- **Emergency mode activation failures**: Verify CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC configuration and check LIB timestamp calculations -- **Emergency mode deactivation issues**: Monitor LIB advancement and ensure emergency_consensus_start_block tracking is accurate -- **Emergency validator problems**: Verify emergency validator account creation and key management during emergency mode activation -- **Hybrid scheduling conflicts**: Check validator schedule overrides and ensure emergency validator has proper signing key configuration -- **Tie-breaking anomalies**: Monitor emergency mode tie-breaking behavior and verify hash-based resolution consistency across network nodes -- **Infinite sync loops**: Check early rejection logic and ensure proper block validation to prevent continuous sync restarts -- **Block validation failures**: Monitor different types of block validation errors and ensure appropriate exception handling -- **HF12 fork comparison failures**: Verify compare_fork_branches() function returns valid results and check validator vote weight calculations -- **Two-level collision resolution issues**: Monitor fork collision timeout counters and ensure stuck-head timeout mechanism is functioning correctly -- **Vote-weighted comparison anomalies**: Check validator vote weight calculations and ensure emergency validator exclusion is working properly -- **Automatic pruning failures**: Verify remove_blocks_by_number() function is cleaning stale competing blocks and check set_max_size() pruning effectiveness -- **Timeout configuration problems**: Adjust fork-collision-timeout-blocks parameter if network experiences frequent fork collisions or insufficient timeout -- **Gap-based rejection issues**: Verify 100-block threshold is working correctly and check that legitimate out-of-order blocks are not being rejected -- **Automatic chain linking failures**: Check _push_next() mechanism and ensure cached unlinked blocks are being processed correctly when parents arrive -- **Linear extension vs fork switch confusion**: Monitor FORK-SWITCH-POP and FORK-RECOVER-POP debug logs to distinguish between different types of fork resolution operations -- **Debug logging issues**: Verify that debug logging prefixes are appearing correctly and check log level configuration for proper visibility -- **Diagnostic monitoring failures**: Verify diagnostic accessors are returning accurate storage statistics and check P2P monitoring system integration -- **Storage health issues**: Monitor fork database storage metrics to identify potential memory exhaustion or performance degradation -- **DLT coverage gaps**: Use diagnostic metrics to identify and resolve gaps between DLT block log and fork database ranges -- **Performance optimization**: Use diagnostic data to optimize fork database capacity and improve synchronization efficiency - -**Section sources** -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [database.cpp:1075-1087](file://libraries/chain/database.cpp#L1075-L1087) -- [database.cpp:259-294](file://libraries/chain/database.cpp#L259-L294) -- [database.cpp:4581-4594](file://libraries/chain/database.cpp#L4581-L4594) -- [database.cpp:1300-1399](file://libraries/chain/database.cpp#L1300-L1399) -- [database.cpp:2125-2142](file://libraries/chain/database.cpp#L2125-L2142) -- [validator.cpp:597-612](file://plugins/validator/validator.cpp#L597-L612) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -## Conclusion -**Updated** The fork resolution and consensus system combines an efficient in-memory fork database with robust chain reorganization, irreversible block persistence, comprehensive DLT mode support, and advanced emergency consensus recovery mechanisms. The system has been significantly enhanced with sophisticated gap-based early rejection logic, comprehensive duplicate detection, DLT mode integration, automatic seeding capabilities, comprehensive emergency consensus implementation, HF12 vote-weighted fork comparison, two-level fork collision resolution, automatic stale fork pruning, and automatic chain linking. The enhanced fork database now supports snapshot-based nodes with immediate P2P synchronization, while the DLT block log provides efficient serving of recent irreversible blocks to peers. The emergency consensus recovery system ensures blockchain continuity through timeout-based activation, hybrid validator scheduling, and deterministic tie-breaking mechanisms. The HF12 fork comparison system provides more robust consensus decisions by weighting chains based on validator vote support with +10% bonus for longer chains. The two-level fork collision resolution system combines immediate vote-weighted comparison with stuck-head timeout to ensure network progress while maintaining consensus integrity. The automatic stale fork pruning system prevents memory bloat and maintains optimal performance under fork collision conditions. The gap-based early rejection logic with 100-block threshold prevents memory bloat from dead-fork chains while maintaining network efficiency. The automatic chain linking system via _push_next() ensures efficient processing of out-of-order blocks. The system integrates tightly with validator scheduling to ensure timely and valid block production, with emergency mode awareness enabling seamless transition between normal and emergency operations. The enhanced APIs enable reliable fork detection, chain validation, and recovery with DLT mode, emergency consensus, HF12 fork comparison, gap-based protection, and automatic chain linking awareness. Performance controls keep resource usage manageable while improving synchronization reliability, network health, and consensus stability during emergency conditions. The sophisticated early rejection logic and block validation mechanisms prevent infinite synchronization loops and system degradation, ensuring robust operation under various network conditions. The separate handling paths for linear extensions vs actual fork switches improve efficiency by avoiding unnecessary operations. The detailed debug logging with FORK-SWITCH-POP and FORK-RECOVER-POP prefixes provides excellent traceability for troubleshooting and monitoring fork resolution operations. **NEW**: The comprehensive diagnostic monitoring system provides real-time insights into fork database storage statistics, enabling proactive optimization and issue detection. The diagnostic accessors offer O(1) access to critical storage metrics including linked/unlinked index sizes and block number ranges, facilitating informed capacity planning and performance tuning. The P2P monitoring integration delivers comprehensive analytics for storage health, DLT coverage gaps, and synchronization performance, ensuring optimal system operation under varying network conditions. - -## Appendices - -### Appendix A: Enhanced Key Data Structures and Complexity -**Updated** Enhanced with improved duplicate detection, caching mechanisms, DLT mode support, automatic seeding capabilities, emergency consensus integration, sophisticated early rejection logic, HF12 fork comparison capabilities, advanced fork collision resolution systems, gap-based early rejection protection, automatic chain linking features, separate handling paths, detailed debug logging, and **NEW**: comprehensive diagnostic monitoring system. - -- fork_item: Stores block data, previous link, and invalid flag -- fork_database: - - push_block: O(log N) average for insertions; unlinked insertion triggers iterative _push_next with duplicate prevention - - fetch_branch_from: O(depth) to traverse both branches to common ancestor - - walk_main_branch_to_num: O(depth) to reach a specific block number - - set_max_size: O(N log N) worst-case pruning across indices with enhanced cleanup - - **New**: Duplicate detection: O(1) lookup for existing block IDs before insertion - - **New**: Enhanced caching: Iterative processing of up to MAX_BLOCK_REORDERING unlinked blocks via _push_next - - **New**: Emergency mode tie-breaking: O(1) hash comparison for tie resolution during emergency periods - - **New**: Enhanced error handling: Comprehensive exception management for different block validation failures - - **New**: Automatic stale fork pruning: O(k) removal of all blocks at specific height (k = number of competing blocks) - - **New**: Enhanced pruning system: O(N) cleanup of both _index and _unlinked_index for optimal memory management - - **New**: Gap-based early rejection: O(1) gap calculation and threshold checking - - **New**: Separate handling paths: O(1) branching between linear extension and fork switch operations - - **New**: Debug logging: Minimal overhead with detailed prefix-based logging for traceability - - **New**: Diagnostic accessors: O(1) access to storage statistics with comprehensive metrics -- **New**: database compare_fork_branches(): - - O(B) where B = number of blocks in longer branch - - Calculates vote weights for each unique validator - - Applies +10% bonus to longer chain - - Returns comparison result (-1, 0, or 1) -- **New**: database early rejection logic: - - Already applied block detection: O(1) lookup for existing block IDs - - Different fork detection: O(1) parent validation in fork database - - Far ahead block rejection: O(1) parent unknown detection with gap calculation - - Gap-based protection: O(1) 100-block threshold checking - - Sophisticated validation prevents unnecessary processing and system degradation -- **New**: dlt_block_log: - - append: O(1) for sequential writes with rolling window management - - read_block_by_num: O(1) for random access within window - - truncate_before: O(n) for window compaction with safe file swapping -- **New**: Emergency consensus configuration: - - CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC: 3600 seconds (1 hour) timeout threshold - - CHAIN_EMERGENCY_WITNESS_ACCOUNT: Emergency validator account name ("committee") - - CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY: Deterministic emergency signing key - - CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS: 21 blocks to trigger emergency mode exit - - Hardfork version: CHAIN_HARDFORK_12 (version 3.1.0) - - Activation time: CHAIN_HARDFORK_12_TIME (Unix timestamp for HF12 activation) -- **New**: DLT mode integration: - - Automatic seeding: O(1) to seed fork database from DLT block log - - Gap handling: O(1) logging suppression with periodic re-enabling -- **New**: Emergency mode integration: - - Activation detection: O(1) timestamp comparison for timeout checks - - Hybrid scheduling: O(N) override of all validator slots to emergency validator - - Penalty reset: O(N) iteration through all validators for penalty clearing -- **New**: HF12 fork comparison: - - Vote-weighted comparison: O(B) where B = number of blocks in longer branch - - Unique validator counting: O(W) where W = number of unique validators per branch - - +10% bonus application: O(1) constant time operation -- **New**: Two-level fork collision resolution: - - Level 1 timeout: O(1) constant time comparison - - Level 2 timeout: O(1) constant time counter check - - Stale fork removal: O(k) where k = number of competing blocks at height -- **New**: Exception handling: - - unlinkable_block_exception: Specific handling for blocks that cannot link - - block_too_old_exception: Specific handling for blocks outside fork window - - Enhanced error categorization prevents system degradation -- **New**: Automatic chain linking: - - _push_next() processing: O(k) where k = number of cached blocks linked - - Gap-based protection: Prevents memory bloat from dead-fork chains -- **New**: Separate handling paths: - - Linear extension: O(1) reset without pop operations - - Fork switch: O(F) where F = number of blocks popped and applied -- **New**: Debug logging: - - Minimal overhead with prefix-based categorization - - FORK-SWITCH-POP: O(1) logging for fork switching operations - - FORK-RECOVER-POP: O(1) logging for fork recovery operations -- **New**: Diagnostic monitoring system: - - linked_size(): O(1) access to linked index size - - unlinked_size(): O(1) access to unlinked index size - - Block number range queries: O(1) access to min/max block numbers - - Real-time analytics: O(1) collection of comprehensive storage metrics - - P2P integration: O(1) metrics delivery for monitoring system - -**Section sources** -- [fork_database.hpp:20-168](file://libraries/chain/include/graphene/chain/fork_database.hpp#L20-L168) -- [fork_database.cpp:48-103](file://libraries/chain/fork_database.cpp#L48-L103) -- [database.cpp:1300-1399](file://libraries/chain/database.cpp#L1300-L1399) -- [database.cpp:1254-1298](file://libraries/chain/database.cpp#L1254-L1298) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) -- [dlt_block_log.cpp:336-340](file://libraries/chain/dlt_block_log.cpp#L336-L340) -- [config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [database.cpp:2125-2142](file://libraries/chain/database.cpp#L2125-L2142) -- [validator.cpp:597-612](file://plugins/validator/validator.cpp#L597-L612) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) - -### Appendix B: Emergency Consensus Configuration Parameters -**New Section** Comprehensive configuration parameters for emergency consensus mode activation and operation. - -Emergency consensus parameters: -- **Timeout threshold**: CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC (default: 3600 seconds) -- **Emergency validator account**: CHAIN_EMERGENCY_WITNESS_ACCOUNT (default: "committee") -- **Emergency validator key**: CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY (deterministic emergency key) -- **Exit condition**: CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS (default: 21 blocks) -- **Hardfork version**: CHAIN_HARDFORK_12 (version 3.1.0) -- **Activation time**: CHAIN_HARDFORK_12_TIME (Unix timestamp for HF12 activation) - -Configuration impact: -- **Timeout sensitivity**: Lower values trigger emergency mode more frequently, higher values require longer downtime -- **Exit timing**: Controls how quickly normal operation resumes after emergency period -- **Security implications**: Emergency key provides deterministic consensus but requires secure key management -- **Network stability**: Emergency mode ensures blockchain continuity but may temporarily reduce decentralization - -**Section sources** -- [config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -### Appendix C: Enhanced Exception Handling and Error Categories -**New Section** Comprehensive breakdown of exception handling categories and their specific behaviors. - -Exception categories and handling: -- **unlinkable_block_exception**: Thrown when blocks cannot link to known chain; caught and cached in fork database via _push_next() for automatic chain linking -- **block_too_old_exception**: Thrown when blocks are outside fork database window; handled gracefully to prevent system overload -- **block_validation_exception**: Thrown when blocks fail validation checks; handled through early rejection logic -- **duplicate_block_exception**: Prevented through comprehensive duplicate detection mechanisms -- **infinite_sync_loop_exception**: Prevented through sophisticated early rejection logic that detects and rejects problematic blocks -- **fork_collision_exception**: Handled through two-level fork collision resolution system with timeout-based fallback -- **gap_based_rejection**: Prevented through 100-block threshold logic that protects against memory bloat -- **linear_extension_exception**: Prevented through separate handling path that avoids unnecessary operations -- **fork_switch_exception**: Handled through detailed debug logging with FORK-SWITCH-POP prefix for troubleshooting -- **diagnostic_access_exception**: Prevented through comprehensive error handling in diagnostic accessors -- **storage_monitoring_exception**: Handled through graceful degradation in P2P monitoring system - -Exception handling strategies: -- **Early rejection**: Prevents unnecessary processing of invalid blocks -- **Graceful degradation**: Allows system to continue operating despite individual block failures -- **Comprehensive logging**: Detailed error reporting for debugging and monitoring -- **Specific exception types**: Differentiates between different types of failures for appropriate handling -- **System resilience**: Prevents cascading failures through proper exception management -- **HF12 integration**: Vote-weighted fork comparison provides additional error context for fork resolution decisions -- **Automatic chain linking**: _push_next() mechanism prevents memory bloat while maintaining network efficiency -- **Gap-based protection**: 100-block threshold prevents accumulation of stale blocks -- **Separate handling paths**: Linear extension vs fork switch processing improves efficiency -- **Debug logging**: FORK-SWITCH-POP and FORK-RECOVER-POP prefixes provide excellent traceability -- **Diagnostic monitoring**: Real-time storage analytics provide early warning of potential issues - -**Section sources** -- [fork_database.cpp:38-46](file://libraries/chain/fork_database.cpp#L38-L46) -- [fork_database.cpp:59-75](file://libraries/chain/fork_database.cpp#L59-L75) -- [database.cpp:1300-1399](file://libraries/chain/database.cpp#L1300-L1399) -- [database.cpp:1390-1465](file://libraries/chain/database.cpp#L1390-L1465) -- [validator.cpp:614-646](file://plugins/validator/validator.cpp#L614-L646) - -### Appendix D: Diagnostic Accessors Usage Examples -**New Section** Practical examples of using diagnostic accessors for monitoring and troubleshooting fork database storage statistics. - -#### Basic Storage Metrics Collection -```cpp -// Example: Collect basic fork database storage metrics -const auto& fork_db = db.get_fork_db(); -size_t linked_count = fork_db.linked_size(); -size_t unlinked_count = fork_db.unlinked_size(); -uint32_t linked_min = fork_db.linked_min_block_num(); -uint32_t linked_max = fork_db.linked_max_block_num(); -uint32_t unlinked_min = fork_db.unlinked_min_block_num(); -uint32_t unlinked_max = fork_db.unlinked_max_block_num(); - -// Example: Analyze storage utilization -double linked_ratio = (double)linked_count / (linked_count + unlinked_count); -double linked_coverage = (double)(linked_max - linked_min + 1) / linked_count; -double unlinked_growth_rate = (double)unlinked_count / (linked_count + 1); -``` - -#### Storage Health Analysis -```cpp -// Example: Analyze fork database health -if (unlinked_count > linked_count * 0.1) { - // High proportion of unlinked blocks indicates network issues - wlog("High unlinked block ratio: {}%", unlinked_count * 100.0 / (linked_count + unlinked_count)); -} - -if (unlinked_growth_rate > 0.05) { - // Rapid growth in unlinked blocks suggests synchronization problems - wlog("Rapid unlinked block growth detected"); -} - -if (linked_coverage < 0.8) { - // Low coverage indicates potential gaps in fork database - wlog("Low fork database coverage: {}%", linked_coverage * 100); -} -``` - -#### Performance Optimization Based on Diagnostics -```cpp -// Example: Optimize fork database capacity based on storage patterns -if (linked_count > fork_db.max_size() * 0.8) { - // Increase fork database capacity to prevent pruning - db.set_max_size(fork_db.max_size() * 1.2); - wlog("Increased fork database capacity to {} blocks", fork_db.max_size()); -} - -if (unlinked_count < fork_db.max_size() * 0.05) { - // Decrease capacity to save memory - db.set_max_size(fork_db.max_size() * 0.8); - wlog("Decreased fork database capacity to {} blocks", fork_db.max_size()); -} -``` - -#### Integration with P2P Monitoring -```cpp -// Example: Integrate diagnostic metrics with P2P monitoring -void collect_fork_storage_metrics() { - const auto& fork_db = db.get_fork_db(); - - // Collect metrics - auto metrics = std::make_shared( - fork_db.linked_size(), - fork_db.unlinked_size(), - fork_db.linked_min_block_num(), - fork_db.linked_max_block_num(), - fork_db.unlinked_min_block_num(), - fork_db.unlinked_max_block_num() - ); - - // Store metrics for analysis - _storage_metrics_history.push_back(metrics); - - // Generate alerts based on thresholds - check_storage_alerts(metrics); -} -``` - -**Section sources** -- [fork_database.hpp:128-150](file://libraries/chain/include/graphene/chain/fork_database.hpp#L128-L150) -- [p2p_plugin.cpp:739-760](file://plugins/p2p/p2p_plugin.cpp#L739-L760) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Memory Management System.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Memory Management System.md deleted file mode 100644 index 48e5ec50f8..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Memory Management System.md +++ /dev/null @@ -1,655 +0,0 @@ -# Memory Management System - - -**Referenced Files in This Document** -- [chainbase.hpp](file://thirdparty/chainbase/include/chainbase/chainbase.hpp) -- [chainbase.cpp](file://thirdparty/chainbase/src/chainbase.cpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [shared_ptr.hpp](file://thirdparty/fc/include/fc/shared_ptr.hpp) -- [shared_ptr.cpp](file://thirdparty/fc/src/shared_ptr.cpp) -- [smart_ref_fwd.hpp](file://thirdparty/fc/include/fc/smart_ref_fwd.hpp) -- [smart_ref_impl.hpp](file://thirdparty/fc/include/fc/smart_ref_impl.hpp) -- [unique_ptr.hpp](file://thirdparty/fc/include/fc/unique_ptr.hpp) -- [file_mapping.hpp](file://thirdparty/fc/include/fc/interprocess/file_mapping.hpp) -- [file_mapping.cpp](file://thirdparty/fc/src/interprocess/file_mapping.cpp) -- [mmap_struct.hpp](file://thirdparty/fc/include/fc/interprocess/mmap_struct.hpp) -- [mmap_struct.cpp](file://thirdparty/fc/src/interprocess/mmap_struct.cpp) -- [flat.hpp](file://thirdparty/fc/include/fc/container/flat.hpp) -- [thread_specific.hpp](file://thirdparty/fc/include/fc/thread/thread_specific.hpp) -- [scoped_exit.hpp](file://thirdparty/fc/include/fc/scoped_exit.hpp) -- [aligned.hpp](file://thirdparty/fc/include/fc/aligned.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) - - -## Update Summary -**Changes Made** -- Added comprehensive documentation for the new shared memory resize barrier system -- Documented the sophisticated atomic operation counter system -- Added detailed coverage of begin_resize_barrier() and end_resize_barrier() functions -- Documented operation guards for lockless reads and their role in preventing shared memory corruption -- Enhanced logging capabilities for resize barrier state transitions -- Updated memory monitoring and resizing sections with new barrier-based approach - -## Table of Contents -1. [Introduction](#introduction) -2. [System Architecture](#system-architecture) -3. [Memory Management Components](#memory-management-components) -4. [Shared Memory System](#shared-memory-system) -5. [Reference Counting](#reference-counting) -6. [Smart Pointers and RAII](#smart-pointers-and-raii) -7. [Memory Allocation Strategies](#memory-allocation-strategies) -8. [Locking and Concurrency](#locking-and-concurrency) -9. [Memory Monitoring and Resizing](#memory-monitoring-and-resizing) -10. [Resize Barrier System](#resize-barrier-system) -11. [Operation Guards](#operation-guards) -12. [Enhanced Logging and Diagnostics](#enhanced-logging-and-diagnostics) -13. [Best Practices](#best-practices) -14. [Troubleshooting Guide](#troubleshooting-guide) -15. [Conclusion](#conclusion) - -## Introduction - -The VIZ CPP Node memory management system is built around a sophisticated shared memory architecture that enables high-performance blockchain operations. The system combines traditional C++ memory management with advanced inter-process shared memory techniques, providing both safety and performance for blockchain state persistence and concurrent access. - -The memory management system centers on ChainBase, a specialized database framework that uses Boost.Interprocess for shared memory management, combined with FC library utilities for smart pointers, RAII patterns, and memory-safe operations. This architecture supports the demanding requirements of blockchain applications, including persistent state, concurrent access, and automatic memory management. - -**Updated** The system now includes a sophisticated resize barrier mechanism that provides atomic operations during shared memory resizing, preventing corruption and ensuring data consistency during memory expansion operations. - -## System Architecture - -The memory management system follows a layered architecture that separates concerns between low-level memory management, database operations, and application-level abstractions. - -```mermaid -graph TB -subgraph "Application Layer" -APP[Blockchain Application] -PLUGINS[Plugin System] -API[API Layer] -end -subgraph "Database Layer" -DATABASE[Database Engine] -INDEXES[Index Management] -SESSIONS[Transaction Sessions] -RESIZE_BARRIER[Resize Barrier System] -OPERATION_GUARDS[Operation Guards] -end -subgraph "Memory Management Layer" -SHARED_MEM[Shared Memory] -ALLOCATORS[Custom Allocators] -LOCKS[Lock Management] -ATOMIC_COUNTER[Atomic Operation Counter] -END_RESIZE_BARRIER[End Resize Barrier] -end -subgraph "Low-Level Memory" -INTERPROCESS[Boost Interprocess] -SMART_PTR[Smart Pointers] -FILE_MAPPING[File Mapping] -end -APP --> DATABASE -PLUGINS --> DATABASE -API --> DATABASE -DATABASE --> INDEXES -DATABASE --> SESSIONS -DATABASE --> RESIZE_BARRIER -RESIZE_BARRIER --> OPERATION_GUARDS -RESIZE_BARRIER --> ATOMIC_COUNTER -RESIZE_BARRIER --> END_RESIZE_BARRIER -INDEXES --> SHARED_MEM -SESSIONS --> SHARED_MEM -SHARED_MEM --> ALLOCATORS -ALLOCATORS --> LOCKS -LOCKS --> INTERPROCESS -INTERPROCESS --> SMART_PTR -INTERPROCESS --> FILE_MAPPING -``` - -**Diagram sources** -- [chainbase.hpp:1319-1328](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1319-L1328) -- [database.cpp:613-653](file://libraries/chain/database.cpp#L613-L653) -- [validator.cpp:503-507](file://plugins/validator/validator.cpp#L503-L507) - -## Memory Management Components - -### Core Memory Management Classes - -The system provides several fundamental memory management components that work together to provide robust memory handling: - -```mermaid -classDiagram -class Retainable { --volatile int32_t _ref_count -+retain() void -+release() void -+retain_count() int32_t -+~retainable() virtual -} -class SharedPtr { --T* _ptr -+shared_ptr() constructor -+shared_ptr(shared_ptr) constructor -+shared_ptr(T*, bool) constructor -+shared_ptr(shared_ptr&&) constructor -+~shared_ptr() destructor -+operator*() T& -+operator->() T* -+get() T* -+reset() void -+swap() void -} -class SmartRef { --T* impl -+smart_ref() constructor -+smart_ref(smart_ref&) constructor -+smart_ref(smart_ref&&) constructor -+operator*() T& -+operator->() T* -+~smart_ref() destructor -} -class UniquePtr { --T* _p -+unique_ptr() constructor -+unique_ptr(unique_ptr&&) constructor -+~unique_ptr() destructor -+operator*() T& -+operator->() T* -+reset() void -+release() T* -} -class OperationGuard { --database& _db --bool _active -+operation_guard(database&) -+~operation_guard() -+release() void -+operation_guard(operation_guard&&) -} -Retainable <|-- SharedPtr : "reference counting" -SharedPtr --> Retainable : "uses" -SmartRef --> T : "owns" -UniquePtr --> T : "owns" -OperationGuard --> database : "manages" -``` - -**Diagram sources** -- [shared_ptr.hpp:13-64](file://thirdparty/fc/include/fc/shared_ptr.hpp#L13-L64) -- [smart_ref_fwd.hpp:9-52](file://thirdparty/fc/include/fc/smart_ref_fwd.hpp#L9-L52) -- [unique_ptr.hpp:7-66](file://thirdparty/fc/include/fc/unique_ptr.hpp#L7-L66) -- [chainbase.hpp:1078-1111](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1111) - -**Section sources** -- [shared_ptr.hpp:1-64](file://thirdparty/fc/include/fc/shared_ptr.hpp#L1-L64) -- [smart_ref_fwd.hpp:1-53](file://thirdparty/fc/include/fc/smart_ref_fwd.hpp#L1-L53) -- [unique_ptr.hpp:1-68](file://thirdparty/fc/include/fc/unique_ptr.hpp#L1-L68) -- [chainbase.hpp:1078-1111](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1111) - -## Shared Memory System - -### Managed Memory File Architecture - -The shared memory system uses Boost.Interprocess to create and manage memory-mapped files that serve as the foundation for the database storage: - -```mermaid -sequenceDiagram -participant App as Application -participant DB as Database -participant MMF as Managed Memory File -participant FM as File Mapping -participant MR as Mapped Region -App->>DB : open(shared_mem_dir, flags, size) -DB->>MMF : create/open managed file -MMF->>FM : create file mapping -FM->>MR : create mapped region -MR->>MMF : allocate memory segment -MMF->>DB : initialize environment check -DB->>App : database ready -Note over App,DB : Memory allocation and indexing -App->>DB : create/modify objects -DB->>MMF : allocate from shared memory -MMF->>App : return object reference -App->>DB : close() -DB->>MMF : flush and cleanup -MMF->>FM : unmap file -FM->>MR : destroy mapped region -``` - -**Diagram sources** -- [chainbase.cpp:70-102](file://thirdparty/chainbase/src/chainbase.cpp#L70-L102) -- [file_mapping.cpp:9-41](file://thirdparty/fc/src/interprocess/file_mapping.cpp#L9-L41) -- [mmap_struct.cpp:20-44](file://thirdparty/fc/src/interprocess/mmap_struct.cpp#L20-L44) - -### Memory Layout and Organization - -The shared memory system organizes data in a structured manner optimized for blockchain operations: - -| Memory Segment | Purpose | Size | Protection | -|----------------|---------|------|------------| -| Environment Check | System compatibility verification | Fixed size | Read-only | -| Index Tables | Object indexing structures | Dynamic | Read-write | -| Object Storage | Actual blockchain data | Dynamic | Read-write | -| Undo Buffers | Transaction rollback support | Dynamic | Read-write | -| Lock Information | Concurrency control data | Fixed size | Read-write | -| Resize Barrier State | Atomic operation counters | Fixed size | Read-write | - -**Section sources** -- [chainbase.cpp:70-102](file://thirdparty/chainbase/src/chainbase.cpp#L70-L102) -- [chainbase.hpp:1189-1193](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1189-L1193) -- [chainbase.hpp:1319-1328](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1319-L1328) - -## Reference Counting - -### Retainable Object Pattern - -The reference counting system uses the retainable pattern to provide automatic memory management for shared objects: - -```mermaid -flowchart TD -Start([Object Creation]) --> InitCount["Initialize retain count = 1"] -InitCount --> UseObject["Use Object"] -UseObject --> Increment{"Need More References?"} -Increment --> |Yes| Retain["retain() - increment count"] -Increment --> |No| Continue["Continue Operation"] -Retain --> Continue -Continue --> Release{"Reference No Longer Needed?"} -Release --> |Yes| Decrement["release() - decrement count"] -Decrement --> CheckZero{"Count == 0?"} -CheckZero --> |Yes| Delete["delete this"] -CheckZero --> |No| Wait["Wait for other references"] -Release --> |No| Continue -Delete --> End([Object Destroyed]) -Wait --> Release -``` - -**Diagram sources** -- [shared_ptr.cpp:16-25](file://thirdparty/fc/src/shared_ptr.cpp#L16-L25) - -### Thread-Safe Reference Operations - -The reference counting implementation ensures thread safety through atomic operations and memory barriers: - -| Operation | Memory Ordering | Purpose | -|-----------|----------------|---------| -| retain() | relaxed | Increment reference count | -| release() | release | Decrement count with acquire barrier | -| retain_count() | acquire | Read current count safely | - -**Section sources** -- [shared_ptr.cpp:1-30](file://thirdparty/fc/src/shared_ptr.cpp#L1-L30) -- [shared_ptr.hpp:13-28](file://thirdparty/fc/include/fc/shared_ptr.hpp#L13-L28) - -## Smart Pointers and RAII - -### Smart Pointer Implementation - -The FC library provides several smart pointer implementations that enhance memory safety: - -```mermaid -classDiagram -class SmartRef { --T* impl -+smart_ref(U&&) constructor -+smart_ref(const smart_ref&) constructor -+smart_ref(smart_ref&&) constructor -+operator*() T& -+operator->() T* -+operator=(smart_ref&&) T& -+~smart_ref() destructor -} -class ScopedExit { --Callback callback -+scoped_exit(C&&) constructor -+~scoped_exit() destructor -+operator=(scoped_exit&&) scoped_exit& -} -class Aligned { --union { T _align; char _data[S]; } _store -+operator char*() -+operator const char*() const -} -SmartRef --> T : "heap allocated" -ScopedExit --> Callback : "executes on destruction" -Aligned --> T : "aligned storage" -``` - -**Diagram sources** -- [smart_ref_impl.hpp:40-134](file://thirdparty/fc/include/fc/smart_ref_impl.hpp#L40-L134) -- [scoped_exit.hpp:5-40](file://thirdparty/fc/include/fc/scoped_exit.hpp#L5-L40) -- [aligned.hpp:4-21](file://thirdparty/fc/include/fc/aligned.hpp#L4-L21) - -### RAII Resource Management - -The scoped_exit pattern ensures proper resource cleanup: - -**Section sources** -- [smart_ref_fwd.hpp:1-53](file://thirdparty/fc/include/fc/smart_ref_fwd.hpp#L1-L53) -- [smart_ref_impl.hpp:1-136](file://thirdparty/fc/include/fc/smart_ref_impl.hpp#L1-L136) -- [scoped_exit.hpp:1-40](file://thirdparty/fc/include/fc/scoped_exit.hpp#L1-L40) -- [aligned.hpp:1-21](file://thirdparty/fc/include/fc/aligned.hpp#L1-L21) - -## Memory Allocation Strategies - -### Custom Allocators - -ChainBase uses custom allocators that integrate with the shared memory system: - -```mermaid -flowchart LR -subgraph "Allocator Types" -BA[Boost Allocator] -CI[ChainBase Allocator] -FA[Flat Allocator] -end -subgraph "Memory Sources" -SM[Shared Memory] -HM[Heap Memory] -FM[File Mapping] -end -subgraph "Container Types" -MV[Managed Vector] -MS[Managed String] -MC[Managed Container] -end -BA --> SM -CI --> HM -FA --> FM -SM --> MV -HM --> MS -FM --> MC -``` - -**Diagram sources** -- [chainbase.hpp:53-59](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L53-L59) -- [flat.hpp:1-140](file://thirdparty/fc/include/fc/container/flat.hpp#L1-L140) - -### Memory Alignment and Padding - -The aligned storage template ensures proper memory alignment for different data types: - -**Section sources** -- [chainbase.hpp:53-59](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L53-L59) -- [flat.hpp:1-140](file://thirdparty/fc/include/fc/container/flat.hpp#L1-L140) -- [aligned.hpp:1-21](file://thirdparty/fc/include/fc/aligned.hpp#L1-L21) - -## Locking and Concurrency - -### Reader-Writer Lock System - -The database implements a sophisticated locking mechanism to handle concurrent access: - -```mermaid -stateDiagram-v2 -[*] --> Idle -Idle --> AcquiringRead : request_read_lock() -Idle --> AcquiringWrite : request_write_lock() -AcquiringRead --> ReadLock : lock acquired -AcquiringWrite --> WriteLock : lock acquired -ReadLock --> Processing : perform read operation -WriteLock --> Processing : perform write operation -Processing --> Releasing : operation complete -Releasing --> Idle : unlock -ReadLock --> Upgrading : upgrade to write -Upgrading --> WriteLock : successful upgrade -WriteLock --> Downgrading : downgrade to read -Downgrading --> ReadLock : successful downgrade -``` - -**Diagram sources** -- [chainbase.hpp:1070-1167](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1070-L1167) - -### Lock Timeout and Retry Mechanisms - -The system implements configurable timeout and retry mechanisms for lock acquisition: - -| Lock Type | Default Wait Time | Max Retries | Behavior | -|-----------|------------------|-------------|----------| -| Weak Read | 500,000 μs | 3 | Fail after retries | -| Strong Read | 1,000,000 μs | 100,000 | Extended wait period | -| Weak Write | 500,000 μs | 3 | Fail after retries | -| Strong Write | 1,000,000 μs | 100,000 | Extended wait period | - -**Section sources** -- [chainbase.hpp:1070-1167](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1070-L1167) - -## Memory Monitoring and Resizing - -### Automatic Memory Management - -The database includes sophisticated memory monitoring and automatic resizing capabilities: - -```mermaid -flowchart TD -Start([Block Processing]) --> CheckMemory["check_free_memory()"] -CheckMemory --> CalcReserved["Calculate reserved memory"] -CalcReserved --> CalcFree["Calculate free memory"] -CalcFree --> Compare{"Free < Min Free?"} -Compare --> |Yes| CheckResize{"Auto-resize enabled?"} -Compare --> |No| Continue["Continue normal operation"] -CheckResize --> |Yes| ScheduleResize["Schedule resize"] -CheckResize --> |No| LogWarning["Log warning message"] -ScheduleResize --> ApplyResize["apply_pending_resize()"] -ApplyResize --> Resize["resize(target_size)"] -Resize --> UpdateStats["Update memory statistics"] -UpdateStats --> Continue -LogWarning --> Continue -Continue --> End([Next Block]) -``` - -**Diagram sources** -- [database.cpp:648-682](file://libraries/chain/database.cpp#L648-L682) -- [database.cpp:609-646](file://libraries/chain/database.cpp#L609-L646) - -### Memory Configuration Options - -The system provides extensive configuration options for memory management: - -| Configuration | Default Value | Description | -|---------------|---------------|-------------| -| shared-file-dir | "state" | Location of shared memory files | -| shared-file-size | "2G" | Initial shared memory size | -| inc-shared-file-size | "2G" | Increment size for growth | -| min-free-shared-file-size | Configurable | Minimum free memory threshold | -| read_wait_micro | 500,000 | Read lock wait time | -| max_read_wait_retries | 3 | Read lock retry attempts | -| write_wait_micro | 500,000 | Write lock wait time | -| max_write_wait_retries | 3 | Write lock retry attempts | - -**Section sources** -- [plugin.cpp:199-211](file://plugins/chain/plugin.cpp#L199-L211) -- [database.cpp:648-682](file://libraries/chain/database.cpp#L648-L682) - -## Resize Barrier System - -### Atomic Operation Counter System - -The resize barrier system introduces a sophisticated atomic operation counter that tracks active database operations during memory resizing: - -```mermaid -sequenceDiagram -participant App as Application Thread -participant DB as Database -participant Barrier as Resize Barrier -participant Ops as Active Operations -App->>DB : begin_resize_barrier() -DB->>Barrier : set _resize_in_progress = true -Barrier->>Ops : wait for _active_operations == 0 -Note over Ops : All active operations complete -Ops->>Barrier : notify_all() -Barrier->>DB : begin_resize_barrier() returns -App->>DB : resize(target_size) -DB->>Barrier : end_resize_barrier() -Barrier->>Ops : set _resize_in_progress = false -Ops->>Barrier : notify_all() -Barrier->>DB : end_resize_barrier() returns -``` - -**Diagram sources** -- [chainbase.cpp:295-310](file://thirdparty/chainbase/src/chainbase.cpp#L295-L310) -- [chainbase.hpp:1319-1323](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1319-L1323) - -### Resize Barrier Implementation Details - -The resize barrier system provides atomic operations during shared memory resizing: - -| Component | Data Type | Purpose | -|-----------|-----------|---------| -| _resize_in_progress | std::atomic | Indicates resize operation status | -| _active_operations | std::atomic | Counts currently active operations | -| _resize_barrier_mutex | std::mutex | Synchronization primitive | -| _resize_barrier_cv | std::condition_variable | Condition variable for blocking | - -**Section sources** -- [chainbase.cpp:295-310](file://thirdparty/chainbase/src/chainbase.cpp#L295-L310) -- [chainbase.hpp:1319-1323](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1319-L1323) - -## Operation Guards - -### Lockless Read Protection - -Operation guards provide protection for lockless reads that do not acquire chainbase locks, preventing shared memory corruption during resize operations: - -```mermaid -flowchart TD -Start([Lockless Read Request]) --> CheckBarrier["Check _resize_in_progress"] -CheckBarrier --> |Resize In Progress| WaitGuard["Wait in operation_guard"] -CheckBarrier --> |No Resize| Proceed["Proceed with Lockless Read"] -WaitGuard --> WaitComplete["Operation Guard Released"] -WaitComplete --> Proceed -Proceed --> Complete["Lockless Read Complete"] -``` - -**Diagram sources** -- [chainbase.hpp:1078-1111](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1111) -- [database.cpp:1554-1556](file://libraries/chain/database.cpp#L1554-L1556) - -### Operation Guard Lifecycle - -Operation guards participate in the resize barrier system: - -1. **Construction**: Calls `enter_operation()` which blocks if resize is in progress -2. **Destruction**: Calls `exit_operation()` which decrements counter and notifies resize thread -3. **Manual Release**: Can be explicitly released before scope exit - -**Section sources** -- [chainbase.hpp:1078-1111](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1111) -- [chainbase.cpp:278-293](file://thirdparty/chainbase/src/chainbase.cpp#L278-L293) -- [database.cpp:1554-1556](file://libraries/chain/database.cpp#L1554-L1556) - -## Enhanced Logging and Diagnostics - -### Resize Barrier State Transition Logging - -The system provides enhanced logging capabilities for resize barrier state transitions: - -```mermaid -flowchart TD -Start([Resize Operation]) --> LogStart["Log: Resize barrier: pausing all database operations"] -LogStart --> BeginBarrier["begin_resize_barrier()"] -BeginBarrier --> WaitOps["Wait for active operations to complete"] -WaitOps --> LogResize["Log: Applying deferred shared memory resize"] -LogResize --> PerformResize["resize(target_size)"] -PerformResize --> LogComplete["Log: Deferred shared memory grow complete"] -LogComplete --> EndBarrier["end_resize_barrier()"] -EndBarrier --> LogResume["Log: Resize barrier: all database operations resumed"] -LogResume --> End([Resize Complete]) -``` - -**Diagram sources** -- [database.cpp:624-652](file://libraries/chain/database.cpp#L624-L652) - -### Diagnostic Information - -The resize barrier system logs comprehensive information about memory usage and resize operations: - -- **Current block number**: Tracks resize timing relative to blockchain progress -- **Used memory before/after**: Shows actual memory consumption changes -- **Target size**: Displays planned resize target -- **Free memory**: Monitors available memory after resize - -**Section sources** -- [database.cpp:624-652](file://libraries/chain/database.cpp#L624-L652) -- [database.cpp:577-610](file://libraries/chain/database.cpp#L577-L610) - -## Best Practices - -### Memory Management Guidelines - -1. **Use Smart Pointers**: Prefer smart pointers over raw pointers for automatic memory management -2. **Implement RAII**: Ensure resources are properly cleaned up using RAII patterns -3. **Monitor Memory Usage**: Regularly check memory usage and implement appropriate resizing strategies -4. **Use Appropriate Locking**: Choose the right lock type based on operation requirements -5. **Handle Exceptions**: Ensure proper cleanup in exception scenarios using scoped_exit patterns -6. **Respect Resize Barriers**: Always use operation guards for lockless reads during resize operations -7. **Implement Proper Synchronization**: Use resize barriers for all shared memory modifications - -### Performance Optimization Tips - -1. **Batch Operations**: Group related operations to minimize lock contention -2. **Efficient Allocators**: Use appropriate allocators for different data types -3. **Memory Alignment**: Ensure proper alignment for optimal performance -4. **Undo Buffer Management**: Monitor and manage undo buffer sizes appropriately -5. **Operation Guard Usage**: Use operation guards strategically to prevent shared memory corruption -6. **Resize Timing**: Schedule resizes during maintenance windows when possible - -## Troubleshooting Guide - -### Common Memory Issues - -**Memory Exhaustion Errors** -- Check free memory thresholds and adjust `min-free-shared-file-size` -- Review application memory usage patterns -- Consider implementing more aggressive garbage collection -- Monitor resize barrier operations for blocking issues - -**Lock Contention Problems** -- Analyze lock wait times and adjust timeouts -- Review concurrent access patterns -- Consider reducing lock scope where possible -- Check operation guard usage for proper synchronization - -**Shared Memory Corruption** -- Verify environment compatibility checks pass -- Check file permissions and disk space -- Review concurrent access patterns for race conditions -- Monitor resize barrier state transitions - -**Resize Barrier Deadlocks** -- Ensure all operation guards are properly released -- Check for nested resize operations -- Verify atomic operation counter integrity -- Monitor condition variable notifications - -### Diagnostic Tools - -The system provides several diagnostic capabilities: - -- Memory usage logging with periodic updates -- Lock contention monitoring -- Environment compatibility verification -- Automatic memory resizing notifications -- Resize barrier state transition logging -- Operation guard lifecycle tracking - -**Section sources** -- [database.cpp:648-682](file://libraries/chain/database.cpp#L648-L682) -- [chainbase.cpp:80-89](file://thirdparty/chainbase/src/chainbase.cpp#L80-L89) -- [database.cpp:624-652](file://libraries/chain/database.cpp#L624-L652) - -## Conclusion - -The VIZ CPP Node memory management system represents a sophisticated approach to handling blockchain-specific memory requirements. By combining shared memory architectures with modern C++ memory management techniques, the system achieves both high performance and reliability. - -**Updated** The recent addition of the resize barrier system significantly enhances the system's ability to handle dynamic memory growth while maintaining data integrity and preventing corruption during shared memory modifications. - -Key strengths of the system include: - -- **High Performance**: Shared memory eliminates context switching overhead -- **Reliability**: Comprehensive error checking and recovery mechanisms -- **Scalability**: Automatic memory resizing and monitoring with atomic operations -- **Safety**: Extensive use of RAII and smart pointers with operation guard protection -- **Flexibility**: Configurable parameters for different deployment scenarios -- **Atomic Operations**: Sophisticated resize barrier system with atomic operation counters -- **Enhanced Diagnostics**: Comprehensive logging for resize barrier state transitions - -The system successfully balances the competing demands of blockchain applications: maintaining fast access to persistent state while ensuring data integrity, providing mechanisms for graceful degradation under memory pressure, and protecting against corruption during critical memory operations. - -The resize barrier system, with its atomic operation counter and operation guard mechanism, provides a robust foundation for safe shared memory resizing operations, making the system more resilient to memory pressure and enabling seamless scaling of blockchain state storage. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Object Model and Persistence.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Object Model and Persistence.md deleted file mode 100644 index 65ff7484cd..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Object Model and Persistence.md +++ /dev/null @@ -1,606 +0,0 @@ -# Object Model and Persistence - - -**Referenced Files in This Document** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp) -- [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp) -- [chain_objects.cpp](file://libraries/chain/chain_objects.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the Object Model and Persistence system that defines the complete blockchain data structure in the VIZ C++ node. It documents the object types (account_object, transaction_object, content_object, witness_object, committee_object, and related objects), their schemas, field types, and validation rules as defined in the chain object type registry. It explains lifecycle management (creation, modification, deletion), indexing strategies via multi-index containers, serialization/deserialization, and persistence mechanisms. It also covers relationships among object types, practical examples of object creation/query/manipulation, and considerations for versioning and schema evolution. - -## Project Structure -The object model and persistence are primarily defined in the Chain library under libraries/chain. Key areas: -- Object type registry and shared types: libraries/chain/include/graphene/chain/chain_object_types.hpp -- Object schemas and indices: libraries/chain/include/graphene/chain/*.hpp -- Database interface and persistence: libraries/chain/include/graphene/chain/database.hpp and implementation libraries/chain/database.cpp -- Supporting property and summary objects: libraries/chain/include/graphene/chain/global_property_object.hpp, block_summary_object.hpp, etc. - -```mermaid -graph TB -subgraph "Chain Objects" -A["account_object.hpp"] -T["transaction_object.hpp"] -C["content_object.hpp"] -W["witness_objects.hpp"] -M["committee_objects.hpp"] -G["global_property_object.hpp"] -B["block_summary_object.hpp"] -I["invite_objects.hpp"] -P["paid_subscription_objects.hpp"] -end -R["chain_object_types.hpp"] -D["database.hpp"] -DC["database.cpp"] -R --> A -R --> T -R --> C -R --> W -R --> M -R --> G -R --> B -R --> I -R --> P -D --> DC -A --> D -T --> D -C --> D -W --> D -M --> D -G --> D -B --> D -I --> D -P --> D -``` - -**Diagram sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L137) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L180) -- [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L49) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L121) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) -- [database.cpp](file://libraries/chain/database.cpp#L198-L200) - -**Section sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) - -## Core Components -This section summarizes the primary object types and their roles in the blockchain state. - -- Dynamic Global Property - - Purpose: Tracks global blockchain state (head block, supply, validator participation, reserve ratios). - - Schema: See [dynamic_global_property_object](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L133). - - Index: Single-entry unique index by id. - -- Account and Related Entities - - account_object: Core account state (balances, vesting, voting power, auction fields). - - account_authority_object: Master/Active/Regular authorities with updates. - - account_metadata_object: JSON metadata per account. - - Vesting delegation objects: Delegation, fixed delegation, and expiration. - - Recovery and master authority history objects. - - References: [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565). - -- Transaction - - transaction_object: Deduplication and expiration tracking for transactions. - - Index: by_id (unique), by_trx_id (hashed), by_expiration (ordered). - - Reference: [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56). - -- Content and Discussions - - content_object: Post/article state (timestamps, rshares, cashout, payouts). - - content_type_object: Title/body/metadata for content. - - content_vote_object: Per-voter per-content vote records. - - Indices: by_id, by_cashout_time, by_permlink, by_root, by_parent, and more; plus content_vote indices. - - Reference: [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270). - -- validators and Voting - - witness_object: validator identity, votes, virtual scheduling, signing key, props. - - witness_vote_object: Voter-to-validator mapping. - - witness_schedule_object: Current shuffled validators and majority version. - - References: [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313). - -- Committee and Proposals - - committee_request_object: Request metadata, amounts, timing, status, payouts. - - committee_vote_object: Voter support per request. - - References: [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L137). - -- Block Summary - - block_summary_object: Minimal info for TaPOS checks. - - Reference: [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L49). - -- Invites and Paid Subscriptions - - invite_object: Invite issuance, keys, balances, status. - - paid_subscription_object and paid_subscribe_object: Creator subscriptions and subscriber records. - - References: [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72), [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L121). - -**Section sources** -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L180) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L137) -- [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L49) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L121) - -## Architecture Overview -The persistence layer is built on chainbase, which provides a typed object store with multi-index containers. The database class extends chainbase::database and exposes blockchain-specific operations. Object schemas are declared with Boost.MultiIndex indices; serialization is handled by fc::raw and reflection macros. - -```mermaid -classDiagram -class Database { -+open(data_dir, shared_mem_dir, ...) -+reindex(...) -+push_block(...) -+push_transaction(...) -+get_account(name) -+get_content(author, permlink) -+get_witness(owner) -+get_dynamic_global_properties() -} -class Object { -+id -} -class dynamic_global_property_object -class account_object -class account_authority_object -class witness_object -class transaction_object -class content_object -class content_type_object -class content_vote_object -class witness_vote_object -class witness_schedule_object -class committee_request_object -class committee_vote_object -class block_summary_object -class invite_object -class paid_subscription_object -class paid_subscribe_object -Database --> dynamic_global_property_object : "manages" -Database --> account_object : "manages" -Database --> witness_object : "manages" -Database --> transaction_object : "manages" -Database --> content_object : "manages" -Database --> content_type_object : "manages" -Database --> content_vote_object : "manages" -Database --> witness_vote_object : "manages" -Database --> witness_schedule_object : "manages" -Database --> committee_request_object : "manages" -Database --> committee_vote_object : "manages" -Database --> block_summary_object : "manages" -Database --> invite_object : "manages" -Database --> paid_subscription_object : "manages" -Database --> paid_subscribe_object : "manages" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L180) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270) -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L137) -- [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L49) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L121) - -## Detailed Component Analysis - -### Object Type Registry and Shared Types -- Defines the canonical object_type enumeration and shared type aliases (object_id<>). -- Declares shared_string and buffer_type and provides fc::variant/raw serialization hooks for object_id and buffers. -- Provides FC_REFLECT_ENUM for object_type and FC_REFLECT_TYPENAME registrations. - -Key references: -- Enumerations and aliases: [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- Serialization helpers: [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L151-L207) - -**Section sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L151-L207) - -### Account Object Lifecycle and Indices -- Creation: Constructed via constructor templates; stored via chainbase insert/update. -- Modification: Fields updated by evaluators; indices maintained automatically. -- Deletion: Removed when account is deleted or merged. -- Indices: - - by_id (unique) - - by_name (unique, lexicographic) - - by_account_on_sale/by_account_on_auction/by_subaccount_on_sale (non-unique booleans) - - by_account_on_sale_start_time (non-unique) - - by_next_vesting_withdrawal (composite: next_vesting_withdrawal + id) -- Related objects: - - account_authority_object (by_account composite) - - account_metadata_object (by_account) - - vesting_delegation_object (by_delegation, by_received) - - fix_vesting_delegation_object (by_id) - - vesting_delegation_expiration_object (by_expiration, by_account_expiration) - - account_recovery_request_object (by_expiration) - - change_recovery_account_request_object (by_effective_date) - - master_authority_history_object (by_account composite) - -References: -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) - -```mermaid -classDiagram -class account_object { -+id -+name -+balance -+vesting_shares -+energy -+next_vesting_withdrawal -+account_on_sale -+account_on_auction -+account_on_sale_start_time -} -class account_authority_object { -+id -+account -+master -+active -+regular -+last_master_update -} -class account_metadata_object { -+id -+account -+json_metadata -} -class vesting_delegation_object { -+id -+delegator -+delegatee -+vesting_shares -+min_delegation_time -} -class fix_vesting_delegation_object { -+id -+delegator -+delegatee -+vesting_shares -} -class vesting_delegation_expiration_object { -+id -+delegator -+vesting_shares -+expiration -} -class account_recovery_request_object { -+id -+account_to_recover -+new_master_authority -+expires -} -class change_recovery_account_request_object { -+id -+account_to_recover -+recovery_account -+effective_on -} -class master_authority_history_object { -+id -+account -+previous_master_authority -+last_valid_time -} -account_object --> account_authority_object : "authorities" -account_object --> account_metadata_object : "metadata" -account_object --> vesting_delegation_object : "delegations" -account_object --> fix_vesting_delegation_object : "fixed delegations" -account_object --> vesting_delegation_expiration_object : "expirations" -account_object --> account_recovery_request_object : "recovery requests" -account_object --> change_recovery_account_request_object : "change requests" -account_object --> master_authority_history_object : "history" -``` - -**Diagram sources** -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) - -**Section sources** -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L20-L565) - -### Transaction Object Lifecycle and Indices -- Purpose: Detect duplicates and enforce expiration. -- Indices: - - by_id (unique) - - by_trx_id (hashed unique) - - by_expiration (non-unique) -- Serialization: object_id and buffer_type raw pack/unpack helpers. - -References: -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L170-L207) - -```mermaid -sequenceDiagram -participant Eval as "Evaluator" -participant DB as "Database" -participant TX as "transaction_object" -Eval->>DB : "store transaction" -DB->>TX : "insert(packed_trx, trx_id, expiration)" -DB-->>Eval : "object_id" -Note over DB,TX : "Lookup by_trx_id for duplicate detection" -DB->>TX : "find by_trx_id" -TX-->>DB : "exists?" -DB-->>Eval : "duplicate or accepted" -``` - -**Diagram sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L39-L49) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L170-L207) - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L170-L207) - -### Content Object Lifecycle and Indices -- Purpose: Store content metadata, voting stats, payout tracking, and permlink hierarchy. -- Indices: - - by_id (unique) - - by_cashout_time (composite) - - by_permlink (composite: author + permlink) - - by_root (composite) - - by_parent (composite) - - by_last_update and by_author_last_update (non-consensus) - - content_vote indices: by_content_voter, by_voter_content, by_voter_last_update, by_content_weight_voter - - content_type_object: by_id, by_content -- Validation rules: - - String comparisons use custom comparator to support shared_string. - - Composite keys ensure uniqueness and efficient range scans. - -References: -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270) - -```mermaid -flowchart TD -Start(["Insert or Update Content"]) --> SetFields["Set timestamps, rshares, cashout_time"] -SetFields --> Insert["Insert into content_index"] -Insert --> VotePath{"Has votes?"} -VotePath --> |Yes| UpdateVotes["Update content_vote_index
and permlink indices"] -VotePath --> |No| Done["Done"] -UpdateVotes --> Done -``` - -**Diagram sources** -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L197-L248) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L144-L184) - -**Section sources** -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L270) - -### validator Object Lifecycle and Indices -- Purpose: Track validator identities, votes, virtual scheduling, and penalties. -- Indices: - - witness_index: by_id, by_work, by_name, by_vote_name, by_counted_vote_name, by_schedule_time - - witness_vote_index: by_id, by_account_witness, by_witness_account - - witness_schedule_index: by_id - - witness_penalty_expire_index: by_id, by_account, by_expiration -- Scheduling: Virtual time algorithm uses fc::uint128_t fields to compute scheduling order. - -References: -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) - -```mermaid -classDiagram -class witness_object { -+id -+owner -+votes -+counted_votes -+virtual_scheduled_time -+signing_key -+props -} -class witness_vote_object { -+id -+validator -+account -} -class witness_schedule_object { -+id -+current_virtual_time -+current_shuffled_witnesses -+majority_version -} -class witness_penalty_expire_object { -+id -+validator -+penalty_percent -+expires -} -witness_object --> witness_vote_object : "votes" -witness_object --> witness_schedule_object : "schedule" -witness_object --> witness_penalty_expire_object : "penalties" -``` - -**Diagram sources** -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) - -**Section sources** -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L313) - -### Committee Objects Lifecycle and Indices -- Purpose: Manage committee requests and votes for funding/work proposals. -- Indices: - - committee_request_index: by_id, by_request_id, by_status, by_creator, by_worker, by_creator_url - - committee_vote_index: by_id, by_voter, by_request_id - -References: -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L137) - -```mermaid -sequenceDiagram -participant Voter as "Account" -participant DB as "Database" -participant Req as "committee_request_object" -participant Vote as "committee_vote_object" -Voter->>DB : "submit vote(request_id, vote_percent)" -DB->>Req : "load by request_id" -Req-->>DB : "request exists" -DB->>Vote : "insert or update vote" -DB-->>Voter : "ack" -``` - -**Diagram sources** -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L87-L122) - -**Section sources** -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L15-L137) - -### Additional Objects -- Block Summary: Minimal per-block info for TaPOS. - - Reference: [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L49) -- Invites: Invite issuance, keys, balances, status. - - Reference: [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72) -- Paid Subscriptions: Creator plans and subscriber records. - - Reference: [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L121) - -**Section sources** -- [block_summary_object.hpp](file://libraries/chain/include/graphene/chain/block_summary_object.hpp#L19-L49) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L72) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L121) - -## Dependency Analysis -The object model relies on: -- chainbase for persistent storage and object lifecycle. -- Boost.MultiIndex for multi-dimensional indexing. -- fc::raw and fc::variant for serialization/deserialization. -- Protocol types (asset, price, authority, operation) for cross-object references. - -```mermaid -graph LR -CT["chain_object_types.hpp"] --> AO["account_object.hpp"] -CT --> TO["transaction_object.hpp"] -CT --> CO["content_object.hpp"] -CT --> WO["witness_objects.hpp"] -CT --> CM["committee_objects.hpp"] -CT --> GP["global_property_object.hpp"] -CT --> BS["block_summary_object.hpp"] -CT --> IO["invite_objects.hpp"] -CT --> PS["paid_subscription_objects.hpp"] -DBH["database.hpp"] --> DB_CPP["database.cpp"] -DBH --> AO -DBH --> TO -DBH --> CO -DBH --> WO -DBH --> CM -DBH --> GP -DBH --> BS -DBH --> IO -DBH --> PS -``` - -**Diagram sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) -- [database.cpp](file://libraries/chain/database.cpp#L198-L200) - -**Section sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L200) - -## Performance Considerations -- Multi-index containers provide O(log n) insertion/search for each index; choose indices carefully to avoid excessive duplication. -- Hashed indices (e.g., by_trx_id) offer near O(1) lookup for deduplication. -- Composite indices enable efficient range queries and uniqueness constraints for complex relationships. -- Shared memory layout and allocation via chainbase minimize memory fragmentation. -- Virtual scheduling for validators uses large integer arithmetic; keep computations localized to reduce overhead. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and diagnostics: -- Duplicate transaction detection: Verify by_trx_id index presence and expiration cleanup. - - Reference: [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L39-L49) -- Missing account or authority: Check by_name and composite indices. - - Reference: [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L291-L315) -- Content not found by permlink: Confirm by_permlink index and string comparison behavior. - - Reference: [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L210-L226) -- validator scheduling anomalies: Inspect virtual_scheduled_time and vote indices. - - Reference: [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L183-L219) -- Committee vote conflicts: Ensure by_voter and by_request_id uniqueness. - - Reference: [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L107-L122) - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L39-L49) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L291-L315) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L210-L226) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L183-L219) -- [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L107-L122) - -## Conclusion -The VIZ blockchain object model leverages a robust registry of object types, multi-index containers for efficient querying, and chainbase-backed persistence. The design cleanly separates consensus-critical indices from API-friendly ones, supports complex relationships (accounts, content, validators, committees), and provides strong serialization hooks. Proper index selection and lifecycle management are key to maintaining performance and correctness. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Example Workflows - -- Create and query an account - - Insert: Use account_object construction and store via chainbase insert. - - Query: Lookup by_name index for fast account retrieval. - - References: [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L291-L315) - -- Submit a transaction - - Pack transaction, store transaction_object, detect duplicates via by_trx_id. - - References: [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L39-L49) - -- Record a content vote - - Insert or update content_vote_object; maintain content_index and permlink indices. - - References: [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L144-L184) - -- Vote for a validator - - Insert witness_vote_object; update witness_object votes and virtual scheduling. - - References: [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L224-L248) - -- Committee voting - - Insert committee_vote_object keyed by voter and request_id. - - References: [committee_objects.hpp](file://libraries/chain/include/graphene/chain/committee_objects.hpp#L107-L122) - -### Versioning and Schema Evolution -- Object type registry: New object types are added to the enumeration and type aliases. - - Reference: [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- Reflection and raw serialization: FC_REFLECT and FC_REFLECT_TYPENAME declarations define wire format; changes require careful migration. - - Reference: [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L209-L246) -- Hardfork handling: Database may evolve indices and fields across hardforks; consult hardfork-aware code paths. - - Reference: [database.cpp](file://libraries/chain/database.cpp#L91-L92) - -**Section sources** -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L44-L144) -- [chain_object_types.hpp](file://libraries/chain/include/graphene/chain/chain_object_types.hpp#L209-L246) -- [database.cpp](file://libraries/chain/database.cpp#L91-L92) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Transaction Processing.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Transaction Processing.md deleted file mode 100644 index 154436e0e1..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Chain Library/Transaction Processing.md +++ /dev/null @@ -1,409 +0,0 @@ -# Transaction Processing - - -**Referenced Files in This Document** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [network_broadcast_api.cpp](file://plugins/network_broadcast_api/network_broadcast_api.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the Transaction Processing system responsible for validating and executing blockchain operations. It covers: -- Transaction metadata and duplication detection via transaction_object -- Evaluator system for operation interpretation -- Validation pipeline including signature verification, authority checks, and operation validation -- Execution context and rollback mechanisms for apply_transaction() and _apply_transaction() -- Pending transaction management, transaction pool operations, and broadcast mechanisms -- Examples of transaction processing workflows, operation evaluation, and error handling -- Interactions with validator scheduling, fee markets, and state transitions -- Transaction size limits, priority handling, and performance optimization strategies - -## Project Structure -The transaction processing logic spans several core modules: -- Protocol-level transaction and signed transaction definitions -- Chain-level evaluator and registry for operation dispatch -- Database-level transaction validation, execution, and persistence -- Plugins for chain ingestion and network broadcast - -```mermaid -graph TB -subgraph "Protocol Layer" -P1["transaction.hpp
Defines transaction, signed_transaction, annotated_signed_transaction"] -end -subgraph "Chain Layer" -C1["evaluator.hpp
Base evaluator interface"] -C2["evaluator_registry.hpp
Registry for operation dispatch"] -C3["transaction_object.hpp
Transaction metadata and duplication index"] -C4["transaction_object.cpp
Index implementation"] -end -subgraph "Database Layer" -D1["database.hpp/.cpp
apply_transaction/_apply_transaction,
validation, rollback, notifications"] -end -subgraph "Plugins" -PL1["plugins/chain/plugin.cpp
Ingestion and skip flags"] -PL2["plugins/network_broadcast_api/network_broadcast_api.cpp
Broadcast mechanism"] -end -P1 --> D1 -C1 --> C2 -C2 --> D1 -C3 --> D1 -C4 --> D1 -PL1 --> D1 -PL2 --> D1 -``` - -**Diagram sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L44) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L6-L73) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L148-L148) -- [network_broadcast_api.cpp](file://plugins/network_broadcast_api/network_broadcast_api.cpp) - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L1-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L1-L73) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L1-L62) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L1-L44) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L1-L136) -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L148-L148) - -## Core Components -- Transaction metadata and duplication detection - - transaction_object stores packed transaction bytes, transaction ID, and expiration, enabling duplicate detection and expiry-based cleanup. - - Multi-index container supports by_id, by_trx_id, and by_expiration indices. - -- Evaluator system - - Base evaluator interface defines apply() and get_type(). - - evaluator_impl provides a template wrapper that routes operations to do_apply() of concrete evaluators. - - evaluator_registry maps operation types to registered evaluators and performs dispatch. - -- Transaction validation and execution - - apply_transaction() and _apply_transaction() orchestrate validation, duplicate checks, bandwidth updates, persistence, and operation application. - - apply_operation() notifies pre/post hooks and delegates to the evaluator registry. - -- Pending transactions and broadcasting - - Pending transactions are staged with temporary undo sessions; successful application merges into the pending block session. - - Notifications are emitted for pending and applied transactions; plugins broadcast transactions. - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L6-L73) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L44) -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L136) - -## Architecture Overview -The transaction processing pipeline integrates protocol, chain, and database layers with plugin-driven ingestion and broadcasting. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant Plugin as "plugins/chain/plugin.cpp" -participant DB as "database.cpp" -participant Reg as "evaluator_registry.hpp" -participant Eval as "evaluator.hpp" -participant TxObj as "transaction_object.hpp" -Client->>Plugin : Submit signed_transaction -Plugin->>DB : validate_transaction() + apply_transaction() -DB->>DB : _apply_transaction()
- Duplicate check
- _validate_transaction()
- Bandwidth updates
- Persist transaction_object -DB->>Reg : get_evaluator(op) -Reg-->>DB : Evaluator instance -DB->>Eval : apply(op) -Eval-->>DB : Operation executed -DB-->>Plugin : Applied transaction notification -Plugin-->>Client : Result -Note over DB,TxObj : Transaction stored for duplicate detection until expiry -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L148-L148) -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L29-L41) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) - -## Detailed Component Analysis - -### Transaction Metadata and Duplication Detection -- Purpose: Detect duplicate transactions and manage expiry windows. -- Storage: transaction_object holds packed_trx, trx_id, and expiration. -- Indexing: Multi-index supports fast lookup by ID, transaction ID, and expiration ordering. -- Lifecycle: After block processing, expired entries are removed from the index. - -```mermaid -classDiagram -class transaction_object { -+id_type id -+bip : : vector packed_trx -+transaction_id_type trx_id -+time_point_sec expiration -} -class transaction_index { -+create(constructor, object_id_type) -+modify(object, modifier) -+add(unique_ptr) -+remove(object_id_type) -+get(object_id_type) const -} -transaction_index --> transaction_object : "indexes" -``` - -**Diagram sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L6-L73) - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L6-L73) - -### Evaluator System -- Base evaluator: Virtual apply() and get_type() define the contract. -- Template evaluator_impl: Wraps concrete evaluators, casting operation to the specific type and invoking do_apply(). -- Registry: Stores evaluators per operation type; get_evaluator() returns the appropriate evaluator instance. - -```mermaid -classDiagram -class evaluator~OperationType~ { -<> -+apply(op : OperationType) void -+get_type() int -} -class evaluator_impl~EvaluatorType, OperationType~ { --database& _db -+apply(op : OperationType) void -+get_type() int -+db() database& -} -class evaluator_registry~OperationType~ { --vector>> _op_evaluators --database& _db -+register_evaluator(args...) -+get_evaluator(op : OperationType) evaluator& -} -evaluator <|-- evaluator_impl -evaluator_registry --> evaluator : "dispatches" -``` - -**Diagram sources** -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L44) - -**Section sources** -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L44) - -### Transaction Validation Pipeline -- Signature verification and authority checks are performed during _validate_transaction(). -- Required authorities are computed from the transaction; bandwidth updates are applied per account and operation type. -- Duplicate transaction detection uses the by_trx_id index; if a duplicate exists, the assertion fails. -- After validation, the transaction is persisted as a transaction_object with expiration. - -```mermaid -flowchart TD -Start(["Start apply_transaction"]) --> SetTrxId["Set current transaction id"] -SetTrxId --> DupCheck{"Duplicate check enabled?"} -DupCheck --> |Yes| FindDup["Lookup by_trx_id"] -FindDup --> DupFound{"Duplicate found?"} -DupFound --> |Yes| FailDup["Fail with duplicate error"] -DupFound --> |No| Validate["_validate_transaction()"] -DupCheck --> |No| Validate -Validate --> ComputeAuth["Compute required authorities"] -ComputeAuth --> Bandwidth["Update account bandwidth"] -Bandwidth --> Persist{"Skip duplicate check?"} -Persist --> |No| Store["Persist transaction_object"] -Persist --> |Yes| Ops["Apply operations"] -Store --> Ops -Ops --> Done(["Done"]) -FailDup --> Done -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L3657-L3711) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3657-L3711) - -### apply_transaction() and _apply_transaction() Methods -- apply_transaction(): Public entrypoint that calls _apply_transaction() and emits on_applied_transaction notification. -- _apply_transaction(): - - Sets execution context (_current_trx_id, _current_virtual_op, _current_op_in_trx) - - Performs duplicate check and validation - - Updates bandwidth accounting for required authorities - - Persists transaction_object (unless skip flag is set) - - Iterates operations and applies via apply_operation() - -```mermaid -sequenceDiagram -participant API as "Caller" -participant DB as "database.cpp" -participant REG as "evaluator_registry.hpp" -participant EVAL as "evaluator.hpp" -API->>DB : apply_transaction(trx, skip) -DB->>DB : _apply_transaction(trx, skip) -DB->>DB : _validate_transaction(trx) -DB->>DB : update_account_bandwidth(...) -DB->>DB : create(transaction_object) -loop For each operation -DB->>REG : get_evaluator(op) -REG-->>DB : evaluator -DB->>EVAL : apply(op) -EVAL-->>DB : success -end -DB-->>API : on_applied_transaction notification -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L29-L41) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) - -### Rollback Mechanisms -- Pending transactions are staged within a temporary undo session created before applying each transaction. -- On success, the temporary session is merged into the pending block session. -- On failure, the temporary session is discarded, preserving the previous clean state. -- During block generation, pending transactions are re-applied with the new block time context; invalid/expired transactions are skipped or postponed. - -```mermaid -flowchart TD -Begin(["Begin pending stage"]) --> TempSess["Start temporary undo session"] -TempSess --> Apply["_apply_transaction(trx)"] -Apply --> Success{"Success?"} -Success --> |Yes| Merge["Merge into pending block session"] -Success --> |No| Discard["Discard temporary session"] -Merge --> Notify["notify_changed_objects()"] -Discard --> End(["End"]) -Notify --> End -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L950-L970) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L950-L970) - -### Pending Transaction Management and Broadcasting -- Pending transactions are appended to an internal list after successful application and merged into the pending block session. -- Notifications are emitted for on_pending_transaction and on_applied_transaction. -- Plugins listen to these notifications to broadcast transactions to peers. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant Notif as "Notifications" -participant Net as "network_broadcast_api.cpp" -DB->>DB : _apply_transaction(trx) -DB->>Notif : notify_on_pending_transaction(trx) -Notif-->>Net : on_pending_transaction callback -DB->>Notif : notify_on_applied_transaction(trx) -Notif-->>Net : on_applied_transaction callback -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L960-L970) -- [database.cpp](file://libraries/chain/database.cpp#L1192-L1198) -- [network_broadcast_api.cpp](file://plugins/network_broadcast_api/network_broadcast_api.cpp) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L960-L970) -- [database.cpp](file://libraries/chain/database.cpp#L1192-L1198) - -### Relationship with validator Scheduling, Fee Markets, and State Transitions -- validator scheduling: The database computes scheduled validators and validates block headers; transaction processing occurs within the context of block production and validation. -- Fee markets and state transitions: Operations modify state objects (e.g., balances, vesting shares, reward funds). Bandwidth accounting influences fee market dynamics indirectly by controlling resource usage and reserve ratios. - -[No sources needed since this section synthesizes relationships without analyzing specific files] - -### Transaction Size Limits, Priority Handling, and Performance Optimization -- Block size limits: During block generation, transactions are included until maximum_block_size is reached; oversized transactions are postponed with a configurable limit. -- Priority handling: Transactions are included in the order they are applied within the pending pool; no explicit prioritization mechanism is shown in the analyzed code. -- Performance optimizations: - - Temporary undo sessions isolate failed transactions without committing state changes. - - Bandwidth updates are applied per-account and per-operation type to prevent excessive data operations. - - Duplicate detection avoids reprocessing identical transactions. - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L1036-L1063) -- [database.cpp](file://libraries/chain/database.cpp#L3675-L3698) - -## Dependency Analysis -The transaction processing system exhibits clear layering: -- Protocol layer defines transaction structures and authority verification helpers. -- Chain layer provides evaluator abstractions and registry for operation dispatch. -- Database layer orchestrates validation, execution, persistence, and notifications. -- Plugins integrate ingestion and broadcasting. - -```mermaid -graph LR -Protocol["transaction.hpp"] --> Database["database.cpp"] -Evaluator["evaluator.hpp"] --> Database -Registry["evaluator_registry.hpp"] --> Database -TxObj["transaction_object.hpp"] --> Database -Plugin["plugins/chain/plugin.cpp"] --> Database -Broadcast["network_broadcast_api.cpp"] --> Plugin -``` - -**Diagram sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L44) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L148-L148) -- [network_broadcast_api.cpp](file://plugins/network_broadcast_api/network_broadcast_api.cpp) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3651-L3722) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L148-L148) - -## Performance Considerations -- Minimize redundant validations by leveraging skip flags appropriately. -- Use temporary undo sessions to avoid expensive rollbacks after failures. -- Monitor average block size and reserve ratio adjustments to tune network capacity. -- Apply bandwidth updates early to prevent oversized operations from consuming resources unnecessarily. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and diagnostics: -- Duplicate transaction errors: Occur when by_trx_id lookup finds an existing transaction_object. -- Validation failures: Thrown by _validate_transaction() during signature verification or authority checks. -- Expiry-related problems: Expired transactions are skipped; ensure proper time synchronization. -- Bandwidth throttling: Excessive data operations may incur additional bandwidth penalties. - -Mitigations: -- Verify transaction signatures and required authorities before submission. -- Monitor pending transaction notifications and error logs. -- Adjust skip flags only when necessary and understood. - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3665-L3667) -- [database.cpp](file://libraries/chain/database.cpp#L3669-L3669) - -## Conclusion -The Transaction Processing system integrates protocol-level transaction definitions, a flexible evaluator registry, and robust database orchestration to validate and execute blockchain operations reliably. It supports duplication detection, bandwidth accounting, rollback via undo sessions, and plugin-driven broadcasting. Understanding the execution context, validation pipeline, and performance characteristics enables effective tuning and troubleshooting of transaction throughput and reliability. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Core Libraries.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Core Libraries.md deleted file mode 100644 index 7edbb80e40..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Core Libraries.md +++ /dev/null @@ -1,389 +0,0 @@ -# Core Libraries - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://libraries/CMakeLists.txt) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the four fundamental library layers that underpin the blockchain node: -- Chain library: blockchain state management, validation, and consensus integration -- Protocol library: operation definitions, transactions, and cryptographic primitives -- Network library: peer-to-peer communication, synchronization, and message transport -- Wallet library: transaction signing, key management, and user-facing APIs - -It documents the architectural patterns used in each layer (observer pattern for event handling, factory-like registries for evaluators), the separation of concerns, and how these libraries integrate to deliver complete blockchain functionality. The design rationale for C++ is grounded in performance and deterministic behavior for cryptographic and consensus-critical operations. - -## Project Structure -The core libraries are organized as independent modules under libraries/, each exporting headers and implementations that are consumed by higher-level applications and plugins. - -```mermaid -graph TB -subgraph "Core Libraries" -CH["Chain Library
libraries/chain"] -PR["Protocol Library
libraries/protocol"] -NW["Network Library
libraries/network"] -WL["Wallet Library
libraries/wallet"] -UT["Utilities Library
libraries/utilities"] -TM["Time Library
libraries/time"] -AP["API Library
libraries/api"] -end -CH --> PR -NW --> PR -WL --> PR -WL --> CH -NW --> CH -CH --> UT -WL --> UT -NW --> UT -CH --> TM -WL --> AP -``` - -**Diagram sources** -- [CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) - -**Section sources** -- [CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) - -## Core Components -- Chain library - - Provides the blockchain state machine, fork resolution, block and transaction validation, and event signaling for observers. - - Exposes APIs to push blocks/transactions, query chain state, and subscribe to lifecycle events. -- Protocol library - - Defines the canonical operation types, transaction structure, and cryptographic primitives (signatures, digests). - - Supplies helpers for authority verification and signature minimization. -- Network library - - Implements a peer-to-peer node with message-oriented transport, peer connection management, and synchronization protocols. - - Offers a delegate interface for integrating chain logic into network callbacks. -- Wallet library - - Offers a high-level API for building, signing, and broadcasting transactions. - - Integrates with remote node APIs via typed remote interfaces and exposes signals for UI updates. - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L355) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -## Architecture Overview -The libraries collaborate through well-defined interfaces and event channels: -- The Chain library emits signals for block application, transaction lifecycle, and operation application. -- The Network library invokes the Chain library’s validation and push routines via a node delegate interface. -- The Protocol library defines the shared data structures and cryptographic semantics used across Chain, Network, and Wallet. -- The Wallet library composes transactions using Protocol types, signs them, and broadcasts via Network or remote node APIs. - -```mermaid -graph TB -subgraph "Network Layer" -NET_NODE["graphene::network::node"] -PEER_CONN["graphene::network::peer_connection"] -MSG_CONN["graphene::network::message_oriented_connection"] -end -subgraph "Chain Layer" -CH_DB["graphene::chain::database"] -EVAL["graphene::chain::evaluator"] -REG["Evaluator Registry"] -end -subgraph "Protocol Layer" -OPS["graphene::protocol::operations"] -TX["graphene::protocol::transaction/sig"] -end -subgraph "Wallet Layer" -WALLET["graphene::wallet::wallet_api"] -REMOTE["graphene::wallet::remote_* APIs"] -end -NET_NODE --> CH_DB -PEER_CONN --> NET_NODE -MSG_CONN --> PEER_CONN -WALLET --> TX -WALLET --> REMOTE -WALLET --> CH_DB -CH_DB --> EVAL -EVAL --> OPS -CH_DB --> TX -NET_NODE --> OPS -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L355) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L355) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L44-L85) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L44-L175) - -## Detailed Component Analysis - -### Chain Library -- Responsibilities - - Manage blockchain state, fork database, block log, and hardfork transitions. - - Validate blocks and transactions according to configurable skip flags. - - Apply operations via evaluators and emit signals for observers. -- Architectural patterns - - Observer pattern: Signals for pre/post operation application, applied block, pending/applied transactions. - - Factory/registry pattern: Evaluator registry and custom operation interpreter registry enable extensibility without modifying core logic. -- Key interfaces - - database: open/reindex, push_block/push_transaction, notify_* signals, get_* queries. - - evaluator: polymorphic apply() with static dispatch to concrete operation types. - - chain_evaluator: macro-generated evaluator declarations for each operation. - -```mermaid -classDiagram -class database { -+open(data_dir, shared_mem_dir, ...) -+reindex(data_dir, shared_mem_dir, ...) -+push_block(...) -+push_transaction(...) -+notify_pre_apply_operation(...) -+notify_post_apply_operation(...) -+notify_applied_block(...) -+notify_on_pending_transaction(...) -+notify_on_applied_transaction(...) -+pre_apply_operation : signal -+post_apply_operation : signal -+applied_block : signal -+on_pending_transaction : signal -+on_applied_transaction : signal -} -class evaluator_T { -<> -+apply(op) -+get_type() int -} -class evaluator_impl_T { --_db : database& -+apply(op) -+get_type() int -+db() database& -} -database --> evaluator_T : "uses registry" -evaluator_impl_T ..|> evaluator_T -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L80) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L56-L561) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L62) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L80) - -### Protocol Library -- Responsibilities - - Define canonical operation types and transaction structure. - - Provide cryptographic helpers: digests, signature computation, authority verification, and signature minimization. -- Architectural patterns - - Static variant for operation union enables efficient dispatch and serialization. - - Visitor-style visit() on transactions to apply visitor to each contained operation. -- Key interfaces - - operations: static_variant union of all operations. - - transaction/sig: structure with operations, extensions, and signature handling. - -```mermaid -classDiagram -class operations { -<> -+union : all operation types -} -class transaction { -+ref_block_num : uint16 -+ref_block_prefix : uint32 -+expiration : time_point_sec -+operations : vector -+extensions : extensions_type -+digest() digest_type -+id() transaction_id_type -+validate() void -+sig_digest(chain_id) digest_type -+set_expiration(time_point_sec) -+set_reference_block(block_id_type) -+visit(visitor) : vector -+get_required_authorities(...) void -} -class signed_transaction { -+signatures : vector -+sign(private_key, chain_id) -+verify_authority(...) -+minimize_required_signatures(...) -+get_signature_keys(chain_id) flat_set -+merkle_digest() digest_type -} -operations --> transaction : "contains" -transaction <|-- signed_transaction -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) - -### Network Library -- Responsibilities - - Peer-to-peer connectivity, message transport, and blockchain synchronization. - - Delegate-driven integration with chain logic for handling blocks, transactions, and sync status. -- Architectural patterns - - Observer pattern: delegate callbacks for block, transaction, message handling, sync status, and connection count changes. - - Message-oriented abstraction: message_oriented_connection encapsulates socket I/O and message framing. - - Factory-like peer connection management: peer_connection maintains queues and negotiation states. -- Key interfaces - - node: listen/connect, add_node, broadcast, sync_from, delegate registration. - - peer_connection: connection states, inventory tracking, message queuing. - - message_oriented_connection: socket binding, sending/receiving messages. - -```mermaid -sequenceDiagram -participant NET as "graphene : : network : : node" -participant PEER as "graphene : : network : : peer_connection" -participant DEL as "node_delegate" -participant CH as "graphene : : chain : : database" -NET->>PEER : "connect_to(endpoint)" -PEER-->>NET : "on_message(block_message)" -NET->>DEL : "handle_block(block_message, sync_mode, contained_txn_ids)" -DEL->>CH : "push_block(next_block)" -CH-->>DEL : "applied_block signal" -DEL-->>NET : "sync_status(...)" -NET-->>PEER : "broadcast(trx_message)" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L355) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L355) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L44-L85) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L355) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L355) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L44-L85) - -### Wallet Library -- Responsibilities - - Build, sign, and propose transactions; manage keys and credentials; expose user-facing APIs. - - Integrate with remote node APIs for read/write operations and broadcasting. -- Architectural patterns - - Remote API façades: typed remote_* structs define method signatures for remote plugins. - - Observer pattern: signals for lock state and quit command. - - Builder pattern: transaction builder handles composing operations and previewing before signing. -- Key interfaces - - wallet_api: transaction builder, signing, account/history queries, memo encryption/decryption. - - remote_* APIs: remote_database_api, remote_network_broadcast_api, etc. - -```mermaid -sequenceDiagram -participant APP as "wallet_api" -participant REM as "remote_network_broadcast_api" -participant NET as "network : : node" -participant CH as "chain : : database" -APP->>APP : "begin_builder_transaction()" -APP->>APP : "add_operation_to_builder_transaction()" -APP->>APP : "preview_builder_transaction()" -APP->>APP : "sign_builder_transaction()" -APP->>REM : "broadcast_transaction(signed_trx)" -REM-->>NET : "broadcast(trx_message)" -NET->>CH : "push_transaction(signed_trx)" -CH-->>APP : "on_applied_transaction signal" -``` - -**Diagram sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L122-L126) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L258-L262) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L268-L275) - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L44-L175) - -## Dependency Analysis -- Inter-library dependencies - - Chain depends on Protocol for operation and transaction types. - - Network depends on Protocol for chain_id and types; integrates with Chain via node delegate. - - Wallet depends on Protocol for transaction types and on Network/Remote APIs for broadcasting. -- Coupling and cohesion - - Loose coupling is achieved via delegate interfaces (node_delegate) and typed remote APIs. - - Cohesion is maintained by keeping each library focused on a single responsibility. - -```mermaid -graph LR -PR["protocol"] --> CH["chain"] -PR --> NW["network"] -PR --> WL["wallet"] -CH --> NW -WL --> NW -WL --> CH -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L3-L8) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L26-L30) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L18-L20) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L3-L8) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L26-L30) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L18-L20) - -## Performance Considerations -- C++ choice - - Deterministic memory layout, zero-overhead abstractions, and fine-grained control over threading and concurrency are essential for cryptographic operations and consensus-critical paths. -- Event-driven design - - Signal emissions in Chain and Wallet avoid synchronous callbacks, reducing contention and enabling asynchronous observers. -- Message-oriented networking - - Encapsulation of socket I/O and message framing reduces overhead and simplifies protocol handling. -- Signature verification and authority checks - - Protocol-level helpers minimize redundant computations and support signature minimization to reduce verification cost. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Validation failures - - Chain library exposes skip flags to bypass expensive checks during reindexing or local operations. Use appropriate skip masks to diagnose validation bottlenecks. -- Transaction signing issues - - Wallet relies on remote node APIs for signature discovery and verification. Confirm remote node connectivity and that required keys are present in the wallet. -- Network synchronization - - Use node delegate callbacks to monitor sync progress and connection counts. Investigate peer inventory lists and sync item requests to identify stalled peers. - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L56-L73) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L929-L931) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L143-L148) - -## Conclusion -The four core libraries form a cohesive, loosely coupled architecture: -- Chain manages state and validation with observer-driven notifications -- Protocol defines shared data structures and cryptographic semantics -- Network provides robust peer-to-peer transport and synchronization -- Wallet offers a practical interface for signing and broadcasting transactions - -This layered design, combined with C++’s performance characteristics, enables a high-throughput, extensible blockchain node capable of evolving with new operations, network protocols, and wallet features. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Message Handling and Protocol.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Message Handling and Protocol.md deleted file mode 100644 index 15d12fdec9..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Message Handling and Protocol.md +++ /dev/null @@ -1,442 +0,0 @@ -# Message Handling and Protocol - - -**Referenced Files in This Document** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [core_messages.cpp](file://libraries/network/core_messages.cpp) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the network message handling and protocol layer used by the node. It focuses on: -- Standard network message types for blocks and transactions -- Generic message packaging, serialization, and deserialization -- Protocol versioning and compliance validation -- Message routing, delivery guarantees, ordering, propagation tracking, and duplicate detection -- Practical patterns for message creation, transmission, and processing -- Compression, fragmentation, and protocol upgrade mechanisms -- Guidance for extending the protocol with custom message types - -## Project Structure -The messaging stack spans several files: -- Core message type definitions and constants -- Generic message container and serializer -- TCP transport abstraction with message framing and padding -- Node-level message caching, propagation tracking, and processing -- Peer connection orchestration and queueing - -```mermaid -graph TB -subgraph "Network Layer" -CM["core_messages.hpp
Core message types"] -MSG["message.hpp
Generic message pack/unpack"] -MOC["message_oriented_connection.cpp
TCP framing + padding"] -CFG["config.hpp
Protocol constants"] -end -subgraph "Peer and Node" -PC["peer_connection.cpp
Queueing + dispatch"] -NODE["node.cpp
Caching + propagation + processing"] -NAPI["node.hpp
Propagation data + callbacks"] -end -CM --> MSG -MSG --> MOC -CFG --> MOC -MOC --> PC -PC --> NODE -NAPI --> NODE -``` - -**Diagram sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L42-L573) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L33-L114) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L244-L300) -- [node.cpp](file://libraries/network/node.cpp#L112-L200) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) - -**Section sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L42-L573) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L33-L114) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L244-L300) -- [node.cpp](file://libraries/network/node.cpp#L112-L200) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) - -## Core Components -- Core message types: block_message, trx_message, item_id, and many handshake/control messages -- Generic message: message_header + payload + hash -- Transport: message_oriented_connection with fixed-size padding and bounds checking -- Node-level caching and propagation metadata -- Peer queueing and dispatch - -Key responsibilities: -- Define standardized message envelopes and types -- Pack/unpack messages with reflection-based raw serialization -- Enforce protocol version and message size limits -- Track message propagation and deduplicate via caches keyed by hashes - -**Section sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L54-L151) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L42-L106) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L160-L183) -- [node.cpp](file://libraries/network/node.cpp#L112-L176) - -## Architecture Overview -The system separates concerns across layers: -- Protocol types live in core_messages.hpp -- Generic message packaging is in message.hpp -- Transport framing and padding are in message_oriented_connection.cpp -- Node-level caching and propagation tracking are in node.cpp and node.hpp -- Peer queueing and dispatch are in peer_connection.cpp - -```mermaid -sequenceDiagram -participant App as "Application" -participant Node as "node.cpp" -participant Peer as "peer_connection.cpp" -participant Conn as "message_oriented_connection.cpp" -participant Net as "TCP Socket" -App->>Node : "Create message (e.g., block_message)" -Node->>Peer : "Queue message for sending" -Peer->>Conn : "send_message(message)" -Conn->>Net : "Write padded frame (header + payload)" -Net-->>Conn : "ACK" -Conn-->>Peer : "Sent" -Peer-->>Node : "Ack" -Node-->>App : "Queuing complete" -``` - -**Diagram sources** -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L70-L105) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L237-L283) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L272-L297) -- [node.cpp](file://libraries/network/node.cpp#L1372-L1380) - -## Detailed Component Analysis - -### Core Message Types: block_message, trx_message, item_id -- item_id: identifies items by type and hash -- block_message: carries a signed block plus its block_id -- trx_message: carries a signed transaction -- Additional core_message_type_enum entries cover inventory, fetch, hello, and connection lifecycle messages - -```mermaid -classDiagram -class item_id { -+uint32_t item_type -+item_hash_t item_hash -+operator==(other) bool -} -class block_message { -+signed_block block -+block_id_type block_id -} -class trx_message { -+signed_transaction trx -} -class message { -+uint32_t size -+uint32_t msg_type -+std : : vector data -+id() message_hash_type -+as() T -} -message <|-- block_message -message <|-- trx_message -item_id --> block_message : "used in inventory/fetch" -item_id --> trx_message : "used in inventory/fetch" -``` - -**Diagram sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L54-L125) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L42-L106) - -**Section sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L54-L125) -- [core_messages.cpp](file://libraries/network/core_messages.cpp#L30-L49) - -### Generic Message Packaging and Serialization -- message_header: size and msg_type -- message: extends header with payload data -- Constructor packs a typed object into raw bytes -- as() validates msg_type and unpacks the payload -- id() computes a deterministic hash of the serialized payload - -```mermaid -flowchart TD -Start(["Create typed message"]) --> Pack["Pack object into raw bytes"] -Pack --> SetHeader["Set size and msg_type"] -SetHeader --> BuildMsg["Build message (header + data)"] -BuildMsg --> Hash["Compute message hash"] -Hash --> Done(["Ready to send"]) -``` - -**Diagram sources** -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L70-L105) - -**Section sources** -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L42-L106) - -### Transport Framing, Padding, and Bounds Checking -- Fixed 8-byte header with size and msg_type -- Read loop reads header, validates size against MAX_MESSAGE_SIZE, allocates payload with padding, reads remainder, truncates padding -- Send pads total frame length to 16-byte multiples -- Thread-safety guard prevents concurrent sends - -```mermaid -flowchart TD -RStart(["Read loop"]) --> ReadHdr["Read 8-byte header"] -ReadHdr --> CheckSize{"size <= MAX_MESSAGE_SIZE?"} -CheckSize --> |No| Error["Log error and disconnect"] -CheckSize --> |Yes| Alloc["Allocate payload with padding"] -Alloc --> ReadBody["Read remaining bytes"] -ReadBody --> Trunc["Truncate padding"] -Trunc --> Dispatch["Delegate.on_message()"] -Dispatch --> RStart -``` - -**Diagram sources** -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L160-L183) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L38-L39) - -**Section sources** -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L38-L39) - -### Protocol Versioning and Compliance Validation -- Protocol version constant is defined centrally -- hello_message and connection_rejected_message carry core_protocol_version -- Rejection reasons include client_too_old and different_chain - -```mermaid -sequenceDiagram -participant A as "Peer A (hello)" -participant B as "Peer B (node)" -A->>B : "hello_message(core_protocol_version, ...)" -alt "Version mismatch or incompatible chain" -B-->>A : "connection_rejected_message(reason_code, ...)" -else "Compatible" -B-->>A : "connection_accepted_message()" -end -``` - -**Diagram sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L306) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L26) - -**Section sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L96-L98) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L306) - -### Message Routing, Delivery Guarantees, Ordering, and Propagation Tracking -- Peer queueing ensures ordered transmission per peer -- Node maintains a blockchain-tied message cache keyed by message hash and content hash -- message_propagation_data tracks received_time, validated_time, and originating_peer -- Duplicate detection uses message hash and content hash indices - -```mermaid -classDiagram -class message_propagation_data { -+time_point received_time -+time_point validated_time -+node_id_t originating_peer -} -class blockchain_tied_message_cache { -+cache_message(message, hash, propagation_data, content_hash) -+get_message(hash) message -+get_message_propagation_data(content_hash) message_propagation_data -+block_accepted() -} -message_propagation_data <.. blockchain_tied_message_cache : "stored with cached messages" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) -- [node.cpp](file://libraries/network/node.cpp#L112-L176) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) -- [node.cpp](file://libraries/network/node.cpp#L112-L200) - -### Message Validation Workflows and Duplicate Detection -- On receipt, message is validated and processed -- Duplicate detection leverages content hash index to avoid reprocessing identical transactions/blocks -- Propagation metadata is recorded for analytics and debugging - -```mermaid -flowchart TD -In(["Receive message"]) --> Validate["Validate type and payload"] -Validate --> Dedup{"Duplicate content?"} -Dedup --> |Yes| Skip["Skip processing"] -Dedup --> |No| Cache["Cache message with propagation data"] -Cache --> Process["Process item (block/trx)"] -Process --> Broadcast["Advertise to peers"] -Skip --> End(["Done"]) -Broadcast --> End -``` - -**Diagram sources** -- [node.cpp](file://libraries/network/node.cpp#L187-L200) -- [node.cpp](file://libraries/network/node.cpp#L729-L745) - -**Section sources** -- [node.cpp](file://libraries/network/node.cpp#L187-L200) -- [node.cpp](file://libraries/network/node.cpp#L729-L745) - -### Examples of Message Creation, Transmission, and Processing Patterns -- Creating a block_message: construct with a signed_block; message wrapper sets msg_type and serializes -- Queuing for sending: peer_connection queues messages; send loop transmits with padding -- Processing: node delegates to appropriate handler; for blocks, it may trigger fork selection and broadcasting - -```mermaid -sequenceDiagram -participant Producer as "Block Producer" -participant Node as "node.cpp" -participant Peer as "peer_connection.cpp" -participant Transport as "message_oriented_connection.cpp" -Producer->>Node : "Produce block_message" -Node->>Peer : "Queue for broadcast" -Peer->>Transport : "send_message(message)" -Transport-->>Peer : "Sent" -Peer-->>Node : "Ack" -``` - -**Diagram sources** -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L70-L75) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L272-L297) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L237-L283) - -**Section sources** -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L70-L75) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L272-L297) -- [node.cpp](file://libraries/network/node.cpp#L1372-L1380) - -### Compression, Fragmentation Handling, and Protocol Upgrade Mechanisms -- Compression: not implemented in the referenced files; payload is raw serialized data -- Fragmentation: handled by fixed-size padding and bounded reads/writes; MAX_MESSAGE_SIZE enforces upper bound -- Protocol upgrades: controlled by core_protocol_version; peers exchange hello and reject incompatible versions - -**Section sources** -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L38-L39) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L306) - -### Implementing Custom Message Types and Extension Points -- Define a new struct with a static type member matching a core_message_type_enum value -- Reflect the struct for serialization -- Ensure msg_type is registered and handled in the node’s dispatcher -- Consider adding to inventory/fetch workflows if the item should be gossiped - -Guidance: -- Keep msg_type unique within the core_message_type_enum range -- Use FC_REFLECT macros for serialization compatibility -- Respect MAX_MESSAGE_SIZE and avoid excessive payloads -- For gossipable items, integrate with inventory and fetch handlers - -**Section sources** -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L95) -- [core_messages.cpp](file://libraries/network/core_messages.cpp#L30-L49) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L478-L560) - -## Dependency Analysis -- core_messages.hpp depends on protocol types and reflection -- message.hpp depends on raw serialization and hashing -- message_oriented_connection.cpp depends on config constants and socket I/O -- node.cpp depends on peer_connection and message propagation metadata -- node.hpp defines the propagation data structure and delegate callbacks - -```mermaid -graph LR -CFG["config.hpp"] --> MOC["message_oriented_connection.cpp"] -MSG["message.hpp"] --> MOC -CMH["core_messages.hpp"] --> MSG -CMH --> MOC -PC["peer_connection.cpp"] --> MOC -NODE["node.cpp"] --> PC -NAPI["node.hpp"] --> NODE -``` - -**Diagram sources** -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L33-L114) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L26-L50) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L29-L31) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L24-L29) -- [node.cpp](file://libraries/network/node.cpp#L64-L66) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) - -**Section sources** -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L26-L50) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L33-L114) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L29-L31) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L24-L29) -- [node.cpp](file://libraries/network/node.cpp#L64-L66) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) - -## Performance Considerations -- Fixed 16-byte padding reduces alignment overhead but increases bandwidth; acceptable given MAX_MESSAGE_SIZE is enforced -- Message size validation prevents oversized packets -- Queueing and per-peer send guards reduce contention -- Blockchain-tied cache evicts based on block clock to bound memory - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and diagnostics: -- Oversized messages: MAX_MESSAGE_SIZE exceeded triggers logs; inspect payload sizes -- Type mismatches: as() throws if msg_type does not match expected type -- Connection closure: read_loop handles EOF and exceptions, invoking on_connection_closed -- Send concurrency: assertion prevents concurrent send_message invocations - -Actions: -- Verify protocol version compatibility in hello messages -- Monitor logs for “message transmission failed” and “disconnected” -- Confirm queue sizes and cache eviction behavior - -**Section sources** -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L168-L169) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L87-L104) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L190-L234) - -## Conclusion -The messaging layer provides a robust, extensible foundation for network communication: -- Strong typing and reflection-based serialization -- Reliable framing with bounds checking and padding -- Clear protocol versioning and rejection semantics -- Built-in propagation tracking and duplicate detection -- Practical patterns for broadcasting and processing - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Appendix A: Message Lifecycle Summary -- Creation: Construct typed message; message wrapper serializes and sets type -- Framing: Header + padded payload written to socket -- Reception: Header read, payload read, padding truncated, type checked -- Processing: Delegate invoked; node caches and tracks propagation -- Broadcasting: Inventory and fetch messages coordinate propagation - -**Section sources** -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp#L70-L105) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L160-L183) -- [node.cpp](file://libraries/network/node.cpp#L112-L176) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Network Library.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Network Library.md deleted file mode 100644 index b5326e6ca8..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Network Library.md +++ /dev/null @@ -1,919 +0,0 @@ -# Network Library - - -**Referenced Files in This Document** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [core_messages.cpp](file://libraries/network/core_messages.cpp) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [peer_database.cpp](file://libraries/network/peer_database.cpp) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) - - -## Update Summary -**Changes Made** -- Enhanced synchronization logging section to document new CLOG_GRAY ANSI color code for gray-colored log output -- Updated logging verbosity documentation to reflect systematic replacement of fc_ilog with fc_dlog throughout sync process -- Added documentation for enhanced logging in fetch_sync_items_loop, blockchain item inventory handling, sync status updates, and sync start procedures with enhanced visibility -- Updated troubleshooting guidance with new logging patterns and gray color coding -- Enhanced logging system documentation with comprehensive fc_dlog vs fc_ilog usage patterns - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Peer Statistics and Metrics System](#peer-statistics-and-metrics-system) -7. [Peer Information Handling and IP Extraction](#peer-information-handling-and-ip-extraction) -8. [Programmatic Synchronization Control](#programmatic-synchronization-control) -9. [Enhanced Peer Connection Logging](#enhanced-peer-connection-logging) -10. [Enhanced Synchronization Logging System](#enhanced-synchronization-logging-system) -11. [Dependency Analysis](#dependency-analysis) -12. [Performance Considerations](#performance-considerations) -13. [Troubleshooting Guide](#troubleshooting-guide) -14. [Conclusion](#conclusion) - -## Introduction -This document describes the Network Library that implements peer-to-peer communication and network protocol for the VIZ node. It covers the node management layer, peer connection orchestration, standard network messages, secure transport, peer address management, and message serialization. The library provides a robust foundation for blockchain synchronization, transaction broadcasting, and block propagation across a distributed network. - -**Updated** Enhanced with comprehensive peer statistics logging system including latency tracking, blocking status reporting, periodic statistics collection, improved peer information handling with reliable IP address extraction and reduced conversion overhead. Added programmatic synchronization control through the new `resync()` method for improved network recovery from various network states. The virtual `resync()` method provides extensibility for derived classes to customize synchronization restart behavior. Enhanced peer connection logging now supports color-coded output for better visibility of network events. **Enhanced synchronization logging system with new CLOG_GRAY ANSI color code and systematic fc_dlog usage throughout sync process for improved log verbosity and clarity.** - -## Project Structure -The network library is organized into cohesive modules: -- Node management and synchronization orchestration with virtual resync method support -- Peer connection lifecycle and message queues with enhanced logging -- Standard network message definitions -- Secure TCP transport with ECDH key exchange -- Peer address database and topology maintenance -- Message serialization/deserialization framework -- Configuration constants for protocol behavior -- **Peer statistics and metrics collection system with improved IP address extraction** -- **P2P plugin integration for peer monitoring and statistics with color-coded logging** -- **Programmatic synchronization control for network recovery with virtual method extensibility** -- **Enhanced synchronization logging system with CLOG_GRAY color coding and fc_dlog usage patterns** - -```mermaid -graph TB -subgraph "Network Layer" -N["node.hpp
node.cpp"] -PC["peer_connection.hpp
peer_connection.cpp"] -MSG["message.hpp"] -CM["core_messages.hpp
core_messages.cpp"] -STCP["stcp_socket.hpp
stcp_socket.cpp"] -PD["peer_database.hpp
peer_database.cpp"] -MOC["message_oriented_connection.hpp"] -CFG["config.hpp"] -STATS["Statistics System"] -P2P["p2p_plugin.cpp"] -RESYNC["Virtual resync() Method"] -SIMNET["simulated_network"] -COLOR["Color Logging Support"] -SYNCLOG["Enhanced Synchronization Logging"] -GRAY["CLOG_GRAY ANSI Color Code"] -FCDLOG["fc_dlog Usage Patterns"] -end -N --> PC -N --> PD -N --> CM -N --> RESYNC -PC --> MOC -PC --> STCP -PC --> MSG -CM --> MSG -STCP --> MOC -PD --> N -CFG --> N -CFG --> PC -CFG --> STCP -STATS --> N -STATS --> PC -P2P --> STATS -P2P --> RESYNC -P2P --> COLOR -SIMNET --> RESYNC -SYNCLOG --> N -SYNCLOG --> PC -GRAY --> SYNCLOG -FCDLOG --> SYNCLOG -``` - -**Diagram sources** -- [node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) -- [core_messages.hpp:72-573](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L573) -- [stcp_socket.hpp:37-93](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message_oriented_connection.hpp:45-79](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [config.hpp:26-106](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) -- [node.cpp:5281-5286](file://libraries/network/node.cpp#L5281-L5286) -- [node.cpp:346-347](file://libraries/network/node.cpp#L346-L347) - -**Section sources** -- [node.hpp:1-355](file://libraries/network/include/graphene/network/node.hpp#L1-L355) -- [peer_connection.hpp:1-383](file://libraries/network/include/graphene/network/peer_connection.hpp#L1-L383) -- [core_messages.hpp:1-573](file://libraries/network/include/graphene/network/core_messages.hpp#L1-L573) -- [stcp_socket.hpp:1-99](file://libraries/network/include/graphene/network/stcp_socket.hpp#L1-L99) -- [peer_database.hpp:1-141](file://libraries/network/include/graphene/network/peer_database.hpp#L1-L141) -- [message.hpp:1-114](file://libraries/network/include/graphene/network/message.hpp#L1-L114) -- [message_oriented_connection.hpp:1-85](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L1-L85) -- [config.hpp:1-106](file://libraries/network/include/graphene/network/config.hpp#L1-L106) -- [p2p_plugin.cpp:1-742](file://plugins/p2p/p2p_plugin.cpp#L1-L742) - -## Core Components -- Node: Central orchestrator for peer discovery, connection management, synchronization, and message broadcasting with virtual resync method support. -- PeerConnection: Manages individual peer sessions, message queuing, inventory tracking, and negotiation states with enhanced logging capabilities. -- CoreMessages: Defines standardized message types for transactions, blocks, inventory, handshake, and operational commands. -- STCP Socket: Provides secure transport via ECDH key exchange and AES encryption. -- PeerDatabase: Maintains peer address records, connection history, and topology hints. -- Message: Encapsulates message headers, payload serialization, and type-safe deserialization. -- MessageOrientedConnection: Bridges secure sockets to message streams with event callbacks. -- **Statistics System: Collects and reports peer performance metrics, latency data, and connection statistics with improved IP address extraction reliability.** -- **P2P Plugin: Integrates peer monitoring, statistics collection, and network diagnostics with enhanced error handling and color-coded logging.** -- **Programmatic Synchronization Control: Enables manual restart of synchronization with all connected peers for network recovery scenarios through virtual method extensibility.** -- **Enhanced Logging: Supports color-coded output for better visibility of network events and peer connection states.** -- **Enhanced Synchronization Logging: Provides systematic fc_dlog usage with CLOG_GRAY color coding for improved synchronization process visibility.** - -**Section sources** -- [node.hpp:182-304](file://libraries/network/include/graphene/network/node.hpp#L182-L304) -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [core_messages.hpp:72-573](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L573) -- [stcp_socket.hpp:37-93](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) -- [message_oriented_connection.hpp:45-79](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) - -## Architecture Overview -The network stack layers securely transport protocol messages between nodes. The Node coordinates peer discovery and synchronization, PeerConnection handles per-peer state and queues, CoreMessages defines the protocol, STCP Socket provides secure transport, and PeerDatabase maintains connectivity hints. - -```mermaid -sequenceDiagram -participant App as "Application" -participant Node as "node" -participant Peer as "peer_connection" -participant MOC as "message_oriented_connection" -participant STCP as "stcp_socket" -participant Stats as "Statistics System" -participant P2P as "p2p_plugin" -participant Net as "Remote Peer" -App->>Node : "connect_to_endpoint(ep)" -Node->>Peer : "create peer_connection" -Peer->>MOC : "connect_to(ep)" -MOC->>STCP : "connect_to(ep)" -STCP->>Net : "TCP connect" -STCP->>Net : "ECDH key exchange" -STCP-->>MOC : "shared secret" -MOC-->>Peer : "secure socket ready" -Peer->>Stats : "record latency metrics" -Peer->>Peer : "send hello_message" -Peer-->>Node : "on_message(hello)" -Node-->>Peer : "broadcast inventory" -Peer-->>App : "handle_block/handle_transaction" -P2P->>Stats : "collect peer statistics" -Stats->>P2P : "enhanced IP address extraction" -Note over Node,P2P : "Virtual resync method support" -P2P->>Node : "resync()" -Node->>Node : "start_synchronizing()" -Note over Node : "Enhanced sync logging with CLOG_GRAY" -``` - -**Diagram sources** -- [node.cpp:780-790](file://libraries/network/node.cpp#L780-L790) -- [peer_connection.cpp:208-242](file://libraries/network/peer_connection.cpp#L208-L242) -- [stcp_socket.cpp:69-72](file://libraries/network/stcp_socket.cpp#L69-L72) -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) -- [node.cpp:5281-5286](file://libraries/network/node.cpp#L5281-L5286) - -## Detailed Component Analysis - -### Node Management (node.hpp, node.cpp) -The Node class is the central coordinator for peer discovery, connection orchestration, synchronization, and message broadcasting. It exposes APIs to: -- Configure listening endpoints and accept incoming connections -- Connect to seed nodes and maintain a peer pool -- Broadcast messages and synchronize with peers -- Track connection counts and network usage statistics -- Manage advanced parameters and peer advertising controls -- **Collect and report peer statistics and call performance metrics with improved IP address extraction** -- **Programmatic synchronization control through the virtual resync() method for extensible behavior** -- **Enhanced synchronization logging with systematic fc_dlog usage and CLOG_GRAY color coding** - -Key responsibilities: -- Peer pool management and connection limits -- Synchronization initiation and progress tracking -- Inventory advertisement and request routing -- Bandwidth monitoring and rate limiting -- Firewall detection and NAT traversal helpers -- **Statistics collection and reporting for network performance analysis with reliable peer information handling** -- **Programmatic synchronization restart for network recovery scenarios through virtual method override capability** -- **Comprehensive synchronization process logging with enhanced verbosity and color coding** - -```mermaid -classDiagram -class node { -+load_configuration(dir) -+listen_on_endpoint(ep, wait) -+accept_incoming_connections(bool) -+connect_to_endpoint(ep) -+listen_on_port(port, wait) -+get_actual_listening_endpoint() -+get_connected_peers() -+get_connection_count() -+broadcast(message) -+broadcast_transaction(trx) -+sync_from(item_id, hard_forks) -+set_total_bandwidth_limit(upload, download) -+network_get_info() -+network_get_usage_stats() -+get_potential_peers() -+disable_peer_advertising() -+get_call_statistics() -+resync() -} -class node_impl { --_active_connections --_handshaking_connections --_potential_peer_db --_tcp_server --_delegate --_rate_limiter -+p2p_network_connect_loop() -+fetch_sync_items_loop() -+fetch_items_loop() -+advertise_inventory_loop() -+bandwidth_monitor_loop() -+on_message(...) -+on_hello_message(...) -+on_connection_accepted_message(...) -+on_connection_rejected_message(...) -+on_address_request_message(...) -+on_address_message(...) -+on_fetch_blockchain_item_ids_message(...) -+on_blockchain_item_ids_inventory_message(...) -+on_fetch_items_message(...) -+on_item_not_available_message(...) -+on_item_ids_inventory_message(...) -+on_closing_connection_message(...) -+on_current_time_request_message(...) -+on_current_time_reply_message(...) -+on_check_firewall_message(...) -+on_check_firewall_reply_message(...) -+on_get_current_connections_request_message(...) -+on_get_current_connections_reply_message(...) -+on_connection_closed(...) -+get_call_statistics() -+start_synchronizing() -+resync() -} -class simulated_network { -+resync() override -} -node --> node_impl : "owns" -node <|-- simulated_network : "inherits" -``` - -**Diagram sources** -- [node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [node.cpp:424-799](file://libraries/network/node.cpp#L424-L799) -- [node.cpp:346-347](file://libraries/network/node.cpp#L346-L347) - -**Section sources** -- [node.hpp:182-304](file://libraries/network/include/graphene/network/node.hpp#L182-L304) -- [node.cpp:424-799](file://libraries/network/node.cpp#L424-L799) - -### Peer Connection (peer_connection.hpp, peer_connection.cpp) -PeerConnection encapsulates a single peer session, managing: -- Negotiation states (hello, accepted, rejected) -- Message queueing with real/virtual queued messages -- Inventory tracking and deduplication -- Request/response coordination -- Connection lifecycle (accept/connect/close/destroy) -- Rate limiting and throttling -- **Latency tracking and round-trip delay measurement** -- **Blocking status reporting and synchronization control** -- **Enhanced peer information handling with reliable IP address extraction** -- **Enhanced logging with color support for better visibility** - -```mermaid -stateDiagram-v2 -[*] --> Disconnected -Disconnected --> JustConnected : "connect_to()/accept()" -JustConnected --> ConnectionAccepted : "connection_accepted" -JustConnected --> ConnectionRejected : "connection_rejected" -ConnectionAccepted --> Connected : "negotiation_complete" -ConnectionRejected --> Closing : "close_connection()" -Connected --> Closing : "close_connection()" -Closing --> Closed : "on_connection_closed" -Closed --> [*] -``` - -**Diagram sources** -- [peer_connection.hpp:82-106](file://libraries/network/include/graphene/network/peer_connection.hpp#L82-L106) -- [peer_connection.cpp:169-206](file://libraries/network/peer_connection.cpp#L169-L206) - -Queueing and throttling: -- Real queued messages: full message payload copied -- Virtual queued messages: item_id only, generated on demand -- Size limits and backpressure to prevent memory pressure - -**Section sources** -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp:41-66](file://libraries/network/peer_connection.cpp#L41-L66) -- [peer_connection.cpp:310-354](file://libraries/network/peer_connection.cpp#L310-L354) - -### Core Messages (core_messages.hpp, core_messages.cpp) -Standardized message types for the network protocol: -- Transactions and blocks -- Inventory announcements and requests -- Blockchain ID synchronization -- Handshake and connection control -- Time synchronization and firewall checks -- Current connections reporting -- **Address information with latency metrics and enhanced peer details** - -Message type enumeration and structures define the protocol contract. Serialization is handled by the message wrapper. - -```mermaid -classDiagram -class message { -+uint32_t size -+uint32_t msg_type -+std : : vector data -+message(T& obj) -+T as~T~() -+id() uint160 -} -class core_messages { -<> -trx_message_type -block_message_type -hello_message_type -connection_accepted_message_type -connection_rejected_message_type -item_ids_inventory_message_type -blockchain_item_ids_inventory_message_type -fetch_blockchain_item_ids_message_type -fetch_items_message_type -item_not_available_message_type -address_request_message_type -address_message_type -closing_connection_message_type -current_time_request_message_type -current_time_reply_message_type -check_firewall_message_type -check_firewall_reply_message_type -get_current_connections_request_message_type -get_current_connections_reply_message_type -} -message <.. core_messages : "serialized/deserialized" -``` - -**Diagram sources** -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) -- [core_messages.hpp:72-573](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L573) -- [core_messages.cpp:30-49](file://libraries/network/core_messages.cpp#L30-L49) - -**Section sources** -- [core_messages.hpp:72-573](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L573) -- [core_messages.cpp:30-49](file://libraries/network/core_messages.cpp#L30-L49) -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) - -### Secure TCP Socket (stcp_socket.hpp, stcp_socket.cpp) -Provides secure transport using ECDH key exchange and AES encryption: -- Generates ephemeral keys and performs key exchange -- Initializes AES encoder/decoder with derived shared secret -- Enforces block-aligned reads/writes for cipher integrity -- Exposes secure read/write primitives - -```mermaid -flowchart TD -Start(["Connect/Accept"]) --> GenKey["Generate ephemeral keypair"] -GenKey --> Exchange["Exchange serialized public keys"] -Exchange --> Derive["Derive shared secret via ECDH"] -Derive --> InitAES["Initialize AES encoder/decoder"] -InitAES --> Ready["Secure socket ready"] -Ready --> ReadWrite["Encrypt/decrypt reads/writes"] -ReadWrite --> Close["Close socket"] -``` - -**Diagram sources** -- [stcp_socket.cpp:49-72](file://libraries/network/stcp_socket.cpp#L49-L72) -- [stcp_socket.cpp:132-177](file://libraries/network/stcp_socket.cpp#L132-L177) - -**Section sources** -- [stcp_socket.hpp:37-93](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [stcp_socket.cpp:49-177](file://libraries/network/stcp_socket.cpp#L49-L177) - -### Peer Database (peer_database.hpp, peer_database.cpp) -Maintains persistent records of potential peers: -- Endpoint, last seen time, and disposition tracking -- Connection attempt counters and failure reasons -- Iteration over entries sorted by last seen time -- JSON-backed persistence with pruning - -```mermaid -classDiagram -class peer_database { -+open(path) -+close() -+clear() -+erase(endpoint) -+update_entry(record) -+lookup_or_create_entry_for_endpoint(endpoint) -+lookup_entry_for_endpoint(endpoint) -+begin() -+end() -+size() -} -class potential_peer_record { -+endpoint -+last_seen_time -+last_connection_disposition -+last_connection_attempt_time -+number_of_successful_connection_attempts -+number_of_failed_connection_attempts -+last_error -} -peer_database --> potential_peer_record : "stores" -``` - -**Diagram sources** -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [peer_database.cpp:41-82](file://libraries/network/peer_database.cpp#L41-L82) - -**Section sources** -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) - -### Message Serialization (message.hpp) -Defines the message envelope and serialization: -- Header with size and type -- Payload storage and hashing -- Template-based pack/unpack for protocol messages -- Type safety and runtime checks - -```mermaid -flowchart TD -Pack["Pack T -> raw::pack"] --> Header["Set size/msg_type"] -Header --> Serialize["Store in data vector"] -Serialize --> Message["message object"] -Message --> As["as()"] -As --> Verify["Verify msg_type"] -Verify --> Unpack["raw::unpack to T"] -Unpack --> Result["Return T"] -``` - -**Diagram sources** -- [message.hpp:70-105](file://libraries/network/include/graphene/network/message.hpp#L70-L105) - -**Section sources** -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) - -## Peer Statistics and Metrics System - -**Updated** The network library now includes a comprehensive peer statistics logging system that provides detailed insights into peer performance and network health with improved IP address extraction reliability. - -### Latency Tracking System -The system tracks round-trip delay and clock offset for each peer connection: - -- **Round Trip Delay**: Measures the time for a message to travel to a peer and back -- **Clock Offset**: Calculates time difference between local and remote peer clocks -- **Latency Reporting**: Exposes peer latency in milliseconds for monitoring and selection - -```mermaid -flowchart TD -Start(["Time Synchronization Request"]) --> SendReq["Send current_time_request_message"] -SendReq --> WaitResp["Wait for reply"] -WaitResp --> CalcDelay["Calculate round_trip_delay"] -CalcDelay --> CalcOffset["Calculate clock_offset"] -CalcOffset --> Report["Report to peer_status"] -Report --> Monitor["Monitor peer performance"] -``` - -**Diagram sources** -- [node.cpp:3710-3723](file://libraries/network/node.cpp#L3710-L3723) - -### Blocking Status Reporting -The system monitors and reports peer blocking conditions that affect synchronization: - -- **Inhibit Fetching Sync Blocks**: Tracks peers that are temporarily blocked from receiving sync data -- **Soft Ban Mechanism**: Implements temporary blocking for peers on losing forks during emergency consensus -- **Blocking Reasons**: Reports specific reasons for peer blocking (fork rejection, etc.) - -### Enhanced Peer Information Reporting -The peer information system now includes comprehensive metrics with improved reliability: - -- **Latency Metrics**: `latency_ms` field showing round-trip delay in milliseconds -- **Blocking Status**: `is_blocked` boolean indicating if peer is currently blocked -- **Blocking Reason**: `blocked_reason` field explaining why peer is blocked -- **Connection Quality**: Firewall status, connection duration, and bandwidth metrics -- **Peer Capabilities**: Platform information, version details, and feature support -- **IP Address Extraction**: Reliable extraction with fallback handling for unknown addresses - -### Periodic Statistics Collection -The system implements continuous statistics collection: - -- **Call Statistics**: Tracks method execution times, delays, and performance metrics -- **Network Usage**: Monitors upload/download rates over various time periods -- **Performance Monitoring**: Collects rolling averages for bandwidth utilization -- **Delegate Thread Coordination**: Measures delays between P2P thread and delegate thread execution - -### Statistics Collection Classes - -```mermaid -classDiagram -class call_statistics_collector { -+time_point call_requested_time -+time_point begin_execution_time -+time_point execution_completed_time -+const char* method_name -+execute() -+starting_execution() -+execution_completed() -} -class statistics_gathering_node_delegate_wrapper { -+node_delegate* delegate -+call_stats_accumulator* execution_accumulator -+call_stats_accumulator* delay_before_accumulator -+call_stats_accumulator* delay_after_accumulator -+get_call_statistics() -} -call_statistics_collector --> statistics_gathering_node_delegate_wrapper : "collects" -``` - -**Diagram sources** -- [node.cpp:312-381](file://libraries/network/node.cpp#L312-L381) -- [node.cpp:383-420](file://libraries/network/node.cpp#L383-L420) - -**Section sources** -- [node.hpp:173-179](file://libraries/network/include/graphene/network/node.hpp#L173-L179) -- [node.cpp:4920-4970](file://libraries/network/node.cpp#L4920-L4970) -- [node.cpp:312-381](file://libraries/network/node.cpp#L312-L381) -- [node.cpp:5128-5131](file://libraries/network/node.cpp#L5128-L5131) -- [core_messages.hpp:322-346](file://libraries/network/include/graphene/network/core_messages.hpp#L322-L346) -- [core_messages.hpp:428-448](file://libraries/network/include/graphene/network/core_messages.hpp#L428-L448) - -## Peer Information Handling and IP Extraction - -**Updated** Critical bug fix implemented in peer information handling that improves IP address extraction reliability and reduces potential conversion overhead in the P2P networking layer. - -### Improved IP Address Extraction Reliability -The system now features enhanced IP address extraction with comprehensive error handling: - -- **Safe IP Address Retrieval**: Uses `static_cast(peer_info.host.get_address())` for reliable address extraction -- **Fallback Error Handling**: Implements try-catch block to handle extraction failures gracefully -- **Default Value Provision**: Sets IP to "(unknown)" when extraction fails, preventing crashes -- **Port Extraction**: Direct port extraction using `peer_info.host.port()` with proper type casting -- **Address Key Generation**: Creates reliable `addr_key = ip + ":" + std::to_string(port)` for peer identification - -### Reduced Conversion Overhead -The new implementation minimizes conversion overhead through: - -- **Direct String Casting**: Uses `static_cast()` for immediate conversion without intermediate steps -- **Efficient Port Handling**: Direct port extraction avoids unnecessary string conversions -- **Optimized Address Key Creation**: Single-line address key generation reduces computational overhead -- **Minimal Memory Allocation**: Reduces temporary string allocations during peer information processing - -### Enhanced Peer Statistics Processing -The P2P plugin now processes peer statistics with improved reliability: - -- **Latency Metrics Collection**: Extracts `latency_ms` with proper integer conversion -- **Bytes Received Tracking**: Processes `bytesrecv` with unsigned integer handling -- **Blocking Status Monitoring**: Handles `is_blocked` boolean values reliably -- **Reason Analysis**: Captures `blocked_reason` strings for diagnostic purposes -- **Delta Calculation**: Computes byte delta with overflow protection - -```mermaid -flowchart TD -Start(["Peer Information Processing"]) --> ExtractIP["Extract IP address safely"] -ExtractIP --> TryCatch{"Try-Catch Block"} -TryCatch --> |Success| ValidIP["Use extracted IP"] -TryCatch --> |Failure| DefaultIP["Set IP to '(unknown)'"] -ValidIP --> ExtractPort["Extract port"] -DefaultIP --> ExtractPort -ExtractPort --> CreateKey["Create addr_key"] -CreateKey --> ProcessMetrics["Process latency, bytes, blocking"] -ProcessMetrics --> CalculateDelta["Calculate byte delta"] -CalculateDelta --> LogStats["Log peer statistics"] -``` - -**Diagram sources** -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) -- [node.cpp:4900-4970](file://libraries/network/node.cpp#L4900-L4970) - -### Peer Information Data Flow -The enhanced system processes peer information through a structured pipeline: - -1. **Endpoint Extraction**: Safely extracts peer endpoint with error handling -2. **Metric Collection**: Gathers latency, bytes received, blocking status, and reasons -3. **Delta Computation**: Calculates byte transfer differences with overflow protection -4. **Statistics Logging**: Outputs comprehensive peer statistics with improved reliability - -**Section sources** -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) -- [node.cpp:4900-4970](file://libraries/network/node.cpp#L4900-L4970) - -## Programmatic Synchronization Control - -**Updated Section** The network library now provides programmatic control over synchronization through the virtual `resync()` method, enabling manual restart of synchronization with all connected peers and supporting extensible behavior in derived classes. - -### Virtual Resync Method Implementation -The `resync()` method provides a clean interface for forcing synchronization restart with enhanced extensibility: - -- **Method Purpose**: Restarts synchronization with all currently connected peers -- **Virtual Design**: Declared as virtual in the base `node` class, allowing derived classes to override behavior -- **Implementation**: Calls `start_synchronizing()` which iterates through all active connections -- **Logging**: Emits detailed log messages showing the number of connected peers being restarted -- **Thread Safety**: Verified to run on the correct thread using `VERIFY_CORRECT_THREAD()` - -### Enhanced Extensibility for Derived Classes -The virtual nature of `resync()` allows for specialized behavior in derived classes: - -- **Base Class Behavior**: Default implementation restarts synchronization with all active peers -- **Simulated Network Override**: `simulated_network` class provides empty implementation for testing -- **Custom Implementations**: Derived classes can override `resync()` to implement custom restart logic -- **Consistent Interface**: All implementations follow the same virtual method contract - -### Synchronization Restart Process -When `resync()` is called, the following sequence occurs: - -1. **Connection Enumeration**: Iterates through all currently active peer connections -2. **Individual Restart**: Calls `start_synchronizing_with_peer()` for each connected peer -3. **State Reset**: Forces peers to re-establish synchronization state -4. **Inventory Refresh**: Peers re-advertise their inventory and synchronization status - -```mermaid -flowchart TD -Start(["resync() Called"]) --> CheckActive["Check Active Connections"] -CheckActive --> LoopPeers{"More Peers?"} -LoopPeers --> |Yes| StartPeer["start_synchronizing_with_peer(peer)"] -StartPeer --> LoopPeers -LoopPeers --> |No| LogMsg["Log restart message"] -LogMsg --> Complete["Resync Complete"] -``` - -**Diagram sources** -- [node.cpp:5281-5286](file://libraries/network/node.cpp#L5281-L5286) -- [node.cpp:4164-4168](file://libraries/network/node.cpp#L4164-L4168) - -### Integration with P2P Plugin -The P2P plugin utilizes the `resync()` method for automatic network recovery: - -- **Stale Sync Detection**: Monitors for periods without block reception -- **Automatic Recovery**: When stale sync is detected, calls `resync()` to restart synchronization -- **Seed Reconnection**: Reconnects to seed nodes after resync to ensure continued connectivity -- **Configuration Options**: Controlled by `p2p-stale-sync-detection` and `p2p-stale-sync-timeout-seconds` options - -### Use Cases for Programmatic Resync -The `resync()` method is particularly useful for: - -- **Network Recovery**: Recovering from partial synchronization failures -- **Manual Intervention**: Operator-driven restart of synchronization -- **Debugging**: Clearing stuck synchronization states during development -- **Network State Changes**: Adapting to significant network topology changes -- **Testing Scenarios**: Simulated network testing with controlled synchronization restarts - -**Section sources** -- [node.hpp:298-304](file://libraries/network/include/graphene/network/node.hpp#L298-L304) -- [node.cpp:5281-5286](file://libraries/network/node.cpp#L5281-L5286) -- [node.cpp:4164-4168](file://libraries/network/node.cpp#L4164-L4168) -- [p2p_plugin.cpp:616-618](file://plugins/p2p/p2p_plugin.cpp#L616-L618) -- [node.cpp:346-347](file://libraries/network/node.cpp#L346-L347) - -## Enhanced Peer Connection Logging - -**Updated Section** The network library now supports enhanced peer connection logging with color-coded output for improved visibility and debugging capabilities. - -### Color Logging Support -The P2P plugin implements ANSI color codes for enhanced console logging: - -- **Cyan Color**: Used for general statistics and informational messages -- **White Color**: Used for detailed block processing information with latency metrics -- **Reset Code**: Returns to default terminal color after colored output -- **ANSI Escape Sequences**: Standard color codes supported by most terminals - -### Color-Coded Log Messages -The logging system provides visual distinction for different types of network events: - -- **Block Processing Messages**: White color for detailed block information and transaction counts -- **Statistics Messages**: Cyan color for periodic statistics and peer monitoring information -- **Error Messages**: Red color for critical errors and warnings (when applicable) -- **Debug Messages**: Orange color for detailed debugging information - -### Logging Implementation Details -The color logging is implemented using preprocessor macros: - -- **CLOG_CYAN**: ANSI escape sequence for cyan text -- **CLOG_WHITE**: ANSI escape sequence for white text -- **CLOG_RESET**: ANSI escape sequence to reset text color -- **Macro Usage**: Color codes are embedded directly in log message strings - -### Benefits of Color Logging -The enhanced logging system provides several advantages: - -- **Visual Distinction**: Different types of messages are easily distinguishable by color -- **Improved Debugging**: Color coding helps identify message categories quickly -- **Better Console Reading**: Color contrast makes log output more readable -- **Operator Efficiency**: Faster identification of important information during monitoring - -**Section sources** -- [p2p_plugin.cpp:16-19](file://plugins/p2p/p2p_plugin.cpp#L16-L19) -- [p2p_plugin.cpp:169-171](file://plugins/p2p/p2p_plugin.cpp#L169-L171) - -## Enhanced Synchronization Logging System - -**Updated Section** The network library now features an enhanced synchronization logging system with new CLOG_GRAY ANSI color code and systematic replacement of fc_ilog with fc_dlog throughout the sync process for improved log verbosity and clarity. - -### CLOG_GRAY ANSI Color Code Implementation -The synchronization system introduces a new ANSI color code specifically for gray-colored log output: - -- **CLOG_GRAY Definition**: `#define CLOG_GRAY "\033[90m"` for dark gray text color -- **CLOG_RESET Definition**: `#define CLOG_RESET "\033[0m"` for resetting text color -- **ANSI Escape Sequences**: Standard color codes compatible with most modern terminals -- **Usage Pattern**: Embedded within log messages to provide visual hierarchy - -### Systematic fc_dlog Usage Patterns -The synchronization system has undergone systematic replacement of fc_ilog with fc_dlog for enhanced logging verbosity: - -#### Fetch Synchronization Items Loop -- **Enhanced Item Status Logging**: Uses fc_dlog with CLOG_GRAY for detailed item availability status -- **Peer Condition Monitoring**: Logs peer inhibition status and idle conditions with color coding -- **Request Volume Tracking**: Monitors and logs the number of peers actively requesting blocks - -#### Blockchain Item Inventory Handling -- **Comprehensive Response Logging**: Uses fc_dlog with CLOG_GRAY for detailed inventory response analysis -- **Block Range Information**: Logs block number ranges and remaining item counts with color coding -- **Validation Diagnostics**: Enhanced logging of validation results and error conditions - -#### Sync Status Updates -- **Progress Tracking**: Uses fc_dlog for detailed synchronization progress updates -- **Peer Communication**: Logs peer-specific synchronization status with color coding -- **Resource Management**: Monitors and logs resource allocation during synchronization - -#### Sync Start Procedures -- **Initialization Logging**: Uses fc_dlog for comprehensive startup procedure logging -- **Configuration Validation**: Logs configuration validation results with detailed status -- **Resource Preparation**: Monitors and logs resource preparation for synchronization - -### Enhanced Logging Verbosity -The new logging system provides significantly improved verbosity: - -- **Detailed Peer Analysis**: Comprehensive logging of peer conditions and capabilities -- **Item Tracking**: Enhanced tracking and logging of synchronization items -- **Performance Metrics**: Detailed logging of performance metrics and optimization opportunities -- **Error Diagnostics**: Enhanced error logging with contextual information - -### Color-Coded Log Categories -The synchronization logging system categorizes information using color coding: - -- **Gray Text (CLOG_GRAY)**: Background synchronization processes and status updates -- **Green Text**: Successful operations and positive outcomes -- **Yellow Text**: Warning conditions and potential issues -- **Red Text**: Critical errors and failure conditions -- **Blue Text**: Debug information and detailed technical data - -### Benefits of Enhanced Synchronization Logging -The new logging system provides several advantages: - -- **Improved Visibility**: Color coding makes synchronization processes easier to understand -- **Better Debugging**: Enhanced verbosity helps identify synchronization issues quickly -- **Performance Monitoring**: Detailed logging enables performance optimization -- **Operator Efficiency**: Clear visual hierarchy helps operators monitor network health -- **Troubleshooting Support**: Comprehensive logging aids in diagnosing complex synchronization issues - -**Section sources** -- [node.cpp:81-81](file://libraries/network/node.cpp#L81-L81) -- [node.cpp:1187-1194](file://libraries/network/node.cpp#L1187-L1194) -- [node.cpp:1200-1202](file://libraries/network/node.cpp#L1200-L1202) -- [node.cpp:2651-2663](file://libraries/network/node.cpp#L2651-L2663) -- [node.cpp:2772-2779](file://libraries/network/node.cpp#L2772-L2779) -- [node.cpp:2790-2796](file://libraries/network/node.cpp#L2790-L2796) - -## Dependency Analysis -The network components depend on each other in a layered fashion: -- Node depends on PeerConnection, PeerDatabase, and CoreMessages -- PeerConnection depends on MessageOrientedConnection and STCP Socket -- MessageOrientedConnection depends on STCP Socket and Message -- CoreMessages depends on Protocol types and Message -- Config constants drive behavior across components -- **Statistics system integrates with Node and PeerConnection for metrics collection** -- **P2P plugin integrates with statistics system for enhanced peer monitoring** -- **Resync functionality integrates with Node synchronization system and supports virtual method extensibility** -- **Color logging integrates with P2P plugin for enhanced console output visualization** -- **Enhanced synchronization logging integrates with Node synchronization system and uses CLOG_GRAY color coding** -- **Systematic fc_dlog usage integrates throughout sync process for improved logging verbosity** - -```mermaid -graph LR -Node["node.hpp/.cpp"] --> PeerConn["peer_connection.hpp/.cpp"] -Node --> PeerDB["peer_database.hpp/.cpp"] -Node --> CoreMsg["core_messages.hpp/.cpp"] -Node --> Resync["Virtual resync() Method"] -PeerConn --> Msg["message.hpp"] -PeerConn --> MOC["message_oriented_connection.hpp"] -PeerConn --> STCP["stcp_socket.hpp/.cpp"] -CoreMsg --> Msg -STCP --> MOC -Node --> Cfg["config.hpp"] -PeerConn --> Cfg -STCP --> Cfg -Stats["Statistics System"] --> Node -Stats --> PeerConn -P2P["p2p_plugin.cpp"] --> Stats -P2P --> Resync -P2P --> Color["Color Logging"] -SimNet["simulated_network"] --> Resync -SyncLog["Enhanced Synchronization Logging"] --> Node -SyncLog --> PeerConn -Gray["CLOG_GRAY Color Code"] --> SyncLog -FCDLog["fc_dlog Usage Patterns"] --> SyncLog -``` - -**Diagram sources** -- [node.hpp:26-28](file://libraries/network/include/graphene/network/node.hpp#L26-L28) -- [peer_connection.hpp:26-29](file://libraries/network/include/graphene/network/peer_connection.hpp#L26-L29) -- [core_messages.hpp:26-28](file://libraries/network/include/graphene/network/core_messages.hpp#L26-L28) -- [stcp_socket.hpp:26-28](file://libraries/network/include/graphene/network/stcp_socket.hpp#L26-L28) -- [message_oriented_connection.hpp:26-27](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L26-L27) -- [config.hpp:26-106](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) - -**Section sources** -- [node.hpp:26-28](file://libraries/network/include/graphene/network/node.hpp#L26-L28) -- [peer_connection.hpp:26-29](file://libraries/network/include/graphene/network/peer_connection.hpp#L26-L29) -- [core_messages.hpp:26-28](file://libraries/network/include/graphene/network/core_messages.hpp#L26-L28) -- [stcp_socket.hpp:26-28](file://libraries/network/include/graphene/network/stcp_socket.hpp#L26-L28) -- [message_oriented_connection.hpp:26-27](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L26-L27) -- [config.hpp:26-106](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) - -## Performance Considerations -- Connection limits: Desired and maximum connections are configurable to balance throughput and resource usage. -- Queueing: Per-peer message queue enforces a maximum size to prevent memory pressure. -- Inventory limits: Caps on advertised inventory prevent flooding and ensure timely block propagation. -- Prefetching: Interleaved fetching of IDs and items reduces latency during synchronization. -- Rate limiting: Bandwidth monitor tracks read/write rates and applies limits. -- Throttling: Transaction fetching can be inhibited during heavy load to prioritize block sync. -- **Statistics overhead**: Metrics collection adds minimal overhead while providing valuable performance insights. -- **Latency monitoring**: Round-trip delay tracking helps identify slow or problematic peers for connection optimization. -- **IP extraction efficiency**: Improved IP address extraction reduces CPU overhead and prevents crashes from malformed addresses. -- **Error handling**: Comprehensive try-catch blocks prevent cascading failures in peer information processing. -- **Resync efficiency**: Programmatic resync restarts only active connections, minimizing disruption to healthy peers. -- **Virtual method overhead**: Virtual dispatch adds minimal overhead while providing extensibility benefits. -- **Color logging overhead**: ANSI color codes add minimal overhead while significantly improving log readability. -- **Enhanced sync logging overhead**: New CLOG_GRAY color coding and fc_dlog usage adds minimal overhead while dramatically improving synchronization visibility. -- **Logging verbosity optimization**: Systematic fc_dlog usage provides better performance than fc_ilog in debug mode. - -## Troubleshooting Guide -Common issues and diagnostics: -- Connection failures: Review peer database entries and last connection dispositions. -- Handshake errors: Validate protocol version and chain ID mismatches. -- Message deserialization errors: Ensure message types match and payloads are intact. -- Memory pressure: Monitor queue sizes and reduce advertised inventory. -- Time synchronization: Use current time request/reply messages to detect clock skew. -- **Latency issues**: Monitor `latency_ms` field to identify slow peers affecting synchronization. -- **Blocking peers**: Check `is_blocked` and `blocked_reason` fields to diagnose synchronization problems. -- **Performance bottlenecks**: Use call statistics to identify slow methods and optimize performance. -- **IP extraction failures**: Monitor for "(unknown)" IP addresses indicating extraction errors. -- **Statistics logging issues**: Verify P2P plugin configuration for statistics collection. -- **Synchronization stalls**: Use `resync()` method to manually restart synchronization with all peers. -- **Virtual method conflicts**: Ensure derived classes properly override `resync()` when extending functionality. -- **Color logging issues**: Verify terminal supports ANSI color codes for proper log output formatting. -- **Enhanced sync logging issues**: Verify CLOG_GRAY color code compatibility and fc_dlog macro definitions. -- **Logging verbosity problems**: Check debug level configuration for fc_dlog vs fc_ilog usage patterns. - -Operational controls: -- Disable peer advertising for debugging isolated networks. -- Adjust bandwidth limits to stabilize performance under load. -- Inspect call statistics and connection counts for bottlenecks. -- **Monitor peer metrics**: Regularly review latency and blocking status for network health assessment. -- **Enable statistics logging**: Use `p2p-stats-enabled` option to activate peer monitoring. -- **Configure logging intervals**: Set appropriate `p2p-stats-interval` for desired monitoring frequency. -- **Configure stale sync detection**: Enable `p2p-stale-sync-detection` to automatically recover from stalled synchronization. -- **Manual resync control**: Use `resync()` method for operator-driven synchronization restarts. -- **Extensibility patterns**: Leverage virtual method design for custom synchronization behaviors in derived classes. -- **Color logging configuration**: Ensure terminal supports ANSI color codes for optimal log visualization. -- **Enhanced sync logging configuration**: Verify CLOG_GRAY color code and fc_dlog usage patterns are properly configured. -- **Logging verbosity tuning**: Adjust debug level settings to control fc_dlog vs fc_ilog logging intensity. - -**Section sources** -- [peer_database.hpp:39-45](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L45) -- [node.hpp:288-298](file://libraries/network/include/graphene/network/node.hpp#L288-L298) -- [message.hpp:85-105](file://libraries/network/include/graphene/network/message.hpp#L85-L105) -- [node.cpp:4920-4970](file://libraries/network/node.cpp#L4920-L4970) -- [p2p_plugin.cpp:500-560](file://plugins/p2p/p2p_plugin.cpp#L500-L560) - -## Conclusion -The Network Library provides a comprehensive, secure, and scalable foundation for peer-to-peer communication. Its modular design separates concerns between node orchestration, peer lifecycle management, protocol messaging, secure transport, and peer topology maintenance. With built-in performance controls, diagnostic capabilities, and extensible message types, it supports efficient blockchain synchronization and robust network operation. - -**Updated** The enhanced peer statistics logging system significantly improves network observability by providing detailed latency tracking, blocking status reporting, and comprehensive peer metrics. The critical bug fix in peer information handling ensures reliable IP address extraction with reduced conversion overhead, preventing crashes and improving overall network stability. The integration with the P2P plugin provides comprehensive monitoring capabilities for operators and developers working with the VIZ blockchain network. The new virtual `resync()` method adds powerful programmatic control for network recovery, enabling manual restart of synchronization with all connected peers and improved resilience against various network states and synchronization failures. The virtual method design provides extensibility for derived classes to customize synchronization behavior while maintaining a consistent interface across the network library ecosystem. The enhanced peer connection logging with color support significantly improves the debugging and monitoring experience by providing visual distinction for different types of network events and messages. **The new enhanced synchronization logging system with CLOG_GRAY ANSI color code and systematic fc_dlog usage dramatically improves synchronization process visibility, providing detailed insights into peer conditions, item availability, and synchronization progress while maintaining minimal performance overhead.** \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Node Management.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Node Management.md deleted file mode 100644 index c207aabdbd..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Node Management.md +++ /dev/null @@ -1,756 +0,0 @@ -# Node Management - - -**Referenced Files in This Document** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [config.ini](file://share/vizd/config/config.ini) - - -## Update Summary -**Changes Made** -- Enhanced peer connection lifecycle management with improved disconnection flow and proper cleanup from _active_connections -- Implemented delayed peer deletion mechanism to prevent peers from staying in _active_connections indefinitely -- Improved remaining_item_count calculation in blockchain item ID requests -- Enhanced inventory deduplication logic with better tracking of items already advertised or requested -- Strengthened disconnect list management with proper peer state transitions - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Enhanced Peer Connection Lifecycle Management](#enhanced-peer-connection-lifecycle-management) -7. [Improved Disconnection Flow and Cleanup](#improved-disconnection-flow-and-cleanup) -8. [Enhanced Inventory Management and Deduplication](#enhanced-inventory-management-and-deduplication) -9. [Advanced Peer State Management](#advanced-peer-state-management) -10. [Performance Considerations](#performance-considerations) -11. [Troubleshooting Guide](#troubleshooting-guide) -12. [Conclusion](#conclusion) - -## Introduction -This document describes the Node Management component responsible for orchestrating network peers, maintaining connectivity, and managing blockchain synchronization in the P2P layer. It covers the node.hpp class interface, the node_delegate integration for blockchain callbacks, configuration and lifecycle APIs, peer management, and network broadcasting with inventory tracking. The documentation now includes comprehensive coverage of enhanced peer connection lifecycle management, improved disconnection flows, enhanced inventory deduplication, and advanced peer state management systems. - -## Project Structure -The Node Management functionality spans several headers and the implementation source file: -- Public interface: node.hpp defines the node class, node_delegate interface, and related types. -- Implementation: node.cpp implements the node lifecycle, peer orchestration, message routing, synchronization, and inventory management with enhanced connection lifecycle handling. -- Peer model: peer_connection.hpp/cpp defines the peer connection abstraction and state machine with comprehensive connection state management. -- Persistence: peer_database.hpp provides persistent peer discovery records. -- Messaging: message.hpp defines the generic message envelope; core_messages.hpp enumerates core P2P message types. -- Networking primitives: stcp_socket.hpp and message_oriented_connection.hpp underpin transport and framing. -- Emergency consensus: fork_database.hpp/cpp and database.cpp implement emergency mode functionality. - -```mermaid -graph TB -subgraph "Network Layer" -N["node.hpp
Public API"] -NI["node.cpp
Enhanced Implementation
with Lifecycle Management
Delayed Deletion
Improved Deduplication"] -PC["peer_connection.hpp
Enhanced Peer Abstraction
with State Management
Connection Lifecycle"] -PCC["peer_connection.cpp
Connection State Transitions
Inventory Management"] -PD["peer_database.hpp
Persistent Peers"] -MSG["message.hpp
Message Envelope"] -CM["core_messages.hpp
Core Message Types"] -end -subgraph "Transport" -STCP["stcp_socket.hpp"] -MOC["message_oriented_connection.hpp"] -end -subgraph "Emergency Consensus" -FD["fork_database.hpp
Emergency Mode"] -DBC["database.cpp
Consensus Logic"] -CFG["config.hpp
Emergency Constants"] -end -subgraph "DLT Mode Support" -DLP["dlt_block_log.cpp
DLT Block Log"] -P2P["p2p_plugin.cpp
Enhanced Error Logging"] -end -N --> NI -NI --> PC -NI --> PCC -NI --> PD -NI --> MSG -NI --> CM -PC --> STCP -PC --> MOC -NI --> FD -FD --> DBC -DBC --> CFG -NI --> DLP -NI --> P2P -``` - -**Diagram sources** -- [node.hpp:180-355](file://libraries/network/include/graphene/network/node.hpp#L180-L355) -- [node.cpp:869-905](file://libraries/network/node.cpp#L869-L905) -- [peer_connection.hpp:79-354](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L354) -- [peer_connection.cpp:419-448](file://libraries/network/peer_connection.cpp#L419-L448) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message.hpp:42-114](file://libraries/network/include/graphene/network/message.hpp#L42-L114) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [fork_database.hpp:111-120](file://libraries/chain/include/graphene/chain/fork_database.hpp#L111-L120) -- [database.cpp:4334-4463](file://libraries/chain/database.cpp#L4334-L4463) -- [config.hpp:110-123](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L123) -- [dlt_block_log.cpp:368-379](file://libraries/chain/dlt_block_log.cpp#L368-L379) -- [p2p_plugin.cpp:330-360](file://plugins/p2p/p2p_plugin.cpp#L330-L360) - -**Section sources** -- [node.hpp:180-355](file://libraries/network/include/graphene/network/node.hpp#L180-L355) -- [node.cpp:869-905](file://libraries/network/node.cpp#L869-L905) - -## Core Components -- node class: Provides P2P orchestration, configuration, peer management, and broadcast APIs with comprehensive connection lifecycle management and enhanced cleanup mechanisms. -- node_delegate interface: Bridges the P2P layer to the blockchain, handling block ingestion, transaction processing, and sync callbacks with enhanced status reporting. -- peer_connection: Encapsulates a single peer link with state machine, inventory tracking, rate-limited messaging, emergency consensus support, and comprehensive connection state management with proper cleanup. -- peer_database: Persistent store of potential peers with connection history and disposition. -- message: Generic envelope for all P2P messages with hashing and typed serialization. -- fork_database: Manages blockchain forks with emergency consensus mode support and deterministic tie-breaking. - -Key responsibilities: -- Lifecycle: Construction, configuration loading, listener setup, and graceful shutdown with detailed logging and proper cleanup. -- Peer orchestration: Connecting to configured seeds, accepting inbound connections, pruning inactive peers, and enforcing connection limits with enhanced state management. -- Synchronization: Requesting and processing blockchain item IDs, fetching blocks/transactions, and notifying the delegate with enhanced progress tracking. -- Broadcasting: Advertising inventory and sending items to peers with detailed synchronization metrics and improved deduplication. -- Inventory management: Tracking what peers have, what we need, and what we've recently processed with enhanced deduplication logic. -- Emergency consensus: Managing soft-bans, automatic flag resets, and emergency mode operations with enhanced diagnostics. -- Advanced peer state management: Comprehensive connection state transitions, proper cleanup from all connection sets, and delayed deletion mechanisms. -- Enhanced connection lifecycle: Prevention of peers staying in _active_connections indefinitely through proper state transitions and cleanup. -- Improved disconnection flow: Better disconnect list management with proper peer state transitions and cleanup. -- Enhanced inventory deduplication: More sophisticated tracking of items already advertised, requested, or being processed. -- Intelligent peer handling: Differentiating between stale fork peers and legitimate sync candidates to prevent infinite loops. -- DLT mode support: Enhanced error logging with comprehensive block range information for distributed ledger technology mode. -- Comprehensive logging: Detailed peer synchronization progress, item counts, block ranges, and timing information for better debugging and monitoring. - -**Section sources** -- [node.hpp:180-355](file://libraries/network/include/graphene/network/node.hpp#L180-L355) -- [node.cpp:869-905](file://libraries/network/node.cpp#L869-L905) -- [peer_connection.hpp:79-354](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L354) -- [peer_connection.cpp:419-448](file://libraries/network/peer_connection.cpp#L419-L448) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message.hpp:42-114](file://libraries/network/include/graphene/network/message.hpp#L42-L114) -- [fork_database.hpp:111-120](file://libraries/chain/include/graphene/chain/fork_database.hpp#L111-L120) - -## Architecture Overview -The node delegates blockchain integration to a node_delegate and coordinates peers via peer_connection instances with enhanced lifecycle management. The node maintains separate queues for sync and normal operation, enforces bandwidth and connection limits, and periodically prunes stale peers. The enhanced peer handling system provides network-level resilience through intelligent soft-ban mechanisms, automatic flag resets, and deterministic tie-breaking to prevent cascading failures and infinite sync loops. The comprehensive logging system provides detailed peer synchronization progress, item counts, block ranges, and timing information for better debugging and monitoring capabilities. - -```mermaid -classDiagram -class node { -+load_configuration(dir) -+listen_to_p2p_network() -+connect_to_p2p_network() -+add_node(endpoint) -+connect_to_endpoint(endpoint) -+listen_on_endpoint(ep, wait) -+accept_incoming_connections(flag) -+listen_on_port(port, wait) -+get_actual_listening_endpoint() -+get_connected_peers() -+get_connection_count() -+broadcast(message) -+broadcast_transaction(trx) -+sync_from(item_id, hard_fork_nums) -+is_connected() -+set_advanced_node_parameters(variant) -+get_advanced_node_parameters() -+get_transaction_propagation_data(tx_id) -+get_block_propagation_data(block_id) -+get_node_id() -+set_allowed_peers(ids) -+clear_peer_database() -+set_total_bandwidth_limit(up, down) -+network_get_info() -+network_get_usage_stats() -+get_potential_peers() -+disable_peer_advertising() -+get_call_statistics() -} -class node_delegate { -+has_item(id) bool -+handle_block(blk_msg, sync_mode, contained_txs) bool -+handle_transaction(trx_msg) -+handle_message(msg) -+get_block_ids(synopsis, out_remaining, limit) vector -+get_item(id) message -+get_blockchain_synopsis(ref_point, num_after) vector -+sync_status(item_type, count) -+connection_count_changed(count) -+get_block_number(id) uint32 -+get_block_time(id) time_point_sec -+get_blockchain_now() time_point_sec -+get_head_block_id() item_hash_t -+estimate_last_known_fork_from_git_revision_timestamp(ts) uint32 -+error_encountered(msg, err) -} -class peer_connection { -+accept_connection() -+connect_to(endpoint, local_ep) -+send_message(msg) -+send_item(item_id) -+close_connection() -+destroy_connection() -+busy() bool -+idle() bool -+is_transaction_fetching_inhibited() bool -+get_remote_endpoint() -+get_total_bytes_sent() -+get_total_bytes_received() -+get_last_message_sent_time() -+get_last_message_received_time() -+fork_rejected_until fc : : time_point -+inhibit_fetching_sync_blocks bool -+soft_ban_expiration_handling() -+intelligent_peer_classification() -+unlinkable_block_strikes uint32 -+clear_old_inventory() -+is_inventory_advertised_to_us_list_full_for_transactions() bool -+is_inventory_advertised_to_us_list_full() bool -} -class fork_database { -+set_emergency_mode(active) -+is_emergency_mode() bool -+push_block(block) -+head() shared_ptr -} -node --> node_delegate : "calls" -node --> peer_connection : "manages" -peer_connection --> fork_database : "uses" -``` - -**Diagram sources** -- [node.hpp:180-355](file://libraries/network/include/graphene/network/node.hpp#L180-L355) -- [peer_connection.hpp:79-354](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L354) -- [fork_database.hpp:111-120](file://libraries/chain/include/graphene/chain/fork_database.hpp#L111-L120) - -## Detailed Component Analysis - -### Node Lifecycle Management -- Construction and destruction: The node allocates an internal node_impl and initializes defaults for connection targets, timeouts, and rate limiting. On destruction, it attempts to gracefully close connections and updates the peer database. -- Configuration: load_configuration reads node-specific settings (listening endpoint, accept flags) from a JSON file in the configuration directory. -- Listener setup: listen_to_p2p_network and listen_on_endpoint/listen_on_port configure the TCP server to accept inbound connections, with optional retry behavior when the port is busy. -- Startup and shutdown: connect_to_p2p_network initiates outbound connections; close() and destructor ensure cleanup. - -Operational loops: -- p2p_network_connect_loop: Periodically connects to candidate peers, respecting retry/backoff and connection caps. -- fetch_sync_items_loop: Requests missing sync items from peers and schedules processing. -- fetch_items_loop: Normal operation fetching of items not yet in local cache. -- advertise_inventory_loop: Broadcasts new inventory to peers with enhanced deduplication. -- terminate_inactive_connections_loop: Detects and disconnects idle/inactive peers with proper cleanup. -- bandwidth_monitor_loop: Updates rolling averages of read/write throughput. -- fetch_updated_peer_lists_loop: Requests updated peer lists periodically. -- dump_node_status_task: Periodically logs comprehensive peer status and synchronization progress. - -```mermaid -sequenceDiagram -participant App as "Application" -participant Node as "node" -participant Impl as "node_impl" -participant DB as "peer_database" -participant Peer as "peer_connection" -App->>Node : "load_configuration(dir)" -Node->>Impl : "load_configuration(...)" -Impl->>DB : "open/read peers.json" -App->>Node : "listen_to_p2p_network()" -Node->>Impl : "listen_to_p2p_network()" -Impl->>Impl : "start accept loop" -App->>Node : "connect_to_p2p_network()" -Node->>Impl : "connect_to_p2p_network()" -Impl->>Impl : "p2p_network_connect_loop()" -Impl->>Peer : "connect_to(endpoint)" -Peer-->>Impl : "on_connection_accepted" -Impl->>Impl : "move_peer_to_active_list" -Impl->>Peer : "address_request" -Peer-->>Impl : "address_message" -Impl->>DB : "update entries" -Impl->>Impl : "trigger_p2p_network_connect_loop()" -``` - -**Diagram sources** -- [node.cpp:952-1047](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp:1623-1654](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp:2282-2350](file://libraries/network/node.cpp#L2282-L2350) - -**Section sources** -- [node.cpp:869-931](file://libraries/network/node.cpp#L869-L931) -- [node.cpp:952-1047](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp:1623-1654](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp:2282-2350](file://libraries/network/node.cpp#L2282-L2350) - -### Enhanced Peer Connection Lifecycle Management - -**Updated** Enhanced peer connection lifecycle management with improved state transitions and cleanup mechanisms. - -The node now implements comprehensive peer connection lifecycle management with enhanced state transitions and proper cleanup from all connection sets. The system prevents peers from staying indefinitely in _active_connections through proper state transitions and delayed deletion mechanisms. - -**Enhanced Lifecycle Features**: -- **Proper State Transitions**: Connections move through well-defined states: handshaking → active → closing → terminating → deleted -- **Delayed Deletion**: schedule_peer_for_deletion() queues peers for deferred deletion to prevent race conditions -- **Cleanup Assertions**: Verifies peers are not found in any connection set before scheduling deletion -- **Thread Safety**: Enhanced mutex protection for peer deletion operations -- **Connection Set Management**: Proper removal from all connection sets during state transitions - -**Section sources** -- [node.cpp:1805-1865](file://libraries/network/node.cpp#L1805-L1865) -- [node.cpp:5281-5320](file://libraries/network/node.cpp#L5281-L5320) - -### Improved Disconnection Flow and Cleanup - -**Updated** Improved disconnection flow with proper cleanup from _active_connections and enhanced disconnect list management. - -The node implements enhanced disconnection flow with proper cleanup mechanisms that ensure peers are properly removed from all connection sets and cleaned up appropriately. - -**Enhanced Disconnection Features**: -- **Proper Cleanup Sequence**: Connections are removed from _active_connections, _handshaking_connections, _closing_connections, and _terminating_connections -- **State Transition Logging**: Detailed logging of connection state transitions for debugging -- **Error Recording**: Connection errors are recorded in peer database for diagnostic purposes -- **Resource Cleanup**: Rate limiter removal and inventory cleanup during disconnection -- **Graceful Handling**: Proper handling of both user-initiated and error-induced disconnections - -**Section sources** -- [node.cpp:3396-3475](file://libraries/network/node.cpp#L3396-L3475) -- [node.cpp:5281-5320](file://libraries/network/node.cpp#L5281-L5320) - -### Enhanced Inventory Management and Deduplication - -**Updated** Enhanced inventory management with improved deduplication logic and better tracking of items already processed or requested. - -The node implements enhanced inventory management with sophisticated deduplication logic that prevents redundant fetches and unbounded growth of fetch queues. - -**Enhanced Inventory Features**: -- **Multi-level Deduplication**: Checks for items currently being processed, recently advertised, and already requested -- **Sophisticated Tracking**: Tracks items advertised to peers, items requested from peers, and items being processed -- **Inventory Expiration**: Regular cleanup of old inventory to prevent memory growth -- **Priority Management**: Updates timestamps for items that arrive from multiple peers to prioritize fresher inventory -- **Transaction Throttling**: Separate limits for transactions vs blocks to maintain network stability - -**Section sources** -- [node.cpp:3280-3351](file://libraries/network/node.cpp#L3280-L3351) -- [peer_connection.cpp:428-448](file://libraries/network/peer_connection.cpp#L428-L448) - -### Advanced Peer State Management - -**Updated** Advanced peer state management with comprehensive connection state tracking and enhanced peer classification. - -The node implements comprehensive peer state management with detailed tracking of peer connection states, synchronization progress, and resource utilization. - -**Advanced State Features**: -- **Connection State Tracking**: Detailed tracking of handshaking, active, closing, and terminating connection states -- **Synchronization Progress**: Monitoring of peer synchronization status and remaining item counts -- **Resource Utilization**: Tracking of peer-specific resource usage including queue depths and memory allocation -- **Performance Metrics**: Latency measurements, round-trip delays, and connection timing information -- **Soft-Ban Status**: Monitoring of fork_rejected_until timestamps and unlinkable_block_strikes counters - -**Section sources** -- [node.cpp:5321-5351](file://libraries/network/node.cpp#L5321-L5351) -- [peer_connection.hpp:276-298](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L298) - -### Peer Connection Establishment -- Outbound: connect_to_endpoint creates a peer_connection and initiates a connect loop; on success, transitions to negotiation and then active. -- Inbound: accept_loop accepts sockets and starts accept_or_connect_task; after hello exchange, moves to active and starts synchronization. -- Handshake validation: Verifies signatures, chain ID, fork compatibility, and prevents self-connections and duplicates. -- Firewall detection: Uses check-firewall messages to infer NAT/firewall status. -- Emergency consensus: Soft-ban peers on fork rejection with automatic expiration handling and intelligent peer classification. - -```mermaid -sequenceDiagram -participant Impl as "node_impl" -participant Peer as "peer_connection" -participant Delegate as "node_delegate" -Impl->>Peer : "connect_to_task(endpoint)" -Peer-->>Impl : "on_hello_message" -Impl->>Impl : "validate hello (sig, chain, fork)" -alt valid -Impl->>Peer : "send connection_accepted" -Peer-->>Impl : "address_request" -Impl->>Peer : "address_message" -Impl->>Impl : "move_peer_to_active_list" -Impl->>Delegate : "new_peer_just_added -> start_synchronizing" -else invalid -Impl->>Peer : "send connection_rejected" -Impl->>Impl : "disconnect_from_peer" -end -``` - -**Diagram sources** -- [node.cpp:2029-2230](file://libraries/network/node.cpp#L2029-L2230) -- [node.cpp:2232-2250](file://libraries/network/node.cpp#L2232-L2250) -- [node.cpp:2282-2350](file://libraries/network/node.cpp#L2282-L2350) - -**Section sources** -- [node.cpp:2029-2230](file://libraries/network/node.cpp#L2029-L2230) -- [node.cpp:2232-2250](file://libraries/network/node.cpp#L2232-L2250) -- [node.cpp:2282-2350](file://libraries/network/node.cpp#L2282-L2350) - -### Network Topology Maintenance -- Peer selection: Maintains a potential peer database with last-seen timestamps, disposition, and attempt counts; applies exponential backoff and retry windows. -- Connection caps: Tracks handshaking, active, closing, and terminating sets; enforces desired/max connection counts. -- Inactivity pruning: Disconnects peers exceeding inactivity thresholds and reschedules outstanding requests to others. -- Peer advertising: Optionally disables advertising to restrict exposure. -- Emergency consensus: Implements soft-ban mechanisms to prevent cascading disconnections during network emergencies. -- Intelligent peer classification: Differentiates between stale fork peers and legitimate sync candidates to prevent infinite loops. - -```mermaid -flowchart TD -Start(["Connect Loop"]) --> CheckWants{"Wants more connections?"} -CheckWants --> |No| Sleep["Sleep and wait for updates"] -CheckWants --> |Yes| Iterate["Iterate potential peers"] -Iterate --> Eligible{"Eligible to connect?"} -Eligible --> |No| NextPeer["Next peer"] -Eligible --> |Yes| Connect["connect_to_endpoint"] -Connect --> NextPeer -NextPeer --> DoneIter{"Any new connection?"} -DoneIter --> |Yes| CheckWants -DoneIter --> |No| Sleep -``` - -**Diagram sources** -- [node.cpp:952-1047](file://libraries/network/node.cpp#L952-L1047) - -**Section sources** -- [node.cpp:952-1047](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp:1400-1621](file://libraries/network/node.cpp#L1400-L1621) - -### Blockchain Integration via node_delegate -- Block handling: handle_block receives new blocks during sync or normal operation; returns whether a fork switch occurred; populates contained transaction IDs for propagation. -- Transaction processing: handle_transaction validates and accepts transactions. -- Sync callbacks: get_block_ids, get_blockchain_synopsis, sync_status, and connection_count_changed inform the delegate about sync progress and peer counts. -- Fork awareness: Estimates last known fork from timestamps and rejects incompatible peers. - -```mermaid -sequenceDiagram -participant Impl as "node_impl" -participant Peer as "peer_connection" -participant Delegate as "node_delegate" -Peer-->>Impl : "block_message" -Impl->>Delegate : "handle_block(block_msg, sync_mode, contained_txs)" -alt sync_mode -Impl->>Impl : "process_backlog_of_sync_blocks" -else normal -Impl->>Impl : "process_block_during_normal_operation" -end -Delegate-->>Impl : "bool fork_switched" -Impl->>Impl : "broadcast transactions from contained_txs" -``` - -**Diagram sources** -- [node.hpp:79-80](file://libraries/network/include/graphene/network/node.hpp#L79-L80) -- [node.cpp:3117-3199](file://libraries/network/node.cpp#L3117-L3199) - -**Section sources** -- [node.hpp:79-80](file://libraries/network/include/graphene/network/node.hpp#L79-L80) -- [node.cpp:3117-3199](file://libraries/network/node.cpp#L3117-L3199) - -### Configuration Methods -- load_configuration: Reads node_config.json and sets listening endpoint, accept flags, and persistence directory. -- listen_on_endpoint/accept_incoming_connections/listen_on_port: Configure the TCP listener and availability behavior. -- set_advanced_node_parameters/get_advanced_node_parameters: Tuning knobs for advanced behavior. -- set_total_bandwidth_limit: Configures upload/download rate limiting. -- disable_peer_advertising: Restricts outbound peer advertisement. - -**Section sources** -- [node.hpp:200-294](file://libraries/network/include/graphene/network/node.hpp#L200-L294) -- [node.cpp:933-950](file://libraries/network/node.cpp#L933-L950) -- [node.cpp:1686-1713](file://libraries/network/node.cpp#L1686-L1713) - -### Peer Management Functions -- add_node/connect_to_endpoint: Adds a seed or forces immediate connection. -- get_connected_peers: Returns status for UI/monitoring with comprehensive peer information. -- get_connection_count/is_connected: Reports current connectivity. -- set_allowed_peers/clear_peer_database: Controls allowed peers and resets peer DB for diagnostics. -- get_potential_peers/disable_peer_advertising: Inspect and control peer discovery. - -**Section sources** -- [node.hpp:211-296](file://libraries/network/include/graphene/network/node.hpp#L211-L296) -- [node.cpp:1788-1841](file://libraries/network/node.cpp#L1788-L1841) -- [node.cpp:2282-2350](file://libraries/network/node.cpp#L2282-L2350) - -### Network Broadcasting and Inventory -- broadcast/broadcast_transaction: Queues outgoing messages and triggers inventory advertisement. -- Inventory tracking: Per-peer inventories (advertised to us/advertised to peer) and node-wide new_inventory set. -- Rate limiting: fc::rate_limiting_group controls bandwidth. -- Message caching: blockchain_tied_message_cache stores recent messages for retrieval. - -```mermaid -flowchart TD -NewItem["New Item Arrives"] --> Cache["Cache in message_cache"] -Cache --> Advertise["Advertise Inventory"] -Advertise --> ForEachPeer{"For each active peer"} -ForEachPeer --> CheckInv["Check inventory overlap"] -CheckInv --> |Not ours| Queue["Queue for send"] -CheckInv --> |Overlap| Skip["Skip"] -Queue --> Send["Send item_ids_inventory_message"] -Send --> Deliver["Deliver item via fetch_items_message"] -``` - -**Diagram sources** -- [node.cpp:1326-1398](file://libraries/network/node.cpp#L1326-L1398) -- [node.cpp:2830-2892](file://libraries/network/node.cpp#L2830-L2892) -- [node.cpp:111-217](file://libraries/network/node.cpp#L111-L217) - -**Section sources** -- [node.cpp:1326-1398](file://libraries/network/node.cpp#L1326-L1398) -- [node.cpp:2830-2892](file://libraries/network/node.cpp#L2830-L2892) -- [node.cpp:111-217](file://libraries/network/node.cpp#L111-L217) - -## Enhanced Peer Connection Lifecycle Management - -### Comprehensive Connection State Transitions -The node implements comprehensive peer connection state transitions with proper cleanup and enhanced logging throughout the connection lifecycle. - -**Enhanced State Transition Features**: -- **Handshaking Phase**: Initial connection establishment with timeout monitoring and activity tracking -- **Active Phase**: Full operational state with synchronization and inventory management -- **Closing Phase**: Graceful disconnection with proper cleanup and reason recording -- **Terminating Phase**: Final cleanup phase with resource deallocation -- **Deletion Phase**: Deferred deletion to prevent race conditions and ensure proper cleanup - -**State Transition Logging Examples**: -``` -New peer is connected (${peer}), now ${count} active peers -Peer connection closing (${peer}), now ${count} active peers -Peer connection closing (${peer}): ${reason}, now ${count} active peers -Peer connection terminating (${peer}), now ${count} active peers -``` - -**Section sources** -- [node.cpp:5281-5320](file://libraries/network/node.cpp#L5281-L5320) -- [node.cpp:3396-3475](file://libraries/network/node.cpp#L3396-L3475) - -### Delayed Peer Deletion Mechanism -The node implements a delayed peer deletion mechanism to prevent peers from staying indefinitely in _active_connections and to handle cleanup safely. - -**Delayed Deletion Features**: -- **Queuing System**: schedule_peer_for_deletion() queues peers for deferred deletion -- **Mutex Protection**: Thread-safe deletion with optional mutex-based queuing -- **Cleanup Verification**: Asserts that peers are not found in any connection set before deletion -- **Batch Processing**: Processes multiple peers in batches to improve performance -- **Race Condition Prevention**: Prevents race conditions during peer cleanup - -**Section sources** -- [node.cpp:1805-1865](file://libraries/network/node.cpp#L1805-L1865) - -### Enhanced Connection Set Management -The node implements enhanced connection set management with proper cleanup from all connection sets during state transitions. - -**Connection Set Management Features**: -- **Multi-set Tracking**: Maintains separate sets for handshaking, active, closing, and terminating connections -- **Proper Removal**: Ensures peers are removed from all relevant connection sets during state transitions -- **State Validation**: Validates peer states before performing state transitions -- **Cleanup Logging**: Logs connection set operations for debugging and monitoring -- **Resource Management**: Proper cleanup of associated resources during connection termination - -**Section sources** -- [node.cpp:5281-5320](file://libraries/network/node.cpp#L5281-L5320) - -## Improved Disconnection Flow and Cleanup - -### Enhanced Disconnection Sequence -The node implements an enhanced disconnection sequence that ensures proper cleanup from all connection sets and maintains system stability. - -**Enhanced Disconnection Features**: -- **Multi-set Cleanup**: Removes connections from _active_connections, _handshaking_connections, _closing_connections, and _terminating_connections -- **Error Recording**: Records connection errors in peer database for diagnostic purposes -- **Rate Limiter Cleanup**: Removes sockets from rate limiter to free resources -- **Inventory Cleanup**: Cleans up associated inventory and request tracking -- **State Transition Logging**: Comprehensive logging of disconnection events and reasons - -**Section sources** -- [node.cpp:3396-3475](file://libraries/network/node.cpp#L3396-L3475) - -### Proper Cleanup from Active Connections -The node ensures that peers are properly cleaned up from _active_connections during disconnection to prevent resource leaks and maintain accurate connection counts. - -**Cleanup Features**: -- **Active Connection Removal**: Ensures peers are removed from _active_connections during disconnection -- **Connection Count Accuracy**: Maintains accurate connection counts throughout the disconnection process -- **Resource Deallocation**: Proper deallocation of resources associated with disconnected peers -- **State Consistency**: Ensures state consistency across all connection management operations -- **Error Handling**: Robust error handling during cleanup operations - -**Section sources** -- [node.cpp:3413-3428](file://libraries/network/node.cpp#L3413-L3428) - -### Enhanced Disconnect List Management -The node implements enhanced disconnect list management with proper peer state transitions and improved cleanup mechanisms. - -**Disconnect List Features**: -- **State Transition Tracking**: Tracks peer state transitions during disconnection -- **Reason Recording**: Records disconnection reasons for diagnostic purposes -- **Cooldown Management**: Implements reconnect cooldown to prevent rapid reconnection loops -- **Firewall Check Handling**: Handles firewall check state during disconnection -- **Request Rescheduling**: Reschedules outstanding requests to other peers during disconnection - -**Section sources** -- [node.cpp:3355-3394](file://libraries/network/node.cpp#L3355-L3394) - -## Enhanced Inventory Management and Deduplication - -### Sophisticated Deduplication Logic -The node implements sophisticated deduplication logic that prevents redundant fetches and maintains efficient inventory management. - -**Enhanced Deduplication Features**: -- **Multi-level Checking**: Checks for items currently being processed, recently advertised, and already requested -- **Inventory Expiration**: Regular cleanup of old inventory to prevent memory growth -- **Priority Updates**: Updates timestamps for items that arrive from multiple peers -- **Transaction Throttling**: Separate limits for transactions vs blocks to maintain network stability -- **Efficient Tracking**: Sophisticated tracking of items across multiple peers and connection states - -**Section sources** -- [node.cpp:3280-3351](file://libraries/network/node.cpp#L3280-L3351) -- [peer_connection.cpp:428-448](file://libraries/network/peer_connection.cpp#L428-L448) - -### Improved Inventory Expiration -The node implements improved inventory expiration with proper cleanup of old inventory items to prevent memory growth and maintain system performance. - -**Inventory Expiration Features**: -- **Timestamp-based Cleanup**: Removes inventory items older than GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES -- **Dual Set Management**: Cleans up both inventory_advertised_to_peer and inventory_peer_advertised_to_us sets -- **Logging and Monitoring**: Logs inventory cleanup operations for debugging and monitoring -- **Memory Management**: Prevents unbounded growth of inventory tracking structures -- **Performance Optimization**: Efficient cleanup algorithms to minimize performance impact - -**Section sources** -- [peer_connection.cpp:428-448](file://libraries/network/peer_connection.cpp#L428-L448) - -### Enhanced Item Tracking and Priority Management -The node implements enhanced item tracking and priority management with sophisticated algorithms for handling duplicate inventory announcements. - -**Enhanced Tracking Features**: -- **Priority Updates**: Updates timestamps for items arriving from multiple peers to prioritize fresher inventory -- **Recently Failed Items**: Tracks items that have been recently fetched but failed to push -- **Multi-peer Coordination**: Coordinates item requests across multiple peers to avoid duplication -- **Efficient Lookup**: Fast lookup and update operations for inventory tracking -- **Resource Optimization**: Optimized data structures for efficient inventory management - -**Section sources** -- [node.cpp:3334-3351](file://libraries/network/node.cpp#L3334-L3351) - -## Advanced Peer State Management - -### Comprehensive Peer State Tracking -The node implements comprehensive peer state tracking with detailed monitoring of peer connection states, synchronization progress, and resource utilization. - -**Peer State Tracking Features**: -- **Connection State Monitoring**: Tracks handshaking, active, closing, and terminating connection states -- **Synchronization Progress**: Monitors peer synchronization status and remaining item counts -- **Resource Utilization**: Tracks peer-specific resource usage including queue depths and memory allocation -- **Performance Metrics**: Measures latency, round-trip delays, and connection timing information -- **Soft-ban Status**: Monitors fork_rejected_until timestamps and unlinkable_block_strikes counters - -**Section sources** -- [node.cpp:5321-5351](file://libraries/network/node.cpp#L5321-L5351) -- [peer_connection.hpp:276-298](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L298) - -### Enhanced Peer Classification and Monitoring -The node implements enhanced peer classification and monitoring with detailed metrics for connection health, synchronization status, and resource utilization. - -**Enhanced Classification Features**: -- **Connection Health Monitoring**: Monitors peer connection quality, latency, and bandwidth utilization -- **Synchronization State Classification**: Classifies peers as in-sync, needing sync, or inhibited from sync -- **Resource Utilization Tracking**: Tracks peer-specific resource allocation and queue management -- **Performance Optimization**: Dynamically adjusts connection parameters based on peer performance -- **Health Assessment**: Comprehensive health assessment of peer connections for network optimization - -**Section sources** -- [node.cpp:5321-5351](file://libraries/network/node.cpp#L5321-L5351) - -### Improved Connection Limit and Bandwidth Monitoring -The node provides comprehensive monitoring of connection limits, bandwidth utilization, and peer resource allocation to ensure optimal network performance. - -**Connection and Bandwidth Monitoring Features**: -- **Connection Limits**: Monitoring of active connections, handshaking peers, and connection caps -- **Bandwidth Utilization**: Real-time tracking of upload/download speeds and bandwidth allocation -- **Resource Allocation**: Monitoring of peer-specific resource allocation and queue management -- **Performance Optimization**: Dynamic adjustment of connection parameters based on network conditions -- **Capacity Planning**: Predictive capacity planning based on connection and bandwidth metrics - -**Section sources** -- [node.cpp:5321-5351](file://libraries/network/node.cpp#L5321-L5351) - -## Performance Considerations -- Connection limits: desired/max connections cap concurrent peers; enforced in is_wanting_new_connections and is_accepting_new_connections. -- Bandwidth throttling: rate limiter updates rolling averages and constrains upload/download rates. -- Prefetching: Limits for sync and normal operations prevent resource exhaustion. -- Inactivity pruning: Keeps the mesh healthy by dropping idle peers and rescheduling requests. -- Enhanced inventory deduplication: Prevents redundant fetches and unbounded growth of fetch queues through sophisticated tracking mechanisms. -- Improved connection lifecycle: Prevents peers from staying indefinitely in _active_connections through proper state transitions and cleanup. -- Enhanced disconnection flow: Better disconnect list management with proper peer state transitions and cleanup. -- Emergency consensus overhead: Minimal performance impact through efficient soft-ban expiration checks. -- Automatic flag management: Reduces manual intervention requirements during extended emergency operations. -- Intelligent peer classification: Optimizes peer selection and reduces wasted bandwidth on stale forks. -- Soft-ban caching: Prevents repeated attempts with problematic peers during emergency periods. -- Trusted peer optimization: Reduced soft-ban duration for trusted peers enables faster network recovery. -- DLT mode monitoring: Enhanced logging provides better visibility into block availability without significant performance impact. -- Peer status reporting: Comprehensive status updates enable better monitoring and resource management. -- Comprehensive logging: Detailed peer synchronization progress, item counts, and timing information provide valuable debugging insights without significant performance impact. -- Memory usage monitoring: Efficient memory tracking helps identify resource bottlenecks and optimize performance. -- Enhanced peer lifecycle management: Improved connection state transitions and cleanup mechanisms reduce resource leaks and improve system stability. -- Delayed deletion mechanism: Prevents race conditions during peer cleanup while maintaining system responsiveness. -- Sophisticated inventory management: Enhanced deduplication logic reduces network traffic and improves efficiency. -- Improved disconnection handling: Better cleanup mechanisms prevent resource leaks and maintain accurate connection counts. - -## Troubleshooting Guide -Common issues and resolutions: -- Port binding conflicts: Use listen_on_port with wait_if_endpoint_is_busy=true to retry; otherwise, allow dynamic port selection. -- Rejection reasons: Review connection_rejected_message reason codes (e.g., connected_to_self, already_connected, not_accepting_connections, different_chain, outdated client). -- Firewall/NAT: Use check-firewall messages to detect; adjust inbound/outbound ports and consider advertised inbound addresses. -- Peer database corruption: Clear peer database via clear_peer_database to reset discovery state. -- Bandwidth saturation: Adjust set_total_bandwidth_limit and review advertised inventory sizes. -- Hard fork incompatibility: Upgrade client if rejected due to inability to process future blocks. -- Emergency mode activation: Monitor logs for "EMERGENCY CONSENSUS MODE activated" messages; system automatically handles recovery. -- Soft-ban effects: If experiencing reduced peer connectivity, check soft-ban expiration timestamps; system should automatically reset flags. -- Flag reset issues: Verify inhibit_fetching_sync_blocks flag resets after soft-ban expiration; manual intervention rarely needed. -- Infinite sync loops: Monitor peer behavior; system now prevents endless sync attempts through intelligent soft-ban mechanisms. -- Stale fork detection: System automatically soft-bans peers on stale forks to prevent wasted resources. -- Trusted peer issues: Verify trusted-snapshot-peer configuration for reduced 5-minute soft-ban duration. -- Block rejection handling: Monitor unlinkable_block_exception patterns to identify stale fork vs legitimate sync scenarios. -- DLT mode errors: Review enhanced error logs for detailed block availability context including available range and dlt_block_log boundaries. -- Sync status monitoring: Use peer status updates to monitor synchronization progress and identify stuck peers. -- Memory usage: Monitor peer queue sizes and memory usage through status reports to identify resource bottlenecks. -- Request timeouts: Review detailed timeout logs with item types, block numbers, and timing thresholds to identify slow or unresponsive peers. -- Connection lifecycle: Monitor connection establishment, closure, and termination events to identify connection stability issues. -- Synchronization progress: Use comprehensive sync status reporting to track synchronization completion and identify bottlenecks. -- **Enhanced connection lifecycle**: Monitor peer state transitions and cleanup operations to identify connection management issues. -- **Delayed deletion mechanism**: Verify that peers are properly queued for deletion and cleaned up without race conditions. -- **Improved disconnection flow**: Monitor disconnection sequences to ensure proper cleanup from all connection sets. -- **Enhanced inventory deduplication**: Monitor inventory tracking to identify deduplication effectiveness and potential issues. -- **Connection set management**: Verify proper cleanup from all connection sets during state transitions. -- **State transition logging**: Use detailed logging to debug connection lifecycle issues and peer state management problems. -- **Cleanup verification**: Ensure that peers are properly removed from _active_connections and other connection sets during disconnection. -- **Race condition prevention**: Monitor delayed deletion mechanism to prevent race conditions during peer cleanup operations. - -**Section sources** -- [node.cpp:2251-2280](file://libraries/network/node.cpp#L2251-L2280) -- [node.cpp:2137-2168](file://libraries/network/node.cpp#L2137-L2168) -- [node.cpp:1686-1713](file://libraries/network/node.cpp#L1686-L1713) -- [node.cpp:1326-1398](file://libraries/network/node.cpp#L1326-L1398) -- [database.cpp:4455-4460](file://libraries/chain/database.cpp#L4455-L4460) -- [p2p_plugin.cpp:633-689](file://plugins/p2p/p2p_plugin.cpp#L633-L689) -- [node.cpp:3540-3562](file://libraries/network/node.cpp#L3540-L3562) -- [node.cpp:3920-3940](file://libraries/network/node.cpp#L3920-L3940) -- [config.ini:103-108](file://share/vizd/config/config.ini#L103-L108) - -## Conclusion -The Node Management component provides a robust, configurable, and efficient P2P orchestration layer with comprehensive emergency consensus support and enhanced peer handling capabilities. The recent enhancements significantly improve connection lifecycle management, disconnection handling, inventory deduplication, and peer state management through comprehensive lifecycle management, improved cleanup mechanisms, enhanced inventory tracking, and advanced peer state management systems. - -The enhanced peer connection lifecycle management system provides detailed state transitions with proper cleanup from all connection sets, preventing peers from staying indefinitely in _active_connections through proper state transitions and delayed deletion mechanisms. The improved disconnection flow ensures proper cleanup from _active_connections and enhanced disconnect list management with proper peer state transitions and cleanup. - -The enhanced inventory management system implements sophisticated deduplication logic that prevents redundant fetches and maintains efficient inventory management through multi-level checking, inventory expiration, and priority updates. The advanced peer state management system provides comprehensive tracking of peer connection states, synchronization progress, and resource utilization with detailed metrics and performance optimization. - -These enhancements ensure the network can recover from extended periods without block production while maintaining operational efficiency and preventing cascading failures. The integration of comprehensive connection lifecycle management, enhanced disconnection handling, sophisticated inventory deduplication, and advanced peer state management creates a powerful toolkit for maintaining network stability under adverse conditions. Proper configuration of limits, bandwidth, peer discovery, emergency consensus parameters, trusted peer settings, and the enhanced connection lifecycle mechanisms, combined with monitoring and troubleshooting practices, yields a stable, performant, and resilient network node capable of handling both normal operations and emergency scenarios with comprehensive diagnostic capabilities and detailed peer connection lifecycle insights. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Originating Peer Tracking.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Originating Peer Tracking.md deleted file mode 100644 index f9082d267d..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Originating Peer Tracking.md +++ /dev/null @@ -1,439 +0,0 @@ -# Originating Peer Tracking - - -**Referenced Files in This Document** -- [node.cpp](file://libraries/network/node.cpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [peer_database.cpp](file://libraries/network/peer_database.cpp) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [System Architecture](#system-architecture) -3. [Core Components](#core-components) -4. [Originating Peer Tracking Mechanism](#originating-peer-tracking-mechanism) -5. [Message Flow Analysis](#message-flow-analysis) -6. [Peer Database Management](#peer-database-management) -7. [Security and Validation](#security-and-validation) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction - -Originating Peer Tracking is a critical component of the VIZ blockchain's peer-to-peer networking infrastructure. This system enables the network to maintain accurate records of peer connections, track connection states, and manage peer relationships effectively. The mechanism ensures that nodes can identify the source of incoming messages, maintain connection integrity, and prevent duplicate connections while optimizing network topology. - -The system operates through a sophisticated combination of peer connection management, database persistence, and message routing mechanisms. It tracks peer identities, connection states, and behavioral patterns to create an efficient and secure peer-to-peer network. - -## System Architecture - -The Originating Peer Tracking system is built around several interconnected components that work together to maintain peer connection integrity and track message origins: - -```mermaid -graph TB -subgraph "Network Layer" -NC[node.cpp - Network Core] -PC[peer_connection.cpp - Connection Manager] -CM[core_messages.hpp - Message Types] -end -subgraph "Peer Management" -PD[peer_database.cpp - Database Manager] -PR[potential_peer_record - Peer Records] -end -subgraph "Connection States" -CS[Connection States] -NS[Negotiation Status] -PS[Peer States] -end -NC --> PC -NC --> PD -PC --> CM -PD --> PR -NC --> CS -PC --> NS -PC --> PS -CS --> |"Tracks"| PC -NS --> |"Monitors"| PC -PS --> |"Identifies"| PC -``` - -**Diagram sources** -- [node.cpp:112-5989](file://libraries/network/node.cpp#L112-L5989) -- [peer_connection.cpp:1-484](file://libraries/network/peer_connection.cpp#L1-L484) -- [peer_database.cpp:1-262](file://libraries/network/peer_database.cpp#L1-L262) - -The architecture consists of three primary layers: - -1. **Network Core Layer**: Handles message routing, peer negotiation, and connection management -2. **Peer Management Layer**: Maintains persistent peer records and connection history -3. **State Management Layer**: Tracks connection states, negotiation progress, and peer identification - -## Core Components - -### Peer Connection Management - -The peer connection system manages individual peer relationships and maintains detailed state information: - -```mermaid -classDiagram -class peer_connection { -+node_id_t node_id -+node_id_t node_public_key -+string user_agent -+uint32_t core_protocol_version -+our_connection_state our_state -+their_connection_state their_state -+connection_negotiation_status negotiation_status -+fc : : ip : : address inbound_address -+uint16_t inbound_port -+uint16_t outbound_port -+on_message(originating_connection, message) -+send_message(message) -+get_remote_endpoint() -} -class peer_connection_delegate { -<> -+on_message(originating_peer, message) -+on_connection_closed(originating_peer) -+get_message_for_item(item) -} -class node_impl { -+get_peer_by_node_id(node_id) -+is_already_connected_to_id(node_id) -+on_hello_message(originating_peer, hello_message) -+on_address_message(originating_peer, address_message) -} -peer_connection --> peer_connection_delegate : "delegates to" -node_impl --> peer_connection : "manages" -``` - -**Diagram sources** -- [peer_connection.hpp:79-363](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L363) -- [node.cpp:1881-1893](file://libraries/network/node.cpp#L1881-L1893) - -### Message Type System - -The system defines comprehensive message types for peer communication: - -| Message Type | Purpose | Origin | -|--------------|---------|--------| -| hello_message | Initial peer handshake | Both directions | -| connection_accepted | Accept connection request | Initiator receives | -| connection_rejected | Reject connection request | Initiator receives | -| address_request | Request peer addresses | Both directions | -| address_message | Provide peer addresses | Both directions | -| blockchain_item_ids_inventory | Provide blockchain item IDs | Both directions | - -**Section sources** -- [core_messages.hpp:72-95](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L95) -- [core_messages.hpp:233-265](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L265) - -### Peer Database Structure - -The peer database maintains persistent records of potential peers: - -```mermaid -erDiagram -POTENTIAL_PEER_RECORD { -fc::ip::endpoint endpoint PK -fc::time_point_sec last_seen_time -enum last_connection_disposition -fc::time_point_sec last_connection_attempt_time -uint32 number_of_successful_connection_attempts -uint32 number_of_failed_connection_attempts -optional exception last_error -} -PEER_CONNECTION { -node_id_t node_id PK -node_id_t node_public_key -fc::ip::endpoint remote_endpoint -enum connection_direction -enum firewalled_state -microseconds round_trip_delay -time_point connection_initiation_time -} -POTENTIAL_PEER_RECORD ||--o{ PEER_CONNECTION : "tracks" -``` - -**Diagram sources** -- [peer_database.hpp:47-71](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) -- [peer_connection.hpp:200-225](file://libraries/network/include/graphene/network/peer_connection.hpp#L200-L225) - -**Section sources** -- [peer_database.hpp:39-71](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L71) -- [peer_database.cpp:41-82](file://libraries/network/peer_database.cpp#L41-L82) - -## Originating Peer Tracking Mechanism - -### Connection State Management - -The system maintains detailed connection state information to track peer origins and connection progress: - -```mermaid -stateDiagram-v2 -[*] --> Disconnected -Disconnected --> Just_Connected : "Initial connection" -Just_Connected --> Connection_Accepted : "Hello accepted" -Just_Connected --> Connection_Rejected : "Hello rejected" -Connection_Accepted --> Negotiation_Complete : "Address exchange" -Connection_Rejected --> Closed : "Disconnect" -Negotiation_Complete --> Active : "Move to active list" -Active --> Closed : "Connection lost" -Closed --> [*] -state Just_Connected { -[*] --> Hello_Sent -Hello_Sent --> Connection_Accepted : "Accepted" -Hello_Sent --> Connection_Rejected : "Rejected" -} -``` - -**Diagram sources** -- [peer_connection.hpp:82-106](file://libraries/network/include/graphene/network/peer_connection.hpp#L82-L106) -- [node.cpp:2102-2303](file://libraries/network/node.cpp#L2102-L2303) - -### Peer Identity Resolution - -The system resolves peer identities through multiple validation mechanisms: - -1. **Node ID Extraction**: Extracts node ID from hello message user_data -2. **Signature Verification**: Validates shared secret signatures -3. **Chain ID Validation**: Ensures compatibility with network chain -4. **Duplicate Detection**: Prevents multiple connections to same peer - -**Section sources** -- [node.cpp:2102-2230](file://libraries/network/node.cpp#L2102-L2230) -- [peer_connection.hpp:200-218](file://libraries/network/include/graphene/network/peer_connection.hpp#L200-L218) - -### Firewall Detection and Address Validation - -The system implements sophisticated firewall detection mechanisms: - -```mermaid -flowchart TD -Start([Incoming Hello Message]) --> ExtractInfo["Extract Reported Addresses"] -ExtractInfo --> CompareIP{"Compare Reported vs Actual"} -CompareIP --> |Match| Not_Firewalled["Mark as Not Firewalled"] -CompareIP --> |Mismatch| Firewalled["Mark as Firewalled"] -Not_Firewalled --> UpdateDB["Update Peer Database"] -Firewalled --> UpdateDB -UpdateDB --> CheckPorts{"Check Port Information"} -CheckPorts --> |Complete| AddToList["Add to Potential Peers"] -CheckPorts --> |Incomplete| MarkUnknown["Mark Unknown"] -AddToList --> End([Connection Established]) -MarkUnknown --> End -``` - -**Diagram sources** -- [node.cpp:2247-2268](file://libraries/network/node.cpp#L2247-L2268) -- [peer_database.cpp:160-166](file://libraries/network/peer_database.cpp#L160-L166) - -**Section sources** -- [node.cpp:2247-2268](file://libraries/network/node.cpp#L2247-L2268) -- [peer_database.cpp:151-158](file://libraries/network/peer_database.cpp#L151-L158) - -## Message Flow Analysis - -### Handshake Process - -The peer handshake process follows a structured sequence for establishing connections: - -```mermaid -sequenceDiagram -participant A as "Initiating Node" -participant B as "Target Node" -participant DB as "Peer Database" -A->>B : hello_message (with user_data) -B->>B : parse_hello_user_data_for_peer() -B->>B : validate_node_id() -B->>B : check_chain_compatibility() -B->>B : check_duplicate_connection() -alt Valid Connection -B->>A : connection_accepted_message -A->>B : address_request_message -B->>A : address_message (peer list) -A->>DB : update_peer_records() -A->>A : move_peer_to_active_list() -else Invalid Connection -B->>A : connection_rejected_message -A->>A : disconnect_from_peer() -end -``` - -**Diagram sources** -- [node.cpp:2102-2303](file://libraries/network/node.cpp#L2102-L2303) -- [node.cpp:2355-2423](file://libraries/network/node.cpp#L2355-L2423) - -### Address Exchange Protocol - -The address exchange mechanism enables peer discovery and network expansion: - -```mermaid -flowchart LR -A[Active Peer] --> B[Address Request] -B --> C[Address Message] -C --> D[Update Last Seen Time] -D --> E[Store in Database] -E --> F[Trigger Connection Loop] -F --> G[Attempt New Connections] -style A fill:#e1f5fe -style G fill:#f3e5f5 -``` - -**Diagram sources** -- [node.cpp:2355-2378](file://libraries/network/node.cpp#L2355-L2378) -- [node.cpp:2380-2423](file://libraries/network/node.cpp#L2380-L2423) - -**Section sources** -- [node.cpp:2355-2423](file://libraries/network/node.cpp#L2355-L2423) - -## Peer Database Management - -### Database Operations - -The peer database provides comprehensive operations for managing peer records: - -| Operation | Description | Implementation | -|-----------|-------------|----------------| -| lookup_or_create_entry | Find existing or create new peer record | [peer_database.cpp:160-166](file://libraries/network/peer_database.cpp#L160-L166) | -| update_entry | Update peer connection statistics | [peer_database.cpp:151-158](file://libraries/network/peer_database.cpp#L151-L158) | -| lookup_entry_for_endpoint | Retrieve specific peer record | [peer_database.cpp:168-174](file://libraries/network/peer_database.cpp#L168-L174) | -| begin/end iterators | Iterate through peer records | [peer_database.cpp:176-182](file://libraries/network/peer_database.cpp#L176-L182) | - -### Record Persistence - -The database maintains peer records with automatic persistence: - -```mermaid -flowchart TD -Connect[Node Startup] --> LoadDB[Load JSON Database] -LoadDB --> CheckFile{"Database Exists?"} -CheckFile --> |Yes| ParseJSON[Parse JSON Records] -CheckFile --> |No| CreateEmpty[Create Empty Database] -ParseJSON --> PruneDB[Prune to Maximum Size] -CreateEmpty --> RunNode[Run Node] -PruneDB --> RunNode -RunNode --> Shutdown[Node Shutdown] -Shutdown --> SaveDB[Save to JSON File] -SaveDB --> End[Database Saved] -``` - -**Diagram sources** -- [peer_database.cpp:100-138](file://libraries/network/peer_database.cpp#L100-L138) - -**Section sources** -- [peer_database.cpp:100-138](file://libraries/network/peer_database.cpp#L100-L138) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) - -## Security and Validation - -### Connection Validation - -The system implements multiple layers of connection validation: - -1. **Signature Validation**: Verifies shared secret signatures using ECC -2. **Chain Compatibility**: Ensures peers operate on compatible blockchain networks -3. **Fork Compatibility**: Validates peer compatibility with network hard forks -4. **Duplicate Prevention**: Detects and prevents multiple connections to same peer - -### Security Measures - -```mermaid -flowchart TD -Incoming[Incoming Connection] --> ValidateSignature[Validate Signature] -ValidateSignature --> CheckChain[Check Chain ID] -CheckChain --> CheckFork[Check Fork Compatibility] -CheckFork --> CheckDuplicate[Check Duplicate Connection] -CheckDuplicate --> Valid{All Checks Pass?} -Valid --> |Yes| AcceptConnection[Accept Connection] -Valid --> |No| RejectConnection[Reject Connection] -AcceptConnection --> UpdateStats[Update Statistics] -RejectConnection --> LogReason[Log Rejection Reason] -UpdateStats --> MonitorBehavior[Monitor Behavior] -MonitorBehavior --> End[Connection Active] -LogReason --> End -``` - -**Diagram sources** -- [node.cpp:2114-2209](file://libraries/network/node.cpp#L2114-L2209) - -**Section sources** -- [node.cpp:2114-2209](file://libraries/network/node.cpp#L2114-L2209) -- [peer_connection.cpp:244-253](file://libraries/network/peer_connection.cpp#L244-L253) - -## Performance Considerations - -### Connection Limits - -The system enforces connection limits to maintain optimal performance: - -- **Maximum Connections**: Configurable limit on active peer connections -- **Queued Message Limits**: Prevents memory exhaustion from excessive message queuing -- **Inventory Management**: Controls advertisement of blockchain items to prevent flooding - -### Memory Management - -```mermaid -graph LR -subgraph "Memory Constraints" -QM[Queued Messages: 2MB Limit] -IS[Inventory Size: 50MB Limit] -PC[Peer Connections: Configurable] -end -subgraph "Performance Impact" -TP[Throughput: 1000+ messages/sec] -LP[Latency: < 100ms RTT] -MP[Memory Usage: < 512MB] -end -QM --> TP -IS --> TP -PC --> TP -TP --> LP -LP --> MP -``` - -**Section sources** -- [peer_connection.cpp:314-335](file://libraries/network/peer_connection.cpp#L314-L335) -- [peer_connection.cpp:451-468](file://libraries/network/peer_connection.cpp#L451-L468) - -## Troubleshooting Guide - -### Common Connection Issues - -| Issue | Symptoms | Solution | -|-------|----------|----------| -| Connection Rejected | Immediate disconnection after hello | Check chain ID compatibility and node ID validation | -| Duplicate Connection | Rejection with "already connected" | Verify peer ID uniqueness and connection state | -| Firewall Detection Failure | Incorrect firewalled state | Check port information and NAT traversal | -| Database Corruption | JSON parsing errors on startup | Clear peer database file and restart | - -### Debugging Tools - -The system provides comprehensive logging and debugging capabilities: - -- **Verbose Logging**: Enable detailed P2P logging for connection issues -- **Connection State Monitoring**: Track peer connection states and transitions -- **Message Flow Tracing**: Monitor message sequences and timing -- **Database Inspection**: Query peer database for connection history - -**Section sources** -- [node.cpp:1895-1933](file://libraries/network/node.cpp#L1895-L1933) -- [peer_connection.cpp:109-155](file://libraries/network/peer_connection.cpp#L109-L155) - -## Conclusion - -The Originating Peer Tracking system represents a sophisticated approach to peer-to-peer network management in blockchain systems. Through its comprehensive state tracking, validation mechanisms, and persistent database management, it ensures reliable peer connections while maintaining network security and performance. - -Key strengths of the system include: - -- **Robust Identity Management**: Multi-layered peer identity verification prevents malicious connections -- **Comprehensive State Tracking**: Detailed connection state management enables precise origin tracking -- **Persistent Database**: Reliable peer record storage supports network discovery and maintenance -- **Security Validation**: Multiple validation layers protect against various attack vectors -- **Performance Optimization**: Connection limits and memory management ensure system stability - -The system's modular design allows for easy extension and modification while maintaining backward compatibility with existing network protocols. Its comprehensive logging and debugging capabilities facilitate troubleshooting and system monitoring. - -Future enhancements could include advanced peer reputation systems, improved NAT traversal mechanisms, and enhanced security measures for emerging threats. The current architecture provides a solid foundation for these improvements while maintaining the system's reliability and performance characteristics. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Peer Connection Management.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Peer Connection Management.md deleted file mode 100644 index d3b811146f..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Peer Connection Management.md +++ /dev/null @@ -1,971 +0,0 @@ -# Peer Connection Management - - -**Referenced Files in This Document** -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [core_messages.cpp](file://libraries/network/core_messages.cpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [peer_database.cpp](file://libraries/network/peer_database.cpp) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp) -- [exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp) -- [plugin.hpp](file://plugins/snapshot/plugin.hpp) -- [plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) -- [config.ini](file://share/vizd/config/config.ini) - - -## Update Summary -**Changes Made** -- Enhanced peer-to-peer networking infrastructure with corrected timestamp reporting mechanisms -- Comprehensive peer logging capabilities with detailed status reporting and closing reason tracking -- Automatic peer soft-banning mechanisms with configurable strike thresholds -- Enhanced peer synchronization with intelligent strike-based enforcement for unlinkable blocks -- Improved peer database operations with unlinkable_block_strikes tracking -- Dual-tier soft-ban system supporting both trusted and regular peers -- Enhanced error diagnostics for peer synchronization issues -- Configurable 20-strike threshold for unlinkable block soft-ban enforcement -- Intelligent sync spam prevention with 50-strike threshold and 5-minute soft-ban duration -- **NEW** sync_spam_strikes counter and fork_rejected_until mechanism for preventing malicious peers from overwhelming the node with repeated sync requests - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive coverage of Peer Connection Management in the VIZ C++ node networking stack. It focuses on the peer_connection.hpp implementation for managing bidirectional peer communication channels, connection state tracking, and message routing. The document explains peer connection establishment protocols, authentication mechanisms, and handshake procedures. It covers connection lifecycle management including initiation, maintenance, graceful disconnection, and error recovery. It details peer state tracking, connection quality metrics, and peer reputation systems. Message queuing, priority handling, and connection multiplexing are documented along with practical examples and guidance on peer selection, balancing, and fault tolerance. - -**Updated** Enhanced with sophisticated network stability improvements including intelligent soft-ban mechanisms with configurable strike-based enforcement, comprehensive peer database operations with unlinkable_block_strikes tracking, improved peer synchronization logging with detailed status reporting, enhanced error diagnostics for peer synchronization issues, intelligent reputation management systems, and **NEW** sync spam prevention system featuring sync_spam_strikes counter and fork_rejected_until mechanism to prevent malicious peers from overwhelming the node with repeated sync requests. - -## Project Structure -The peer connection management system is composed of several interconnected components with enhanced network stability features: -- Peer-level abstraction: peer_connection encapsulates a single peer's state and messaging with enhanced error handling, soft-ban support, improved peer state fields, closing_reason tracking, and **NEW** sync_spam_strikes counter for configurable strike-based reputation management. -- Transport abstraction: message_oriented_connection wraps a secure transport socket and handles message framing with improved logging. -- Security: stcp_socket performs ECDH key exchange and AES encryption for secure communication. -- Protocol messages: core_messages defines the handshake and operational messages exchanged between peers with reliable IP address handling. -- Node orchestration: node coordinates peer connections, maintains peer databases, and manages lifecycle events with enhanced exception safety, soft-ban functionality, ANSI color-coded notifications, and **NEW** intelligent strike-based enforcement mechanisms including sync spam prevention. -- Configuration: config.hpp centralizes tunable constants for timeouts, limits, and behavior. -- Chain integration: database and fork_database handle block validation with proper exception propagation for P2P layer consumption. -- **Enhanced** Network stability: Intelligent soft-ban mechanisms with configurable strike thresholds, enhanced peer disconnect logging, improved peer database dumping capabilities, and **NEW** sync spam prevention system. - -```mermaid -graph TB -subgraph "Peer Layer" -PC["peer_connection
Bidirectional channel
Enhanced Error Handling
Soft-ban Support
Improved State Fields
Closing Reason Tracking
Unlinkable Block Strikes Counter
Sync Spam Strikes Counter
Intelligent Reputation Management
Sync Spam Prevention"] -end -subgraph "Transport Layer" -MOC["message_oriented_connection
Message framing
Robust Logging"] -STCP["stcp_socket
ECDH + AES"] -end -subgraph "Protocol Layer" -CM["core_messages
Handshake & ops
Reliable IP Extraction"] -MSG["message
Header + payload"] -end -subgraph "Node Orchestration" -N["node
Connection manager
Exception Safety
Soft-ban Logic
ANSI Color Notifications
Intelligent Strike Enforcement
Enhanced Diagnostics
Sync Spam Prevention"] -PD["peer_database
Peer reputation
Enhanced Dumping
JSON Serialization"] -END -subgraph "Network Stability" -SB["Soft-ban Mechanisms
Intelligent Enforcement
Configurable Strike Thresholds"] -CR["Enhanced Logging
Detailed Status Reporting
Closing Reason Tracking"] -DB["Database Operations
Comprehensive Tracking
Improved Serialization"] -SS["Sync Spam Prevention
50-strike Threshold
5-minute Duration
fork_rejected_until Mechanism"] -END -subgraph "Chain Integration" -DBCHAIN["database
Block validation
Exception propagation
Memory resize handling"] -FD["fork_database
Fork management
Link handling"] -EX["exceptions
Network exceptions
Soft-ban types
Memory resize exceptions"] -END -PC --> MOC -MOC --> STCP -PC --> CM -CM --> MSG -N --> PC -N --> PD -N --> SB -N --> CR -N --> DB -N --> SS -N --> DBCHAIN -DBCHAIN --> FD -DBCHAIN --> EX -N --> EX -``` - -**Diagram sources** -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [message_oriented_connection.hpp:45-79](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [stcp_socket.hpp:37-93](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [core_messages.hpp:72-95](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L95) -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) -- [node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) -- [database.cpp:1215-1246](file://libraries/chain/database.cpp#L1215-L1246) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [exceptions.hpp:33-45](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L45) - -**Section sources** -- [peer_connection.hpp:1-386](file://libraries/network/include/graphene/network/peer_connection.hpp#L1-L386) -- [message_oriented_connection.hpp:1-85](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L1-L85) -- [stcp_socket.hpp:1-99](file://libraries/network/include/graphene/network/stcp_socket.hpp#L1-L99) -- [core_messages.hpp:1-573](file://libraries/network/include/graphene/network/core_messages.hpp#L1-L573) -- [node.hpp:1-374](file://libraries/network/include/graphene/network/node.hpp#L1-L374) -- [peer_database.hpp:1-141](file://libraries/network/include/graphene/network/peer_database.hpp#L1-L141) -- [message.hpp:1-114](file://libraries/network/include/graphene/network/message.hpp#L1-L114) -- [database.cpp:1-6389](file://libraries/chain/database.cpp#L1-L6389) -- [fork_database.cpp:1-271](file://libraries/chain/fork_database.cpp#L1-L271) -- [exceptions.hpp:1-49](file://libraries/network/include/graphene/network/exceptions.hpp#L1-L49) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -## Core Components -- peer_connection: Manages a single peer's connection state, queues outgoing messages, tracks inventory, and exposes metrics with enhanced error handling, IP address extraction reliability, and **Enhanced** closing_reason field for improved logging. It delegates message delivery to message_oriented_connection and integrates with node-level callbacks. **Enhanced** with fork_rejected_until and inhibit_fetching_sync_blocks fields for soft-ban functionality, improved peer state management, and **NEW** sync_spam_strikes counter for configurable strike-based reputation management. -- message_oriented_connection: Provides a message-oriented API over a secure socket, handling read/write loops, padding, and error propagation with improved logging mechanisms. -- stcp_socket: Implements ECDH key exchange and AES encryption for secure transport. -- core_messages: Defines the protocol messages used during handshake and runtime operations with reliable IP address handling and enhanced error reporting. -- node: Orchestrates peer connections, manages peer databases, and coordinates synchronization and broadcasting with better exception safety, soft-ban logic, fork rejection handling, ANSI color-coded notification support, and **NEW** intelligent strike-based enforcement mechanisms including sync spam prevention with configurable 20-strike threshold for unlinkable blocks and **NEW** 50-strike threshold for sync spam prevention. -- peer_database: Tracks potential peers, connection attempts, and outcomes for peer selection and reputation with improved error handling and enhanced JSON serialization for database dumping. -- **Enhanced** Soft-ban mechanisms: Intelligent soft-ban enforcement with configurable strike thresholds, reduced soft-ban duration from 3600 seconds to 900 seconds, and trusted peer-aware soft-ban duration calculation. -- **Enhanced** Closing reason tracking: Enhanced peer disconnect logging with closing_reason field for improved troubleshooting and debugging capabilities. -- **Enhanced** Database dumping: Improved peer database dumping capabilities with enhanced JSON serialization and error handling. -- **NEW** Intelligent strike-based reputation system: unlinkable_block_strikes counter accumulates violations for unlinkable blocks at/below head, with automatic soft-ban activation when threshold (20 strikes) is reached, providing tolerant handling of occasional stale fork violations. -- **NEW** Sync spam prevention: sync_spam_strikes counter accumulates repeated sync requests for competing forks, with automatic soft-ban activation when threshold (50 strikes) is reached after 5 minutes duration, utilizing fork_rejected_until mechanism to prevent malicious peers from overwhelming the node. - -Key responsibilities: -- Handshake and authentication: ECDH key exchange via stcp_socket, hello/connection_accepted messages via core_messages with reliable IP address extraction. -- Lifecycle management: Connect, accept, close, destroy, and cleanup with enhanced error recovery and exception safety, including soft-ban mechanisms with ANSI color-coded notifications and **NEW** intelligent strike-based enforcement including sync spam prevention. -- Message routing: Queueing, priority, and multiplexing across peers with improved logging and monitoring. -- Metrics and reputation: Connection times, bytes sent/received, inventory lists, and peer selection with robust error handling. -- **Enhanced** Network stability: Intelligent soft-ban enforcement with configurable thresholds, enhanced peer disconnect logging for troubleshooting. -- **Enhanced** Block processing: Proper handling of blocks returned as false by chain, conversion of unlinkable_block_exception to network exceptions, soft-ban functionality for peer management, comprehensive memory resize exception handling, trusted peer-aware soft-ban duration calculation, and **NEW** intelligent strike-based enforcement for unlinkable blocks and **NEW** sync spam prevention. -- **Enhanced** Peer state management: fork_rejected_until timestamp tracking, inhibit_fetching_sync_blocks flag management, automatic soft-ban expiration handling, trusted peer IP address storage for efficient lookup, closing_reason field for improved logging, and **NEW** sync_spam_strikes counter for reputation management. -- **NEW** Intelligent soft-ban enforcement: Automatic accumulation of unlinkable_block_strikes for peers sending blocks at or below head, with 20-strike threshold triggering soft-ban with fork_rejected_until timestamp and inhibit_fetching_sync_blocks flag, providing tolerant handling of stale fork violations while preventing systematic abuse. -- **NEW** Sync spam prevention: Automatic accumulation of sync_spam_strikes for peers repeatedly requesting sync for competing forks, with 50-strike threshold triggering 5-minute soft-ban with fork_rejected_until timestamp, preventing sync ping-pong loops and resource exhaustion attacks. - -**Section sources** -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp:68-162](file://libraries/network/peer_connection.cpp#L68-L162) -- [message_oriented_connection.cpp:128-140](file://libraries/network/message_oriented_connection.cpp#L128-L140) -- [stcp_socket.cpp:49-72](file://libraries/network/stcp_socket.cpp#L49-L72) -- [core_messages.hpp:233-306](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L306) -- [node.cpp:424-799](file://libraries/network/node.cpp#L424-L799) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) -- [database.cpp:1215-1246](file://libraries/chain/database.cpp#L1215-L1246) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [exceptions.hpp:33-45](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L45) - -## Architecture Overview -The peer connection architecture follows a layered design with enhanced error handling, intelligent soft-ban functionality, ANSI color-coded notifications, and **NEW** configurable strike-based soft-ban enforcement including sync spam prevention: -- Application (node) controls peer lifecycle and delegates message processing to the node delegate with improved exception safety, soft-ban logic, enhanced notification capabilities, and **NEW** intelligent strike-based enforcement including sync spam prevention. -- Peer (peer_connection) holds per-peer state and queues messages with robust error handling mechanisms, including soft-ban state tracking, improved peer state fields, closing_reason field for logging, and **NEW** sync_spam_strikes counter for reputation management. -- Transport (message_oriented_connection) frames messages and manages the read/write loop with enhanced logging. -- Security (stcp_socket) negotiates keys and encrypts traffic. -- Protocol (core_messages) defines the message types and semantics with reliable IP address extraction. -- **Enhanced** Chain integration (database/fork_database) validates blocks and propagates exceptions to the P2P layer for proper peer management, including comprehensive memory resize exception handling. -- **Enhanced** Network stability features (intelligent soft-ban mechanisms, enhanced logging) provide improved network resilience and troubleshooting capabilities. -- **NEW** Intelligent reputation system provides configurable enforcement of unlinkable block violations with tolerant handling of occasional stale forks. -- **NEW** Sync spam prevention provides protection against resource exhaustion attacks through configurable strike thresholds and automatic soft-ban enforcement with fork_rejected_until mechanism. - -```mermaid -sequenceDiagram -participant Node as "node" -participant Peer as "peer_connection" -participant MOC as "message_oriented_connection" -participant STCP as "stcp_socket" -participant Remote as "Remote Peer" -participant Chain as "database/fork_database" -Node->>Peer : "connect_to(endpoint)" -Peer->>MOC : "connect_to(endpoint)" -MOC->>STCP : "connect_to(endpoint)" -STCP->>Remote : "TCP connect" -STCP->>Remote : "ECDH key exchange" -Remote-->>STCP : "Public key" -STCP-->>STCP : "Derive shared secret" -STCP-->>MOC : "Encrypted channel ready" -MOC-->>Peer : "read_loop started" -Peer->>Peer : "send hello with IP extraction" -Peer->>Node : "on_message(hello)" -Node->>Peer : "on_hello_message()" -Peer->>Peer : "send connection_accepted" -Peer->>Node : "on_connection_accepted()" -Note over Node,Peer : "Negotiation complete with enhanced error handling, intelligent soft-ban support,
ANSI notifications, NEW intelligent strike-based enforcement,
and NEW sync spam prevention" -``` - -**Diagram sources** -- [peer_connection.cpp:208-242](file://libraries/network/peer_connection.cpp#L208-L242) -- [message_oriented_connection.cpp:135-140](file://libraries/network/message_oriented_connection.cpp#L135-L140) -- [stcp_socket.cpp:69-72](file://libraries/network/stcp_socket.cpp#L69-L72) -- [core_messages.hpp:233-272](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L272) -- [node.cpp:662-718](file://libraries/network/node.cpp#L662-L718) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -## Detailed Component Analysis - -### peer_connection: Enhanced Bidirectional Channel and State Machine -peer_connection encapsulates: -- Connection states: our_connection_state, their_connection_state, and connection_negotiation_status with improved error handling. -- Message queueing: real_queued_message and virtual_queued_message for immediate and deferred message generation with enhanced logging. -- Inventory tracking: sets for advertised and requested items, sync state, and throttling with robust error recovery. -- Metrics: bytes sent/received, last message timestamps, connection durations, and shared secret exposure with improved monitoring. -- **Enhanced** Soft-ban state: fork_rejected_until timestamp and inhibit_fetching_sync_blocks flag for peer management during emergency scenarios. -- **Enhanced** Peer trust integration: Automatic soft-ban duration calculation based on peer trust status for dual-tier soft-ban system. -- **Enhanced** Closing reason tracking: closing_reason field for enhanced logging and debugging capabilities. -- **NEW** Intelligent reputation: unlinkable_block_strikes counter for tracking violations of unlinkable blocks at/below head with configurable threshold enforcement. -- **NEW** Sync spam prevention: sync_spam_strikes counter for tracking repeated sync requests for competing forks with configurable threshold enforcement and fork_rejected_until mechanism. - -```mermaid -classDiagram -class peer_connection { -+peer_connection_delegate* _node -+message_oriented_connection _message_connection -+queue~unique_ptr~ _queued_messages -+size_t _total_queued_messages_size -+connection_negotiation_status negotiation_status -+our_connection_state our_state -+their_connection_state their_state -+node_id_t node_id -+node_id_t node_public_key -+uint32_t core_protocol_version -+std : : string user_agent -+fc : : ip : : address inbound_address -+uint16_t inbound_port -+uint16_t outbound_port -+bool inhibit_fetching_sync_blocks -+fc : : time_point fork_rejected_until -+std : : string closing_reason -+uint32_t unlinkable_block_strikes -+uint32_t sync_spam_strikes -+uint64_t get_total_bytes_sent() -+uint64_t get_total_bytes_received() -+void send_message(message) -+void send_item(item_id) -+void close_connection() -+void destroy_connection() -+Enhanced error handling with try-catch fallbacks -+Improved IP address extraction reliability -+Intelligent soft-ban state management with ANSI notifications -+Trusted peer awareness for soft-ban duration calculation -+Closing reason tracking for enhanced logging -+Intelligent reputation management for unlinkable blocks -+Sync spam prevention for competing fork requests -+fork_rejected_until mechanism for sync spam protection -} -class queued_message { -<> -+get_message(peer_connection_delegate*) message -+get_size_in_queue() size_t -} -class real_queued_message { -+message message_to_send -+size_t message_send_time_field_offset -+get_message() message -+get_size_in_queue() size_t -} -class virtual_queued_message { -+item_id item_to_send -+get_message(peer_connection_delegate*) message -+get_size_in_queue() size_t -} -peer_connection --> queued_message : "queues" -queued_message <|-- real_queued_message -queued_message <|-- virtual_queued_message -``` - -**Diagram sources** -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp:41-66](file://libraries/network/peer_connection.cpp#L41-L66) - -Key behaviors: -- Outgoing message pipeline: send_message enqueues a real_queued_message with enhanced error handling; send_item enqueues a virtual_queued_message; send_queueable_message validates queue size and triggers send_queued_messages_task with improved logging. -- Inbound message pipeline: on_message delegates to node delegate with robust error recovery; on_connection_closed transitions negotiation_status and notifies node with proper exception handling. -- Lifecycle: accept_connection and connect_to manage transport setup with enhanced error handling; close_connection and destroy_connection coordinate teardown with improved exception safety. -- **Enhanced** Soft-ban management: fork_rejected_until tracks soft-ban expiration; inhibit_fetching_sync_blocks prevents sync operations during ban period; ANSI color-coded notifications for ban events; trusted peer-aware soft-ban duration calculation. -- **Enhanced** Closing reason logging: Enhanced peer disconnect logging with closing_reason field for improved troubleshooting. -- **NEW** Intelligent enforcement: unlinkable_block_strikes counter accumulates violations for blocks at or below head; automatic soft-ban activation when threshold (20 strikes) is reached; automatic reset to 0 upon soft-ban enforcement. -- **NEW** Sync spam prevention: sync_spam_strikes counter accumulates repeated sync requests for competing forks; automatic soft-ban activation when threshold (50 strikes) is reached after 5-minute duration using fork_rejected_until mechanism; automatic reset to 0 upon soft-ban enforcement. - -**Section sources** -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp:244-338](file://libraries/network/peer_connection.cpp#L244-L338) -- [peer_connection.hpp:240-278](file://libraries/network/include/graphene/network/peer_connection.hpp#L240-L278) - -### message_oriented_connection: Enhanced Message Framing and Transport Loop -message_oriented_connection: -- Wraps stcp_socket for secure transport with improved error handling. -- Implements read_loop to decode messages, enforce size limits, and dispatch to delegate with enhanced logging. -- Provides send_message with padding to 16-byte boundaries and flush behavior with robust error recovery. -- Exposes connection metrics and shared secret access with improved monitoring capabilities. - -```mermaid -flowchart TD -Start(["send_message"]) --> Pad["Pad to 16-byte boundary"] -Pad --> Encrypt["Encrypt via AES encoder"] -Encrypt --> Write["Write to socket"] -Write --> Flush["Flush"] -Flush --> UpdateStats["Update bytes_sent + last_message_sent_time"] -UpdateStats --> EnhancedLogging["Enhanced error logging and monitoring"] -EnhancedLogging --> End(["Return with improved reliability"]) -ReadLoopStart(["read_loop"]) --> ReadHeader["Read fixed header"] -ReadHeader --> ValidateSize["Validate size <= MAX_MESSAGE_SIZE"] -ValidateSize --> ReadBody["Read padded body"] -ReadBody --> Dispatch["Delegate.on_message"] -Dispatch --> EnhancedMonitoring["Enhanced monitoring and logging"] -EnhancedMonitoring --> ReadLoopStart -``` - -**Diagram sources** -- [message_oriented_connection.cpp:237-283](file://libraries/network/message_oriented_connection.cpp#L237-L283) -- [message_oriented_connection.cpp:148-235](file://libraries/network/message_oriented_connection.cpp#L148-L235) - -**Section sources** -- [message_oriented_connection.hpp:45-79](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [message_oriented_connection.cpp:128-140](file://libraries/network/message_oriented_connection.cpp#L128-L140) -- [message_oriented_connection.cpp:237-283](file://libraries/network/message_oriented_connection.cpp#L237-L283) -- [message_oriented_connection.cpp:148-235](file://libraries/network/message_oriented_connection.cpp#L148-L235) - -### stcp_socket: Secure Transport with ECDH and AES -stcp_socket: -- Performs ECDH key exchange on connect/accept with enhanced error handling. -- Derives shared secret and initializes AES encoder/decoder with improved reliability. -- Reads/writes in 16-byte increments for AES compatibility with robust error recovery. -- Exposes get_shared_secret for upper layers with enhanced monitoring capabilities. - -```mermaid -sequenceDiagram -participant A as "Local stcp_socket" -participant B as "Remote stcp_socket" -A->>B : "Send serialized public key" -B-->>A : "Send serialized public key" -A->>A : "Compute shared secret (ECDH)" -A->>A : "Init AES encoder/decoder" -B->>B : "Compute shared secret (ECDH)" -B->>B : "Init AES encoder/decoder" -Note over A,B : "Secure channel ready with enhanced error handling" -``` - -**Diagram sources** -- [stcp_socket.cpp:49-72](file://libraries/network/stcp_socket.cpp#L49-L72) -- [stcp_socket.cpp:132-177](file://libraries/network/stcp_socket.cpp#L132-L177) - -**Section sources** -- [stcp_socket.hpp:37-93](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [stcp_socket.cpp:49-72](file://libraries/network/stcp_socket.cpp#L49-L72) -- [stcp_socket.cpp:132-177](file://libraries/network/stcp_socket.cpp#L132-L177) - -### Handshake and Authentication Protocols -Handshake flow with enhanced IP address extraction: -- ECDH key exchange via stcp_socket during connect/accept with improved error handling. -- Hello message exchange with user agent, protocol version, ports, and node identifiers with reliable IP address extraction. -- Connection accepted or rejected messages finalize negotiation with enhanced logging and monitoring. - -```mermaid -sequenceDiagram -participant Local as "Local peer_connection" -participant Remote as "Remote peer_connection" -participant Node as "Local node" -participant RemoteNode as "Remote node" -Local->>Remote : "TCP connect" -Remote-->>Local : "TCP accept" -Local->>Remote : "hello_message with IP extraction" -Remote->>RemoteNode : "on_hello_message" -alt "Accept" -Remote->>Local : "connection_accepted_message" -Local->>Node : "on_connection_accepted" -else "Reject" -Remote->>Local : "connection_rejected_message" -Local->>Node : "on_connection_rejected" -end -Note over Local,Remote : "Enhanced error handling and IP address reliability" -``` - -**Diagram sources** -- [core_messages.hpp:233-306](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L306) -- [node.cpp:662-718](file://libraries/network/node.cpp#L662-L718) -- [peer_connection.cpp:208-242](file://libraries/network/peer_connection.cpp#L208-L242) - -**Section sources** -- [core_messages.hpp:233-306](file://libraries/network/include/graphene/network/core_messages.hpp#L233-L306) -- [node.cpp:662-718](file://libraries/network/node.cpp#L662-L718) -- [peer_connection.cpp:208-242](file://libraries/network/peer_connection.cpp#L208-L242) - -### Connection Lifecycle Management -Lifecycle stages with enhanced error handling, intelligent soft-ban functionality, ANSI color-coded notifications, and **NEW** configurable strike-based soft-ban enforcement including sync spam prevention: -- Initiation: connect_to for outbound, accept_connection for inbound with improved exception safety. -- Negotiation: hello/connection_accepted or connection_rejected with enhanced logging and monitoring. -- Operation: message exchange, inventory advertisement, sync with robust error recovery mechanisms, soft-ban enforcement, ANSI color-coded notifications, trusted peer-aware soft-ban duration calculation, closing_reason logging, and **NEW** intelligent strike-based enforcement for unlinkable blocks and **NEW** sync spam prevention. -- Maintenance: keep-alive via time requests, bandwidth monitoring with improved reliability, soft-ban expiration checking with color-coded logging, closing_reason tracking, and **NEW** strike counter management. -- Graceful disconnection: closing_connection message, close_connection, destroy_connection with enhanced error handling and closing_reason logging. -- Error recovery: queue overflow closes connection with proper cleanup, peer database updates with improved logging, retry timers with better exception safety. -- **Enhanced** Soft-ban management: fork_rejected_until timestamp enforcement, inhibit_fetching_sync_blocks flag management, automatic soft-ban expiration handling, ANSI color-coded ban notifications, reduced soft-ban duration from 3600 seconds to 900 seconds, trusted peer-aware soft-ban duration calculation. -- **NEW** Intelligent enforcement: unlinkable_block_strikes counter accumulation for blocks at or below head, 20-strike threshold triggering soft-ban with automatic reset to 0, tolerant handling of occasional stale fork violations. -- **NEW** Sync spam prevention: sync_spam_strikes counter accumulation for repeated sync requests for competing forks, 50-strike threshold triggering 5-minute soft-ban with fork_rejected_until timestamp and automatic reset to 0, prevention of sync ping-pong loops. - -```mermaid -stateDiagram-v2 -[*] --> Disconnected -Disconnected --> Connecting : "connect_to() with error handling" -Disconnected --> Accepting : "accept_connection() with enhanced safety" -Connecting --> Connected : "hello + accepted" -Accepting --> Connected : "hello + accepted" -Connected --> NegotiationComplete : "inventory sync" -NegotiationComplete --> SoftBan : "fork_rejected_until set with ANSI notification
Enhanced closing reason logging
NEW : unlinkable_block_strikes counter
Intelligent 20-strike threshold enforcement" -SoftBan --> Connected : "soft-ban expired with color reset" -Connected --> IntelligentEnforcement : "20 unlinkable block strikes
Automatic soft-ban activation
inhibit_fetching_sync_blocks
Automatic strike reset to 0" -IntelligentEnforcement --> Connected : "soft-ban expired with color reset" -Connected --> SyncSpamPrevention : "50 sync spam strikes
Automatic 5-minute soft-ban
fork_rejected_until mechanism
Automatic strike reset to 0" -SyncSpamPrevention --> Connected : "soft-ban expired with color reset" -Connected --> Closing : "close_connection() with enhanced logging
closing_reason tracking" -Closing --> Closed : "on_connection_closed with enhanced logging
Enhanced diagnostics" -Closed --> [*] -``` - -**Diagram sources** -- [peer_connection.hpp:82-106](file://libraries/network/include/graphene/network/peer_connection.hpp#L82-L106) -- [peer_connection.cpp:356-369](file://libraries/network/peer_connection.cpp#L356-L369) -- [node.cpp:718-740](file://libraries/network/node.cpp#L718-L740) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5272-5274](file://libraries/network/node.cpp#L5272-L5274) - -**Section sources** -- [peer_connection.cpp:169-242](file://libraries/network/peer_connection.cpp#L169-L242) -- [peer_connection.cpp:356-369](file://libraries/network/peer_connection.cpp#L356-L369) -- [node.cpp:718-740](file://libraries/network/node.cpp#L718-L740) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5272-5274](file://libraries/network/node.cpp#L5272-L5274) - -### Message Queuing, Priority, and Multiplexing -- Queuing: real_queued_message stores full messages with enhanced error handling; virtual_queued_message defers generation via node delegate with improved logging. -- Limits: GRAPHENE_NET_MAXIMUM_QUEUED_MESSAGES_IN_BYTES prevents memory pressure with better exception safety; exceeding triggers closure with proper cleanup. -- Priority: During sync, prioritized_item_id sorts blocks before transactions with enhanced monitoring; during normal operation, FIFO per peer with throttling and improved error recovery. -- Multiplexing: Multiple peer_connection instances share node delegate with enhanced error handling; each peer has independent queues and state with improved reliability. - -```mermaid -flowchart TD -Enqueue["Enqueue message"] --> CheckQueue["Check queue size with enhanced validation"] -CheckQueue --> |OK| Push["Push to queue with error logging"] -CheckQueue --> |Too large| Close["Close connection with proper cleanup"] -Push --> StartTask{"send_queued_messages_task running?"} -StartTask --> |No| Launch["Launch async task with enhanced monitoring"] -StartTask --> |Yes| Wait["Wait for completion with error handling"] -Launch --> SendLoop["Send loop drains queue with improved logging"] -Wait --> SendLoop -SendLoop --> EnhancedMonitoring["Enhanced monitoring and error recovery"] -EnhancedMonitoring --> Done["Done with proper cleanup"] -``` - -**Diagram sources** -- [peer_connection.cpp:310-338](file://libraries/network/peer_connection.cpp#L310-L338) -- [peer_connection.cpp:255-308](file://libraries/network/peer_connection.cpp#L255-L308) -- [config.hpp:58-58](file://libraries/network/include/graphene/network/config.hpp#L58-L58) - -**Section sources** -- [peer_connection.cpp:310-338](file://libraries/network/peer_connection.cpp#L310-L338) -- [peer_connection.cpp:255-308](file://libraries/network/peer_connection.cpp#L255-L308) -- [config.hpp:58-58](file://libraries/network/include/graphene/network/config.hpp#L58-L58) - -### Peer State Tracking, Metrics, and Reputation -Peer state tracking with enhanced error handling, intelligent soft-ban support, ANSI color-coded notifications, and **NEW** intelligent reputation management including sync spam prevention: -- Connection states: negotiated status, direction, firewalled state, clock offset, round-trip delay with improved monitoring and logging. -- Inventory: advertised to peer, advertised to us, requested, sync state, throttling windows with robust error recovery mechanisms. -- Metrics: bytes sent/received, last message times, connection duration, termination time with enhanced logging and monitoring. -- **Enhanced** Soft-ban state: fork_rejected_until timestamp tracks soft-ban expiration; inhibit_fetching_sync_blocks prevents sync operations during ban period. -- **Enhanced** Trusted peer management: Automatic soft-ban duration calculation based on peer trust status; efficient IP address lookup for trusted peer detection. -- **Enhanced** Closing reason logging: Enhanced peer disconnect logging with closing_reason field for improved troubleshooting. -- **NEW** Intelligent reputation: unlinkable_block_strikes counter tracks violations for unlinkable blocks at/below head; automatic soft-ban activation when threshold (20 strikes) is reached; automatic reset to 0 upon enforcement. -- **NEW** Sync spam prevention: sync_spam_strikes counter tracks repeated sync requests for competing forks; automatic soft-ban activation when threshold (50 strikes) is reached after 5-minute duration using fork_rejected_until mechanism; automatic reset to 0 upon enforcement. - -Reputation and selection with improved reliability: -- peer_database tracks endpoints, last seen, disposition, and attempt counts with enhanced error handling. -- node selects peers based on desired/max connections, retry timeouts, and peer database entries with better exception safety. -- Enhanced logging and monitoring throughout the peer selection and balancing process with ANSI color-coded notifications. -- **Enhanced** Soft-ban enforcement: Automatic soft-ban detection and enforcement during block processing with color-coded logging. -- **Enhanced** Trusted peer awareness: Peer trust status influences soft-ban duration and network behavior. -- **Enhanced** Database dumping: Enhanced peer database dumping with enhanced JSON serialization and error handling. -- **NEW** Intelligent enforcement: Configurable 20-strike threshold for unlinkable blocks at/below head with tolerant handling of occasional stale forks. -- **NEW** Sync spam prevention: Configurable 50-strike threshold for repeated sync requests with 5-minute soft-ban duration using fork_rejected_until mechanism for preventing resource exhaustion attacks. - -**Section sources** -- [peer_connection.hpp:175-279](file://libraries/network/include/graphene/network/peer_connection.hpp#L175-L279) -- [peer_connection.cpp:428-480](file://libraries/network/peer_connection.cpp#L428-L480) -- [peer_database.hpp:47-71](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) -- [node.cpp:518-526](file://libraries/network/node.cpp#L518-L526) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5265-5274](file://libraries/network/node.cpp#L5265-L5274) - -### Enhanced Network Stability Features -**Enhanced** Network stability improvements with comprehensive error handling, intelligent soft-ban mechanisms, ANSI color-coded notifications, and **NEW** configurable strike-based soft-ban enforcement including sync spam prevention: -- **Intelligent soft-ban mechanisms**: Soft-ban duration reduced from 3600 seconds (1 hour) to 900 seconds (15 minutes) for improved network responsiveness; trusted peers receive 5-minute soft-ban duration; regular peers receive 15-minute (reduced) soft-ban duration. -- **Intelligent strike-based enforcement**: Configurable 20-strike threshold for unlinkable blocks at/below head; automatic soft-ban activation with fork_rejected_until timestamp and inhibit_fetching_sync_blocks flag; automatic strike counter reset to 0 upon enforcement. -- **Enhanced peer disconnect logging**: closing_reason field provides detailed information about why peers disconnect for improved troubleshooting. -- **Improved peer database dumping**: Enhanced JSON serialization and error handling for better database management. -- **NEW** Sync spam prevention: Configurable 50-strike threshold for repeated sync requests with 5-minute soft-ban duration using fork_rejected_until mechanism for preventing resource exhaustion attacks. -- Node layer implements intelligent soft-ban functionality for peer management during emergency scenarios with ANSI color-coded notifications. -- **Enhanced** Trusted peer integration: Automatic soft-ban duration calculation based on peer trust status; 5-minute soft-ban for trusted peers, 15-minute (reduced) soft-ban for regular peers. -- P2P plugin converts chain exceptions to network exceptions for consistent handling. -- ANSI color codes (CLOG_RED, CLOG_RESET) provide visual emphasis for ban notifications in terminal output. -- **Enhanced** Memory management: Deferred resize operations during block processing handled gracefully without penalizing peers. -- **NEW** Intelligent reputation management: Configurable 20-strike threshold for unlinkable blocks at/below head; automatic soft-ban activation with fork_rejected_until timestamp and inhibit_fetching_sync_blocks flag; automatic strike counter reset to 0 upon enforcement. -- **NEW** Sync spam prevention: Configurable 50-strike threshold for repeated sync requests with 5-minute soft-ban duration using fork_rejected_until mechanism for preventing resource exhaustion attacks. - -```mermaid -flowchart TD -BlockIn["Block received from peer"] --> ProcessBlock["Process block in database"] -ProcessBlock --> CheckChain["Check chain state"] -CheckChain --> |Valid| AcceptBlock["Accept block"] -CheckChain --> |Unlinkable| CheckPosition["Check block position relative to head"] -CheckPosition --> |At or Below Head| StrikeCounter["Increment unlinkable_block_strikes
Configurable 20-strike threshold"] -StrikeCounter --> CheckThreshold{"Strikes >= 20?"} -CheckThreshold --> |Yes| SoftBan["Soft-ban peer with fork_rejected_until
Set inhibit_fetching_sync_blocks
Reset unlinkable_block_strikes to 0"] -CheckThreshold --> |No| LogStrike["Log strike count with enhanced details"] -CheckPosition --> |Above Head| RestartSync["Restart sync with peer
Fetch missing parents"] -CheckChain --> |Too old| ThrowOld["Throw block_older_than_undo_history"] -CheckChain --> |Memory Resize| ThrowResize["Throw deferred_resize_exception"] -ThrowOld --> ConvertNet["Convert to network exception"] -ThrowResize --> ConvertNet -ConvertNet --> NodeHandle["Node handles exception"] -NodeHandle --> PeerTrust{"Peer is trusted?"} -PeerTrust --> |Yes| SetShortBan["Set fork_rejected_until + inhibit_fetching_sync_blocks
5-minute soft-ban (trusted peers)
ANSI Red Notification"] -PeerTrust --> |No| SetReducedBan["Set fork_rejected_until + inhibit_fetching_sync_blocks
900 sec (15 min) soft-ban (regular peers)
ANSI Red Notification"] -SetShortBan --> Broadcast["Broadcast to peers"] -SetReducedBan --> Broadcast -ThrowResize --> NoBan["No soft-ban (deferred resize)
Just retry block later"] -NoBan --> Broadcast -AcceptBlock --> Broadcast -LogStrike --> Broadcast -SoftBan --> Broadcast -RestartSync --> Broadcast -``` - -**Diagram sources** -- [database.cpp:1215-1246](file://libraries/chain/database.cpp#L1215-L1246) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [node.cpp:3874-3908](file://libraries/network/node.cpp#L3874-L3908) -- [node.cpp:3598-3626](file://libraries/network/node.cpp#L3598-L3626) -- [node.cpp:5272-5274](file://libraries/network/node.cpp#L5272-L5274) -- [p2p_plugin.cpp:172-182](file://plugins/p2p/p2p_plugin.cpp#L172-L182) -- [node.cpp:599-600](file://libraries/network/node.cpp#L599-L600) - -**Section sources** -- [database.cpp:1215-1246](file://libraries/chain/database.cpp#L1215-L1246) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [node.cpp:3874-3908](file://libraries/network/node.cpp#L3874-L3908) -- [node.cpp:3598-3626](file://libraries/network/node.cpp#L3598-L3626) -- [node.cpp:5272-5274](file://libraries/network/node.cpp#L5272-L5274) -- [p2p_plugin.cpp:172-182](file://plugins/p2p/p2p_plugin.cpp#L172-L182) -- [node.cpp:599-600](file://libraries/network/node.cpp#L599-L600) - -### Enhanced Peer Database Operations -**Enhanced** Improved peer database operations with comprehensive tracking and enhanced serialization: -- **Enhanced** JSON serialization: Enhanced JSON serialization for peer database records with improved error handling and logging. -- **Comprehensive** tracking: Enhanced peer database dumping with unlinkable_block_strikes tracking for improved reputation management. -- **Robust** file operations: Enhanced file operations with proper error handling for database loading and saving. -- **Database** management: Improved peer database management with enhanced dumping capabilities for debugging and maintenance. -- **Error** handling: Comprehensive error handling for database operations with detailed logging for troubleshooting. - -```mermaid -flowchart TD -DumpRequest["Peer database dump request"] --> LoadRecords["Load peer records from database"] -LoadRecords --> SerializeJSON["Serialize records to JSON with enhanced tracking"] -SerializeJSON --> SaveFile["Save JSON to file with error handling"] -SaveFile --> Success["Database dumped successfully"] -SaveFile --> |Error| LogError["Log error and continue"] -LogError --> Success -``` - -**Diagram sources** -- [peer_database.cpp:120-137](file://libraries/network/peer_database.cpp#L120-L137) - -**Section sources** -- [peer_database.cpp:120-137](file://libraries/network/peer_database.cpp#L120-L137) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) - -### Enhanced Closing Reason Tracking -**Enhanced** Closing reason tracking with detailed logging for improved troubleshooting: -- **Reason storage**: closing_reason field stores the reason for peer disconnection before moving to closing list. -- **Enhanced logging**: Detailed logging includes both remote and local reasons for disconnection. -- **Error handling**: Enhanced error handling for disconnection scenarios with proper reason logging. -- **Debugging support**: Improved debugging capabilities through detailed closing reason information. - -```mermaid -flowchart TD -DisconnectEvent["Peer disconnection event"] --> StoreReason["Store closing_reason on peer"] -StoreReason --> LogReason["Log reason with enhanced details"] -LogReason --> CloseConnection["Close connection gracefully"] -CloseConnection --> Cleanup["Cleanup resources and state"] -``` - -**Diagram sources** -- [node.cpp:5013-5014](file://libraries/network/node.cpp#L5013-L5014) -- [node.cpp:3061-3062](file://libraries/network/node.cpp#L3061-L3062) - -**Section sources** -- [node.cpp:5013-5014](file://libraries/network/node.cpp#L5013-L5014) -- [node.cpp:3061-3062](file://libraries/network/node.cpp#L3061-L3062) - -### Enhanced Intelligent Soft-Ban Enforcement -**NEW** Configurable intelligent soft-ban mechanism for unlinkable blocks at/below head: -- **Intelligent strike accumulation**: unlinkable_block_strikes counter increments for each unlinkable block received from peers when block number is at or below current head. -- **Configurable threshold enforcement**: When strikes reach 20, automatic soft-ban is triggered with fork_rejected_until timestamp and inhibit_fetching_sync_blocks flag set. -- **Automatic reset**: unlinkable_block_strikes counter resets to 0 after soft-ban enforcement. -- **Tolerant handling**: Provides tolerance for occasional stale fork violations while preventing systematic abuse. -- **Integration**: Seamlessly integrates with existing soft-ban infrastructure and trusted peer considerations. -- **Intelligent enforcement**: Combines configurable thresholds with trusted peer awareness for optimal network stability. - -```mermaid -flowchart TD -BlockReceived["Unlinkable block received"] --> CheckHead["Check if block <= head"] -CheckHead --> |Below or Equal| IncrementStrikes["Increment unlinkable_block_strikes"] -IncrementStrikes --> CheckThreshold{"Strikes >= 20?"} -CheckThreshold --> |Yes| TriggerSoftBan["Trigger soft-ban:
Set fork_rejected_until
Set inhibit_fetching_sync_blocks
Reset strikes to 0"] -CheckThreshold --> |No| LogStrikes["Log current strike count"] -CheckHead --> |Above Head| NormalBehavior["Normal sync behavior
Restart sync with peer"] -TriggerSoftBan --> Broadcast["Broadcast to peers"] -LogStrikes --> Broadcast -NormalBehavior --> Broadcast -``` - -**Diagram sources** -- [node.cpp:3874-3908](file://libraries/network/node.cpp#L3874-L3908) -- [peer_connection.hpp:279-283](file://libraries/network/include/graphene/network/peer_connection.hpp#L279-L283) - -**Section sources** -- [node.cpp:3874-3908](file://libraries/network/node.cpp#L3874-L3908) -- [peer_connection.hpp:279-283](file://libraries/network/include/graphene/network/peer_connection.hpp#L279-L283) - -### Enhanced Sync Spam Prevention -**NEW** Configurable intelligent sync spam prevention mechanism for repeated sync requests: -- **Intelligent strike accumulation**: sync_spam_strikes counter increments for each repeated sync request for competing forks when peer block number is at or below current head. -- **Configurable threshold enforcement**: When strikes reach 50, automatic 5-minute soft-ban is triggered with fork_rejected_until timestamp set. -- **Automatic reset**: sync_spam_strikes counter resets to 0 after soft-ban enforcement. -- **Ping-pong loop prevention**: Prevents endless sync restart loops between peers on competing forks at the same height. -- **Integration**: Seamlessly integrates with existing soft-ban infrastructure and sync management. -- **Resource protection**: Protects network resources from sync spam attacks while maintaining legitimate sync operations. -- **fork_rejected_until mechanism**: Uses timestamp-based soft-ban enforcement for precise timing control. - -```mermaid -flowchart TD -SyncRequest["Repeated sync request detected"] --> CheckFork["Check if peer block <= head"] -CheckFork --> |True| IncrementStrikes["Increment sync_spam_strikes"] -IncrementStrikes --> CheckThreshold{"Strikes >= 50?"} -CheckThreshold --> |Yes| TriggerSoftBan["Trigger 5-minute soft-ban:
Set fork_rejected_until
Reset strikes to 0"] -CheckThreshold --> |No| LogStrikes["Log current strike count"] -CheckFork --> |False| RestartSync["Restart sync with peer
Fetch missing blocks"] -TriggerSoftBan --> Broadcast["Broadcast to peers"] -LogStrikes --> Broadcast -RestartSync --> Broadcast -``` - -**Diagram sources** -- [node.cpp:2520-2590](file://libraries/network/node.cpp#L2520-L2590) -- [peer_connection.hpp:285-289](file://libraries/network/include/graphene/network/peer_connection.hpp#L285-L289) - -**Section sources** -- [node.cpp:2520-2590](file://libraries/network/node.cpp#L2520-L2590) -- [peer_connection.hpp:285-289](file://libraries/network/include/graphene/network/peer_connection.hpp#L285-L289) - -### Examples and Patterns -- Peer connection setup with enhanced error handling: - - Outbound: peer_connection::connect_to(endpoint) -> message_oriented_connection::connect_to -> stcp_socket::connect_to -> ECDH -> hello -> connection_accepted with improved logging. - - Inbound: accept_connection -> ECDH -> hello -> connection_accepted with enhanced error recovery. -- Message exchange with robust monitoring: - - send_message queues a real message with enhanced error handling; send_item queues a virtual message; send_queued_messages_task sends them with improved logging. -- Connection monitoring with enhanced reliability: - - get_total_bytes_sent/get_total_bytes_received, last_message_sent_time/last_message_received, get_connection_time/get_connection_terminated_time with improved error recovery. -- Peer selection and balancing with better exception safety: - - node maintains desired/max connections, peer database, and retry timers; balances by selecting candidates from peer_database and initiating connect_to with enhanced error handling. -- **Enhanced** Intelligent soft-ban management: - - Automatic soft-ban detection for peers sending unlinkable blocks; fork_rejected_until timestamp enforcement; inhibit_fetching_sync_blocks flag management; automatic soft-ban expiration handling; ANSI color-coded ban notifications; reduced soft-ban duration from 3600 seconds to 900 seconds; trusted peer-aware soft-ban duration calculation. -- **Enhanced** Closing reason tracking: - - Enhanced peer disconnect logging with closing_reason field; detailed reason storage and logging for improved troubleshooting. -- **Enhanced** Trusted peer integration: - - Automatic trusted peer registration from config.ini trusted-snapshot-peer options; efficient IP address lookup for trust detection; dual-tier soft-ban system with 5-minute duration for trusted peers; seamless P2P-snapshot plugin coordination. -- **Enhanced** Memory resize exception handling: - - Deferred shared memory resize operations during block processing; proper exception propagation through P2P layer; no peer penalization for transient memory resize operations; trusted peer consideration in exception handling. -- **Enhanced** Database dumping: - - Enhanced peer database dumping with improved JSON serialization; robust file operations with error handling; comprehensive logging for database management. -- **NEW** Intelligent reputation management: - - Configurable 20-strike threshold for unlinkable blocks at/below head; automatic soft-ban activation with fork_rejected_until timestamp; automatic strike counter reset to 0; tolerant handling of occasional stale fork violations; integration with existing soft-ban infrastructure. -- **NEW** Sync spam prevention: - - Configurable 50-strike threshold for repeated sync requests; automatic 5-minute soft-ban activation with fork_rejected_until timestamp; automatic strike counter reset to 0; prevention of sync ping-pong loops; protection against resource exhaustion attacks; fork_rejected_until mechanism for precise timing control. - -**Section sources** -- [peer_connection.cpp:208-242](file://libraries/network/peer_connection.cpp#L208-L242) -- [peer_connection.cpp:340-354](file://libraries/network/peer_connection.cpp#L340-L354) -- [peer_connection.cpp:371-399](file://libraries/network/peer_connection.cpp#L371-L399) -- [node.cpp:518-526](file://libraries/network/node.cpp#L518-L526) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [node.cpp:5272-5274](file://libraries/network/node.cpp#L5272-L5274) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -## Dependency Analysis -The peer connection subsystem exhibits clear layering and low coupling with enhanced error handling, intelligent soft-ban functionality, ANSI color-coded notifications, and **NEW** configurable strike-based soft-ban enforcement including sync spam prevention: -- peer_connection depends on message_oriented_connection and node delegate with improved exception safety. -- message_oriented_connection depends on stcp_socket and delegates to peer_connection with enhanced logging. -- stcp_socket depends on fc crypto primitives and tcp socket with robust error recovery. -- node orchestrates peer_connection instances and peer_database with better exception handling, soft-ban logic, ANSI notification support, and **NEW** intelligent strike-based enforcement including sync spam prevention. -- core_messages defines protocol contracts used across layers with reliable IP address handling. -- **Enhanced** database and fork_database depend on chain exceptions and propagate network exceptions to P2P layer. -- **Enhanced** p2p_plugin converts chain exceptions to network exceptions for consistent handling and manages trusted peer registration. -- **Enhanced** snapshot_plugin provides trusted-snapshot-peer configuration and peer discovery for trusted peer management. -- **Enhanced** database_exceptions defines deferred_resize_exception for memory resize operations. -- **Enhanced** Network stability features create dependencies between node and peer_connection for intelligent enforcement mechanisms. -- **NEW** Intelligent reputation system creates dependency between node and peer_connection for strike counter management and threshold enforcement. -- **NEW** Sync spam prevention creates dependency between node and peer_connection for sync_spam_strikes counter management and threshold enforcement using fork_rejected_until mechanism. - -```mermaid -graph LR -PC["peer_connection"] --> MOC["message_oriented_connection"] -MOC --> STCP["stcp_socket"] -PC --> CM["core_messages"] -N["node"] --> PC -N --> PD["peer_database"] -N --> DB["database"] -N --> SB["Intelligent Soft-ban
Configurable Strike Thresholds"] -N --> CR["Enhanced Logging
Detailed Status Reporting"] -N --> DBOPS["Database Operations
Comprehensive Tracking"] -N --> SS["Sync Spam Prevention
50-strike Threshold
5-minute Duration
fork_rejected_until Mechanism"] -P2P["p2p_plugin"] --> N -P2P --> SNAP["snapshot_plugin"] -SNAP --> N -DB --> FD["fork_database"] -DB --> EX["exceptions"] -N --> EX -EX --> DR["deferred_resize_exception"] -PC --> SB -PC --> SS -``` - -**Diagram sources** -- [peer_connection.hpp:79-351](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [message_oriented_connection.hpp:45-79](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [stcp_socket.hpp:37-93](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [core_messages.hpp:72-95](file://libraries/network/include/graphene/network/core_messages.hpp#L72-L95) -- [node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [peer_database.hpp:104-134](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message.hpp:42-106](file://libraries/network/include/graphene/network/message.hpp#L42-L106) -- [database.cpp:1215-1246](file://libraries/chain/database.cpp#L1215-L1246) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [exceptions.hpp:33-45](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L45) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -**Section sources** -- [peer_connection.hpp:26-45](file://libraries/network/include/graphene/network/peer_connection.hpp#L26-L45) -- [message_oriented_connection.hpp:26-28](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L26-L28) -- [stcp_socket.hpp:26-28](file://libraries/network/include/graphene/network/stcp_socket.hpp#L26-L28) -- [core_messages.hpp:26-35](file://libraries/network/include/graphene/network/core_messages.hpp#L26-L35) -- [node.hpp:26-31](file://libraries/network/include/graphene/network/node.hpp#L26-L31) -- [peer_database.hpp:26-35](file://libraries/network/include/graphene/network/peer_database.hpp#L26-L35) -- [message.hpp:26-31](file://libraries/network/include/graphene/network/message.hpp#L26-L31) -- [database.cpp:1215-1246](file://libraries/chain/database.cpp#L1215-L1246) -- [fork_database.cpp:34-46](file://libraries/chain/fork_database.cpp#L34-L46) -- [exceptions.hpp:33-45](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L45) -- [node.cpp:593-601](file://libraries/network/node.cpp#L593-L601) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -## Performance Considerations -- Message sizing: MAX_MESSAGE_SIZE caps payload; padding to 16 bytes ensures AES compatibility with enhanced error handling. -- Queue limits: GRAPHENE_NET_MAXIMUM_QUEUED_MESSAGES_IN_BYTES prevents memory growth under heavy load with better exception safety. -- Throttling: Inventory lists and transaction fetching inhibition mitigate flooding with improved monitoring. -- Bandwidth monitoring: node tracks read/write rates and applies rate limiting groups with enhanced logging. -- Sync optimization: interleaved prefetching and prioritization reduce sync time with robust error recovery mechanisms. -- Enhanced error handling: Comprehensive try-catch fallbacks throughout peer statistics logging ensure more robust operation of the P2P network layer. -- **Enhanced** Soft-ban optimization: Intelligent soft-ban enforcement prevents cascading disconnections during emergency scenarios, improving network stability with reduced soft-ban duration from 3600 seconds to 900 seconds. -- **Enhanced** Block processing efficiency: Proper exception handling reduces unnecessary reprocessing and improves overall network performance. -- **Enhanced** Memory management: Deferred resize operations prevent blocking during shared memory expansion, improving system responsiveness. -- **Enhanced** Notification performance: ANSI color-coded logging provides visual emphasis without impacting performance significantly. -- **Enhanced** Trusted peer performance: O(1) IP address lookup for trusted peer detection minimizes overhead; efficient configuration parsing reduces startup time. -- **Enhanced** Dual-tier optimization: Separate soft-ban duration calculation eliminates redundant calculations while providing flexible peer management. -- **Enhanced** Intelligent reputation management: Configurable 20-strike threshold provides optimal balance between tolerance and enforcement; minimal performance impact through simple counter increment and comparison operations. -- **Enhanced** Sync spam prevention: Configurable 50-strike threshold provides optimal protection against resource exhaustion attacks; minimal performance impact through simple counter increment and comparison operations. -- **NEW** Intelligent enforcement efficiency: Configurable 20-strike threshold provides optimal balance between tolerance and enforcement; minimal performance impact through simple counter increment and comparison operations. -- **NEW** Sync spam prevention efficiency: Configurable 50-strike threshold provides optimal protection against resource exhaustion attacks; minimal performance impact through simple counter increment and comparison operations using fork_rejected_until mechanism. - -## Troubleshooting Guide -Common issues and remedies with enhanced error handling, intelligent soft-ban functionality, ANSI color-coded notifications, and **NEW** configurable strike-based soft-ban enforcement including sync spam prevention: -- Connection refused or rejected: Review rejection reasons in connection_rejected_message with improved logging; check protocol version, chain ID, and node policies with better error reporting. -- Handshake failures: Verify ECDH key exchange succeeded with enhanced error handling; inspect stcp_socket logs with improved monitoring; ensure endpoints are reachable with robust error recovery. -- Queue overflow: Monitor queue size with enhanced logging; adjust rate or reduce message sizes; consider disconnecting misbehaving peers with proper cleanup. -- Idle peers: Use inactivity timeouts with improved exception safety; terminate inactive connections; rebalance peers with better error handling. -- Peer reputation: Inspect peer_database entries with enhanced logging; prune failed peers; respect retry delays with improved error recovery mechanisms. -- IP address extraction issues: Enhanced safe static_cast operations with try-catch fallback mechanisms ensure reliable IP address extraction throughout peer information handling. -- **Enhanced** Soft-ban issues: Check fork_rejected_until timestamps and inhibit_fetching_sync_blocks flags; verify automatic soft-ban expiration handling; monitor soft-ban effectiveness; review ANSI color-coded ban notifications for quick identification; verify trusted peer soft-ban duration calculation; verify reduced soft-ban duration from 3600 seconds to 900 seconds. -- **Enhanced** Closing reason tracking: Verify closing_reason field logging; check enhanced peer disconnect logging for troubleshooting; review detailed reason information for improved debugging. -- **Enhanced** Trusted peer configuration: Verify trusted-snapshot-peer entries in config.ini are valid IP:port format; check automatic registration in P2P plugin logs; ensure IP address parsing succeeds; verify O(1) lookup functionality. -- **Enhanced** Block processing errors: Review unlinkable_block_exception handling and soft-ban enforcement; verify proper exception conversion from chain to network exceptions; check memory resize exception handling; verify trusted peer-aware soft-ban duration calculation. -- **Enhanced** Memory resize issues: Monitor deferred_resize_exception occurrences; verify proper exception propagation through P2P layer; ensure no peer penalization for transient memory resize operations. -- **Enhanced** Notification visibility: Verify ANSI color codes are properly displayed in terminal; check CLOG_RED and CLOG_RESET definitions for proper formatting. -- **Enhanced** Plugin integration: Verify snapshot plugin loads trusted-snapshot-peer configuration; check P2P plugin registration success; ensure seamless coordination between plugins. -- **Enhanced** Database dumping: Verify enhanced peer database dumping functionality; check JSON serialization and error handling; ensure proper database management capabilities. -- **NEW** Intelligent reputation issues: Monitor unlinkable_block_strikes counter values; verify 20-strike threshold enforcement; check automatic soft-ban activation and strike reset behavior; ensure tolerant handling of occasional stale fork violations; verify integration with existing soft-ban infrastructure. -- **NEW** Sync spam prevention issues: Monitor sync_spam_strikes counter values; verify 50-strike threshold enforcement; check automatic 5-minute soft-ban activation and fork_rejected_until mechanism; verify automatic strike reset behavior; ensure prevention of sync ping-pong loops; verify integration with existing sync management. -- **NEW** fork_rejected_until mechanism: Verify timestamp-based soft-ban enforcement; check 5-minute duration precision; ensure automatic soft-ban expiration handling; verify integration with sync spam prevention logic. - -**Section sources** -- [core_messages.hpp:285-306](file://libraries/network/include/graphene/network/core_messages.hpp#L285-L306) -- [config.hpp:48-50](file://libraries/network/include/graphene/network/config.hpp#L48-L50) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) -- [peer_connection.cpp:314-325](file://libraries/network/peer_connection.cpp#L314-L325) -- [node.cpp:3448-3470](file://libraries/network/node.cpp#L3448-L3470) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [node.cpp:5272-5274](file://libraries/network/node.cpp#L5272-L5274) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -## Conclusion -Peer Connection Management in this codebase provides a robust, layered architecture for secure, multiplexed peer communication with enhanced error handling, reliability, and **Enhanced** network stability features. It supports comprehensive lifecycle management, strict authentication via ECDH/AES, and sophisticated message queuing with priority and throttling. The node orchestrates peers, maintains reputation, and optimizes selection and balancing with improved exception safety. Enhanced peer information handling with reliable IP address extraction using safe static_cast operations with try-catch fallback mechanisms, combined with improved error handling and performance optimizations throughout peer statistics logging, ensures more robust operation of the P2P network layer. - -**Enhanced** The system now includes sophisticated network stability improvements featuring intelligent soft-ban mechanisms with configurable strike-based enforcement, comprehensive peer database operations with unlinkable_block_strikes tracking, improved peer synchronization logging with detailed status reporting, enhanced error diagnostics for peer synchronization issues, and intelligent reputation management systems. These enhancements provide superior network stability, improved operational visibility through color-coded terminal notifications, enhanced troubleshooting capabilities through detailed closing reason tracking, intelligent enforcement mechanisms through configurable strike thresholds, and intelligent reputation management through configurable enforcement that tolerates occasional stale fork violations while preventing systematic abuse. - -**NEW** Additionally, the system now features advanced sync spam prevention mechanisms that protect against resource exhaustion attacks through configurable strike thresholds and automatic soft-ban enforcement using fork_rejected_until mechanism, ensuring network resilience against malicious or misconfigured peers attempting to overwhelm the system with repeated sync requests. The implementation includes precise 5-minute soft-ban duration control and automatic strike counter reset to prevent permanent peer blocking while effectively mitigating spam attacks. - -## Appendices - -### Configuration Constants -Important tunables affecting peer connection behavior: -- GRAPHENE_NET_PROTOCOL_VERSION: Protocol version for compatibility. -- MAX_MESSAGE_SIZE: Maximum message size in bytes. -- GRAPHENE_NET_MAXIMUM_QUEUED_MESSAGES_IN_BYTES: Queue size cap with enhanced error handling. -- GRAPHENE_NET_DEFAULT_DESIRED_CONNECTIONS / GRAPHENE_NET_DEFAULT_MAX_CONNECTIONS: Target and hard limits. -- GRAPHENE_NET_PEER_HANDSHAKE_INACTIVITY_TIMEOUT / GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT: Timeout thresholds with improved exception safety. -- **Enhanced** TRUSTED_SOFT_BAN_DURATION_SEC / SOFT_BAN_DURATION_SEC: 300 seconds (5 minutes) vs 900 seconds (15 minutes) for trusted vs regular peers. -- **Enhanced** DISCONNECT_RECONNECT_COOLDOWN_SEC: 30-second cooldown period for per-IP disconnect management. -- **NEW** UNLINKABLE_BLOCK_STRIKE_THRESHOLD: 20-strike threshold for configurable soft-ban enforcement on unlinkable blocks at/below head. -- **NEW** SYNC_SPAM_STRIKE_THRESHOLD: 50-strike threshold for configurable soft-ban enforcement on sync spam attacks. -- **NEW** SYNC_SPAM_BAN_DURATION_SEC: 300-second (5-minute) duration for sync spam soft-bans. -- **NEW** INTELLIGENT_SOFT_BAN_ENFORCEMENT: Configurable threshold with trusted peer awareness for optimal network stability. -- **NEW** fork_rejected_until mechanism: Timestamp-based soft-ban enforcement for precise timing control. - -**Section sources** -- [config.hpp:26-106](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [node.cpp:599-600](file://libraries/network/node.cpp#L599-L600) - -### Network Exception Types -**Enhanced** Exception types for improved error handling: -- unlinkable_block_exception: Used for blocks from dead forks with parents not in fork database. -- block_older_than_undo_history: Used for blocks too old for fork database processing. -- peer_is_on_an_unreachable_fork: Used when peers are on incompatible forks. -- **Enhanced** deferred_resize_exception: Used for shared memory resize operations during block processing, indicating transient memory expansion requiring block retry. -- Enhanced error propagation: Chain exceptions converted to network exceptions for consistent P2P layer handling. -- **Enhanced** Trusted peer consideration: Deferred resize exceptions do not trigger soft-bans as they represent local memory conditions. -- **Enhanced** Closing reason tracking: Enhanced peer disconnect logging with detailed reason information. -- **NEW** Intelligent enforcement: Configurable 20-strike threshold for unlinkable blocks at/below head with automatic soft-ban activation. -- **NEW** Sync spam prevention: Configurable 50-strike threshold for repeated sync requests with 5-minute soft-ban activation using fork_rejected_until mechanism. - -**Section sources** -- [exceptions.hpp:33-45](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L45) -- [p2p_plugin.cpp:172-182](file://plugins/p2p/p2p_plugin.cpp#L172-L182) -- [database.cpp:1239-1241](file://libraries/chain/database.cpp#L1239-L1241) -- [database_exceptions.hpp:86-86](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L86-L86) - -### ANSI Color Code Definitions -**Enhanced** Terminal formatting support for enhanced notifications: -- CLOG_RED: ANSI escape sequence for red text formatting. -- CLOG_ORANGE: ANSI escape sequence for orange text formatting. -- CLOG_RESET: ANSI escape sequence to reset terminal formatting. -- Used extensively in soft-ban notifications and other important system messages for improved visual emphasis. - -**Section sources** -- [node.cpp:79-82](file://libraries/network/node.cpp#L79-L82) -- [node.cpp:3278-3281](file://libraries/network/node.cpp#L3278-L3281) -- [node.cpp:3633-3636](file://libraries/network/node.cpp#L3633-L3636) -- [node.cpp:3653-3656](file://libraries/network/node.cpp#L3653-L3656) -- [node.cpp:3671-3674](file://libraries/network/node.cpp#L3671-L3674) - -### Enhanced Network Stability Configuration -**Enhanced** Configuration options for network stability improvements: -- **Intelligent soft-ban mechanisms**: Soft-ban duration reduced from 3600 seconds (1 hour) to 900 seconds (15 minutes) for improved network responsiveness; trusted peers receive 5-minute soft-ban duration; regular peers receive 15-minute (reduced) soft-ban duration. -- **Intelligent strike-based enforcement**: Configurable 20-strike threshold for unlinkable blocks at/below head with automatic soft-ban activation. -- **Enhanced peer disconnect logging**: closing_reason field provides detailed information about why peers disconnect for improved troubleshooting. -- **Improved peer database dumping**: Enhanced JSON serialization and error handling for better database management. -- **NEW** Sync spam prevention: Configurable 50-strike threshold for repeated sync requests with 5-minute soft-ban duration using fork_rejected_until mechanism for preventing resource exhaustion attacks. -- Automatic registration: P2P plugin automatically registers trusted peers from snapshot plugin configuration. -- IP-based trust detection: Efficient O(1) lookup using 32-bit IP address storage. -- Dual-tier soft-ban system: 5-minute duration for trusted peers, 15-minute (reduced) duration for regular peers. -- **NEW** Intelligent reputation management: Configurable 20-strike threshold for unlinkable blocks at/below head with automatic soft-ban activation. -- **NEW** Sync spam prevention: Configurable 50-strike threshold for repeated sync requests with 5-minute soft-ban duration using fork_rejected_until mechanism for preventing resource exhaustion attacks. - -**Section sources** -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) -- [plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [node.cpp:5240-5274](file://libraries/network/node.cpp#L5240-L5274) -- [node.cpp:4472-4479](file://libraries/network/node.cpp#L4472-L4479) -- [node.cpp:5016-5021](file://libraries/network/node.cpp#L5016-L5021) -- [node.cpp:5013-5014](file://libraries/network/node.cpp#L5013-L5014) -- [node.cpp:3061-3062](file://libraries/network/node.cpp#L3061-L3062) - -### Enhanced Peer Database Management -**Enhanced** Improved peer database management with enhanced capabilities: -- **Enhanced** JSON serialization: Improved JSON serialization for peer database records with better error handling. -- **Comprehensive** tracking: Enhanced peer database dumping with unlinkable_block_strikes tracking for improved reputation management. -- **Robust** file operations: Enhanced file operations with proper error handling for database loading and saving. -- **Comprehensive** logging: Detailed logging for database operations with improved troubleshooting capabilities. -- **Database** dumping: Enhanced peer database dumping functionality for debugging and maintenance purposes. -- **Error** handling: Comprehensive error handling for database operations with detailed logging for troubleshooting. - -**Section sources** -- [peer_database.cpp:120-137](file://libraries/network/peer_database.cpp#L120-L137) -- [peer_database.cpp:100-174](file://libraries/network/peer_database.cpp#L100-L174) - -### NEW Intelligent Reputation System -**NEW** Configurable intelligent reputation management system for unlinkable block violations: -- **Configurable** threshold: 20-strike maximum before soft-ban activation for tolerant handling of stale fork violations. -- **Automatic** enforcement: Soft-ban triggered when threshold reached with fork_rejected_until timestamp and inhibit_fetching_sync_blocks flag. -- **Automatic** reset: unlinkable_block_strikes counter reset to 0 after soft-ban enforcement. -- **Integration** with existing infrastructure: Seamless integration with existing soft-ban infrastructure and trusted peer considerations. -- **Performance** optimization: Minimal performance impact through simple counter operations and threshold comparison. -- **Intelligent** enforcement: Combines configurable thresholds with trusted peer awareness for optimal network stability. - -**Section sources** -- [peer_connection.hpp:279-283](file://libraries/network/include/graphene/network/peer_connection.hpp#L279-L283) -- [node.cpp:3874-3908](file://libraries/network/node.cpp#L3874-L3908) - -### NEW Sync Spam Prevention System -**NEW** Configurable intelligent sync spam prevention system for repeated sync requests: -- **Configurable** threshold: 50-strike maximum before 5-minute soft-ban activation for preventing resource exhaustion attacks. -- **Automatic** enforcement: Soft-ban triggered when threshold reached with fork_rejected_until timestamp mechanism. -- **Automatic** reset: sync_spam_strikes counter reset to 0 after soft-ban enforcement. -- **Integration** with existing infrastructure: Seamless integration with existing soft-ban infrastructure and sync management. -- **Performance** optimization: Minimal performance impact through simple counter operations and threshold comparison. -- **Security** enhancement: Prevents sync ping-pong loops and protects network resources from malicious attacks using fork_rejected_until mechanism for precise timing control. - -**Section sources** -- [peer_connection.hpp:285-289](file://libraries/network/include/graphene/network/peer_connection.hpp#L285-L289) -- [node.cpp:2520-2590](file://libraries/network/node.cpp#L2520-L2590) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Peer Database and Discovery.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Peer Database and Discovery.md deleted file mode 100644 index 633e9ab206..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Peer Database and Discovery.md +++ /dev/null @@ -1,419 +0,0 @@ -# Peer Database and Discovery - - -**Referenced Files in This Document** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [peer_database.cpp](file://libraries/network/peer_database.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [config.ini](file://share/vizd/config/config.ini) -- [seednodes](file://share/vizd/seednodes) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the Peer Database and Discovery subsystem responsible for: -- Persistent storage of peer addresses and connection history -- Network topology maintenance and peer reputation tracking -- Peer discovery via seed nodes and address exchange -- Connection diversity and load balancing strategies -- Network partition recovery and pruning policies - -It focuses on the peer_database implementation, the node’s discovery and connection orchestration, and the P2P plugin integration. - -## Project Structure -The peer database and discovery logic spans three primary areas: -- Network core: peer_database and node infrastructure -- P2P plugin: CLI configuration, seed node handling, and lifecycle integration -- Configuration and seed files: runtime configuration and bootstrap peers - -```mermaid -graph TB -subgraph "Network Core" -PD["peer_database.hpp/.cpp
Persistent peer records"] -N["node.hpp/.cpp
Discovery, connection loops,
address exchange, pruning"] -end -subgraph "P2P Plugin" -P2PH["p2p_plugin.hpp/.cpp
CLI options, seed parsing,
startup/connect workflow"] -end -subgraph "Configuration" -CFG["config.ini
p2p-endpoint, p2p-seed-node"] -SEED["seednodes
bootstrap endpoints"] -end -P2PH --> N -CFG --> P2PH -SEED --> P2PH -N --> PD -``` - -**Diagram sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L41-L82) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) -- [config.ini](file://share/vizd/config/config.ini#L1-L10) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -**Section sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L1-L141) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L1-L262) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L355) -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L1-L57) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) -- [config.ini](file://share/vizd/config/config.ini#L1-L10) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -## Core Components -- Peer Database - - Stores potential peer records with endpoint, timestamps, connection disposition, counters, and last error - - Provides CRUD-like operations: open/close, clear, erase, update_entry, lookup_or_create_entry_for_endpoint, lookup_entry_for_endpoint, iterators - - Maintains a multi-index container keyed by endpoint and ordered by last_seen_time -- Node Discovery and Maintenance - - Connect loop selects peers based on retry timeouts and last disposition - - Periodic address requests refresh peer lists - - Inactivity watchdog disconnects idle peers - - Prunes stale entries and maintains database size limits -- P2P Plugin Integration - - Parses CLI options for p2p-endpoint, p2p-max-connections, p2p-seed-node - - Seeds initial connections and starts node listeners - -**Section sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L134) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L41-L186) -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp](file://libraries/network/node.cpp#L1400-L1621) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) - -## Architecture Overview -The peer database underpins the node’s discovery and maintenance routines. The P2P plugin initializes the node, injects seed endpoints, and exposes configuration options. - -```mermaid -sequenceDiagram -participant CLI as "CLI/Config" -participant P2P as "p2p_plugin" -participant Node as "node" -participant DB as "peer_database" -participant Net as "Network" -CLI->>P2P : Parse p2p-endpoint, p2p-max-connections, p2p-seed-node -P2P->>Node : Construct node, load configuration -P2P->>Node : listen_on_endpoint(p2p-endpoint) -P2P->>Node : add_node(seed) -P2P->>Node : connect_to_endpoint(seed) -P2P->>Node : set_advanced_node_parameters(max connections) -P2P->>Node : listen_to_p2p_network() -P2P->>Node : connect_to_p2p_network() -Node->>DB : open(peers.json) -Node->>Net : Start connect loop -Net->>DB : Lookup candidate peers (ordered by last_seen_time) -Net->>Net : Retry policy based on last disposition and counters -Net->>Net : Periodic address requests to refresh peers -Net->>DB : Update entries on success/failure -Node->>DB : Close and persist on shutdown -``` - -**Diagram sources** -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp](file://libraries/network/node.cpp#L1623-L1654) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) - -## Detailed Component Analysis - -### Peer Database: Data Model and Operations -- Data model - - potential_peer_record fields: endpoint, last_seen_time, last_connection_disposition, last_connection_attempt_time, number_of_successful_connection_attempts, number_of_failed_connection_attempts, last_error - - Indexes: hashed endpoint for O(1) lookup; ordered last_seen_time for traversal -- Operations - - open(filename): loads JSON array of records; prunes to a maximum size - - close(): persists records to JSON - - update_entry(): insert or replace record by endpoint - - lookup_or_create_entry_for_endpoint(): return existing or new record - - lookup_entry_for_endpoint(): optional lookup - - begin()/end(): iterator over last_seen_time index - - size(): count of records - -```mermaid -classDiagram -class potential_peer_record { -+endpoint -+last_seen_time -+last_connection_disposition -+last_connection_attempt_time -+number_of_successful_connection_attempts -+number_of_failed_connection_attempts -+last_error -} -class peer_database { -+open(filename) -+close() -+clear() -+erase(endpoint) -+update_entry(record) -+lookup_or_create_entry_for_endpoint(endpoint) potential_peer_record -+lookup_entry_for_endpoint(endpoint) optional -+begin() iterator -+end() iterator -+size() size_t -} -class peer_database_impl { --potential_peer_set _potential_peer_set --path _peer_database_filename -+open() -+close() -+clear() -+erase() -+update_entry() -+lookup_or_create_entry_for_endpoint() -+lookup_entry_for_endpoint() -+begin() -+end() -+size() -} -peer_database --> peer_database_impl : "owns" -peer_database_impl --> potential_peer_record : "stores" -``` - -**Diagram sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L134) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L41-L82) - -**Section sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L134) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L186) - -### Peer Discovery and Connection Management -- Connect loop - - Iterates peers ordered by last_seen_time - - Applies retry policy based on last disposition and failed attempt counts - - Initiates connections when needed and updates records on outcomes -- Address exchange - - Periodically sends address_request_message and processes address_message to refresh peer lists - - Merges received addresses into the peer database -- Inactivity and pruning - - Disconnects inactive peers and sends keep-alives - - Prunes failed items cache periodically - - Database size capped at startup - -```mermaid -flowchart TD -Start(["Connect Loop Tick"]) --> CheckWants["Check desired/max connections"] -CheckWants --> |Need more| Iterate["Iterate peers ordered by last_seen_time"] -Iterate --> Candidate{"Candidate eligible?"} -Candidate --> |Yes| RetryDelay["Compute retry delay"] -RetryDelay --> Connect["connect_to_endpoint()"] -Connect --> Update["Update peer record on outcome"] -Update --> Next["Next candidate"] -Candidate --> |No| Next -Next --> Sleep["Sleep or wait for trigger"] -Sleep --> CheckWants -``` - -**Diagram sources** -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) - -**Section sources** -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp](file://libraries/network/node.cpp#L1400-L1621) - -### P2P Plugin Initialization and Seed Configuration -- CLI options - - p2p-endpoint: local listen endpoint - - p2p-max-connections: maximum connections - - p2p-seed-node: bootstrap peers (supports multiple) -- Startup workflow - - Creates node, loads configuration directory - - Listens on endpoint, adds seeds, connects immediately - - Sets advanced parameters and starts network listeners - - Syncs from current head block - -```mermaid -sequenceDiagram -participant App as "Application" -participant P2P as "p2p_plugin" -participant Node as "node" -participant Seed as "Seed Endpoints" -App->>P2P : Initialize with CLI/config -P2P->>Node : new node(user_agent) -P2P->>Node : load_configuration(data_dir/p2p) -P2P->>Node : listen_on_endpoint(p2p-endpoint) -P2P->>Node : add_node(seed) x N -P2P->>Node : connect_to_endpoint(seed) x N -P2P->>Node : set_advanced_node_parameters(max connections) -P2P->>Node : listen_to_p2p_network() -P2P->>Node : connect_to_p2p_network() -P2P->>Node : sync_from(head_block, []) -``` - -**Diagram sources** -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) - -**Section sources** -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L467-L566) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) -- [config.ini](file://share/vizd/config/config.ini#L1-L10) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -### Peer Address Validation and Reputation -- Validation - - Rejects self-connections and duplicates - - Validates chain_id and hard fork compatibility - - Verifies hello signatures and user data -- Reputation - - Tracks last_connection_disposition and attempt counters - - Uses last_seen_time ordering to prioritize fresh peers - - Records last_error on closure for diagnostics - -**Section sources** -- [node.cpp](file://libraries/network/node.cpp#L2029-L2230) -- [node.cpp](file://libraries/network/node.cpp#L2251-L2280) -- [node.cpp](file://libraries/network/node.cpp#L3036-L3080) - -### Network Topology Maintenance and Diversity -- Load balancing - - Distributes item requests across peers based on idle status and pending requests - - Limits per-peer request volume during normal operation -- Diversity - - Periodic address requests from all active peers - - Merge logic updates last_seen_time for freshness - - Handshake and firewall detection influence inclusion decisions - -**Section sources** -- [node.cpp](file://libraries/network/node.cpp#L1177-L1316) -- [node.cpp](file://libraries/network/node.cpp#L1844-L1860) -- [node.cpp](file://libraries/network/node.cpp#L2282-L2350) - -### Peer Selection Strategies and Recovery -- Selection - - Prefer peers with recent activity (last_seen_time) - - Respect retry timeouts and disposition to avoid stuck peers -- Recovery - - Re-trigger connect loop on new address information - - Periodic keep-alives and inactivity watchdogs - - Hard fork and chain_id checks to drop incompatible peers - -**Section sources** -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [node.cpp](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp](file://libraries/network/node.cpp#L2029-L2230) - -## Dependency Analysis -- peer_database depends on Boost.MultiIndex for composite indexing -- node integrates peer_database for discovery and persistence -- p2p_plugin orchestrates node lifecycle and seed injection -- Configuration and seed files feed the plugin and node - -```mermaid -graph LR -P2P["p2p_plugin.cpp"] --> Node["node.cpp"] -Node --> DB["peer_database.cpp"] -P2P --> CFG["config.ini"] -P2P --> Seed["seednodes"] -Node --> DB -``` - -**Diagram sources** -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L41-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L10) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -**Section sources** -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L566) -- [node.cpp](file://libraries/network/node.cpp#L952-L1047) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L41-L82) - -## Performance Considerations -- Database sizing - - Startup prune to a fixed maximum ensures bounded memory and IO -- Indexing - - Hashed endpoint index for fast updates/lookups - - Ordered last_seen_time index for fair rotation and freshness -- Connection scheduling - - Retry delays scale with failed attempts to reduce churn -- Request distribution - - Per-peer caps and idle checks prevent hot-spotting and improve throughput - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Peer database fails to load or corrupt - - The loader logs and continues with a clean database; inspect logs for errors -- Frequent rejections or timeouts - - Review last_connection_disposition and counters; adjust retry timeouts -- Stale peers overwhelming discovery - - Verify last_seen_time updates on address exchange and pruning -- Inactivity disconnects - - Confirm keep-alive messages and inactivity thresholds - -**Section sources** -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L114-L118) -- [node.cpp](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp](file://libraries/network/node.cpp#L1400-L1621) - -## Conclusion -The peer database and discovery system combines a compact, indexed record store with robust connection orchestration. Together with the P2P plugin’s seed configuration, it provides resilient network bootstrapping, continuous topology refresh, and operational safeguards against stale or hostile peers. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Database Schema Notes -- File: peers.json (loaded at node startup; saved on shutdown) -- Fields: endpoint, last_seen_time, last_connection_disposition, last_connection_attempt_time, number_of_successful_connection_attempts, number_of_failed_connection_attempts, last_error -- Indexes: endpoint (hashed), last_seen_time (ordered) - -**Section sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L134) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) - -### Example Workflows - -- Peer database initialization - - Open: load peers.json; prune to maximum size - - Close: persist records to peers.json - -- Peer lookup operations - - lookup_or_create_entry_for_endpoint(endpoint) - - lookup_entry_for_endpoint(endpoint) - - Iterate over begin()/end() ordered by last_seen_time - -- Network discovery workflow - - Periodic address_request_message - - Merge received addresses into peer database - - Trigger connect loop on new information - -**Section sources** -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) -- [node.cpp](file://libraries/network/node.cpp#L1623-L1654) -- [node.cpp](file://libraries/network/node.cpp#L2307-L2350) - -### Backup and Migration Guidance -- Backup - - Copy peers.json from the node’s configuration directory before upgrades or restores -- Migration - - Ensure schema compatibility; if fields change, maintain backward-compatible parsing and prune as needed -- Maintenance - - Monitor prune behavior at startup and manual cleanup via clear() when debugging - -**Section sources** -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L284-L288) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Transport Layer and Sockets.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Transport Layer and Sockets.md deleted file mode 100644 index c03e750808..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Network Library/Transport Layer and Sockets.md +++ /dev/null @@ -1,349 +0,0 @@ -# Transport Layer and Sockets - - -**Referenced Files in This Document** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [node.cpp](file://libraries/network/node.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the Transport Layer and Sockets implementation for secure TCP communication in the project. It focuses on the stcp_socket class that provides encrypted messaging over TCP using Elliptic Curve Diffie-Hellman (ECDH) key exchange and AES encryption. It covers secure connection establishment, shared secret derivation, buffered I/O, lifecycle management, and integration with higher-level components such as message-oriented connections and peer connections. It also documents configuration options, buffer management, performance characteristics, error handling, recovery, and operational guidance for debugging and tuning. - -## Project Structure -The transport stack is organized into layered components: -- stcp_socket: Secure socket wrapper over TCP with ECDH key exchange and AES encryption. -- message_oriented_connection: Stream-based message framing over stcp_socket. -- peer_connection: Peer session manager that orchestrates handshake, negotiation, and message queuing. -- node: Top-level orchestrator managing peers, connection pools, and lifecycle. - -```mermaid -graph TB -subgraph "Transport Layer" -STCP["stcp_socket
ECDH + AES over TCP"] -MOC["message_oriented_connection
Message framing over stcp_socket"] -PC["peer_connection
Peer session & negotiation"] -N["node
Peer pool & lifecycle"] -end -N --> PC -PC --> MOC -MOC --> STCP -``` - -**Diagram sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [node.cpp](file://libraries/network/node.cpp#L424-L837) - -**Section sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [node.cpp](file://libraries/network/node.cpp#L424-L837) - -## Core Components -- stcp_socket: Provides secure I/O over TCP with ECDH key exchange and AES encryption. It exposes read/write methods aligned to AES block sizes, maintains shared secrets, and integrates with fc::tcp_socket. -- message_oriented_connection: Wraps stcp_socket to deliver a stream of framed messages, handling padding, buffering, and read/write loops. -- peer_connection: Manages peer sessions, negotiates connection states, queues outgoing messages, and coordinates with message_oriented_connection. -- node: Maintains a pool of active peers, controls connection lifecycle, and enforces policies such as desired/max connections and timeouts. - -Key responsibilities: -- stcp_socket: Key exchange, shared secret derivation, AES encoder/decoder initialization, buffered read/write with alignment to AES blocks, and socket lifecycle. -- message_oriented_connection: Message framing, padding to 16-byte boundaries, read loop, send loop, and connection metrics. -- peer_connection: Session state machine, message queuing, rate limiting, and graceful close. -- node: Peer discovery, connection orchestration, and termination policies. - -**Section sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L49-L192) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L208-L242) -- [node.cpp](file://libraries/network/node.cpp#L424-L837) - -## Architecture Overview -The secure transport pipeline: -- TCP socket is established. -- ECDH key exchange is performed to derive a shared secret. -- AES encoders/decoders are initialized from the shared secret. -- Messages are framed and padded to 16-byte boundaries for AES streaming mode. -- Read/write loops handle encrypted I/O and maintain throughput. - -```mermaid -sequenceDiagram -participant Client as "peer_connection" -participant MOC as "message_oriented_connection" -participant STCP as "stcp_socket" -participant TCP as "TCP Socket" -Client->>MOC : connect_to(endpoint) -MOC->>STCP : connect_to(endpoint) -STCP->>TCP : connect_to(endpoint) -STCP->>TCP : write(public_key) -TCP-->>STCP : read(public_key) -STCP->>STCP : derive shared secret (ECDH) -STCP->>STCP : init AES encoders/decoders -MOC->>MOC : start read_loop() -Client->>MOC : send_message(msg) -MOC->>STCP : write(padded_msg) -STCP->>TCP : write(ciphertext) -TCP-->>STCP : read(ciphertext) -STCP->>MOC : decode plaintext -MOC-->>Client : on_message(msg) -``` - -**Diagram sources** -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L49-L72) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L135-L145) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L208-L242) - -## Detailed Component Analysis - -### stcp_socket: Secure TCP Wrapper -Responsibilities: -- Establish TCP connection and perform ECDH key exchange. -- Derive shared secret and initialize AES encoder/decoder for bidirectional encryption. -- Provide read/write methods aligned to AES block size (16 bytes) with internal buffering. -- Expose shared secret for higher-level protocols and integrate with fc::tcp_socket. - -Implementation highlights: -- Key exchange: Generates ephemeral EC private key, serializes public key, exchanges with peer, and computes shared secret. -- Encryption: Initializes AES encoders/decoders using derived shared secret for confidentiality. -- Read path: Ensures reads are aligned to 16-byte boundaries, buffers leftover data, and decrypts into caller’s buffer. -- Write path: Pads plaintext to 16-byte boundaries, encrypts, and writes ciphertext. -- Lifecycle: Connect/bind/accept/close with proper exception handling. - -```mermaid -classDiagram -class stcp_socket { -+stcp_socket() -+~stcp_socket() -+get_socket() tcp_socket& -+accept() -+connect_to(endpoint) -+bind(endpoint) -+readsome(buffer, len) size_t -+readsome(buf, len, offset) size_t -+eof() bool -+writesome(buffer, len) size_t -+writesome(buf, len, offset) size_t -+flush() -+close() -+get_shared_secret() sha512 --do_key_exchange() --_shared_secret : sha512 --_priv_key : private_key --_buf : array --_sock : tcp_socket --_send_aes : aes_encoder --_recv_aes : aes_decoder --_read_buffer : shared_ptr --_write_buffer : shared_ptr -} -``` - -**Diagram sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -**Section sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L49-L192) - -### message_oriented_connection: Message Framing Over stcp_socket -Responsibilities: -- Frame messages with headers and pad to 16-byte boundaries. -- Manage read loop to receive complete messages and dispatch to delegate. -- Track connection metrics (bytes sent/received, timestamps). -- Provide access to underlying stcp_socket and shared secret. - -Key behaviors: -- Padding: Rounds payload size to nearest 16-byte boundary to align with AES block size. -- Read loop: Reads fixed header, validates size, reads remainder, trims padding, and invokes delegate. -- Send loop: Copies message header and data into padded buffer, writes to stcp_socket, flushes. - -```mermaid -flowchart TD -Start(["send_message(msg)"]) --> Pad["Compute padded size to multiple of 16"] -Pad --> Build["Copy header + data into padded buffer"] -Build --> WriteSock["Write padded buffer to stcp_socket"] -WriteSock --> Flush["Flush to ensure delivery"] -Flush --> UpdateStats["Update bytes_sent + last_message_sent_time"] -UpdateStats --> End(["Return"]) -%% Read loop -RStart(["read_loop()"]) --> ReadHeader["Read fixed header (16 bytes)"] -ReadHeader --> ValidateSize{"Validate size <= MAX_MESSAGE_SIZE"} -ValidateSize --> |No| Error["Throw exception"] -ValidateSize --> |Yes| ReadRemainder["Read remaining padded payload"] -ReadRemainder --> Trim["Trim padding to original size"] -Trim --> Dispatch["Delegate.on_message(msg)"] -Dispatch --> LoopBack["Continue read_loop()"] -``` - -**Diagram sources** -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L237-L283) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) - -**Section sources** -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L237-L283) - -### peer_connection: Peer Session Management -Responsibilities: -- Orchestrate connection lifecycle: bind/connect, accept, negotiate, and close. -- Queue and send messages via message_oriented_connection. -- Track connection states, metrics, and peer identity. -- Integrate with node for peer pool management. - -Notable behaviors: -- Connection states: Enumerated states for “our” and “their” sides, plus negotiation statuses. -- Outgoing connections: Initiates bind/connect, transitions negotiation status, and logs successful connection. -- Incoming connections: Accepts via message_oriented_connection, performs key exchange, and transitions states. -- Graceful close: Sets negotiation status, closes underlying connection, and schedules deletion. - -```mermaid -stateDiagram-v2 -[*] --> Disconnected -Disconnected --> Connecting : "connect_to()" -Disconnected --> Accepting : "accept_connection()" -Connecting --> Connected : "connect_to() succeeds" -Accepting --> Accepted : "accept() succeeds" -Connected --> HelloSent : "send hello" -Accepted --> HelloSent : "send hello" -HelloSent --> NegotiationComplete : "exchange complete" -NegotiationComplete --> Closing : "close_connection()" -Closing --> Closed : "destroy_connection()" -Closed --> [*] -``` - -**Diagram sources** -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L82-L106) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L208-L242) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L356-L369) - -**Section sources** -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L208-L242) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L356-L369) - -### node: Peer Pool and Lifecycle Management -Responsibilities: -- Maintain sets of handshaking, active, closing, and terminating connections. -- Enforce connection limits, retry policies, and inactivity timeouts. -- Trigger connect loops, advertise inventory, and manage bandwidth. - -Operational highlights: -- Connection limits: Desired and maximum connection counts influence connect loop behavior. -- Inactivity handling: Disconnects peers that exceed configured inactivity thresholds. -- Bandwidth monitoring: Tracks average read/write speeds and updates periodically. - -**Section sources** -- [node.cpp](file://libraries/network/node.cpp#L424-L837) -- [node.cpp](file://libraries/network/node.cpp#L1400-L1599) - -## Dependency Analysis -The transport stack composes tightly around fc::tcp_socket and fc crypto primitives: -- stcp_socket depends on fc::tcp_socket, fc::ecc::private_key, fc::sha512, fc::aes_encoder, fc::aes_decoder. -- message_oriented_connection depends on stcp_socket and message framing logic. -- peer_connection depends on message_oriented_connection and node for lifecycle. -- node orchestrates peer_connection instances and enforces policies. - -```mermaid -graph LR -TCP["fc::tcp_socket"] --> STCP["stcp_socket"] -ECC["fc::ecc::private_key"] --> STCP -SHA["fc::sha512"] --> STCP -AES["fc::aes_encoder / aes_decoder"] --> STCP -STCP --> MOC["message_oriented_connection"] -MOC --> PC["peer_connection"] -PC --> N["node"] -``` - -**Diagram sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L26-L28) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L49-L66) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L26-L27) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L28-L29) -- [node.cpp](file://libraries/network/node.cpp#L527-L528) - -**Section sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L26-L28) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L49-L66) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L26-L27) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L28-L29) -- [node.cpp](file://libraries/network/node.cpp#L527-L528) - -## Performance Considerations -- Buffering: Internal buffers of fixed size (e.g., 4096 bytes) are reused for read/write to reduce allocations. -- Alignment: Read/write sizes are constrained to 16-byte multiples to satisfy AES block cipher requirements. -- Padding: Messages are padded to 16-byte boundaries; ensure message sizes are planned accordingly to minimize overhead. -- Throughput: The read loop reads fixed header size first, then remainder in chunks; batching messages can improve efficiency. -- Concurrency: Assertions guard single-threaded usage of read/write buffers; concurrent calls are not supported without modifications. - -Recommendations: -- Tune message sizes to align with 16-byte boundaries to avoid extra padding overhead. -- Monitor bytes_sent/bytes_received metrics exposed by message_oriented_connection to track throughput. -- Adjust node-level connection limits and retry timeouts to balance connectivity and resource usage. - -**Section sources** -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L107-L121) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L156-L173) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L237-L283) -- [node.cpp](file://libraries/network/node.cpp#L869-L902) - -## Troubleshooting Guide -Common issues and remedies: -- Handshake failures: Verify ECDH key exchange completes; inspect exceptions thrown during connect/accept. -- Read/write errors: Ensure readsome/writesome are called with 16-byte aligned lengths; check for EOF conditions. -- Connection drops: Review inactivity timeout logic and peer closure reasons; confirm graceful close sequences. -- Resource leaks: Confirm destroy_connection cancels read loops and closes sockets; verify peer deletion tasks complete. - -Operational tips: -- Enable logging around read_loop and send_message to capture exceptions and disconnections. -- Inspect shared secret availability via message_oriented_connection::get_shared_secret for debugging. -- Use node-level statistics and bandwidth monitors to diagnose performance regressions. - -**Section sources** -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L148-L235) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L285-L313) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L106-L157) -- [node.cpp](file://libraries/network/node.cpp#L1400-L1599) - -## Conclusion -The transport layer integrates a robust secure socket abstraction with message-oriented framing to deliver encrypted, reliable peer-to-peer communication. stcp_socket encapsulates ECDH key exchange and AES encryption, while message_oriented_connection ensures proper message framing and buffered I/O. Together with peer_connection and node, the system provides lifecycle management, connection pooling, and operational resilience. Following the guidance herein will help you deploy, tune, and troubleshoot secure TCP communications effectively. - -## Appendices - -### Security Features and Best Practices -- Encryption: AES-based symmetric encryption initialized from ECDH-derived shared secret. -- Authentication: Public keys exchanged during handshake; shared secret confirms mutual participation. -- Integrity: AES streaming mode with padding; ensure message size validation and bounds checking. -- Best practices: - - Keep shared secrets ephemeral and derive per-connection. - - Validate message sizes against maximum allowed limits. - - Use node-level policies to enforce connection limits and timeouts. - - Log and monitor connection states and metrics for anomaly detection. - -**Section sources** -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp#L49-L66) -- [message_oriented_connection.cpp](file://libraries/network/message_oriented_connection.cpp#L168-L170) -- [node.cpp](file://libraries/network/node.cpp#L869-L902) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Authority Management.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Authority Management.md deleted file mode 100644 index e6bcbf8f6d..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Authority Management.md +++ /dev/null @@ -1,404 +0,0 @@ -# Authority Management - - -**Referenced Files in This Document** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp) -- [authority.cpp](file://libraries/protocol/authority.cpp) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the Authority Management subsystem responsible for permission systems and access control in the blockchain. It covers: -- Authority structures and thresholds -- Weight calculations and validation -- Multi-signature authority requirements -- Permission hierarchies (master, active, regular) -- Signature validation and authority checking during transaction processing via sign_state -- Authority inheritance patterns and delegation -- Relationship between authorities and operations requiring specific permissions - -## Project Structure -The authority system spans protocol-level definitions and chain-level persistence and usage: -- Protocol-level authority definitions and helpers -- Transaction signing and authority checking -- Shared-memory compatible authority representation -- Account authority storage and hierarchical roles -- Operation definitions specifying which authorities are required - -```mermaid -graph TB -subgraph "Protocol Layer" -A["authority.hpp
authority.cpp"] -S["sign_state.hpp
sign_state.cpp"] -T["types.hpp"] -O["operations.hpp"] -CO["chain_operations.hpp"] -end -subgraph "Chain Layer" -SA["shared_authority.hpp"] -AO["account_object.hpp"] -end -A --> S -T --> A -O --> CO -CO --> A -SA --> AO -SA --> A -AO --> A -``` - -**Diagram sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [authority.cpp](file://libraries/protocol/authority.cpp#L1-L228) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L1-L107) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L1-L235) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L1-L113) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L1-L565) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L1-L200) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [authority.cpp](file://libraries/protocol/authority.cpp#L1-L228) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L1-L107) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L1-L235) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L1-L113) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L1-L565) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L1-L200) - -## Core Components -- Authority: A permission container combining weighted account and key authorities with a numeric threshold. Provides helpers to add authorities, compute weights, validate, and detect impossibilities. -- Sign State: Validates signatures and checks authority requirements against provided keys and approvals, recursively resolving nested authorities up to a configured depth. -- Types: Defines public key, account name, weight, and related types used by authorities. -- Shared Authority: A shared-memory-compatible variant of authority for persistent storage. -- Account Authority Object: Stores hierarchical authorities (master, active, regular) per account. -- Operations: Define which authorities are required for each operation (active/master/regular). - -Key responsibilities: -- Build and validate authority structures -- Enforce threshold-based multi-signature checks -- Resolve nested authorities via delegation -- Integrate with transaction signing and evaluation - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [authority.cpp](file://libraries/protocol/authority.cpp#L7-L48) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L6-L59) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L147) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L99) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L62) - -## Architecture Overview -Authority management integrates at three layers: -- Protocol-level definitions and helpers -- Transaction signing/validation pipeline -- Chain-level persistence and retrieval - -```mermaid -sequenceDiagram -participant OP as "Operation" -participant AU as "Authority (protocol)" -participant SS as "Sign State" -participant DB as "Account Authority Store" -OP->>AU : "Define required authorities" -OP->>SS : "Provide provided signatures/approvals" -SS->>DB : "Resolve active/master/regular authorities" -SS->>AU : "Check key weights vs threshold" -SS->>AU : "Recurse for account authorities" -AU-->>SS : "Pass/Fail" -SS-->>OP : "Final authority validation result" -``` - -**Diagram sources** -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L62) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L19-L59) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) - -## Detailed Component Analysis - -### Authority Structure and Threshold Validation -The authority structure holds: -- A numeric threshold -- Two maps: account authorities (name -> weight) and key authorities (public key -> weight) - -Core behaviors: -- Add authorities for accounts and keys -- Compute total weight from included keys and accounts -- Detect impossible authorities (total weight below threshold) -- Validate account names -- Equality comparison - -Weight calculation and threshold validation: -- Sum weights from included keys and accounts -- Compare sum against threshold to decide pass/fail -- Early exit when threshold is met - -```mermaid -flowchart TD -Start(["Authority.validate"]) --> ForEachAcc["Iterate account_auths"] -ForEachAcc --> AccValid{"Account name valid?"} -AccValid --> |No| Fail["Fail validation"] -AccValid --> |Yes| NextKeys["Iterate key_auths"] -NextKeys --> SumWeights["Sum weights from accounts and keys"] -SumWeights --> CheckThreshold{"Total weight >= threshold?"} -CheckThreshold --> |Yes| Pass["Pass"] -CheckThreshold --> |No| Impossible["Impossible (below threshold)"] -``` - -**Diagram sources** -- [authority.cpp](file://libraries/protocol/authority.cpp#L44-L48) -- [authority.cpp](file://libraries/protocol/authority.cpp#L24-L33) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [authority.cpp](file://libraries/protocol/authority.cpp#L7-L48) - -### Multi-Signature Authority Requirements and Hierarchies -Hierarchical authorities per account: -- Master: typically backup control, can set master or active -- Active: monetary operations, can set active or regular -- Regular: voting and regular actions - -Operations specify which authorities are required: -- Some operations require active/master/regular -- Others require regular or none depending on payload - -```mermaid -classDiagram -class Authority { -+uint32_t weight_threshold -+account_authority_map account_auths -+key_authority_map key_auths -+add_authority(public_key_type, weight_type) -+add_authority(account_name_type, weight_type) -+get_keys() vector -+is_impossible() bool -+num_auths() uint32_t -+clear() void -+validate() void -} -class AccountAuthorityObject { -+account_name_type account -+shared_authority master -+shared_authority active -+shared_authority regular -} -class Operation { -+get_required_active_authorities(flat_set) -+get_required_master_authorities(flat_set) -+get_required_regular_authorities(flat_set) -} -AccountAuthorityObject --> Authority : "stores" -Operation --> Authority : "requires" -``` - -**Diagram sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L62) - -**Section sources** -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L99) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L62) - -### Signature Validation and Authority Checking via Sign State -Sign state orchestrates: -- Determining whether a key has been signed or can be produced -- Resolving account authorities via callbacks -- Recursively validating nested authorities up to a maximum recursion depth -- Tracking used and unused signatures/approvals - -Workflow: -- For each key authority, mark as signed if present in provided signatures or available keys -- For each account authority, resolve active authority and recurse if depth allows -- Accumulate weights and compare to threshold - -```mermaid -sequenceDiagram -participant TX as "Transaction" -participant SS as "Sign State" -participant AK as "Provided Keys" -participant AG as "Authority Getter" -participant AU as "Authority" -TX->>SS : "Initialize with provided signatures and keys" -SS->>AK : "Check availability" -SS->>AG : "Get active authority for account" -SS->>AU : "Check key weights" -SS->>AU : "Recurse for account authorities (depth-limited)" -SS-->>TX : "Pass/Fail" -``` - -**Diagram sources** -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L6-L59) - -**Section sources** -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L6-L59) - -### Authority Inheritance Patterns and Delegation -- Accounts maintain separate authorities for master, active, and regular roles -- Operations may require one or more of these roles -- Nested authority resolution occurs when an authority includes account names; sign_state resolves those accounts’ active authorities recursively up to a configured depth -- Shared authority enables persistent storage in shared memory - -```mermaid -flowchart TD -Req["Operation requires role X"] --> HasRole{"Account has role X?"} -HasRole --> |No| Fail["Reject"] -HasRole --> |Yes| CheckAuth["Load authority X"] -CheckAuth --> Keys["Check provided key weights"] -CheckAuth --> Accounts["Resolve account authorities (recursive)"] -Keys --> Threshold{"Meets threshold?"} -Accounts --> Threshold -Threshold --> |Yes| Approve["Approve"] -Threshold --> |No| Fail -``` - -**Diagram sources** -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L62) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L28-L59) - -**Section sources** -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L20-L99) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L28-L59) - -### Custom Authority Types and Name Validation -- Public key and account name types are defined with strict validation rules -- Account name validation enforces length, character sets, and domain-like naming rules -- Domain name validation supports hierarchical naming patterns - -```mermaid -flowchart TD -N["Input name"] --> Len["Check length bounds"] -Len --> |Invalid| Reject["Reject"] -Len --> |Valid| Parts["Split by '.'"] -Parts --> EachPart["Validate each label"] -EachPart --> Chars["Chars allowed and positions"] -Chars --> |Invalid| Reject -Chars --> |Valid| Accept["Accept"] -``` - -**Diagram sources** -- [authority.cpp](file://libraries/protocol/authority.cpp#L66-L218) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L147) - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L147) -- [authority.cpp](file://libraries/protocol/authority.cpp#L50-L218) - -### Examples of Authority Configuration and Permission Scenarios -- Creating an account with master/active/regular authorities -- Updating an account’s authorities -- Performing a transfer requiring active/master authority depending on asset type -- Voting and content operations requiring regular authority - -These examples demonstrate: -- How authorities are attached to operations -- How thresholds and weights influence pass/fail -- How recursive resolution works when authorities include account names - -**Section sources** -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L62) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -## Dependency Analysis -Authority management depends on: -- Types for cryptographic keys, account names, and weights -- Shared authority for persistent storage -- Account authority objects for role storage -- Operations for declaring required authorities - -```mermaid -graph LR -T["types.hpp"] --> A["authority.hpp"] -A --> S["sign_state.hpp"] -A --> SA["shared_authority.hpp"] -SA --> AO["account_object.hpp"] -CO["chain_operations.hpp"] --> A -O["operations.hpp"] --> CO -``` - -**Diagram sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L147) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L1-L113) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L1-L565) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L1-L200) - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L147) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [shared_authority.hpp](file://libraries/chain/include/graphene/chain/shared_authority.hpp#L1-L113) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L1-L565) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L1-L200) - -## Performance Considerations -- Threshold short-circuit: weight accumulation stops once threshold is met -- Recursion depth limit prevents deep cycles and protects evaluation time -- Efficient maps and sets minimize lookup costs -- Shared authority reduces allocation overhead in persistent contexts - -## Troubleshooting Guide -Common issues and resolutions: -- Impossible authority: total weight below threshold; adjust weights or reduce threshold -- Invalid account names: ensure names meet length and character constraints -- Missing signatures/approvals: ensure provided keys match authority keys and required approvals are recorded -- Excessive recursion: verify nested authorities do not exceed configured depth - -**Section sources** -- [authority.cpp](file://libraries/protocol/authority.cpp#L24-L33) -- [authority.cpp](file://libraries/protocol/authority.cpp#L77-L218) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L41-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L61-L91) - -## Conclusion -Authority Management provides a robust, extensible framework for permission control: -- Clear separation of roles (master, active, regular) -- Threshold-based multi-signature enforcement -- Recursive authority resolution with depth limits -- Persistent storage support via shared authority -- Tight integration with operations and transaction signing - -## Appendices -- Data model summary: - - Authority: threshold, account_auths, key_auths - - Account Authority Object: master, active, regular authorities - - Sign State: provided signatures, approved by, recursion depth - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp#L145-L165) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Block Structures.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Block Structures.md deleted file mode 100644 index 8515728009..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Block Structures.md +++ /dev/null @@ -1,396 +0,0 @@ -# Block Structures - - -**Referenced Files in This Document** -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp) -- [block.cpp](file://libraries/protocol/block.cpp) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the blockchain block format and consensus mechanisms in the VIZ C++ node. It focuses on: -- Block header structure and metadata -- Transaction inclusion and Merkle root computation -- validator signature validation and block hashing -- Validation rules for blocks (Merkle root, validator signature, fork resolution) -- Block production and propagation workflows -- Network synchronization and state progression - -## Project Structure -The block-related logic spans protocol-level definitions and chain-level validation and persistence: -- Protocol-level block definitions and cryptographic helpers -- Chain-level validation, fork management, and block logging -- validator scheduling and participation metrics - -```mermaid -graph TB -subgraph "Protocol Layer" -P_Block["block.hpp"] -P_Header["block_header.hpp"] -P_Impl["block.cpp"] -P_Types["types.hpp"] -end -subgraph "Chain Layer" -C_DB_API["database.hpp"] -C_DB_IMPL["database.cpp"] -C_Fork["fork_database.hpp"] -C_Log["block_log.hpp"] -C_Wit["witness_objects.hpp"] -end -P_Block --> P_Impl -P_Header --> P_Impl -P_Types --> P_Impl -P_Block --> C_DB_API -P_Header --> C_DB_API -C_DB_API --> C_DB_IMPL -C_DB_IMPL --> C_Fork -C_DB_IMPL --> C_Log -C_DB_IMPL --> C_Wit -``` - -**Diagram sources** -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L105-L110) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L1-L200) - -**Section sources** -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L105-L110) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L1-L200) - -## Core Components -- Block header and signed header: define block metadata, hash computation, and validator signature validation. -- Signed block: extends the signed header with a transaction list and Merkle root calculation. -- Types: defines cryptographic primitives used by blocks and transactions. -- Fork database: manages unlinked and linked forks, head selection, and branch resolution. -- Database validation pipeline: validates block headers, Merkle roots, sizes, and validator scheduling; pushes blocks and updates state. -- Block log: persists blocks to disk for replay and synchronization. -- validator objects: track scheduling eligibility, participation, and penalties. - -**Section sources** -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L35) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L13) -- [block.cpp](file://libraries/protocol/block.cpp#L6-L64) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L105-L110) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) - -## Architecture Overview -The block lifecycle integrates protocol definitions, validation, persistence, and consensus: -- Protocol layer defines block structures and cryptographic helpers. -- Chain layer validates incoming blocks against local state and schedules. -- Fork database resolves competing chains and selects the heaviest branch. -- Block log persists blocks and supports fast random access by number. -- validator objects enforce scheduling and participation rules. - -```mermaid -sequenceDiagram -participant Peer as "Peer" -participant DB as "database.cpp" -participant Fork as "fork_database.hpp" -participant Log as "block_log.hpp" -Peer->>DB : "Push signed_block" -DB->>DB : "validate_block()" -DB->>Fork : "push_block(new_block)" -Fork-->>DB : "new_head" -DB->>DB : "apply_block() if needed" -DB->>Log : "append(block) on success" -DB-->>Peer : "result" -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L800-L925) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L78-L91) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L50-L56) - -## Detailed Component Analysis - -### Block Header and Hashing -- The block header contains previous block identifier, timestamp, validator name, and the transaction Merkle root. -- The signed block header adds the validator signature and exposes signing and validation helpers. -- The block ID is derived from a compact hash with the block number embedded. - -```mermaid -classDiagram -class block_header { -+block_id_type previous -+time_point_sec timestamp -+string validator -+checksum_type transaction_merkle_root -+digest() digest_type -+num_from_id(id) uint32_t -} -class signed_block_header { -+block_id_type id() -+public_key signee() -+void sign(signer) -+bool validate_signee(expected) -+signature_type witness_signature -} -class signed_block { -+vector~signed_transaction~ transactions -+calculate_merkle_root() checksum_type -} -signed_block_header --|> block_header -signed_block --|> signed_block_header -``` - -**Diagram sources** -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L35) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L13) -- [block.cpp](file://libraries/protocol/block.cpp#L6-L33) - -**Section sources** -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L35) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L13) -- [block.cpp](file://libraries/protocol/block.cpp#L6-L33) - -### Merkle Root Computation -- The Merkle root is computed from transaction digests using a standard binary hash tree. -- The computation is exposed by the signed block and compared against the header’s Merkle root during validation. - -```mermaid -flowchart TD -Start(["Start calculate_merkle_root"]) --> Empty{"transactions empty?"} -Empty --> |Yes| Zero["Return zero checksum"] -Empty --> |No| Init["Init ids[] with tx.merkle_digest()"] -Init --> Loop["While current_number_of_hashes > 1"] -Loop --> Pair["Hash IDs in pairs"] -Pair --> Next["Advance indices"] -Next --> Done{"Done this round?"} -Done --> |No| Loop -Done --> |Yes| Reduce["Reduce to k elements"] -Reduce --> Loop -Loop --> Single{"== 1?"} -Single --> |Yes| HashEnd["Hash last digest"] -Single --> |No| Loop -HashEnd --> Cast["Cast to checksum_type"] -Cast --> End(["Return Merkle root"]) -``` - -**Diagram sources** -- [block.cpp](file://libraries/protocol/block.cpp#L35-L64) - -**Section sources** -- [block.cpp](file://libraries/protocol/block.cpp#L35-L64) - -### Block Validation Rules -- Merkle root verification: The computed Merkle root must match the header’s field. -- Block size check: Enforced against dynamic global properties. -- validator signature validation: The validator signature must be recoverable to the expected signing key. -- validator scheduling: The block must be produced by the scheduled validator for the slot derived from the timestamp. -- Fork resolution: If the new block does not extend the current head, the fork database chooses the heaviest chain and replays or undoes blocks as needed. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant FH as "validate_block_header" -participant Fork as "fork_database.hpp" -DB->>DB : "validate_block(new_block)" -DB->>DB : "calculate_merkle_root()" -DB->>DB : "compare with header" -DB->>FH : "validate_signee(validator.signing_key)" -FH-->>DB : "ok" -DB->>DB : "check scheduled validator" -DB->>Fork : "push_block(new_block)" -Fork-->>DB : "new_head" -DB->>DB : "apply_block() if needed" -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L737-L792) -- [database.cpp](file://libraries/chain/database.cpp#L3724-L3747) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L78-L91) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L737-L792) -- [database.cpp](file://libraries/chain/database.cpp#L3724-L3747) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L78-L91) - -### Block Production and Propagation -- Block production is governed by validator scheduling and participation. The validator that produces a block is determined by the slot derived from the block timestamp and the validator schedule. -- Propagation occurs when peers receive blocks and validate them before pushing to their chain. -- Persistence is handled by the block log, which stores blocks in an append-only manner and supports random access by block number. - -```mermaid -sequenceDiagram -participant W as "validator" -participant Net as "Network" -participant Node as "database.cpp" -participant Log as "block_log.hpp" -W->>W : "produce signed_block" -W->>Net : "broadcast block" -Net->>Node : "receive block" -Node->>Node : "validate_block()" -Node->>Log : "append(block)" -Node-->>Net : "accepted" -``` - -**Diagram sources** -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L50-L56) - -**Section sources** -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L50-L56) - -### Fork Resolution Criteria -- The fork database maintains linked forks and selects the head based on the longest chain. -- When a new block does not extend the current head, the system computes branches from the new head and current head, pops blocks from the current chain until the fork point, and replays blocks from the new branch. -- Invalid blocks are removed and the head restored to the previous valid state. - -```mermaid -flowchart TD -A["New block received"] --> B["fork_db.push_block()"] -B --> C{"new_head extends current head?"} -C --> |Yes| D["apply_block()"] -C --> |No| E["fetch_branch_from(new_head, head)"] -E --> F["pop blocks until fork point"] -F --> G["push blocks from new fork"] -G --> H["success"] -E --> I["exception"] -I --> J["remove invalid blocks"] -J --> K["restore head to previous valid"] -K --> L["abort"] -``` - -**Diagram sources** -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L78-L91) -- [database.cpp](file://libraries/chain/database.cpp#L847-L925) - -**Section sources** -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L78-L91) -- [database.cpp](file://libraries/chain/database.cpp#L847-L925) - -### Relationship Between Blocks and Blockchain State Progression -- Dynamic global properties are updated per block, including head block number/id, timestamp, participation metrics, and reserve ratios. -- validator participation and penalties are tracked; missed blocks increment counters and can lead to penalties and potential shutdown of inactive validators. -- The irreversible block number advances when sufficient validator validations reach consensus thresholds. - -```mermaid -flowchart TD -S["Start apply_block"] --> U["update_global_dynamic_data()"] -U --> P["compute missed_blocks and participation"] -P --> W["modify validator stats (missed, penalties)"] -W --> D["update dgp fields (head, time, sizes)"] -D --> IR["check irreversible threshold"] -IR --> E["advance last_irreversible_block_num if met"] -E --> F["persist via block_log"] -F --> T["End"] -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L3759-L3873) -- [database.cpp](file://libraries/chain/database.cpp#L3875-L3899) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3759-L3873) -- [database.cpp](file://libraries/chain/database.cpp#L3875-L3899) - -## Dependency Analysis -- Protocol definitions depend on cryptographic types and reflection macros. -- Chain validation depends on protocol structures, fork database, and block log. -- validator objects provide scheduling and participation data used by validation. - -```mermaid -graph LR -Types["types.hpp"] --> Header["block_header.hpp"] -Header --> Block["block.hpp"] -Block --> Impl["block.cpp"] -Impl --> DBAPI["database.hpp"] -DBAPI --> DBIMPL["database.cpp"] -DBIMPL --> Fork["fork_database.hpp"] -DBIMPL --> Log["block_log.hpp"] -DBIMPL --> Wit["witness_objects.hpp"] -``` - -**Diagram sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L105-L110) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L1-L200) - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L105-L110) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [database.cpp](file://libraries/chain/database.cpp#L737-L929) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L1-L200) - -## Performance Considerations -- Merkle root computation is O(n log n) for n transactions due to the binary hash tree. -- Fork resolution involves popping and pushing blocks; keep the maximum reordering window reasonable to avoid excessive memory and CPU usage. -- Block size checks prevent oversized blocks from consuming resources. -- validator participation metrics and penalties help maintain network health and reduce spam. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common validation failures and remedies: -- Merkle mismatch: Verify transaction ordering and ensure the Merkle root is recomputed consistently. -- validator signature mismatch: Confirm the validator signing key matches the expected key and that the digest used for signing is correct. -- Incorrect validator scheduling: Ensure the block timestamp yields the correct slot and that the scheduled validator matches the block producer. -- Fork conflicts: Investigate why the new block did not extend the current head; check timestamps, validator assignments, and fork database logs. -- Disk or memory errors during block push: The system attempts to resize shared memory on allocation failure; ensure adequate disk space and memory. - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L757-L792) -- [database.cpp](file://libraries/chain/database.cpp#L3724-L3747) -- [database.cpp](file://libraries/chain/database.cpp#L847-L925) - -## Conclusion -Blocks in the VIZ node are defined by a clear protocol structure, validated rigorously by the chain layer, and persisted for resilience. Consensus relies on deterministic validator scheduling, strict signature validation, and robust fork resolution. Understanding these components enables reliable block construction, validation, propagation, and synchronization across the network. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Example Workflows -- Constructing a block: Build a signed block with transactions, compute the Merkle root, populate the header fields, and sign with the validator key. -- Validating a block: Compute the Merkle root, compare against the header; verify the validator signature; confirm the scheduled validator; enforce block size limits. -- Resolving a fork: Use the fork database to select the heaviest chain; if necessary, pop blocks from the current chain and push blocks from the new fork. - -[No sources needed since this section provides general guidance] \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Data Types and Serialization.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Data Types and Serialization.md deleted file mode 100644 index 9b50b03485..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Data Types and Serialization.md +++ /dev/null @@ -1,553 +0,0 @@ -# Data Types and Serialization - - -**Referenced Files in This Document** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp) -- [types.cpp](file://libraries/protocol/types.cpp) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the blockchain data types and serialization mechanisms used by the protocol layer. It focuses on: -- Basic and specialized blockchain types (keys, identifiers, amounts, authorities) -- Smart contract-like operation types and their variants -- Serialization/deserialization for operations, transactions, and blocks -- Utilities for operation introspection and helper functions -- Variant serialization, enum handling, and custom type serialization patterns -- Examples of usage and protocol versioning implications - -## Project Structure -The relevant data types and serialization logic live primarily under the protocol library’s include and implementation files. The most important areas are: -- Core types and cryptographic keys -- Operations and their static variants -- Transactions and blocks -- Asset and authority types -- Versioning and extension types - -```mermaid -graph TB -subgraph "Protocol Types" -T["types.hpp
Core types and aliases"] -A["asset.hpp
Asset and Price"] -AU["authority.hpp
Authority and classification"] -V["version.hpp
Version and Hardfork Version"] -end -subgraph "Operations" -O["operations.hpp
Operation static_variant"] -CO["chain_operations.hpp
Concrete operations"] -PO["proposal_operations.hpp
Proposal ops"] -VO["chain_virtual_operations.hpp
Virtual ops"] -B["base.hpp
base_operation/virtual_operation"] -end -subgraph "Serialization" -TR["transaction.hpp
transaction/singed_transaction"] -BLK["block.hpp
signed_block"] -BH["block_header.hpp
block_header/siged_block_header"] -OU["operation_util.hpp
DECLARE_OPERATION_TYPE macros"] -OUI["operation_util_impl.cpp
name_from_type"] -end -T --> O -O --> CO -O --> PO -O --> VO -B --> CO -B --> PO -B --> VO -TR --> O -BLK --> TR -BH --> BLK -AU --> TR -A --> CO -V --> BH -OU --> O -OUI --> OU -``` - -**Diagram sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L34-L209) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L10-L181) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L14-L156) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L8-L131) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L9-L1189) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L48-L129) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L62) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L34-L209) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L8-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L62) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L10-L181) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L1189) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L48-L129) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L14-L156) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -## Core Components -- Basic types and aliases: identifiers, hashes, time, safe integers, and reflection-friendly containers -- Cryptographic keys: public, extended public/private keys with Base58-pack/unpack serialization -- Asset and price: token representation with string conversions and arithmetic -- Authority: weighted multi-signature structures with classification and validation -- Operations: static_variant of all operations and virtual operations -- Transaction and Block: container structures with digest, signing, and Merkle roots - -Key responsibilities: -- Provide compact, deterministic serialization via fc::raw and fc::reflector -- Support runtime polymorphism via static_variant for operations -- Encode cryptographic material in a standardized way for hashing and signing - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L36-L209) -- [types.cpp](file://libraries/protocol/types.cpp#L13-L178) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L14-L181) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) - -## Architecture Overview -The serialization architecture centers on fc reflection and raw packing: -- Types are reflected with FC_REFLECT/FC_REFLECT_TYPENAME for deterministic field ordering -- Static variants (operation, versioned chain properties) serialize discriminant + payload -- Enumerations are handled via FC_REFLECT_ENUM or fc::enum_type -- Custom types (keys, asset) provide to_variant/from_variant for human-readable formats and pack/unpack for binary - -```mermaid -classDiagram -class PublicKeyType { -+key_data -+operator std : : string() -+operator fc : : ecc : : public_key() -} -class ExtendedPublicKeyType { -+key_data -+operator std : : string() -+operator fc : : ecc : : extended_public_key() -} -class ExtendedPrivateKeyType { -+key_data -+operator std : : string() -+operator fc : : ecc : : extended_private_key() -} -class Asset { -+amount -+symbol -+to_string() -+from_string() -} -class Authority { -+weight_threshold -+account_auths -+key_auths -+validate() -} -class Operation { -<> -} -class Transaction { -+ref_block_num -+ref_block_prefix -+expiration -+operations -+extensions -+id() -+sig_digest() -} -class SignedBlock { -+transactions -+calculate_merkle_root() -} -PublicKeyType <.. Asset : "used in ops" -ExtendedPublicKeyType <.. Transaction : "signing" -ExtendedPrivateKeyType <.. Transaction : "signing" -Asset <.. Operation : "fees" -Authority <.. Transaction : "required authorities" -Operation <.. Transaction : "payload" -Transaction <.. SignedBlock : "contains" -``` - -**Diagram sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L113-L207) -- [types.cpp](file://libraries/protocol/types.cpp#L13-L178) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L14-L181) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) - -## Detailed Component Analysis - -### Basic Data Types and Keys -- Aliases: chain_id_type, block_id_type, transaction_id_type, digest_type, signature_type, share_type, weight_type -- Public keys and extended keys: encapsulate ECC data with binary_key for pack/unpack and Base58 string conversion -- String comparisons and ordering: string_less supports std::string and fc::fixed_string - -Serialization highlights: -- Binary form uses fc::raw::pack/unpack of binary_key structs -- Human-readable form uses Base58 with a chain-specific address prefix -- Reflection-based serialization via FC_REFLECT for key structs - -Usage examples (conceptual): -- Convert a public key to a string for display or storage -- Pack a key into bytes for inclusion in transactions or blocks -- Unpack bytes back into a key for signature verification - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L207) -- [types.cpp](file://libraries/protocol/types.cpp#L13-L178) -- [types.cpp](file://libraries/protocol/types.cpp#L183-L206) - -### Asset and Price Types -- Asset stores amount and symbol; provides arithmetic, comparison, and string conversions -- Price pairs base and quote assets; includes normalization helpers and validation - -Serialization highlights: -- FC_REFLECT for asset and price -- to_variant/from_variant convert to/from human-readable strings - -Usage examples (conceptual): -- Serialize an asset to JSON for APIs -- Deserialize a string into an asset for validation and arithmetic - -**Section sources** -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L14-L181) - -### Authority and Classification -- Authority aggregates thresholds and maps of accounts/keys with weights -- Classification enumerates master, active, key, regular for operation authority derivation -- Validation ensures satisfiability and structural correctness - -Serialization highlights: -- FC_REFLECT for authority and its internal maps -- FC_REFLECT_ENUM for classification - -Usage examples (conceptual): -- Build an authority requiring multiple signatures -- Extract required authorities from an operation - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) - -### Operations and Variants -- operation is a static_variant over all concrete operations and virtual operations -- is_virtual_operation/is_data_operation classify variants -- operation_wrapper resolves circular dependencies for proposals - -Serialization highlights: -- FC_REFLECT_TYPENAME for operation and wrapper -- DECLARE_OPERATION_TYPE macro generates to_variant/from_variant and validation/authority extraction stubs - -Usage examples (conceptual): -- Serialize an operation to bytes for signing -- Deserialize bytes into the appropriate operation variant -- Validate an operation before applying - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) - -### Operation Utilities and Helpers -- DECLARE_OPERATION_TYPE macro defines to_variant, from_variant, operation_validate, and operation_get_required_authorities -- name_from_type extracts a concise name from a fully qualified type name - -Usage examples (conceptual): -- Implement to_variant/from_variant for a new operation type -- Use operation_get_required_authorities to compute signers for a transaction - -**Section sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -### Transactions and Blocks -- transaction: container with expiration, reference block fields, operations, and extensions -- signed_transaction: extends transaction with signatures and authority helpers -- annotated_signed_transaction: attaches block and transaction indices -- block_header/sigend_block_header: header with digest/signature; signed_block contains transactions and computes Merkle root - -Serialization highlights: -- FC_REFLECT for all structures -- visit pattern on operations for visitor-based processing -- Merkle root computation for block integrity - -Usage examples (conceptual): -- Build a transaction, add operations, compute digest, sign, and broadcast -- Verify signatures and required authorities for a signed transaction -- Serialize a block for persistence or P2P propagation - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) - -### Virtual Operations and Base Types -- base_operation/virtual_operation define the hierarchy; virtual operations are non-executed events -- block_header_extensions/future_extensions are static variants for extensibility - -Usage examples (conceptual): -- Emit virtual operations during evaluation to notify APIs and observers -- Extend block headers with versioning or hardfork votes - -**Section sources** -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L62) - -### Concrete Operations and Extensions -- chain_operations.hpp: account creation/update, transfers, vesting, validators, escrow, chain properties, invites, subscriptions, sales, awards, bids -- proposal_operations.hpp: proposal lifecycle operations -- chain_virtual_operations.hpp: reward payouts, hardfork triggers, committee actions, validator rewards, subscription actions, sales, and auction events - -Serialization highlights: -- FC_REFLECT for each operation struct -- Extensions fields for future extensibility - -Usage examples (conceptual): -- Construct a transfer operation with fee, amount, memo -- Create a proposal with multiple wrapped operations and expiration - -**Section sources** -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L1189) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L48-L129) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) - -### Versioning and Protocol Evolution -- version and hardfork_version represent semantic versioning -- hardfork_version_vote carries voting for hardfork activation -- block_header_extensions includes version reporting and hardfork votes - -Serialization highlights: -- FC_REFLECT for version types -- to_variant/from_variant for human-readable version strings - -Usage examples (conceptual): -- Report current node version in block headers -- Vote for a hardfork version and record timestamp - -**Section sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L14-L156) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L43-L54) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L25-L35) - -## Architecture Overview - -```mermaid -sequenceDiagram -participant App as "Application" -participant Tx as "transaction" -participant Ops as "operations.hpp" -participant Var as "static_variant" -participant Ser as "fc : : raw / fc : : variant" -App->>Tx : "Add operations" -Tx->>Ops : "operations static_variant" -Ops->>Var : "Select operation type" -App->>Ser : "Serialize operations (bytes)" -Ser-->>App : "Raw bytes" -App->>Ser : "Serialize operations (JSON)" -Ser-->>App : "Variant (human-readable)" -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L18-L49) - -## Detailed Component Analysis - -### Variant Serialization and Enum Handling -- static_variant serializes the index of the contained type followed by the serialized payload -- enums are reflected via FC_REFLECT_ENUM or fc::enum_type -- Custom types provide to_variant/from_variant for JSON-friendly formats - -```mermaid -flowchart TD -Start(["Serialize Variant"]) --> GetIndex["Get type index"] -GetIndex --> SerializePayload["Serialize payload bytes"] -SerializePayload --> Combine["Write index + payload"] -Combine --> End(["Done"]) -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L114-L115) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L152-L156) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L114-L115) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L152-L156) - -### Custom Type Serialization Patterns -- Keys: pack/unpack binary_key, encode/decode Base58 with chain prefix -- Asset: string-based to/from variant for JSON compatibility -- Authorities: reflection-based serialization with weighted maps - -```mermaid -flowchart TD -KStart(["Key to_bytes"]) --> Pack["Pack binary_key"] -Pack --> Hash["Hash key data"] -Hash --> Check["Verify checksum"] -Check --> KEnd(["Bytes"]) -AStart(["Asset to_variant"]) --> ToString["Convert to string"] -ToString --> AEnd(["Variant"]) -``` - -**Diagram sources** -- [types.cpp](file://libraries/protocol/types.cpp#L51-L57) -- [types.cpp](file://libraries/protocol/types.cpp#L104-L110) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L170-L177) - -**Section sources** -- [types.cpp](file://libraries/protocol/types.cpp#L13-L178) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L170-L177) - -### Operation Utilities and Helper Functions -- DECLARE_OPERATION_TYPE macro centralizes the generation of serialization and validation hooks -- name_from_type helps derive operation names from type names for logging or UI - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Macro as "DECLARE_OPERATION_TYPE" -participant Impl as "operation_util_impl.cpp" -Dev->>Macro : "Define OperationType" -Macro-->>Dev : "to_variant/from_variant + validate + authorities" -Dev->>Impl : "Use name_from_type(type_name)" -Impl-->>Dev : "Clean name" -``` - -**Diagram sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -**Section sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -### Data Type Usage Scenarios -- Signing a transaction: compute sig_digest(chain_id), sign with private key, append signature -- Deserializing a block: unpack signed_block, iterate transactions, verify signatures and Merkle roots -- Validating an operation: call operation_validate and extract required authorities for permission checks - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L27-L101) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) - -### Relationship Between Data Types and Protocol Versioning -- version and hardfork_version provide structured versioning -- block_header_extensions carry version reporting and hardfork votes -- Static variants (operation, versioned chain properties) evolve by adding new alternatives without breaking existing encodings - -**Section sources** -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L14-L156) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L43-L54) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L635-L640) - -## Dependency Analysis - -```mermaid -graph LR -types_hpp["types.hpp"] --> operations_hpp["operations.hpp"] -operations_hpp --> chain_ops_hpp["chain_operations.hpp"] -operations_hpp --> proposal_ops_hpp["proposal_operations.hpp"] -operations_hpp --> virt_ops_hpp["chain_virtual_operations.hpp"] -operations_hpp --> base_hpp["base.hpp"] -transaction_hpp["transaction.hpp"] --> operations_hpp -block_hpp["block.hpp"] --> transaction_hpp -block_header_hpp["block_header.hpp"] --> block_hpp -asset_hpp["asset.hpp"] --> chain_ops_hpp -authority_hpp["authority.hpp"] --> transaction_hpp -version_hpp["version.hpp"] --> block_header_hpp -operation_util_hpp["operation_util.hpp"] --> operations_hpp -operation_util_impl_cpp["operation_util_impl.cpp"] --> operation_util_hpp -``` - -**Diagram sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L34-L209) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L8-L131) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L9-L1189) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L48-L129) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L62) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L10-L181) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L14-L156) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L34-L209) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L8-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L62) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L10-L181) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L9-L1189) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L48-L129) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) -- [version.hpp](file://libraries/protocol/include/graphene/protocol/version.hpp#L14-L156) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operation_util_impl.cpp](file://libraries/protocol/operation_util_impl.cpp#L5-L11) - -## Performance Considerations -- Prefer fc::raw serialization for compact binary storage and deterministic hashing -- Use fc::variant for human-readable interchange; avoid in hot paths -- Keep operation payloads minimal; leverage extensions for optional data -- Static variants enable efficient dispatch without virtual calls -- Avoid repeated pack/unpack cycles; cache digests when possible - -## Troubleshooting Guide -Common issues and remedies: -- Key deserialization failures: verify Base58 prefix and checksum match -- Asset parsing errors: ensure symbol and precision align with chain configuration -- Authority validation failures: confirm thresholds and weights satisfy required sets -- Operation variant mismatches: ensure the correct type is selected during deserialization -- Signature verification failures: recompute sig_digest with the correct chain_id and reference block - -**Section sources** -- [types.cpp](file://libraries/protocol/types.cpp#L24-L39) -- [types.cpp](file://libraries/protocol/types.cpp#L84-L97) -- [types.cpp](file://libraries/protocol/types.cpp#L137-L150) -- [asset.hpp](file://libraries/protocol/include/graphene/protocol/asset.hpp#L170-L177) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L49-L49) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L27-L101) - -## Conclusion -The protocol layer provides a robust, extensible foundation for blockchain data types and serialization. By combining fc reflection, static variants, and custom serializers, it achieves deterministic encoding, clear versioning, and flexible evolution. Developers can extend operations and types while maintaining backward compatibility and efficient serialization for production-grade nodes and applications. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Operations Definition.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Operations Definition.md deleted file mode 100644 index 279b4d4899..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Operations Definition.md +++ /dev/null @@ -1,318 +0,0 @@ -# Operations Definition - - -**Referenced Files in This Document** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp) -- [operations.cpp](file://libraries/protocol/operations.cpp) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp) -- [1.hf](file://libraries/chain/hardfork.d/1.hf) -- [10.hf](file://libraries/chain/hardfork.d/10.hf) -- [11.hf](file://libraries/chain/hardfork.d/11.hf) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the Operations Definition system in the VIZ blockchain protocol. It focuses on the static_variant-based operation type registry, the operation_wrapper mechanism, and the hierarchical categorization of operations into on-chain and virtual categories. It also documents validation and serialization hooks, deprecated operations and their replacements, operation ordering and hardfork implications, and practical examples of operation creation, validation, and serialization. - -## Project Structure -The operation system spans several protocol headers and a small implementation module: -- The central operation registry is defined as a static_variant in operations.hpp. -- Operation structs are declared in chain_operations.hpp and chain_virtual_operations.hpp. -- Validation and authority extraction are defined in operation_util.hpp and implemented in operations.cpp. -- Operation-specific validation logic is implemented in chain_operations.cpp. -- Proposal operations introduce operation_wrapper to resolve circular dependencies. -- Specialized operation families (invites, paid subscriptions) are defined alongside their domain objects. - -```mermaid -graph TB -subgraph "Protocol Layer" -OP["operations.hpp
static_variant"] -BO["base.hpp
base_operation / virtual_operation"] -CO["chain_operations.hpp
On-chain ops"] -VO["chain_virtual_operations.hpp
Virtual ops"] -PR["proposal_operations.hpp
proposal_* + operation_wrapper"] -OU["operation_util.hpp
DECLARE_OPERATION_TYPE / helpers"] -end -subgraph "Implementation" -OC["chain_operations.cpp
validate() impls"] -OI["operations.cpp
is_virtual_operation()
is_data_operation()"] -end -OP --> CO -OP --> VO -OP --> PR -OP --> OU -BO --> CO -BO --> VO -PR --> OP -OC --> CO -OI --> OP -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L10-L102) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L41) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L112) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L304) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L37-L128) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L39-L447) -- [operations.cpp](file://libraries/protocol/operations.cpp#L8-L57) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L10-L102) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L41) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) - -## Core Components -- Static variant operation registry: A single type alias that aggregates all on-chain and virtual operations in a deterministic order. -- Base operation types: base_operation and virtual_operation define common behavior and virtual-only validation semantics. -- Operation wrapper: operation_wrapper is a thin container used to break circular definitions in proposals. -- Validation and authority helpers: Macros and functions to serialize/deserialize operations and compute required authorities. -- Operation families: Account, asset/vesting, content/vote, governance (proposals), virtual rewards, and specialized operations (committee, invites, paid subscriptions). - -Key responsibilities: -- Registry ordering controls hardfork sensitivity. -- Each operation defines validate() and required authority methods. -- Virtual operations are emitted by evaluators and not included in transactions. - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L10-L102) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L41) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L37-L62) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) - -## Architecture Overview -The operation architecture centers on a static_variant that enumerates all operations. Evaluators apply on-chain operations and emit virtual operations. Serialization is handled via the DECLARE_OPERATION_TYPE macro and fc::variant converters. - -```mermaid -classDiagram -class base_operation { -+validate() void -+get_required_authorities(...) -+get_required_active_authorities(...) -+get_required_master_authorities(...) -+get_required_regular_authorities(...) -+is_virtual() bool -} -class virtual_operation { -+is_virtual() bool -+validate() void -} -class operation { -<> -} -class operation_wrapper { -+op : operation -} -base_operation <|-- virtual_operation -operation ..> base_operation : "holds" -operation ..> virtual_operation : "holds" -operation_wrapper --> operation : "contains" -``` - -**Diagram sources** -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L41) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L37-L53) - -## Detailed Component Analysis - -### Static Variant Operation Registry -- The operation type is a static_variant that lists all on-chain operations followed by virtual operations. The comment explicitly warns that reordering prior to virtual operations triggers a hardfork. -- Categories include: - - Account operations: transfer, transfer_to_vesting, withdraw_vesting, account_update, account_create, account_metadata, account-related authorities and recovery. - - Asset/vesting operations: vesting transfers, withdrawals, delegation, and withdrawal routing. - - Content/vote operations: content, delete_content, vote (both marked deprecated in the registry). - - Governance operations: proposals (create, update, delete), chain property updates, versioned chain properties. - - Virtual reward operations: author_reward, curation_reward, content_reward, fill_vesting_withdraw, shutdown_witness, hardfork, content_payout_update, content_benefactor_reward, return_vesting_delegation. - - Committee operations: worker requests, cancellations, votes, payouts/payments. - - Invite operations: create_invite, claim_invite_balance, invite_registration, use_invite_balance. - - Award operations: award, receive_award, benefactor_award. - - Paid subscription operations: set_paid_subscription, paid_subscribe, paid_subscription_action, cancel_paid_subscription. - - Account sales operations: set_account_price, set_subaccount_price, buy_account, account_sale. - - Escrow and related: escrow_transfer, escrow_dispute, escrow_release, escrow_approve, expire_escrow_ratification. - - HF11 operations: fixed_award, target_account_sale, bid, outbid. - -Serialization and reflection: -- The registry exposes FC_REFLECT_TYPENAME and FC_REFLECT for operation_wrapper, enabling fc::variant conversions. - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L10-L102) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L115-L131) - -### Operation Wrapper and Circular Dependencies -- operation_wrapper is a minimal struct containing a single operation field. It is used inside proposal operations to avoid circular definitions between operation and proposal_create_operation. -- The proposal family declares operation_wrapper before defining proposal operations, ensuring the static_variant can reference it. - -```mermaid -sequenceDiagram -participant P as "proposal_create_operation" -participant W as "operation_wrapper" -participant O as "operation" -P->>W : proposed_operations.push_back(operation_wrapper(op)) -Note right of P : operation_wrapper holds operation -Note over P,O : Circular dependency avoided by forward-declaring operation_wrapper -``` - -**Diagram sources** -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L37-L53) -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L48-L62) - -**Section sources** -- [proposal_operations.hpp](file://libraries/protocol/include/graphene/protocol/proposal_operations.hpp#L37-L62) - -### Base Operation Types and Virtual Operations -- base_operation provides default no-op implementations for validation and authority extraction, plus is_virtual() returning false. -- virtual_operation inherits from base_operation and overrides is_virtual() to return true and enforces that virtual operations cannot be validated as on-chain operations. -- Virtual operations are emitted by evaluators and processed separately from transaction operations. - -**Section sources** -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L41) - -### Operation Validation and Authority Extraction -- Validation: Each operation implements validate(). The implementation performs domain-specific checks (e.g., account name validity, UTF-8, JSON, symbol types, amounts). -- Authority extraction: Each operation optionally implements get_required_*_authorities(...) to declare which accounts/keys must authorize the operation. -- Utility macros: DECLARE_OPERATION_TYPE declares to_variant/from_variant and operation_validate/operation_get_required_authorities for the operation type. - -```mermaid -flowchart TD -Start(["Operation Received"]) --> Validate["Call validate()"] -Validate --> Valid{"Valid?"} -Valid --> |No| Reject["Reject Transaction"] -Valid --> |Yes| Auth["Compute Required Authorities"] -Auth --> Sign["Collect Signatures"] -Sign --> Apply["Apply Evaluator (on-chain)"] -Apply --> EmitVirt["Emit Virtual Ops (if any)"] -EmitVirt --> End(["Done"]) -``` - -**Diagram sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L39-L447) - -**Section sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L39-L447) - -### Deprecated Operations and Replacements -- The registry marks vote_operation and content_operation as deprecated. These remain present in the static_variant for backward compatibility. -- Evaluators and APIs should route legacy votes and content creation to newer equivalents or deprecation pathways as per network policy. - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L14-L15) - -### Operation Families and Hierarchical Organization -- Account operations: account_create, account_update, account_metadata, authorities and recovery operations. -- Asset/Vesting operations: transfer, transfer_to_vesting, withdraw_vesting, set_withdraw_vesting_route, delegate_vesting_shares. -- Content/Vote operations: content, delete_content, vote (deprecated). -- Governance: proposal_create, proposal_update, proposal_delete; chain_properties_update, versioned_chain_properties_update; witness_update, account_witness_vote, account_witness_proxy. -- Virtual reward operations: author_reward, curation_reward, content_reward, fill_vesting_withdraw, hardfork, content_payout_update, content_benefactor_reward, return_vesting_delegation. -- Committee operations: committee_worker_create_request, committee_worker_cancel_request, committee_vote_request, committee_cancel_request, committee_approve_request, committee_payout_request, committee_pay_request. -- Invite operations: create_invite, claim_invite_balance, invite_registration, use_invite_balance. -- Award operations: award, receive_award, benefactor_award. -- Paid subscription operations: set_paid_subscription, paid_subscribe, paid_subscription_action, cancel_paid_subscription. -- Account sales operations: set_account_price, set_subaccount_price, buy_account, account_sale. -- Escrow operations: escrow_transfer, escrow_dispute, escrow_release, escrow_approve, expire_escrow_ratification. -- HF11 operations: fixed_award, target_account_sale, bid, outbid. - -These families are organized as separate headers and are aggregated into the single operation static_variant. - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L112) -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L304) -- [invite_objects.hpp](file://libraries/chain/include/graphene/chain/invite_objects.hpp#L14-L38) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L33) - -### Examples: Creation, Validation, and Serialization -- Creation: Instantiate an operation struct with required fields (e.g., account names, amounts, metadata). Populate extensions if applicable. -- Validation: Call validate() on the operation struct. This performs domain checks (names, UTF-8, JSON, symbol types, amounts). -- Serialization: Use fc::to_variant and fc::from_variant to convert between binary and human-readable forms. The DECLARE_OPERATION_TYPE macro ensures these functions are available for the operation type. - -Note: The repository does not include example client code; consult the macro declarations and fc::variant usage in the protocol headers for integration points. - -**Section sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L39-L447) - -## Dependency Analysis -- The operation registry depends on: - - chain_operations.hpp for on-chain operation structs. - - chain_virtual_operations.hpp for virtual operation structs. - - proposal_operations.hpp for proposal operations and operation_wrapper. - - base.hpp for base_operation and virtual_operation. -- Implementation dependencies: - - operations.cpp implements is_virtual_operation and is_data_operation using static_variant visitors. - - chain_operations.cpp implements validate() for each operation struct. - -```mermaid -graph LR -OP["operations.hpp"] --> CO["chain_operations.hpp"] -OP --> VO["chain_virtual_operations.hpp"] -OP --> PR["proposal_operations.hpp"] -OP --> OU["operation_util.hpp"] -CO --> BO["base.hpp"] -VO --> BO -OI["operations.cpp"] --> OP -OC["chain_operations.cpp"] --> CO -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L3-L6) -- [operations.cpp](file://libraries/protocol/operations.cpp#L1-L57) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L1-L448) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L3-L6) -- [operations.cpp](file://libraries/protocol/operations.cpp#L1-L57) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L1-L448) - -## Performance Considerations -- static_variant dispatch is efficient for operation handling; keep the variant compact and avoid excessive nesting. -- Validation functions should short-circuit on failure and minimize allocations. -- Virtual operations are computed during evaluation and do not increase transaction size; they are processed separately. - -## Troubleshooting Guide -Common issues and remedies: -- Validation failures: Review operation.validate() constraints (account names, UTF-8, JSON, symbol types, amounts). Fix malformed inputs or incorrect asset symbols. -- Missing authorities: Ensure get_required_*_authorities(...) returns the correct set of accounts/keys. Verify multisig thresholds and key weights. -- Serialization errors: Confirm fc::to_variant and fc::from_variant are used consistently with the operation type. Check that the operation_wrapper is used for proposals. -- Hardfork ordering: Do not reorder operations before the virtual operations in the static_variant. Introduce new operations at the end of the appropriate category to avoid breaking changes. - -**Section sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L16-L34) -- [operations.cpp](file://libraries/protocol/operations.cpp#L8-L57) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L39-L447) - -## Conclusion -The VIZ operation system is a robust, extensible framework built on a static_variant registry. It cleanly separates on-chain and virtual operations, supports flexible authority and validation semantics, and provides clear hooks for serialization and introspection. Careful adherence to ordering and deprecation policies ensures safe evolution across hardforks. - -## Appendices - -### Operation Ordering and Hardfork Implications -- The registry comment explicitly warns that changing the order of operations before virtual operations constitutes a hardfork. -- Hardfork markers in the codebase indicate activation points for protocol changes. - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L10-L12) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) -- [10.hf](file://libraries/chain/hardfork.d/10.hf#L1-L7) -- [11.hf](file://libraries/chain/hardfork.d/11.hf#L1-L7) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Protocol Library.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Protocol Library.md deleted file mode 100644 index 6928acec34..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Protocol Library.md +++ /dev/null @@ -1,418 +0,0 @@ -# Protocol Library - - -**Referenced Files in This Document** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp) -- [operations.cpp](file://libraries/protocol/operations.cpp) -- [transaction.cpp](file://libraries/protocol/transaction.cpp) -- [authority.cpp](file://libraries/protocol/authority.cpp) -- [block.cpp](file://libraries/protocol/block.cpp) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the Protocol Library that defines the blockchain’s operational framework. It covers: -- Transaction operations and their categorization -- Transaction structure, validation, signature verification, and serialization -- Authority requirements, multi-signature validation, and permission checks -- Block and block header structures, Merkle roots, and consensus-related signing -- Blockchain-specific operations and their evaluation logic -- Core data types and serialization formats -- Practical examples of operation creation, transaction building, authority verification, and block validation -- The relationship between protocol definitions and chain library implementations - -## Project Structure -The Protocol Library resides under libraries/protocol and exposes headers for operations, transactions, authorities, blocks, types, and supporting utilities. Implementation files provide concrete behavior for validation, signing, and Merkle tree computation. - -```mermaid -graph TB -subgraph "Protocol Headers" -OP["operations.hpp"] -TX["transaction.hpp"] -AUTH["authority.hpp"] -BH["block_header.hpp"] -B["block.hpp"] -CO["chain_operations.hpp"] -TYPES["types.hpp"] -BASE["base.hpp"] -OU["operation_util.hpp"] -SS["sign_state.hpp"] -end -subgraph "Protocol Implementations" -OPCPP["operations.cpp"] -TXCPP["transaction.cpp"] -AUTHCPP["authority.cpp"] -BHC["block.cpp"] -COCPP["chain_operations.cpp"] -end -OP --> OU -OP --> CO -TX --> OP -TX --> SS -B --> BH -CO --> TYPES -CO --> BASE -AUTH --> TYPES -BH --> TYPES -TXCPP --> TX -TXCPP --> SS -AUTHCPP --> AUTH -BHC --> B -COCPP --> CO -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L1-L136) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L1-L800) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L1-L235) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L1-L62) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L1-L35) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [operations.cpp](file://libraries/protocol/operations.cpp#L1-L58) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [authority.cpp](file://libraries/protocol/authority.cpp#L1-L228) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L1-L448) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L1-L136) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L1-L800) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L1-L235) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L1-L62) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L1-L35) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) - -## Core Components -- Operations: A static variant enumerating all transaction operations, including account, asset, content, governance, and virtual operations. Includes helpers to detect virtual and data operations. -- Transaction: Transaction and signed transaction structures with validation, signature digest computation, Merkle digest, and authority extraction/minimization. -- Authority: Multi-signature authority model with thresholds, account and key maps, validation, and account name rules. -- Blocks: Block header and signed block structures with Merkle root computation and validator signature verification. -- Chain Operations: Strongly-typed operation structs with validation logic and required authority hooks. -- Types: Core blockchain types (names, keys, amounts, hashes, digests) and serialization support. - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L10-L131) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L800) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L235) - -## Architecture Overview -The Protocol Library composes operations into transactions, validates each operation, computes digests for signing and Merkle roots, and enforces authority requirements. Blocks encapsulate transactions and enforce validator signatures. - -```mermaid -graph TB -OP["operation (static_variant)"] -TX["transaction
signed_transaction"] -AUTH["authority"] -SS["sign_state"] -BH["block_header
signed_block_header"] -SB["signed_block"] -CO["chain_operations (structs)"] -OP --> TX -TX --> AUTH -TX --> SS -SB --> BH -SB --> TX -CO --> TYPES["types.hpp"] -AUTH --> TYPES -BH --> TYPES -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L35) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L13) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L29) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L112) - -## Detailed Component Analysis - -### Operations: Transaction Operation Types -- The operation static variant enumerates all supported operations, including deprecated entries, regular operations, and virtual operations. It also exposes helpers to classify operations (virtual/data). -- The operation wrapper supports reflection and serialization. - -Key responsibilities: -- Define the canonical operation set -- Provide classification helpers -- Enable reflection-based serialization - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [operations.cpp](file://libraries/protocol/operations.cpp#L8-L52) - -### Transaction: Validation, Serialization, and Authority -- Transaction structure includes reference block fields, expiration, operations, and extensions. It computes digests for validation and signing. -- Signed transaction extends transaction with signatures and provides signing, signature minimization, and authority verification. -- Authority verification logic: - - Extracts required authorities from operations - - Supports regular authority vs active/master mixing constraints - - Uses sign_state to track provided signatures/approvals and validate recursively - -Processing highlights: -- Transaction::validate ensures at least one operation and delegates per-operation validation -- Transaction::sig_digest constructs the digest used for signing -- Signed transaction methods compute Merkle digest and minimize required signatures - -```mermaid -sequenceDiagram -participant U as "User" -participant TRX as "signed_transaction" -participant SIG as "sign_state" -participant AUTH as "Authority Getters" -U->>TRX : Build operations and set reference block/expiry -TRX->>TRX : validate() -U->>TRX : sign(private_key, chain_id) -TRX-->>U : signature appended -U->>TRX : verify_authority(chain_id, get_active, get_master, get_regular) -TRX->>SIG : construct with provided signatures -SIG->>AUTH : resolve authorities for accounts -SIG-->>TRX : pass/fail verification -``` - -**Diagram sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L222) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L11-L361) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L8-L45) - -### Authority: Requirement Calculation and Multi-Signature Validation -- Authority model supports thresholds and maps of account weights and key weights. -- Provides validation of account names, domain names, and impossibility checks. -- Offers helpers to enumerate keys and count authorities. - -Validation logic: -- Name validation enforces length, character sets, and domain rules -- Threshold satisfaction checked during verification -- Recursive checks via sign_state with configurable depth - -```mermaid -flowchart TD -Start(["Authority.validate()"]) --> CheckAccounts["Validate each account name"] -CheckAccounts --> End(["Done"]) -``` - -**Diagram sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L44-L48) -- [authority.cpp](file://libraries/protocol/authority.cpp#L44-L48) - -**Section sources** -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L115) -- [authority.cpp](file://libraries/protocol/authority.cpp#L7-L228) - -### Blocks and Block Headers: Validation and Consensus -- Block header includes previous ID, timestamp, validator, and Merkle root of transactions. -- Signed block header adds validator signature and methods to compute ID and validate signee. -- Signed block computes Merkle root over transaction digests. - -Consensus implications: -- validator signature verification ensures block validity -- Merkle root ensures transaction integrity - -```mermaid -flowchart TD -A["signed_block.calculate_merkle_root()"] --> B["Map transactions to merkle_digest()"] -B --> C["Pair-wise hash until single root"] -C --> D["Set signed_block.transaction_merkle_root"] -``` - -**Diagram sources** -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L35) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L13) -- [block.cpp](file://libraries/protocol/block.cpp#L35-L64) - -**Section sources** -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L8-L43) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L19) -- [block.cpp](file://libraries/protocol/block.cpp#L6-L68) - -### Chain Operations: Evaluation Logic and Constraints -- Strongly typed operation structs define required authorities and validation rules. -- Examples include account creation/update/metadata, transfers, vesting, validator updates, chain property updates, escrow operations, custom operations, and governance-related operations (committee, invite, paid subscription, account sales, awards, etc.). -- Validation enforces symbol types, numeric bounds, UTF-8 and JSON constraints, permlink rules, and account name rules. - -Evaluation highlights: -- Each operation implements validate() and required authority hooks -- Static variants of chain properties support hardfork evolution - -**Section sources** -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L800) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L39-L448) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L12-L41) - -### Types: Data Types and Serialization -- Defines blockchain primitives: account names, keys, amounts, digests, signatures, and hashes. -- Provides reflection and serialization support for types and public keys. -- Includes comparison functors and convenience typedefs. - -Serialization characteristics: -- Public keys support base58 encoding/decoding and binary representation -- Safe integer types and big-integer types for precise arithmetic - -**Section sources** -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L235) - -## Dependency Analysis -- operations.hpp depends on operation utilities and chain operations -- transaction.hpp depends on operations, sign_state, and types -- authority.hpp depends on types -- block.hpp depends on block_header.hpp and transaction.hpp -- chain_operations.hpp depends on base.hpp, block_header.hpp, and asset definitions -- Implementations depend on fc library for hashing, crypto, and serialization - -```mermaid -graph LR -OU["operation_util.hpp"] --> OP["operations.hpp"] -CO["chain_operations.hpp"] --> BASE["base.hpp"] -CO --> BH["block_header.hpp"] -TX["transaction.hpp"] --> OP -TX --> SS["sign_state.hpp"] -B["block.hpp"] --> BH -AUTH["authority.hpp"] --> TYPES["types.hpp"] -BH --> TYPES -TXCPP["transaction.cpp"] --> TX -AUTHCPP["authority.cpp"] --> AUTH -BHC["block.cpp"] --> B -COCPP["chain_operations.cpp"] --> CO -``` - -**Diagram sources** -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L1-L35) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L3-L6) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L3-L6) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L3-L6) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L3-L4) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L3-L5) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L3-L4) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L3-L4) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L3-L7) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L3-L4) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [authority.cpp](file://libraries/protocol/authority.cpp#L1-L228) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L1-L448) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L3-L6) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L3-L5) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L3-L4) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L3-L4) -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L3-L6) -- [base.hpp](file://libraries/protocol/include/graphene/protocol/base.hpp#L3-L6) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L3-L7) -- [operation_util.hpp](file://libraries/protocol/include/graphene/protocol/operation_util.hpp#L1-L35) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L3-L4) - -## Performance Considerations -- Authority verification recursion depth is bounded to prevent excessive computation -- Signature minimization prunes unnecessary signatures while preserving validity -- Merkle root computation scales logarithmically with transaction count -- Validation enforces early exits on constraint violations to reduce overhead - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and diagnostics: -- Transaction missing required signatures or approvals -- Irrelevant signatures or approvals detected during verification -- Authority thresholds not met or invalid account names -- Block header signature mismatch or invalid validator - -Diagnostics: -- verify_authority throws specific exceptions for missing authorities and irrelevant inputs -- assert_unused_approvals reports unused signatures/approvals -- Authority::is_impossible helps detect unsatisfiable authorities - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L76-L222) -- [authority.cpp](file://libraries/protocol/authority.cpp#L24-L33) - -## Conclusion -The Protocol Library provides a robust foundation for blockchain operations, transactions, authorities, and blocks. Its design emphasizes strong typing, clear separation of concerns, and extensibility for governance and content features. Implementations ensure correctness through rigorous validation, cryptographic signing, and Merkle integrity checks. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Example Workflows - -#### Operation Creation and Classification -- Create operations using strongly typed structs from chain_operations.hpp -- Use is_virtual_operation and is_data_operation helpers from operations.cpp to classify operations - -**Section sources** -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L138) -- [operations.cpp](file://libraries/protocol/operations.cpp#L17-L52) - -#### Transaction Building and Signing -- Assemble operations into a transaction, set reference block and expiration -- Sign with private key and append signature; verify authority using provided getters - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L101) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L45-L92) - -#### Authority Verification -- Extract required authorities from operations and verify via sign_state -- Respect mixing constraints between regular and active/master authorities - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) - -#### Block Validation -- Compute signed block header ID and validate validator signature -- Recompute Merkle root from transaction digests and compare with block header - -**Section sources** -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L25-L35) -- [block.cpp](file://libraries/protocol/block.cpp#L14-L64) - -### Relationship Between Protocol Definitions and Chain Library Implementations -- Protocol headers define operation semantics, types, and structures -- Chain library evaluators consume operations and apply state transitions -- Protocol implementations (transaction.cpp, authority.cpp, block.cpp, chain_operations.cpp) provide runtime behavior aligned with protocol definitions - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [authority.cpp](file://libraries/protocol/authority.cpp#L1-L228) -- [block.cpp](file://libraries/protocol/block.cpp#L1-L68) -- [chain_operations.cpp](file://libraries/protocol/chain_operations.cpp#L1-L448) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Transaction Processing.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Transaction Processing.md deleted file mode 100644 index a9add893f4..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Protocol Library/Transaction Processing.md +++ /dev/null @@ -1,468 +0,0 @@ -# Transaction Processing - - -**Referenced Files in This Document** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [transaction.cpp](file://libraries/protocol/transaction.cpp) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the Transaction Processing subsystem in the VIZ C++ node. It covers transaction structure, validation, signing, multi-signature authority checks, serialization, and network transport. It also describes the relationship between transactions and blocks, and outlines the transaction lifecycle from creation to inclusion in a block. - -## Project Structure -The transaction processing logic spans several libraries: -- Protocol layer: transaction definition, signing, authority verification, and types -- Chain layer: persistence and indexing of transactions -- Network layer: message framing for transaction broadcast -- Utilities: example signing tool - -```mermaid -graph TB -subgraph "Protocol Layer" -T["transaction.hpp
signed_transaction, annotated_signed_transaction"] -TS["sign_state.hpp
sign_state"] -OP["operations.hpp
operation variants"] -AU["authority.hpp
authority"] -TY["types.hpp
chain_id, digests, keys"] -BLK["block.hpp
signed_block"] -end -subgraph "Chain Layer" -TXO["transaction_object.hpp
transaction_object, index"] -end -subgraph "Network Layer" -CM["core_messages.hpp
trx_message"] -end -subgraph "Utilities" -STU["sign_transaction.cpp"] -end -T --> TS -T --> OP -T --> TY -BLK --> T -TXO --> T -CM --> T -STU --> T -TS --> AU -``` - -**Diagram sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L235) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L12-L27) - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L1-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L1-L45) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L1-L107) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L1-L131) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L1-L115) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L1-L235) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L1-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L1-L73) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L1-L573) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L1-L54) - -## Core Components -- Transaction: Base structure with reference block fields, expiration, operation array, and extensions. Provides ID calculation, validation, and signature digest computation. -- Signed Transaction: Extends transaction with signatures and authority helpers for signing, verification, minimal signature sets, and Merkle hashing. -- Annotated Signed Transaction: Adds block number and transaction number metadata for post-inclusion reporting. -- Sign State: Multi-signature validation engine that tracks provided signatures, required authorities, recursion depth, and unused approvals/signatures filtering. -- Authority: Composite authority model supporting key weights, account delegations, thresholds, and classification. -- Operations: Static variant of supported operations; each operation contributes required authorities and validates itself. -- Types: Core cryptographic and identity types (chain ID, digests, signatures, keys). -- Block: Contains a vector of signed transactions forming the block payload. -- Transaction Object: Chain-side persisted representation for duplicate detection and expiration management. -- Network Messages: Defines the transaction message type for P2P broadcast. - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L17-L361) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L6-L107) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L235) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) - -## Architecture Overview -End-to-end flow from transaction creation to block inclusion and network propagation: - -```mermaid -sequenceDiagram -participant App as "Application" -participant Tx as "signed_transaction" -participant Sig as "sign_state" -participant Net as "Network Layer" -participant Chain as "Chain Database" -App->>Tx : "Construct transaction
set_expiration()
set_reference_block()" -App->>Tx : "Add operations
validate()" -App->>Tx : "sign(private_key, chain_id)" -Tx->>Sig : "verify_authority()
get_required_signatures()" -Sig-->>Tx : "Required keys / approvals" -Tx-->>App : "Ready to broadcast" -App->>Net : "Send trx_message" -Net-->>Peers : "Broadcast signed_transaction" -Peers->>Chain : "Receive and validate" -Chain-->>Peers : "Accept or reject" -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L45-L357) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L19-L59) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -## Detailed Component Analysis - -### Transaction Structure and Lifecycle -- Fields: - - Reference block number and prefix for replay protection - - Expiration timestamp - - Operation array (non-empty requirement enforced) - - Extensions placeholder -- Methods: - - ID calculation via digest - - Validation ensuring at least one operation and per-operation validation - - Signature digest computation for signing - - Helper setters for expiration and reference block - - Required authorities extraction from operations -- Lifecycle: - - Creation: set reference block and expiration - - Validation: validate operations and structure - - Signing: compute sig_digest and append signatures - - Broadcast: wrap in network message - - Execution: included in signed_block.transactions during block production/acceptance - - Persistence: indexed by transaction_object for duplicate detection and expiration cleanup - -```mermaid -classDiagram -class transaction { -+uint16_t ref_block_num -+uint32_t ref_block_prefix -+time_point_sec expiration -+vector~operation~ operations -+extensions_type extensions -+digest() digest_type -+id() transaction_id_type -+validate() void -+sig_digest(chain_id) digest_type -+set_expiration(time_point_sec) -+set_reference_block(block_id) -+get_required_authorities(...) -} -class signed_transaction { -+vector~signature_type~ signatures -+sign(private_key, chain_id) -+sign(private_key, chain_id) const -+get_required_signatures(...) -+verify_authority(...) -+minimize_required_signatures(...) -+get_signature_keys(chain_id) flat_set -+merkle_digest() digest_type -+clear() -} -class annotated_signed_transaction { -+transaction_id_type transaction_id -+uint32_t block_num -+uint32_t transaction_num -} -transaction <|-- signed_transaction -signed_transaction <|-- annotated_signed_transaction -``` - -**Diagram sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L17-L74) - -### Validation Rules -- Structural validation: - - Non-empty operations array - - Per-operation validation invoked during transaction.validate() -- Expiration: - - Expiration enforced by higher layers; transaction stores expiration and computes sig_digest using chain_id and transaction body -- Operation validation: - - Each operation’s validate() is called during transaction.validate() -- Authority verification: - - Extract required authorities from operations - - Enforce authority mixing rules (e.g., regular authority cannot mix with active/master in same transaction) - - Verify signatures against keys and account delegations up to recursion depth - -```mermaid -flowchart TD -Start(["validate()"]) --> CheckOps["Check operations not empty"] -CheckOps --> OpIter["For each operation"] -OpIter --> OpValidate["operation_validate(op)"] -OpValidate --> NextOp{"More operations?"} -NextOp --> |Yes| OpIter -NextOp --> |No| Done(["Validation OK"]) -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) - -### Multi-Signature Validation and Authority Checking -- sign_state tracks: - - Provided signatures keyed by public key - - Used vs unused signatures after verification - - Accounts approved by existing approvals - - Unused approvals filtered out -- Authority evaluation: - - Key weights meet threshold - - Account delegations evaluated via authority getters - - Recursion depth controlled by max_recursion -- Verification modes: - - Regular authority mode enforces stricter separation - - Active/master authority modes combine delegated checks -- Helpers: - - get_required_signatures(): determines minimal keys required given available keys - - minimize_required_signatures(): removes redundant signatures while preserving validity - - verify_authority(): end-to-end authority verification - -```mermaid -classDiagram -class sign_state { -+authority_getter get_active -+flat_set~public_key_type~ available_keys -+flat_map~public_key_type,bool~ provided_signatures -+vector~public_key_type~ unused_signatures -+vector~public_key_type~ used_signatures -+flat_map~account_name_type,bool~ approved_by -+uint32_t max_recursion -+signed_by(public_key_type) bool -+check_authority(account_name_type) bool -+check_authority(authority, depth) bool -+remove_unused_signatures() bool -+filter_unused_approvals() bool -} -``` - -**Diagram sources** -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) - -**Section sources** -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L6-L107) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L240-L357) - -### Serialization and Network Transmission -- Transaction serialization: - - Digest computed by raw packing of the transaction - - Signature digest computed by raw packing of chain_id followed by transaction - - Merkle digest for signed transactions computed by raw packing the signed transaction -- Network messages: - - trx_message carries a signed_transaction for P2P broadcast - - Core message type for transactions is defined and reflected -- Block integration: - - signed_block contains a vector of signed_transaction - -```mermaid -sequenceDiagram -participant Tx as "signed_transaction" -participant Net as "trx_message" -participant Peer as "Peer Nodes" -Tx->>Tx : "sig_digest(chain_id)" -Tx->>Tx : "merkle_digest()" -Tx-->>Net : "Wrap in trx_message" -Net-->>Peer : "Transmit" -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L11-L28) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L11-L28) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -### Relationship Between Transactions and Blocks -- A block is a signed container holding a sequence of signed transactions. -- Each block maintains a Merkle root derived from its transactions. -- On acceptance, transactions become part of the blockchain state transitions. - -```mermaid -classDiagram -class signed_block { -+vector~signed_transaction~ transactions -+calculate_merkle_root() checksum_type -} -signed_block --> "1..*" signed_transaction : "contains" -``` - -**Diagram sources** -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -**Section sources** -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -### Transaction Lifecycle Management -- Duplicate detection and expiration: - - transaction_object persists packed transactions with expiration and id - - Index supports lookup by id and expiration ordering - - Expiration-based cleanup occurs during block processing -- Lifecycle stages: - - Constructed locally - - Validated and signed - - Broadcast via P2P - - Verified by peers - - Included in a block - - Indexed for duplicate detection and eventual expiration removal - -```mermaid -stateDiagram-v2 -[*] --> Created -Created --> Validated : "validate()" -Validated --> Signed : "sign(...)" -Signed --> Broadcast : "trx_message" -Broadcast --> Accepted : "peer verification" -Accepted --> Included : "included in block" -Included --> Indexed : "persist via transaction_object" -Indexed --> Expired : "expiration passes" -Expired --> [*] -``` - -**Diagram sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L6-L73) - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [transaction_object.cpp](file://libraries/chain/transaction_object.cpp#L6-L73) - -## Dependency Analysis -Key dependencies among transaction processing components: - -```mermaid -graph LR -OP["operations.hpp"] --> TRX["transaction.hpp"] -AU["authority.hpp"] --> TRX -TY["types.hpp"] --> TRX -TRX --> TRXC["transaction.cpp"] -TRXC --> SS["sign_state.hpp"] -SS --> SSC["sign_state.cpp"] -TRX --> BLK["block.hpp"] -TRX --> TXO["transaction_object.hpp"] -TRX --> CM["core_messages.hpp"] -TRX --> STU["sign_transaction.cpp"] -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L235) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L1-L107) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L12-L27) - -**Section sources** -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L136) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [sign_state.hpp](file://libraries/protocol/include/graphene/protocol/sign_state.hpp#L10-L42) -- [sign_state.cpp](file://libraries/protocol/sign_state.cpp#L1-L107) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L131) -- [authority.hpp](file://libraries/protocol/include/graphene/protocol/authority.hpp#L9-L57) -- [types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L235) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L56) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L12-L27) - -## Performance Considerations -- Signature verification cost scales with number of operations and authorities; minimize required signatures using minimize_required_signatures. -- Authority recursion depth limits prevent deep traversal; tune max_recursion appropriately. -- Serialization overhead is linear in transaction size; keep operations minimal and avoid unnecessary extensions. -- Network bandwidth: broadcast only validated transactions; leverage peer inventory messages to reduce redundant transmissions. - -## Troubleshooting Guide -Common validation and signing issues: -- Missing or extra signatures: - - verify_authority reports unused signatures and missing authorities - - assert_unused_approvals filters and reports unused approvals -- Authority mismatches: - - Mixing regular authority with active/master in same transaction is disallowed - - Missing required active/master/regular authorities cause assertion failures -- Duplicate signatures: - - get_signature_keys enforces uniqueness of signatures -- Expiration: - - Ensure expiration is set and within acceptable range; transactions past expiration are rejected - -Operational tips: -- Use the signing utility to compute digest and sig_digest for debugging -- Inspect transaction id and block_num/transaction_num in annotated_signed_transaction for post-inclusion tracing - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L76-L92) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L225-L237) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L12-L27) - -## Conclusion -The transaction processing subsystem integrates a robust transaction model, strong authority verification, efficient serialization, and clear lifecycle management. By leveraging signed_transaction helpers, sign_state, and transaction_object, the system ensures secure, deterministic, and efficient transaction handling across the network and into the blockchain. - -## Appendices - -### Example Workflows - -- Constructing and signing a transaction: - - Set reference block and expiration - - Add operations and validate - - Compute sig_digest and append signatures - - Broadcast via trx_message - -- Authority minimization: - - Determine required signatures given available keys - - Minimize signatures while preserving authority coverage - -- Network transmission: - - Wrap signed_transaction in trx_message - - Send to peers; handle inventory and fetch requests - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L58-L65) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L240-L357) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp#L99-L110) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L28-L54) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Wallet Library.md b/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Wallet Library.md deleted file mode 100644 index 2be020cd50..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Core Libraries/Wallet Library.md +++ /dev/null @@ -1,680 +0,0 @@ -# Wallet Library - - -**Referenced Files in This Document** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp) -- [api_documentation.hpp](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp) -- [reflect_util.hpp](file://libraries/wallet/include/graphene/wallet/reflect_util.hpp) -- [wallet.cpp](file://libraries/wallet/wallet.cpp) -- [api_documentation_standin.cpp](file://libraries/wallet/api_documentation_standin.cpp) -- [generate_api_documentation.pl](file://libraries/wallet/generate_api_documentation.pl) -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt) -- [main.cpp](file://programs/cli_wallet/main.cpp) -- [content_api_object.hpp](file://libraries/api/include/graphene/api/content_api_object.hpp) -- [committee_api_object.hpp](file://libraries/api/include/graphene/api/committee_api_object.hpp) -- [invite_api_object.hpp](file://libraries/api/include/graphene/api/invite_api_object.hpp) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp) -- [paid_subscription_api_object.hpp](file://libraries/api/include/graphene/api/paid_subscription_api_object.hpp) - - -## Update Summary -**Changes Made** -- Added comprehensive documentation for the new VIZ DNS nameserver helper system -- Documented DNS metadata validation functions, data structures, and transaction operations -- Updated wallet API surface to include DNS management operations -- Enhanced transaction builder API with DNS record management capabilities -- Added detailed coverage of DNS record validation, metadata creation, and extraction functions - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [DNS Nameserver Helper System](#dns-nameserver-helper-system) -7. [Advanced Wallet Operations](#advanced-wallet-operations) -8. [Dependency Analysis](#dependency-analysis) -9. [Performance Considerations](#performance-considerations) -10. [Troubleshooting Guide](#troubleshooting-guide) -11. [Conclusion](#conclusion) -12. [Appendices](#appendices) - -## Introduction -This document describes the Wallet Library that provides transaction signing capabilities and wallet management functionality for the blockchain node. It covers: -- Wallet state management, key storage, and transaction building -- Remote node integration via JSON-RPC-like APIs -- API documentation system for dynamic API generation and type introspection -- Encryption, key derivation, and security best practices -- Transaction construction, signature aggregation, and broadcast mechanisms -- Examples of wallet creation, key import/export, transaction signing workflows, and remote node integration -- Backup strategies, recovery procedures, and security considerations for key management -- **Updated**: Comprehensive coverage of the VIZ DNS nameserver helper system for managing DNS records through blockchain metadata - -## Project Structure -The Wallet Library is organized around a public API header and an implementation module, with supporting utilities for documentation and reflection. The CLI wallet demonstrates integration with a remote node. - -```mermaid -graph TB -subgraph "Wallet Library" -H["wallet.hpp"] -R["remote_node_api.hpp"] -A["api_documentation.hpp"] -F["reflect_util.hpp"] -C["wallet.cpp"] -S["api_documentation_standin.cpp"] -P["generate_api_documentation.pl"] -M["CMakeLists.txt"] -end -subgraph "CLI Wallet" -CLI["main.cpp"] -end -subgraph "Advanced Features" -CO["content_api_object.hpp"] -CM["committee_api_object.hpp"] -IO["invite_api_object.hpp"] -PSO["paid_subscription_objects.hpp"] -PSAPI["paid_subscription_api_object.hpp"] -DNS["DNS Nameserver Helper"] -end -H --> C -R --> C -A --> C -F --> C -S --> A -P --> A -M --> C -CLI --> H -CLI --> R -H --> CO -H --> CM -H --> IO -H --> PSO -H --> PSAPI -H --> DNS -``` - -**Diagram sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1-L1569) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L1-L295) -- [api_documentation.hpp](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L1-L79) -- [reflect_util.hpp](file://libraries/wallet/include/graphene/wallet/reflect_util.hpp#L1-L91) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1-L2887) -- [api_documentation_standin.cpp](file://libraries/wallet/api_documentation_standin.cpp#L1-L64) -- [generate_api_documentation.pl](file://libraries/wallet/generate_api_documentation.pl#L1-L180) -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L1-L85) -- [main.cpp](file://programs/cli_wallet/main.cpp#L1-L340) -- [content_api_object.hpp](file://libraries/api/include/graphene/api/content_api_object.hpp#L1-L71) -- [committee_api_object.hpp](file://libraries/api/include/graphene/api/committee_api_object.hpp#L1-L63) -- [invite_api_object.hpp](file://libraries/api/include/graphene/api/invite_api_object.hpp#L1-L39) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L1-L121) -- [paid_subscription_api_object.hpp](file://libraries/api/include/graphene/api/paid_subscription_api_object.hpp#L1-L63) - -**Section sources** -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L26-L85) -- [main.cpp](file://programs/cli_wallet/main.cpp#L166-L175) - -## Core Components -- wallet_api: Public interface for wallet operations, including key management, account queries, transaction building, signing, and broadcasting. -- wallet_api_impl: Internal implementation managing remote connections, local key storage, transaction assembly, and cryptographic operations. -- remote_node_api: Dummy classes and FC_API declarations that define the remote API surface for interacting with plugins on a remote node. -- api_documentation: Runtime or generated documentation container for wallet API methods. -- reflect_util: Utilities for dynamic operation name-to-ID mapping and variant conversion. -- CLI integration: Demonstrates connecting to a remote node, registering the wallet API, and exposing it over WebSocket/HTTP/TLS endpoints. -- **Updated**: DNS nameserver helper system for managing DNS records through blockchain metadata, including validation functions, data structures, and transaction operations. - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1569) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L183-L2887) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L44-L295) -- [api_documentation.hpp](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L43-L79) -- [reflect_util.hpp](file://libraries/wallet/include/graphene/wallet/reflect_util.hpp#L9-L91) -- [main.cpp](file://programs/cli_wallet/main.cpp#L166-L226) - -## Architecture Overview -The wallet connects to a remote node via fc::api connections to multiple plugin APIs. It maintains an in-memory keystore and uses remote APIs to fetch blockchain state, compute required signatures, and broadcast transactions. - -```mermaid -sequenceDiagram -participant CLI as "CLI Wallet" -participant Wallet as "wallet_api" -participant Impl as "wallet_api_impl" -participant RemoteDB as "remote_database_api" -participant RemoteNet as "remote_network_broadcast_api" -CLI->>Wallet : "unlock/set_password/import_key" -CLI->>Wallet : "begin_builder_transaction/add_operation_to_builder_transaction" -Wallet->>Impl : "preview_builder_transaction/sign_builder_transaction" -Impl->>RemoteDB : "get_accounts/get_dynamic_global_properties" -Impl->>Impl : "minimize_required_signatures" -Impl->>Impl : "sign(tx)" -Impl->>RemoteNet : "broadcast_transaction_synchronous(tx)" -RemoteNet-->>Impl : "annotated_signed_transaction" -Impl-->>Wallet : "signed transaction" -Wallet-->>CLI : "result" -``` - -**Diagram sources** -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L673-L820) -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L44-L252) -- [main.cpp](file://programs/cli_wallet/main.cpp#L177-L226) - -## Detailed Component Analysis - -### Wallet API Surface (wallet_api) -The public API exposes: -- Wallet lifecycle: is_new, is_locked, lock, unlock, set_password, load_wallet_file, save_wallet_file, quit -- Key management: import_key, list_keys, get_private_key, get_private_key_from_password, normalize_brain_key -- Queries: info, database_info, about, list_my_accounts, list_accounts, list_witnesses, get_account, get_block, get_ops_in_block, get_account_history, get_withdraw_routes -- Transactions: begin_builder_transaction, add_operation_to_builder_transaction, replace_operation_in_builder_transaction, preview_builder_transaction, sign_builder_transaction, propose_builder_transaction, remove_builder_transaction, approve_proposal, get_proposed_transactions, get_prototype_operation, serialize_transaction, sign_transaction -- Operations: create_account, create_account_with_keys, update_account, update_account_auth_key, update_account_auth_account, update_account_auth_threshold, update_account_meta, update_account_memo_key, delegate_vesting_shares, update_witness, update_chain_properties, versioned_update_chain_properties, set_voting_proxy, vote_for_witness, transfer, escrow_transfer, escrow_approve, escrow_dispute, escrow_release, transfer_to_vesting, withdraw_vesting, set_withdraw_vesting_route, post_content, vote, set_transaction_expiration, request_account_recovery, recover_account, change_recovery_account, get_master_history, get_encrypted_memo, decrypt_memo, get_inbox, get_outbox, follow - -**Updated**: Advanced operations including custom operations broadcasting, content management, committee system operations, invite functionality, reward systems, subscription management, account marketplace features, and DNS nameserver helper operations. - -Security and privacy helpers: -- Memo encryption/decryption and safety checks -- Password-based key derivation for account roles -- Brain key suggestion and normalization - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L104-L1569) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1062-L2887) - -### Wallet Implementation Internals (wallet_api_impl) -Key responsibilities: -- Remote API connections to database_api, operation_history, account_history, social_network, network_broadcast_api, follow, private_message, account_by_key, witness_api -- Local keystore management: _keys, _checksum, encrypt_keys/decrypt_keys -- Transaction builder: _builder_transactions -- Prototype operations for dynamic operation creation -- Result formatters for CLI display -- Chain ID and expiration handling - -```mermaid -classDiagram -class wallet_api { -+help() -+gethelp(method) -+info() -+database_info() -+about() -+list_my_accounts() -+list_accounts(lowerbound, limit) -+list_witnesses(lowerbound, limit) -+get_account(name) -+get_block(num) -+get_ops_in_block(num, only_virtual) -+get_account_history(account, from, limit) -+get_withdraw_routes(account, type) -+begin_builder_transaction() -+add_operation_to_builder_transaction(handle, op) -+replace_operation_in_builder_transaction(handle, op_index, op) -+preview_builder_transaction(handle) -+sign_builder_transaction(handle, broadcast) -+propose_builder_transaction(handle, author, title, memo, expiration, review, broadcast) -+remove_builder_transaction(handle) -+approve_proposal(author, title, delta, broadcast) -+get_proposed_transactions(account, from, limit) -+get_prototype_operation(operation_name) -+serialize_transaction(tx) -+sign_transaction(tx, broadcast) -+create_account(...) -+create_account_with_keys(...) -+update_account(...) -+update_account_auth_key(...) -+update_account_auth_account(...) -+update_account_auth_threshold(...) -+update_account_meta(...) -+update_account_memo_key(...) -+delegate_vesting_shares(...) -+update_witness(...) -+update_chain_properties(...) -+versioned_update_chain_properties(...) -+set_voting_proxy(...) -+vote_for_witness(...) -+transfer(...) -+escrow_transfer(...) -+escrow_approve(...) -+escrow_dispute(...) -+escrow_release(...) -+transfer_to_vesting(...) -+withdraw_vesting(...) -+set_withdraw_vesting_route(...) -+post_content(...) -+vote(...) -+set_transaction_expiration(seconds) -+request_account_recovery(...) -+recover_account(...) -+change_recovery_account(...) -+get_master_history(account) -+get_encrypted_memo(from, to, memo) -+decrypt_memo(encrypted_memo) -+get_inbox(to, newest, limit, offset) -+get_outbox(from, newest, limit, offset) -+follow(follower, following, what, broadcast) -+get_result_formatters() -+lock() -+unlock(password) -+set_password(password) -+import_key(wif_key) -+list_keys() -+get_private_key(pubkey) -+get_private_key_from_password(account, role, password) -+normalize_brain_key(s) -+suggest_brain_key() -+serialize_transaction(tx) -+get_transaction(tx_id) -+get_active_witnesses() -+get_wallet_filename() -+set_wallet_filename(filename) -+copy_wallet_file(destination) -+quit() -} -class wallet_api_impl { --method_documentation : api_documentation --_remote_database_api : fc : : api --_remote_operation_history : fc : : api --_remote_account_history : fc : : api --_remote_social_network : fc : : api --_remote_network_broadcast_api : fc : : api --_remote_follow : fc : : api --_remote_private_message : fc : : api --_remote_account_by_key : fc : : api --_remote_witness_api : fc : : api --_keys : map --_checksum : fc : : sha512 --_builder_transactions : map --_prototype_ops : flat_map --_tx_expiration_seconds : uint32_t -+info() -+database_info() -+about() -+begin_builder_transaction() -+add_operation_to_builder_transaction(...) -+replace_operation_in_builder_transaction(...) -+preview_builder_transaction(handle) -+sign_builder_transaction(handle, broadcast) -+propose_builder_transaction(...) -+remove_builder_transaction(handle) -+approve_proposal(...) -+get_proposed_transactions(...) -+get_account(name) -+get_prototype_operation(name) -+encrypt_keys() -+copy_wallet_file(destination) -+is_locked() -+sign_transaction(tx, broadcast) -+get_result_formatters() -} -wallet_api --> wallet_api_impl : "owns" -``` - -**Diagram sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1569) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L183-L2887) - -**Section sources** -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L183-L2887) - -### Remote Node API Contracts (remote_node_api) -Dummy classes define the remote API surface for each plugin. FC_API macros declare the method names and signatures exposed to the wallet. - -- remote_database_api: get_block, get_block_header, get_config, get_dynamic_global_properties, get_chain_properties, get_hardfork_version, get_next_scheduled_hardfork, lookup_account_names, lookup_accounts, get_account_count, get_master_history, get_recovery_request, get_escrow, get_withdraw_routes, get_transaction_hex, get_required_signatures, get_potential_signatures, verify_authority, verify_account_authority, get_accounts, get_database_info, get_proposed_transactions -- remote_operation_history: get_ops_in_block, get_transaction -- remote_account_history: get_account_history -- remote_social_network: get_trending_tags, get_tags_used_by_author, get_active_votes, get_account_votes, get_content, get_content_replies, get_discussions_by_* variants, get_replies_by_last_update -- remote_network_broadcast_api: broadcast_transaction, broadcast_transaction_synchronous, broadcast_block -- remote_follow: get_followers, get_following, get_follow_count, get_feed_entries, get_feed, get_blog_entries, get_blog, get_reblogged_by, get_blog_authors -- remote_private_message: get_inbox, get_outbox -- remote_account_by_key: get_key_references -- remote_witness_api: get_active_witnesses, get_witness_schedule, get_witnesses, get_witnesses_by_vote, get_witness_by_account, lookup_witness_accounts, get_witness_count - -These are consumed by wallet_api_impl to fetch blockchain state and broadcast transactions. - -**Section sources** -- [remote_node_api.hpp](file://libraries/wallet/include/graphene/wallet/remote_node_api.hpp#L44-L295) - -### API Documentation System -There are two modes: -- Generated documentation: Uses Doxygen and Perl to parse comments and generate a static api_documentation.cpp with method descriptions. -- Runtime reflection: When Doxygen/Perl are unavailable, api_documentation_standin.cpp builds descriptions by reflecting the wallet API at runtime. - -The api_documentation class stores method_name -> brief/detailed descriptions and exposes get_brief_description, get_detailed_description, and get_method_names. - -```mermaid -flowchart TD -Start(["Build"]) --> CheckTools{"Doxygen + Perl available?"} -CheckTools --> |Yes| RunPerl["Run generate_api_documentation.pl
Generate api_documentation.cpp"] -CheckTools --> |No| UseStandin["Copy api_documentation_standin.cpp"] -RunPerl --> Compile["Compile api_documentation.cpp"] -UseStandin --> Compile -Compile --> Runtime["At runtime, api_documentation reads method names"] -Runtime --> Help["help()/gethelp() return formatted descriptions"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L6-L24) -- [generate_api_documentation.pl](file://libraries/wallet/generate_api_documentation.pl#L1-L180) -- [api_documentation_standin.cpp](file://libraries/wallet/api_documentation_standin.cpp#L1-L64) -- [api_documentation.hpp](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L43-L79) - -**Section sources** -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L6-L24) -- [generate_api_documentation.pl](file://libraries/wallet/generate_api_documentation.pl#L34-L88) -- [api_documentation_standin.cpp](file://libraries/wallet/api_documentation_standin.cpp#L54-L60) -- [api_documentation.hpp](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L43-L79) - -### Reflection Utilities (reflect_util) -Provides: -- Static variant mapping for operations to simplify parsing operations by name -- Helper visitors to populate name-to-which maps and reconstruct variants from which() - -This enables dynamic operation creation and parsing without hardcoding operation IDs. - -**Section sources** -- [reflect_util.hpp](file://libraries/wallet/include/graphene/wallet/reflect_util.hpp#L9-L91) - -### CLI Wallet Integration -The CLI wallet: -- Connects to a remote node via WebSocket -- Creates a wallet_api instance bound to the connection -- Registers the wallet API over WebSocket/HTTP/TLS endpoints -- Supports non-interactive command execution -- Handles reconnection on server disconnect - -```mermaid -sequenceDiagram -participant User as "User" -participant CLI as "cli_wallet main.cpp" -participant WS as "WebSocket Client" -participant Wallet as "wallet_api" -participant RPC as "RPC Server" -User->>CLI : "Start cli_wallet" -CLI->>WS : "connect(ws_server)" -CLI->>Wallet : "create wallet_api(...)" -CLI->>Wallet : "load_wallet_file()" -CLI->>RPC : "register_api(wallet_api)" -RPC-->>User : "Expose JSON-RPC methods" -WS-->>CLI : "Closed event" -CLI->>WS : "Reconnect" -``` - -**Diagram sources** -- [main.cpp](file://programs/cli_wallet/main.cpp#L166-L226) - -**Section sources** -- [main.cpp](file://programs/cli_wallet/main.cpp#L166-L226) - -## DNS Nameserver Helper System - -### DNS Data Structures and Constants -The wallet library introduces a comprehensive DNS nameserver helper system with the following core data structures: - -- **ns_record**: Represents a single DNS record tuple [type, value] with string type ("A" or "TXT") and string value (IPv4 address or TXT content like "ssl=") -- **ns_metadata_options**: Options for creating NS metadata including vector of A records, optional SSL hash, and TTL value -- **ns_summary**: Summary of NS data extracted from account metadata with A records, SSL hash, TTL, and presence flag -- **ns_validation_result**: Result of NS metadata validation with boolean validity flag and error messages array - -DNS helper constants: -- NS_DEFAULT_TTL: Default TTL of 28800 seconds (8 hours) -- NS_MAX_TXT_LENGTH: Maximum TXT record length per NS standard (256 characters) -- NS_SHA256_HEX_LENGTH: SHA256 hash length in hex characters (64 characters) - -### DNS Validation Functions -The system provides comprehensive validation functions: - -- **ns_validate_ipv4()**: Validates IPv4 address format with proper octet ranges (0-255) and rejects invalid formats -- **ns_validate_sha256_hash()**: Validates SHA256 hash format requiring exactly 64 hexadecimal characters -- **ns_validate_ttl()**: Ensures TTL values are positive integers -- **ns_validate_ssl_txt_record()**: Validates SSL TXT record format "ssl=<64-char-hex-hash>" -- **ns_validate_metadata()**: Performs complete validation of NS metadata with comprehensive error reporting - -### DNS Metadata Operations -The system offers complete DNS record management through blockchain metadata: - -- **ns_create_metadata()**: Creates NS metadata JSON string from options, building ns arrays with A and TXT records -- **ns_get_summary()**: Extracts complete NS summary from account metadata, parsing A records and SSL hashes -- **ns_extract_a_records()**: Extracts IPv4 addresses from account metadata -- **ns_extract_ssl_hash()**: Extracts SSL certificate hash from TXT records -- **ns_extract_ttl()**: Extracts TTL value from account metadata -- **ns_set_records()**: Sets NS records for an account, merging with existing metadata while preserving other fields -- **ns_remove_records()**: Removes NS records from account metadata while preserving other metadata fields - -### Transaction Integration -DNS operations integrate seamlessly with the wallet's transaction system: -- All DNS operations return signed transactions that can be broadcast -- Uses account_metadata_operation for blockchain updates -- Maintains backward compatibility with existing account metadata -- Supports both validation-only operations and transaction creation - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L24-L62) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1311-L1420) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L2578-L2887) - -## Advanced Wallet Operations - -### Custom Operations Broadcasting -The wallet now supports broadcasting custom operations with flexible authorization requirements: - -- **custom()**: Broadcast custom operations with specified active and regular authority requirements -- Supports arbitrary JSON data payloads for custom business logic -- Enables decentralized application development on the blockchain - -### Content Management System -Comprehensive content creation, modification, and reward distribution capabilities: - -- **post_content()**: Create or update content with title, body, metadata, and curation settings -- **delete_content()**: Remove content with proper authorization validation -- **vote()**: Vote on content with weighted influence affecting reward distribution -- **Content API Objects**: Rich content metadata including voting statistics, payout information, and beneficiary configurations - -### Committee System Operations -Decentralized governance and funding mechanisms: - -- **committee_worker_create_request()**: Create funding requests for community projects -- **committee_worker_cancel_request()**: Cancel pending funding requests -- **committee_vote_request()**: Vote on funding requests with percentage-based weighting -- **Committee API Objects**: Request tracking, voting states, and funding distribution details - -### Invite System Operations -Account creation and fund transfer facilitation: - -- **create_invite()**: Generate invites with configurable balances and keys -- **claim_invite_balance()**: Claim funds from successful invites -- **invite_registration()**: Register new accounts using invite secrets -- **use_invite_balance()**: Transfer invite balance to vesting shares -- **Invite API Objects**: Invite lifecycle tracking, claiming states, and balance management - -### Reward Distribution System -Energy-based and fixed reward mechanisms: - -- **award()**: Energy-based rewards with customizable energy allocation and beneficiaries -- **fixed_award()**: Fixed amount rewards with maximum energy constraints -- **Beneficiary Routing**: Configurable reward distribution to multiple accounts -- **Virtual Operations**: Automatic reward processing and distribution tracking - -### Paid Subscription Management -Subscription-based content monetization: - -- **set_paid_subscription()**: Create subscription plans with pricing tiers -- **paid_subscribe()**: Subscribe to content creators with auto-renewal options -- **Subscription API Objects**: Active subscriber tracking, renewal scheduling, and revenue analytics -- **Marketplace Integration**: Creator earnings and subscriber management - -### Account Marketplace Operations -Secondary market for digital assets: - -- **set_account_price()**: Put accounts up for sale with listing parameters -- **set_subaccount_price()**: Offer subaccount creation services -- **buy_account()**: Purchase accounts with key replacement and delegation handling -- **target_account_sale()**: Direct sales to specific buyers with escrow protection -- **Auction Integration**: Bid management and sale completion tracking - -**Updated**: DNS nameserver helper operations including validation functions, metadata creation, record extraction, and transaction management for comprehensive DNS record management on the blockchain. - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L958-L1569) -- [content_api_object.hpp](file://libraries/api/include/graphene/api/content_api_object.hpp#L12-L58) -- [committee_api_object.hpp](file://libraries/api/include/graphene/api/committee_api_object.hpp#L23-L51) -- [invite_api_object.hpp](file://libraries/api/include/graphene/api/invite_api_object.hpp#L13-L30) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L75) -- [paid_subscription_api_object.hpp](file://libraries/api/include/graphene/api/paid_subscription_api_object.hpp#L32-L51) - -## Dependency Analysis -The wallet library depends on: -- Protocol and chain types for operations and signing -- Plugins for remote APIs (database_api, operation_history, account_history, social_network, tags, network_broadcast_api, follow, private_message, account_by_key, witness_api) -- Utilities for key conversion and word lists -- fc for networking, RPC, crypto, and containers -- **Updated**: Advanced feature dependencies for content management, committee operations, invite system, reward distribution, paid subscriptions, marketplace functionality, and DNS nameserver helper system. - -```mermaid -graph LR -Wallet["graphene_wallet"] --> Protocol["graphene_protocol"] -Wallet --> Chain["graphene_chain"] -Wallet --> Network["graphene_network"] -Wallet --> DB["graphene::database_api"] -Wallet --> OpHist["graphene::operation_history"] -Wallet --> AccHist["graphene::account_history"] -Wallet --> Social["graphene::social_network"] -Wallet --> Tags["graphene::tags"] -Wallet --> NetBCast["graphene::network_broadcast_api"] -Wallet --> Follow["graphene::follow"] -Wallet --> PM["graphene::private_message"] -Wallet --> AccByKey["graphene::account_by_key"] -Wallet --> validator["graphene::witness_api"] -Wallet --> Util["graphene_utilities"] -Wallet --> FC["fc"] -Wallet --> ContentAPI["content_api_object.hpp"] -Wallet --> CommitteeAPI["committee_api_object.hpp"] -Wallet --> InviteAPI["invite_api_object.hpp"] -Wallet --> PaidSubAPI["paid_subscription_api_object.hpp"] -Wallet --> DNSHelper["DNS Nameserver Helper"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L50-L70) -- [content_api_object.hpp](file://libraries/api/include/graphene/api/content_api_object.hpp#L1-L71) -- [committee_api_object.hpp](file://libraries/api/include/graphene/api/committee_api_object.hpp#L1-L63) -- [invite_api_object.hpp](file://libraries/api/include/graphene/api/invite_api_object.hpp#L1-L39) -- [paid_subscription_api_object.hpp](file://libraries/api/include/graphene/api/paid_subscription_api_object.hpp#L1-L63) - -**Section sources** -- [CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L50-L70) - -## Performance Considerations -- Minimal caching: The wallet assumes a high-bandwidth, low-latency connection to the node and performs minimal caching, optimizing for responsiveness over persistence. -- Transaction signing minimization: The implementation computes minimal required signatures to reduce signing overhead. -- Batched operations: Use builder transactions to assemble multiple operations efficiently before signing and broadcasting. -- Avoid unnecessary remote calls: Reuse cached account data where possible and batch queries. -- **Updated**: Advanced feature performance considerations including content indexing, committee voting calculations, subscription billing cycles, marketplace transaction optimization, and DNS metadata parsing efficiency. - -## Troubleshooting Guide -Common issues and remedies: -- Wallet locked/uninitialized: Use set_password to initialize a new wallet, then unlock with the password. -- Importing keys: Ensure the WIF key is valid; invalid keys will cause errors during import. -- Broadcasting failures: Verify network connectivity to the remote node and that the transaction is valid and within expiration. -- Memo decryption failures: Ensure the correct private key is present in the wallet for memo decryption. -- Authority errors: When updating authorities, ensure thresholds and weights are valid; impossible authorities can cause assertion failures. -- **Updated**: Advanced feature troubleshooting including content validation errors, committee request processing failures, invite claim issues, subscription payment problems, marketplace transaction conflicts, and DNS metadata validation failures. - -**Section sources** -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1203-L1232) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1006-L1023) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L807-L820) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1995-L2025) - -## Conclusion -The Wallet Library provides a robust, extensible framework for managing keys, constructing transactions, and interacting with a remote blockchain node. Its modular design, strong security practices, and dynamic API documentation system make it suitable for both interactive and automated use cases. **Updated**: The library now supports comprehensive advanced blockchain interaction capabilities including custom operations broadcasting, content management, committee governance, invite systems, reward distribution, subscription management, marketplace functionality, and a complete DNS nameserver helper system for managing DNS records through blockchain metadata, making it a complete solution for modern decentralized applications. - -## Appendices - -### Security Best Practices -- Protect wallet files: Use strong passwords and restrict filesystem permissions. -- Back up wallets: Regularly copy wallet files and verify backups. -- Avoid exposing private keys: Never paste private keys into untrusted terminals or logs. -- Validate memos: Use built-in memo safety checks to prevent accidental exposure of private keys. -- Use remote signing: Prefer offline signing workflows when possible. -- **Updated**: Advanced security considerations including content moderation, committee voting integrity, invite system protection, subscription billing security, marketplace transaction validation, and DNS metadata integrity verification. - -**Section sources** -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L243-L270) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1203-L1232) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1740-L1794) - -### Example Workflows - -- Wallet creation and initialization - - Create a new wallet file and set a password - - Unlock the wallet to perform operations - - Import keys or generate brain keys for new accounts - -- Key import/export - - Import WIF keys into the wallet - - List keys and export them securely - -- Transaction signing workflow - - Build a transaction using the builder API - - Preview and sign the transaction - - Optionally propose or broadcast the transaction - -- Remote node integration - - Connect to a remote node via WebSocket - - Register the wallet API over RPC endpoints - - Execute commands and receive formatted results - -- **Updated**: Advanced workflow examples including content creation with voting rewards, committee participation for project funding, invite-based account creation, subscription management, marketplace transactions, and DNS record management through blockchain metadata. - -**Section sources** -- [main.cpp](file://programs/cli_wallet/main.cpp#L166-L226) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L132-L179) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L1062-L1123) - -### Advanced Feature Usage Examples - -#### Content Management Workflow -1. Create content with post_content() operation -2. Monitor voting and reward accumulation -3. Update content with subsequent posts -4. Handle content deletion with proper authorization - -#### Committee Participation Workflow -1. Review active funding requests -2. Evaluate project proposals and technical merit -3. Cast weighted votes on funding requests -4. Track funding distribution and project completion - -#### Invite System Workflow -1. Generate invites with appropriate funding -2. Share invite secrets with intended recipients -3. Process successful claims and registrations -4. Manage invite lifecycle and expiration - -#### Subscription Management Workflow -1. Create subscription plans with pricing tiers -2. Monitor active subscribers and renewals -3. Process automatic payments and revenue distribution -4. Handle subscription cancellations and refunds - -#### Marketplace Operations Workflow -1. List accounts or subaccounts for sale -2. Monitor bidding activity and auction progress -3. Execute sales with proper key replacement -4. Handle target sales to specific buyers - -#### DNS Nameserver Helper Workflow -1. Validate DNS metadata using ns_validate_metadata() before creation -2. Create NS metadata with ns_create_metadata() including A records and SSL hashes -3. Extract existing DNS records using ns_get_summary() for verification -4. Set DNS records with ns_set_records() to update account metadata -5. Remove DNS records with ns_remove_records() when needed -6. Monitor TTL values and record validity through extraction functions - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L958-L1569) -- [content_object.hpp](file://libraries/chain/include/graphene/chain/content_object.hpp#L56-L114) -- [committee_api_object.hpp](file://libraries/api/include/graphene/api/committee_api_object.hpp#L23-L51) -- [invite_api_object.hpp](file://libraries/api/include/graphene/api/invite_api_object.hpp#L13-L30) -- [paid_subscription_objects.hpp](file://libraries/chain/include/graphene/chain/paid_subscription_objects.hpp#L15-L75) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1311-L1420) -- [wallet.cpp](file://libraries/wallet/wallet.cpp#L2578-L2887) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/API Request Processing.md b/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/API Request Processing.md deleted file mode 100644 index 9dcdb8f062..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/API Request Processing.md +++ /dev/null @@ -1,319 +0,0 @@ -# API Request Processing - - -**Referenced Files in This Document** -- [main.cpp](file://programs/vizd/main.cpp) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp) -- [api.cpp](file://plugins/database_api/api.cpp) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp) -- [account_api_object.cpp](file://libraries/api/account_api_object.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains how API requests are processed from receipt to response generation in the node. It covers JSON-RPC request parsing, method routing, parameter validation, database query execution, result formatting, and the webserver plugin’s role in handling HTTP and WebSocket traffic. It also documents authentication support, error handling strategies, response formatting standards, and performance monitoring techniques. - -## Project Structure -The API request lifecycle spans several components: -- Application entry point initializes plugins and starts the node. -- Webserver plugin handles HTTP and WebSocket connections and forwards JSON-RPC payloads to the JSON-RPC plugin. -- JSON-RPC plugin parses requests, validates method signatures, routes to registered APIs, executes handlers, and serializes responses. -- Database API plugin implements core blockchain queries and returns formatted results. -- Authentication utility plugin provides signature verification APIs used by clients and services. - -```mermaid -graph TB -Client["Client"] -WS["WebSocket Endpoint
HTTP Endpoint"] -Web["Webserver Plugin"] -RPC["JSON-RPC Plugin"] -DB["Database API Plugin"] -Auth["Auth Utility Plugin"] -Client --> WS -WS --> Web -Web --> RPC -RPC --> DB -RPC --> Auth -``` - -**Diagram sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L402-L423) -- [api.cpp](file://plugins/database_api/api.cpp#L218-L223) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L86-L90) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L266-L331) - -## Core Components -- Webserver plugin: Accepts HTTP and WebSocket connections, defers HTTP responses, posts work to a thread pool, and delegates JSON-RPC payloads to the JSON-RPC plugin. -- JSON-RPC plugin: Parses JSON-RPC 2.0 requests, validates method names and parameters, routes to registered API methods, and serializes responses with proper error handling. -- Database API plugin: Implements core blockchain queries (blocks, accounts, globals, authority helpers) and returns typed results suitable for JSON serialization. -- Authentication utility plugin: Provides signature verification APIs for client-side or service-side authentication checks. - -**Section sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L151-L370) -- [api.cpp](file://plugins/database_api/api.cpp#L218-L353) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L69-L78) - -## Architecture Overview -The request processing pipeline is event-driven and asynchronous: -- Incoming HTTP requests are deferred and handled asynchronously; WebSocket messages are posted to a thread pool. -- The JSON-RPC plugin parses the request, validates the method and parameters, and invokes the appropriate API handler. -- Handlers execute database reads behind weak read locks and return results as fc::variant, which the JSON-RPC plugin serializes to JSON. -- Errors are captured and returned as JSON-RPC error objects with standardized codes and messages. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "WebSocket/HTTP" -participant Web as "Webserver Plugin" -participant RPC as "JSON-RPC Plugin" -participant DB as "Database API Plugin" -Client->>WS : "Send JSON-RPC request" -WS->>Web : "Dispatch message" -Web->>RPC : "Forward payload" -RPC->>RPC : "Parse & validate" -RPC->>DB : "Invoke API handler" -DB-->>RPC : "Return result" -RPC-->>Web : "Serialize response" -Web-->>Client : "JSON-RPC response" -``` - -**Diagram sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L215-L256) -- [api.cpp](file://plugins/database_api/api.cpp#L218-L223) - -## Detailed Component Analysis - -### JSON-RPC Request Parsing and Method Routing -- Request parsing: - - Validates presence of jsonrpc equals "2.0" and method field. - - Supports two forms: direct "api.method" and legacy "call" with params array containing [api, method, args]. - - Extracts method name and parameters, sets msg_pack id for response correlation. -- Method routing: - - Maintains a registry of registered APIs keyed by plugin name and method. - - Resolves method to a callable handler and prepares arguments for invocation. -- Parameter validation: - - Enforces argument count and types via macros and assertions. - - Returns structured errors for parse failures, invalid params, and missing methods. - -```mermaid -flowchart TD -Start(["Receive JSON-RPC"]) --> CheckVersion["Check 'jsonrpc' == '2.0'"] -CheckVersion --> |Invalid| ErrVersion["Return INVALID_REQUEST"] -CheckVersion --> |Valid| CheckMethod["Check 'method' present"] -CheckMethod --> |Missing| ErrNoMethod["Return INVALID_REQUEST"] -CheckMethod --> ParseParams["Parse params (direct or 'call')"] -ParseParams --> FindMethod["Find registered API handler"] -FindMethod --> |Not Found| ErrNotFound["Return METHOD_NOT_FOUND"] -FindMethod --> ExecCall["Execute handler with args"] -ExecCall --> Result["Serialize result"] -ExecCall --> |Exception| ErrDuringCall["Return ERROR_DURING_CALL"] -Result --> End(["Send response"]) -ErrDuringCall --> End -ErrNotFound --> End -ErrNoMethod --> End -ErrVersion --> End -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L215-L256) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L213) - -**Section sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L215-L256) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L213) - -### Parameter Validation and Error Handling -- Validation patterns: - - Argument count checks using macros to enforce exact or range-based counts. - - Type assertions during parameter extraction. -- Error handling: - - Dedicated JSON-RPC error codes for invalid requests, parse errors, method not found, and errors during call. - - Exceptions are caught and mapped to JSON-RPC error objects with optional data payloads. - - Unknown errors are captured and reported with a generic message. - -```mermaid -flowchart TD -A["Extract args"] --> B{"Arg count ok?"} -B --> |No| E1["Return INVALID_PARAMS"] -B --> |Yes| C["Cast types"] -C --> D{"Type ok?"} -D --> |No| E2["Return INVALID_PARAMS"] -D --> |Yes| F["Proceed to handler"] -``` - -**Diagram sources** -- [api.cpp](file://plugins/database_api/api.cpp#L17-L21) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L295-L310) - -**Section sources** -- [api.cpp](file://plugins/database_api/api.cpp#L17-L21) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L295-L310) - -### Database Query Execution and Result Formatting -- Handlers execute database reads behind a weak read lock to avoid blocking writers. -- Results are returned as typed structures (e.g., account objects, blocks) that serialize naturally to JSON. -- Example handlers: - - Block retrieval by number. - - Account lookup by names. - - Dynamic global properties and chain configuration. -- Result formatting: - - fc::variant is used internally; JSON serialization is performed by the JSON-RPC plugin. - -```mermaid -sequenceDiagram -participant RPC as "JSON-RPC Plugin" -participant DB as "Database API Plugin" -participant Chain as "Chain Database" -RPC->>DB : "get_block_header(args)" -DB->>Chain : "Fetch block by number" -Chain-->>DB : "Optional" -DB-->>RPC : "Return fc : : variant" -RPC-->>RPC : "Serialize to JSON-RPC response" -``` - -**Diagram sources** -- [api.cpp](file://plugins/database_api/api.cpp#L218-L223) -- [api.cpp](file://plugins/database_api/api.cpp#L225-L231) - -**Section sources** -- [api.cpp](file://plugins/database_api/api.cpp#L218-L223) -- [api.cpp](file://plugins/database_api/api.cpp#L225-L231) -- [account_api_object.cpp](file://libraries/api/account_api_object.cpp#L9-L45) - -### Webserver Plugin: HTTP/WS Handling and Response Serialization -- Endpoint configuration supports separate HTTP and WebSocket endpoints or a combined endpoint. -- Thread pool sizing controls concurrency for request handling. -- WebSocket: - - Text payloads are forwarded to the JSON-RPC plugin; non-string payloads return an error message. -- HTTP: - - Requests are deferred; the response body is set asynchronously and sent after completion. - - Errors during parsing are handled and a standardized HTTP response is returned. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "WebSocket Server" -participant Web as "Webserver Plugin" -participant RPC as "JSON-RPC Plugin" -Client->>WS : "Text frame" -WS->>Web : "handle_ws_message" -Web->>RPC : "api->call(payload, callback)" -RPC-->>Web : "Serialized JSON-RPC" -Web-->>WS : "Send response" -WS-->>Client : "Response" -``` - -**Diagram sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L214) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L216-L246) - -**Section sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L254-L264) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L112-L165) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L192-L246) - -### Authentication Mechanisms -- Signature verification API: - - Verifies signatures against account authorities (active/master/regular) for given digest and signatures. - - Returns the derived public keys used for verification. -- Integration: - - Clients can call this API to validate claims or proofs-of-authority before invoking protected operations. - -```mermaid -flowchart TD -Start(["Client provides account, level, digest, signatures"]) --> Verify["Verify authority signatures"] -Verify --> Result{"Has authority?"} -Result --> |Yes| ReturnKeys["Return derived public keys"] -Result --> |No| Err["Throw assertion error"] -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L67) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L69-L78) - -**Section sources** -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L31-L67) -- [plugin.cpp](file://plugins/auth_util/plugin.cpp#L69-L78) - -### Rate Limiting -- No explicit rate limiting is implemented in the analyzed components. Consider deploying external rate limiting at the reverse proxy or firewall level if needed. - -[No sources needed since this section provides general guidance] - -## Dependency Analysis -- Application bootstrap registers plugins and starts the node; the webserver plugin depends on the JSON-RPC plugin. -- JSON-RPC plugin depends on fc::variant serialization and websocketpp for transport. -- Database API plugin depends on the chain database and exposes typed API objects. -- Authentication utility plugin depends on chain database and protocol types. - -```mermaid -graph LR -App["Application Entry"] -Web["Webserver Plugin"] -RPC["JSON-RPC Plugin"] -DB["Database API Plugin"] -Auth["Auth Utility Plugin"] -App --> Web -Web --> RPC -RPC --> DB -RPC --> Auth -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L314-L327) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L378-L395) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L314-L327) - -## Performance Considerations -- Concurrency model: - - The webserver plugin uses a configurable thread pool to handle requests concurrently. - - WebSocket and HTTP paths both post work to the same pool, enabling efficient handling of mixed traffic. -- Monitoring: - - The JSON-RPC plugin logs timing for each request, including elapsed time and error conditions, aiding performance diagnostics. -- Recommendations: - - Tune thread pool size according to CPU cores and expected load. - - Monitor JSON-RPC timings and error rates to identify hotspots. - - Offload heavy operations to background tasks if necessary and keep the request path lightweight. - -**Section sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L266-L269) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L258-L288) - -## Troubleshooting Guide -- Common JSON-RPC errors: - - INVALID_REQUEST: Missing or invalid jsonrpc/version or missing method. - - METHOD_NOT_FOUND: Unknown method or API. - - INVALID_PARAMS: Wrong argument count or wrong types. - - ERROR_DURING_CALL: Exception thrown inside handler. -- Symptoms and fixes: - - Empty or malformed responses: Verify request payload and method naming. - - 500-class HTTP responses: Inspect server logs for exceptions and error messages. - - Slow responses: Review JSON-RPC timing logs and adjust thread pool size. - -**Section sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L295-L310) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L231-L244) - -## Conclusion -The API request processing pipeline is robust, modular, and designed for high concurrency. JSON-RPC parsing, method routing, and parameter validation occur early, while database queries are executed efficiently behind read locks. The webserver plugin provides flexible HTTP/WS handling with strong error reporting and performance logging. Authentication utilities complement the stack by offering signature verification capabilities. For production deployments, consider external rate limiting and continuous monitoring of request latency and error rates. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Block Processing and Validation.md b/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Block Processing and Validation.md deleted file mode 100644 index 92d302accc..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Block Processing and Validation.md +++ /dev/null @@ -1,447 +0,0 @@ -# Block Processing and Validation - - -**Referenced Files in This Document** -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp) -- [block_header.hpp](file://libraries/protocol/include/graphene/protocol/block_header.hpp) -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp) - - -## Update Summary -**Changes Made** -- Enhanced fork handling with comprehensive fallback mechanisms for missing irreversible blocks during fork resolution and chain reorganization events -- Improved block log accessibility with dual-path retrieval: block_log → dlt_block_log → fork_database fallback chain -- Added robust gap detection and logging for DLT block log gaps during LIB advancement -- Strengthened chain validation reliability by preventing chain stalls due to missing blocks -- Enhanced Validator Plugin block post-validation logic with defensive programming to prevent runtime errors when validator accounts aren't found during validation - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the complete block processing and validation pipeline in the VIZ node, including header validation, transaction extraction, state application, fork resolution, block persistence, and validator block production coordination. It also covers performance characteristics and optimization techniques used for high-throughput block processing. - -**Updated** Enhanced with comprehensive fallback mechanisms for missing irreversible blocks during fork resolution and chain reorganization events, improving chain validation reliability and preventing chain stalls. The block log accessibility system now provides a robust fallback chain: block_log → dlt_block_log → fork_database, ensuring blocks remain accessible even when intermediate storage systems are unavailable. - -## Project Structure -The block processing pipeline spans three primary subsystems: -- Protocol-level block representation and header definitions -- Chain database that orchestrates validation, fork management, and state application -- Dual block log system with fallback mechanisms for durable, append-only persistence of blocks - -```mermaid -graph TB -subgraph "Protocol Layer" -BH["block_header.hpp"] -SB["block.hpp"] -end -subgraph "Chain Layer" -DBH["database.hpp"] -DBC["database.cpp"] -FDH["fork_database.hpp"] -FDC["fork_database.cpp"] -BLH["block_log.hpp"] -BLC["block_log.cpp"] -DLTH["dlt_block_log.hpp"] -D LTCPP["dlt_block_log.cpp"] -end -subgraph "Plugins" -WIT["validator.hpp"] -WITCPP["validator.cpp"] -end -BH --> SB -SB --> DBH -DBH --> DBC -DBC --> FDH -DBC --> BLH -DBC --> DLTH -FDH --> FDC -BLH --> BLC -DLTH --> D LTCPP -WIT --> DBH -WITCPP --> DBH -``` - -**Diagram sources** -- [block_header.hpp:1-43](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.hpp:1-19](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [database.hpp:1-561](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [database.cpp:737-913](file://libraries/chain/database.cpp#L737-L913) -- [fork_database.hpp:1-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [block_log.cpp:238-300](file://libraries/chain/block_log.cpp#L238-L300) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) -- [dlt_block_log.cpp:162-242](file://libraries/chain/dlt_block_log.cpp#L162-L242) -- [validator.hpp:1-70](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L1-L70) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) - -**Section sources** -- [block_header.hpp:1-43](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [block.hpp:1-19](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [database.hpp:1-561](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp:1-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) -- [validator.hpp:1-70](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L1-L70) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) - -## Core Components -- Protocol block model: Defines the signed block structure and signed block header, including merkle roots and validator signatures. -- Fork database: Maintains a tree of candidate blocks, supports branching, linking, and selection of the heaviest chain. -- Dual block log system: Provides append-only, random-access persistence of blocks with fallback mechanisms: - - Primary block_log for irreversible blocks - - DLT rolling block_log for recent blocks with sliding window capability - - Fork database as final fallback for reversible blocks -- Database: Orchestrates validation, fork resolution, state application, and block logging with comprehensive fallback logic. -- Validator Plugin: Coordinates block production for validator nodes and defines acceptance criteria with enhanced defensive programming. - -**Updated** The dual block log system now provides comprehensive fallback mechanisms, with block retrieval following the chain: block_log → dlt_block_log → fork_database. This ensures chain validation reliability by preventing chain stalls due to missing blocks in any single storage system. - -**Section sources** -- [block.hpp:9-13](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L13) -- [block_header.hpp:25-35](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L25-L35) -- [fork_database.hpp:53-96](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L96) -- [block_log.hpp:38-68](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L68) -- [dlt_block_log.hpp:13-33](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L13-L33) -- [database.hpp:36-287](file://libraries/chain/include/graphene/chain/database.hpp#L36-L287) -- [validator.hpp:20-32](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) - -## Architecture Overview -The block processing pipeline integrates protocol definitions, fork management, and dual persistent storage systems with comprehensive fallback mechanisms: - -```mermaid -sequenceDiagram -participant Net as "Network/P2P" -participant DB as "database.cpp" -participant FH as "fork_database.cpp" -participant BL as "block_log.cpp" -participant DLT as "dlt_block_log.cpp" -Net->>DB : "Receive signed_block" -DB->>DB : "validate_block()" -DB->>DB : "_validate_block() + validate_block_header()" -DB->>FH : "push_block(new_block)" -alt "Links to known chain" -FH-->>DB : "new_head" -DB->>DB : "apply_block() -> _apply_block()" -DB->>BL : "append(block) (optional)" -DB-->>Net : "accepted" -else "Unlinkable" -FH-->>DB : "store in unlinked cache" -DB-->>Net : "deferred" -end -DB->>DB : "update_last_irreversible_block()" -alt "LIB advancement" -DB->>BL : "append(block) to block_log" -DB->>DLT : "append(block) to dlt_block_log (fallback)" -DB->>FH : "fetch from fork_database" -end -``` - -**Diagram sources** -- [database.cpp:737-913](file://libraries/chain/database.cpp#L737-L913) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [block_log.cpp:253-257](file://libraries/chain/block_log.cpp#L253-L257) -- [dlt_block_log.cpp:336-340](file://libraries/chain/dlt_block_log.cpp#L336-L340) -- [validator.hpp:20-32](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) - -## Detailed Component Analysis - -### Enhanced Block Validation Pipeline with Fallback Mechanisms -The validation pipeline is exposed via the database interface and now includes comprehensive fallback mechanisms: -- Header validation: Verifies cryptographic signatures and structural constraints. -- Transaction extraction: Iterates transactions embedded in the block. -- State application: Applies each transaction's operations against the current state. -- Hardfork handling: Enforces consensus rules per hardfork schedule. -- Optional checks: Signature verification, TAPoS, block size limits, and authority checks depending on skip flags. -- **Enhanced fallback chain**: Block retrieval follows block_log → dlt_block_log → fork_database hierarchy for maximum reliability. - -```mermaid -flowchart TD -Start(["validate_block()"]) --> CheckSkip["Compute skip flags"] -CheckSkip --> ValidateHeader["validate_block_header()"] -ValidateHeader --> ExtractTxs["Iterate transactions"] -ExtractTxs --> ApplyTx["apply_transaction() per tx"] -ApplyTx --> Hardforks["process_hardforks() / apply_hardfork()"] -Hardforks --> FallbackChain["Block Access Fallback Chain"] -FallbackChain --> BL["block_log.read_block_by_num()"] -BL --> |Missing| DLT["dlt_block_log.read_block_by_num()"] -DLT --> |Missing| FD["fork_database.fetch_block_on_main_branch_by_number()"] -FD --> |Found| Done(["Validation OK"]) -FD --> |Missing| Error["Return block_id_type()"] -Error --> Done -``` - -**Diagram sources** -- [database.hpp:194-206](file://libraries/chain/include/graphene/chain/database.hpp#L194-L206) -- [database.cpp:737-757](file://libraries/chain/database.cpp#L737-L757) -- [database.cpp:3443-3509](file://libraries/chain/database.cpp#L3443-L3509) -- [database.cpp:812-825](file://libraries/chain/database.cpp#L812-L825) - -**Section sources** -- [database.hpp:56-73](file://libraries/chain/include/graphene/chain/database.hpp#L56-L73) -- [database.hpp:194-206](file://libraries/chain/include/graphene/chain/database.hpp#L194-L206) -- [database.cpp:737-757](file://libraries/chain/database.cpp#L737-L757) -- [database.cpp:3443-3509](file://libraries/chain/database.cpp#L3443-L3509) -- [database.cpp:812-825](file://libraries/chain/database.cpp#L812-L825) - -### Enhanced Fork Resolution and Chain Selection with Gap Detection -Fork resolution maintains a tree of candidate blocks and selects the heaviest chain with comprehensive gap detection: -- Unlinkable blocks are cached and later linked when their parent arrives. -- The head advances to the highest-numbered block. -- Branch-from algorithm computes divergent branches to a common ancestor for reorganization decisions. -- **Enhanced gap detection**: DLT block log gaps are logged and handled gracefully during LIB advancement. - -```mermaid -classDiagram -class fork_database { -+push_block(b) -+set_head(h) -+fetch_branch_from(first, second) -+walk_main_branch_to_num(n) -+fetch_block_on_main_branch_by_number(n) -+_push_block(item) -+_push_next(new_item) -} -class fork_item { -+num -+id -+data -+prev -+invalid -+previous_id() -} -fork_database --> fork_item : "stores" -``` - -**Diagram sources** -- [fork_database.hpp:53-96](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L96) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) - -**Section sources** -- [fork_database.hpp:53-96](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L96) -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [fork_database.cpp:168-210](file://libraries/chain/fork_database.cpp#L168-L210) - -### Enhanced Block Logging and Persistence with Fallback Chain -Blocks are persisted to dual log systems with comprehensive fallback mechanisms: -- Main file stores serialized blocks with forward pointers. -- Index file maps block number to file offset. -- DLT rolling block_log provides sliding window storage for recent blocks. -- Startup routines reconcile log and index, reconstructing the index if needed. -- Append operations are atomic with respect to index alignment. -- **Enhanced fallback chain**: During LIB advancement, blocks are written to both block_log and dlt_block_log, with graceful handling of gaps. - -```mermaid -flowchart TD -Open(["open(file)"]) --> CheckFiles["Check log/index existence"] -CheckFiles --> |Both present| CompareHeads["Compare heads"] -CompareHeads --> |Mismatch| RebuildIdx["construct_index()"] -CompareHeads --> |Match| Ready["Ready"] -RebuildIdx --> Ready -Ready --> Append["append(block)"] -Append --> WriteMain["Write block + forward ptr"] -WriteMain --> WriteIdx["Write index entry"] -WriteIdx --> UpdateHead["Update head"] -``` - -**Diagram sources** -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [block_log.cpp:115-132](file://libraries/chain/block_log.cpp#L115-L132) -- [block_log.cpp:195-219](file://libraries/chain/block_log.cpp#L195-L219) -- [dlt_block_log.cpp:162-242](file://libraries/chain/dlt_block_log.cpp#L162-L242) - -**Section sources** -- [block_log.hpp:13-36](file://libraries/chain/include/graphene/chain/block_log.hpp#L13-L36) -- [block_log.cpp:134-193](file://libraries/chain/block_log.cpp#L134-L193) -- [block_log.cpp:115-132](file://libraries/chain/block_log.cpp#L115-L132) -- [block_log.cpp:253-257](file://libraries/chain/block_log.cpp#L253-L257) -- [dlt_block_log.hpp:13-33](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L13-L33) -- [dlt_block_log.cpp:162-242](file://libraries/chain/dlt_block_log.cpp#L162-L242) - -### Role of Fork Database in Chain History and Reorganizations -- Maintains a bounded window of unlinked blocks to handle reordering up to a configured limit. -- Supports walking branches and selecting the heaviest chain head. -- Flags invalid blocks to prevent further growth on top of them. -- **Enhanced gap handling**: Graceful handling of missing blocks during LIB advancement with detailed logging. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant FD as "fork_database.cpp" -DB->>FD : "push_block(new_block)" -alt "Links to known chain" -FD-->>DB : "new_head" -DB->>DB : "apply_block(new_head)" -else "Unlinkable" -FD-->>DB : "cache in unlinked_index" -DB->>FD : "_push_next(new_item)" -loop "Recursive linking" -FD-->>DB : "insert linked blocks" -end -end -``` - -**Diagram sources** -- [fork_database.cpp:33-90](file://libraries/chain/fork_database.cpp#L33-L90) -- [database.cpp:846-913](file://libraries/chain/database.cpp#L846-L913) - -**Section sources** -- [fork_database.cpp:47-71](file://libraries/chain/fork_database.cpp#L47-L71) -- [fork_database.cpp:79-90](file://libraries/chain/fork_database.cpp#L79-L90) -- [database.cpp:846-913](file://libraries/chain/database.cpp#L846-L913) - -### Enhanced Block Production Coordination for validator Nodes -**Updated** validator nodes coordinate block production through a dedicated plugin with enhanced defensive programming: -- Acceptance criteria include synchronization status, turn-based scheduling, time windows, participation thresholds, and availability of signing keys. -- The plugin integrates with the chain plugin and P2P plugin to manage production loops. -- **Enhanced defensive programming**: Added error handling to gracefully skip validator accounts that aren't found in the validator index during block post-validation, preventing runtime errors and improving system reliability. -- **Enhanced gap detection**: DLT block log gaps are logged with detailed information about LIB advancement progress. - -```mermaid -flowchart TD -Init(["Initialize Validator Plugin"]) --> Options["Parse options"] -Options --> Startup["Startup"] -Startup --> Schedule["Compute slot times"] -Schedule --> PostValidation["Block Post-Validation Loop"] -PostValidation --> CheckWitness["Check validator account exists"] -CheckWitness --> |Exists| ValidateBlock["Validate block and sign"] -CheckWitness --> |Not Found| Skip["Skip with warning log"] -ValidateBlock --> Broadcast["Broadcast to peers"] -Skip --> Continue["Continue with next validator"] -Continue --> Produce{"Is validator turn?"} -Produce --> |Yes| Build["generate_block()"] -Produce --> |No| Wait["Wait for next slot"] -Build --> Sign["Sign block header"] -Sign --> Broadcast --> Apply["apply_block()"] -Apply --> Persist["append to block_log"] -``` - -**Diagram sources** -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) -- [database.hpp:214-226](file://libraries/chain/include/graphene/chain/database.hpp#L214-L226) -- [database.cpp:3443-3509](file://libraries/chain/database.cpp#L3443-L3509) - -**Section sources** -- [validator.hpp:20-32](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) -- [database.hpp:214-226](file://libraries/chain/include/graphene/chain/database.hpp#L214-L226) - -### Enhanced Transaction Extraction and State Application -- Transactions are extracted from the block and validated individually. -- Each transaction's operations are evaluated against the current state, with hooks for pre/post operation notifications and virtual operations. -- Hardforks and special processing steps are executed during block application. -- **Enhanced fallback chain**: Block retrieval follows block_log → dlt_block_log → fork_database hierarchy for maximum reliability. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant Tx as "Transaction" -DB->>Tx : "validate_transaction()" -Tx-->>DB : "OK" -DB->>DB : "apply_transaction()" -DB->>DB : "apply_operation() per op" -DB-->>DB : "notify_applied_block() / signals" -``` - -**Diagram sources** -- [database.hpp:423-428](file://libraries/chain/include/graphene/chain/database.hpp#L423-L428) -- [database.cpp:3443-3509](file://libraries/chain/database.cpp#L3443-L3509) - -**Section sources** -- [database.hpp:423-428](file://libraries/chain/include/graphene/chain/database.hpp#L423-L428) -- [database.cpp:3443-3509](file://libraries/chain/database.cpp#L3443-L3509) - -## Dependency Analysis -The following diagram shows key dependencies among components involved in block processing with enhanced fallback mechanisms: - -```mermaid -graph LR -PBlock["protocol::block.hpp"] --> DBH["database.hpp"] -PHeader["protocol::block_header.hpp"] --> DBH -DBH --> DBC["database.cpp"] -DBC --> FDH["fork_database.hpp"] -DBC --> BLH["block_log.hpp"] -DBC --> DLTH["dlt_block_log.hpp"] -FDH --> FDC["fork_database.cpp"] -BLH --> BLC["block_log.cpp"] -DLTH --> D LTCPP["dlt_block_log.cpp"] -WIT["validator.hpp"] --> DBH -WITCPP["validator.cpp"] --> DBH -``` - -**Diagram sources** -- [block.hpp:1-19](file://libraries/protocol/include/graphene/protocol/block.hpp#L1-L19) -- [block_header.hpp:1-43](file://libraries/protocol/include/graphene/protocol/block_header.hpp#L1-L43) -- [database.hpp:1-561](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp:1-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) -- [validator.hpp:1-70](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L1-L70) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) - -**Section sources** -- [database.hpp:1-561](file://libraries/chain/include/graphene/chain/database.hpp#L1-L561) -- [fork_database.hpp:1-125](file://libraries/chain/include/graphene/chain/fork_database.hpp#L1-L125) -- [block_log.hpp:1-75](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [dlt_block_log.hpp:1-76](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L1-L76) -- [validator.hpp:1-70](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L1-L70) -- [validator.cpp:295-341](file://plugins/validator/validator.cpp#L295-L341) - -## Performance Considerations -- Skip flags: Validation and checks can be selectively disabled during reindexing or trusted operations to reduce overhead. -- Memory-mapped IO: Both block logs use memory-mapped files for efficient random access and reduced syscall overhead. -- Bounded fork cache: Limits memory usage by capping the number of unlinked blocks and pruning older entries. -- Flush intervals: Controlled flushing reduces disk sync frequency while preserving durability guarantees. -- Parallelism: Network and plugin layers can operate concurrently with chain operations under appropriate locking. -- **Enhanced fallback mechanisms**: Comprehensive fallback chain (block_log → dlt_block_log → fork_database) improves reliability without significant performance impact. -- **Gap detection optimization**: DLT block log gap detection prevents unnecessary retries and reduces logging overhead during normal operations. -- **Enhanced defensive programming**: The Validator Plugin now includes additional error checking and graceful degradation to prevent runtime errors during block post-validation, improving overall system stability. - -## Troubleshooting Guide -Common issues and diagnostics: -- Unlinkable blocks: Detected when a block's previous ID is not present in the fork database; the block is cached and linked when the parent arrives. -- Hardfork application errors: Exceptions indicate attempting to apply unknown hardforks or missing hardfork state. -- Block log inconsistencies: Startup reconciliation reconstructs the index if the log and index are out of sync. -- validator production failures: Conditions such as not being scheduled, insufficient participation, or missing private keys prevent block production. -- **Enhanced error handling**: validator accounts not found in validator index are now handled gracefully with warning logs, preventing runtime errors and allowing the system to continue processing other validators. -- **DLT gap detection**: DLT block log gaps are logged with detailed information about LIB advancement progress, helping diagnose synchronization issues. -- **Fallback chain failures**: When all fallback mechanisms fail, the system returns block_id_type(), indicating the block is not available in any storage system. - -**Section sources** -- [fork_database.cpp:38-44](file://libraries/chain/fork_database.cpp#L38-L44) -- [database_exceptions.hpp:83-83](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L83-L83) -- [block_log.cpp:163-193](file://libraries/chain/block_log.cpp#L163-L193) -- [validator.hpp:20-32](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) -- [validator.cpp:305-307](file://plugins/validator/validator.cpp#L305-L307) -- [database.cpp:5395-5419](file://libraries/chain/database.cpp#L5395-L5419) - -## Conclusion -The VIZ node implements a robust block processing pipeline that separates concerns between protocol definitions, fork management, and durable persistence with comprehensive fallback mechanisms. The fork database ensures resilient handling of out-of-order and conflicting blocks, while the dual block log system (block_log + dlt_block_log) provides efficient random access and reindexing support with graceful fallback to the fork database. - -**Updated** The enhanced fork handling system now includes comprehensive fallback mechanisms for missing irreversible blocks during fork resolution and chain reorganization events, significantly improving chain validation reliability and preventing chain stalls. The block log accessibility system provides a robust fallback chain: block_log → dlt_block_log → fork_database, ensuring blocks remain accessible even when intermediate storage systems are unavailable. - -The database orchestrates validation, state application, and integration with plugins such as the validator node coordinator. The enhanced defensive programming in the Validator Plugin prevents runtime errors when validator accounts aren't found during block post-validation, significantly improving system reliability and error handling. - -Performance is optimized through selective validation, memory-mapped IO, bounded caches, and controlled flushing, enabling high-throughput operation in production environments. The addition of comprehensive fallback mechanisms and gap detection further enhances the system's resilience and operational stability, making it highly reliable for production blockchain operations. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Data Flow and Processing.md b/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Data Flow and Processing.md deleted file mode 100644 index a8cfcaa554..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Data Flow and Processing.md +++ /dev/null @@ -1,367 +0,0 @@ -# Data Flow and Processing - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp) -- [operation_notification.hpp](file://libraries/chain/include/graphene/chain/operation_notification.hpp) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [block_log.cpp](file://libraries/chain/block_log.cpp) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the end-to-end data flow and processing patterns in the VIZ node. It covers how incoming JSON-RPC requests are routed to APIs, how transactions are validated and applied, how blocks are validated and integrated, and how state changes are persisted. It also documents the observer pattern used for event-driven architecture, the fork resolution mechanism, and performance-related strategies such as caching and memory management. - -## Project Structure -At a high level, the VIZ node is organized around: -- Protocol primitives (transactions, blocks, operations) -- Chain database and state management -- Fork database for out-of-order block handling -- Block log for durable storage -- Plugins for API exposure (JSON-RPC) -- Network layer for peer-to-peer synchronization - -```mermaid -graph TB -subgraph "Network Layer" -NET["network::node"] -end -subgraph "Plugins" -RPC["json_rpc::plugin"] -end -subgraph "Chain Core" -DB["chain::database"] -FD["chain::fork_database"] -BL["chain::block_log"] -end -subgraph "Protocol" -TRX["protocol::signed_transaction"] -BLOCK["protocol::signed_block"] -end -NET --> DB -RPC --> DB -DB --> FD -DB --> BL -DB --> TRX -DB --> BLOCK -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L180-L304) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L101) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L180-L304) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L101) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -## Core Components -- chain::database: central state machine managing blockchain lifecycle, validation, and persistence. Provides push_block, push_transaction, and related hooks. -- chain::fork_database: maintains a tree of candidate blocks for fork resolution and out-of-order pushes. -- chain::block_log: durable append-only storage of blocks with random access via index. -- protocol::signed_transaction and protocol::signed_block: typed structures for transactions and blocks. -- json_rpc::plugin: exposes APIs over JSON-RPC and dispatches calls to registered methods. -- network::node: handles peer synchronization and relaying of blocks/transactions. - -Key responsibilities: -- Validation flags and skip modes for performance tuning -- Merkle roots, TAPOS, and block size limits -- Undo sessions, revision management, and memory scaling -- Signals for observers (pre/post operation, applied block, pending transactions) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L56-L73) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L194-L227) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L101) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) - -## Architecture Overview -The VIZ node follows a layered architecture: -- Application layer: JSON-RPC plugin receives requests and routes them to APIs. -- Chain layer: database orchestrates validation, evaluation, and persistence. -- Storage layer: fork_database caches reversible blocks; block_log persists irreversible blocks. -- Network layer: node manages synchronization and propagation. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant RPC as "json_rpc : : plugin" -participant DB as "chain : : database" -participant FD as "chain : : fork_database" -participant BL as "chain : : block_log" -Client->>RPC : "JSON-RPC request" -RPC->>DB : "Dispatch API call" -alt "Block push" -DB->>FD : "push_block()" -FD-->>DB : "head updated?" -DB->>BL : "append() when irreversible" -else "Transaction push" -DB->>DB : "validate_transaction()" -DB->>DB : "apply_transaction()" -end -DB-->>RPC : "Response" -RPC-->>Client : "JSON-RPC response" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L402-L423) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L194-L227) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L45) -- [block_log.cpp](file://libraries/chain/block_log.cpp#L253-L257) - -## Detailed Component Analysis - -### JSON-RPC Request Pipeline -- The JSON-RPC plugin parses incoming messages, validates method names, and dispatches to registered API methods. -- It supports batch requests and returns responses in order. -- Errors are normalized into JSON-RPC error codes. - -```mermaid -sequenceDiagram -participant C as "Client" -participant P as "json_rpc : : plugin" -participant D as "chain : : database" -C->>P : "Call {api, method, args}" -P->>P : "process_params()" -P->>D : "Invoke API method" -D-->>P : "Result or exception" -P-->>C : "JSON-RPC response" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L256) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L402-L423) - -**Section sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L256) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L402-L423) - -### Transaction Processing Pipeline -- Incoming signed transactions are validated against chain parameters (TAPOS, expiration, signatures) and then applied to state. -- Validation can be partially skipped for performance (e.g., during reindex) via skip flags. -- Transactions are evaluated via registered evaluators; each operation triggers pre/post operation notifications. - -```mermaid -flowchart TD -Start(["Receive signed_transaction"]) --> Validate["validate_transaction(skip)"] -Validate --> Valid{"Valid?"} -Valid --> |No| Reject["Reject with error"] -Valid --> |Yes| Apply["apply_transaction(skip)"] -Apply --> Eval["Apply operations via evaluators"] -Eval --> NotifyPre["notify_pre_apply_operation"] -NotifyPre --> Ops["Each operation applied"] -Ops --> NotifyPost["notify_post_apply_operation"] -NotifyPost --> Done(["Transaction applied"]) -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L423-L424) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L468-L476) -- [operation_notification.hpp](file://libraries/chain/include/graphene/chain/operation_notification.hpp#L11-L23) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L423-L424) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L468-L476) -- [operation_notification.hpp](file://libraries/chain/include/graphene/chain/operation_notification.hpp#L11-L23) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) - -### Block Processing Flow -- validate_block performs Merkle root and block size checks. -- push_block integrates a block into the chain, updating dynamic properties, validator participation, and last irreversible block. -- apply_block coordinates per-block state transitions and emits applied_block signals. - -```mermaid -sequenceDiagram -participant N as "network : : node" -participant D as "chain : : database" -participant F as "chain : : fork_database" -participant L as "chain : : block_log" -N->>D : "handle_block(signed_block)" -D->>D : "validate_block()" -D->>F : "push_block()" -F-->>D : "new head or cached" -D->>D : "apply_block()" -D->>L : "append() when irreversible" -D-->>N : "Fork switch?" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L79-L80) -- [database.cpp](file://libraries/chain/database.cpp#L738-L756) -- [database.cpp](file://libraries/chain/database.cpp#L794-L800) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L45) -- [block_log.cpp](file://libraries/chain/block_log.cpp#L253-L257) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L738-L756) -- [database.cpp](file://libraries/chain/database.cpp#L794-L800) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L45) -- [block_log.cpp](file://libraries/chain/block_log.cpp#L253-L257) - -### Observer Pattern and Signals -- The database exposes fc::signal callbacks for: - - pre_apply_operation and post_apply_operation - - applied_block - - on_pending_transaction and on_applied_transaction -- Plugins subscribe to these signals to react to state changes without tight coupling. - -```mermaid -classDiagram -class database { -+pre_apply_operation -+post_apply_operation -+applied_block -+on_pending_transaction -+on_applied_transaction -} -class plugin_json_rpc { -+subscribe_signals() -} -database --> plugin_json_rpc : "emits signals" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) - -### Data Persistence and Fork Resolution -- fork_database stores candidate blocks and resolves forks by walking branches to a common ancestor. -- block_log provides durable storage with an index enabling O(1) random access by block number. -- Memory management includes reserved/shared memory sizing and periodic resizing during reindex. - -```mermaid -flowchart TD -A["New block received"] --> B["fork_database.push_block()"] -B --> C{"Links to known chain?"} -C --> |No| D["Cache in unlinked_index"] -C --> |Yes| E["Insert into main index"] -E --> F{"Exceeds max size?"} -F --> |Yes| G["Trim older blocks"] -F --> |No| H["Update head"] -D --> I["Try linking later"] -I --> E -``` - -**Diagram sources** -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L90) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L92-L124) - -**Section sources** -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L33-L90) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L92-L124) -- [block_log.cpp](file://libraries/chain/block_log.cpp#L134-L193) -- [database.cpp](file://libraries/chain/database.cpp#L368-L430) - -## Dependency Analysis -- chain::database depends on: - - chain::fork_database for reversible blocks - - chain::block_log for persistent storage - - protocol::signed_transaction and signed_block for data structures - - evaluator registry for operation application -- json_rpc::plugin depends on appbase and fc variants for request/response handling. -- network::node delegates block and transaction handling to the chain database. - -```mermaid -graph LR -RPC["json_rpc::plugin"] --> DB["chain::database"] -NET["network::node"] --> DB -DB --> FD["chain::fork_database"] -DB --> BL["chain::block_log"] -DB --> EV["evaluator registry"] -DB --> TRX["protocol::signed_transaction"] -DB --> BLOCK["protocol::signed_block"] -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L180-L304) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L122) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L38-L71) -- [transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L101) -- [block.hpp](file://libraries/protocol/include/graphene/protocol/block.hpp#L9-L18) - -**Section sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L180-L304) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) - -## Performance Considerations -- Skip flags: The database supports extensive skip masks to bypass expensive validations during reindex or trusted operations. -- Memory scaling: Shared memory is resized dynamically when free memory drops below thresholds; reserved memory protects critical operations. -- Caching: TAPOS buffers and block summary indices accelerate block ID lookups; fork_database caches recent blocks up to a configurable maximum. -- Batch processing: JSON-RPC plugin supports batch requests and streams responses efficiently. - -Recommendations: -- Tune skip flags for trusted environments (e.g., skip signatures for non-validator nodes). -- Monitor free memory and adjust shared file sizing to avoid frequent resizes. -- Use checkpoints to reduce validation overhead on startup. -- Leverage evaluators’ early exits and minimal authority checks where safe. - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L56-L73) -- [database.cpp](file://libraries/chain/database.cpp#L368-L430) -- [database.cpp](file://libraries/chain/database.cpp#L270-L350) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L313-L336) - -## Troubleshooting Guide -Common issues and diagnostics: -- Block validation failures: Check Merkle root mismatch and block size violations; consult fork database state and block log head consistency. -- Transaction rejection: Inspect TAPOS, expiration, and authority verification errors; review skip flags used during validation. -- Memory exhaustion: Watch logs indicating low free memory and automatic shared memory resizing; increase reserved size or shared file size. -- Fork instability: Verify fork database head and branch resolution; ensure block ordering and previous ID consistency. - -Operational tips: -- Use reindex mode with appropriate skip flags to recover from inconsistent state. -- Subscribe to on_applied_transaction and applied_block signals for observability. -- Validate chain state against block log head to detect divergence. - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L738-L792) -- [database.cpp](file://libraries/chain/database.cpp#L270-L350) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp#L47-L71) -- [block_log.cpp](file://libraries/chain/block_log.cpp#L134-L193) - -## Conclusion -The VIZ node implements a robust, event-driven data flow from JSON-RPC to state application and persistence. Its layered design separates concerns between networking, API exposure, state management, and storage, while providing powerful mechanisms for validation, fork resolution, and performance tuning. Observers can react to state changes via signals, and plugins integrate seamlessly with the core database through well-defined interfaces. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Event-Driven Communication Patterns.md b/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Event-Driven Communication Patterns.md deleted file mode 100644 index daf2e4c6c7..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Event-Driven Communication Patterns.md +++ /dev/null @@ -1,369 +0,0 @@ -# Event-Driven Communication Patterns - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp) -- [main.cpp](file://programs/cli_wallet/main.cpp) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the event-driven architecture and observer pattern implementation in the VIZ node. It focuses on how signals and slots enable decoupled communication between plugins and core components, how state changes propagate through event notifications, and how Boost.Signals2 and fc::signal are used to manage subscriptions and callbacks. It also covers plugin communication mechanisms, inter-component messaging patterns, and practical examples such as account updates, transaction confirmations, and block notifications. Finally, it addresses performance implications and optimization strategies for high-frequency events. - -## Project Structure -The event-driven design spans three primary layers: -- Core chain engine: emits domain-specific events (operations, blocks, transactions). -- Plugin layer: subscribes to events and implements specialized behaviors. -- Application integrations: CLI, web server, and RPC plugins consume events for user-facing features. - -```mermaid -graph TB -subgraph "Chain Core" -DB["database.hpp/.cpp
emits fc::signal events"] -end -subgraph "Plugins" -CHAIN_P["plugins/chain/plugin.hpp
Boost.Signals2 on_sync"] -BLOCK_INFO["plugins/block_info/plugin.cpp
applied_block.connect(...)"] -DB_API["plugins/database_api/api.cpp
block_applied_callback_info"] -DEBUG_NODE["plugins/debug_node/plugin.cpp
applied_block.connect(...)"] -NET_BROADCAST["plugins/network_broadcast_api/...
uses Boost.Signals2 connections"] -end -subgraph "Applications" -CLI["programs/cli_wallet/main.cpp
Boost.Signals2 scoped_connection"] -WEB["plugins/webserver/webserver_plugin.cpp
chain_sync_con"] -end -DB --> CHAIN_P -DB --> BLOCK_INFO -DB --> DB_API -DB --> DEBUG_NODE -DB --> NET_BROADCAST -CHAIN_P --> CLI -CHAIN_P --> WEB -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [database.cpp](file://libraries/chain/database.cpp#L1157-L1198) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L78-L78) -- [main.cpp](file://programs/cli_wallet/main.cpp#L180-L196) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L108-L108) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [database.cpp](file://libraries/chain/database.cpp#L1157-L1198) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L78-L78) -- [main.cpp](file://programs/cli_wallet/main.cpp#L180-L196) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L108-L108) - -## Core Components -- Chain database events: - - Operation lifecycle: pre_apply_operation and post_apply_operation. - - Block lifecycle: applied_block. - - Transaction lifecycle: on_pending_transaction and on_applied_transaction. -- Plugin-level synchronization: chain plugin emits on_sync via Boost.Signals2. -- Observer pattern in plugins: - - Plugins connect to database signals to receive notifications. - - Callbacks are wrapped in scoped_connection/connection to manage lifetime safely. - -Key event emitters and receivers: -- Emitters (database): - - notify_pre_apply_operation, notify_post_apply_operation, notify_applied_block, notify_on_pending_transaction, notify_on_applied_transaction. -- Receivers (plugins): - - block_info plugin connects to applied_block. - - database_api plugin manages block_applied_callback_info with connection-based callbacks. - - debug_node plugin connects to applied_block. - - chain plugin exposes on_sync for external synchronization. - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L238-L275) -- [database.cpp](file://libraries/chain/database.cpp#L1157-L1198) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) - -## Architecture Overview -The event architecture follows a publish-subscribe model: -- Publishers: chain database emits fc::signal events during block/application processing. -- Subscribers: plugins register callbacks to receive notifications. -- Synchronization: chain plugin emits on_sync for coordination with external systems. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant OP as "pre/post_apply_operation" -participant BLK as "applied_block" -participant TX as "pending/applied_transaction" -participant BI as "block_info plugin" -participant DA as "database_api plugin" -participant DN as "debug_node plugin" -DB->>OP : emit pre_apply_operation(note) -OP-->>BI : notify subscribers -OP-->>DA : notify subscribers -OP-->>DN : notify subscribers -DB->>BLK : emit applied_block(block) -BLK-->>BI : on_applied_block(block) -BLK-->>DA : on_applied_block(block) -DB->>TX : emit on_pending_transaction(tx) -TX-->>DA : pending callback -DB->>TX : emit on_applied_transaction(tx) -TX-->>DA : applied callback -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L1157-L1198) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) - -## Detailed Component Analysis - -### Chain Database Event Emission -The database defines and emits multiple fc::signal events: -- pre_apply_operation and post_apply_operation for operation lifecycle. -- applied_block for block lifecycle. -- on_pending_transaction and on_applied_transaction for transaction lifecycle. - -Implementation highlights: -- notify_* methods wrap CHAIN_TRY_NOTIFY to dispatch events to subscribers. -- Virtual operations and current context (block/trx/op indices) are attached to notifications. - -```mermaid -flowchart TD -Start(["Operation Applied"]) --> PreNotify["notify_pre_apply_operation(note)"] -PreNotify --> EmitPre["Emit pre_apply_operation"] -EmitPre --> PostNotify["notify_post_apply_operation(note)"] -PostNotify --> EmitPost["Emit post_apply_operation"] -Start2(["Block Applied"]) --> BlockNotify["notify_applied_block(block)"] -BlockNotify --> EmitBlock["Emit applied_block"] -Start3(["Transaction Added/Pending"]) --> PendingNotify["notify_on_pending_transaction(tx)"] -PendingNotify --> EmitPending["Emit on_pending_transaction"] -Start4(["Transaction Applied"]) --> AppliedNotify["notify_on_applied_transaction(tx)"] -AppliedNotify --> EmitApplied["Emit on_applied_transaction"] -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L1157-L1198) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L238-L275) -- [database.cpp](file://libraries/chain/database.cpp#L1157-L1198) - -### Chain Plugin Synchronization Signal -The chain plugin exposes on_sync via Boost.Signals2 to signal when the blockchain is syncing or live. This enables dependent components to coordinate their startup and state transitions. - -```mermaid -sequenceDiagram -participant CH as "chain plugin.hpp" -participant APP as "external consumers" -CH->>APP : emit on_sync() -APP-->>APP : synchronize local state -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) - -### Block Information Plugin Subscription -The block_info plugin subscribes to applied_block to maintain per-block metadata. It uses a scoped_connection to ensure automatic disconnection. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant BI as "block_info plugin.cpp" -DB->>BI : applied_block(block) -BI->>BI : on_applied_block(block)
update block_info_ -``` - -**Diagram sources** -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) - -**Section sources** -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) - -### Database API Plugin Callback Management -The database_api plugin maintains a container of block_applied_callback_info entries. Each entry holds a callback and a connection. Connections are disconnected upon callback exceptions to prevent leaks. - -```mermaid -classDiagram -class block_applied_callback_info { -+callback -+connection -+it -+connect(sig, free_cont, cb) -} -class api_impl { -+active_block_applied_callback -+free_block_applied_callback -} -api_impl --> block_applied_callback_info : "manages" -``` - -**Diagram sources** -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) - -**Section sources** -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) - -### Debug Node Plugin Subscription -The debug_node plugin subscribes to applied_block to conditionally re-apply debug updates on block application. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant DN as "debug_node plugin.cpp" -DB->>DN : applied_block(block) -DN->>DN : on_applied_block()
apply_debug_updates() -``` - -**Diagram sources** -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) - -**Section sources** -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) - -### Network Broadcast API Plugin Signal Connection -The network broadcast API plugin uses Boost.Signals2 connections to integrate with chain events. The header declares a scoped_connection member for lifecycle management. - -```mermaid -classDiagram -class network_broadcast_api_plugin { -+on_applied_block_connection : scoped_connection -} -``` - -**Diagram sources** -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L78-L78) - -**Section sources** -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L78-L78) - -### CLI Wallet Signal Usage -The CLI wallet registers scoped_connection instances for various signals (e.g., websocket closed, quit command, lock state changes) to react to runtime events. - -```mermaid -sequenceDiagram -participant CLI as "cli_wallet main.cpp" -participant WS as "websocket" -CLI->>WS : closed.connect(...) -CLI->>CLI : quit_command.connect(...) -CLI->>CLI : lock_changed.connect(...) -``` - -**Diagram sources** -- [main.cpp](file://programs/cli_wallet/main.cpp#L180-L196) - -**Section sources** -- [main.cpp](file://programs/cli_wallet/main.cpp#L180-L196) - -### Webserver Plugin Chain Sync Connection -The webserver plugin creates a scoped_connection to monitor chain synchronization events. - -```mermaid -classDiagram -class webserver_plugin { -+chain_sync_con : connection -} -``` - -**Diagram sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L108-L108) - -**Section sources** -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L108-L108) - -## Dependency Analysis -The event-driven architecture exhibits loose coupling: -- Database publishes events without knowing subscribers. -- Plugins subscribe independently, enabling modular extensions. -- External applications (CLI, webserver) subscribe to chain plugin’s on_sync. - -```mermaid -graph LR -DB["database.hpp/.cpp"] --> |fc::signal| SUB1["block_info"] -DB --> |fc::signal| SUB2["database_api"] -DB --> |fc::signal| SUB3["debug_node"] -CH["chain plugin.hpp"] --> |Boost.Signals2| EXT1["cli_wallet"] -CH --> |Boost.Signals2| EXT2["webserver"] -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) -- [main.cpp](file://programs/cli_wallet/main.cpp#L180-L196) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L108-L108) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L89-L90) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp#L140-L142) -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L27-L51) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L131-L133) -- [main.cpp](file://programs/cli_wallet/main.cpp#L180-L196) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L108-L108) - -## Performance Considerations -High-frequency event loops (transactions, operations, blocks) require careful optimization: -- Minimize work inside event handlers: - - Defer heavy computations to background threads or queues. - - Use lightweight callbacks that primarily enqueue work. -- Batch updates: - - Group frequent notifications (e.g., multiple transactions) into batches. -- Connection lifecycle: - - Use scoped_connection/connection to avoid dangling callbacks and reduce cleanup overhead. -- Throttle UI/API updates: - - Rate-limit or debounce notifications to clients to prevent overload. -- Lock contention: - - Keep event handlers short; avoid holding database write locks for extended periods. -- Memory pressure: - - Monitor callback containers and clean up inactive subscriptions promptly. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and remedies: -- Handlers crash or throw: - - database_api wraps callbacks in try/catch and disconnects failing connections to keep the system stable. -- Stale or missing subscriptions: - - Ensure scoped_connection/connection remains alive for the handler’s lifetime; verify plugin initialization order. -- Deadlocks or long-running handlers: - - Move blocking work off the event thread; use async dispatch patterns. -- Excessive event volume: - - Apply rate limiting or filtering; consider bloom filters for subscription items. - -**Section sources** -- [database_api_plugin.cpp](file://plugins/database_api/api.cpp#L42-L49) - -## Conclusion -The VIZ node employs a robust event-driven architecture centered on fc::signal emissions from the chain database and Boost.Signals2-based synchronization in plugins. This design enables decoupled communication, modular plugin development, and scalable inter-component messaging. By adhering to best practices—such as short handlers, safe connection lifecycles, batching, and throttling—developers can maintain responsiveness under high-frequency event loads while preserving system stability. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Transaction Processing Pipeline.md b/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Transaction Processing Pipeline.md deleted file mode 100644 index 0feac2c849..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Data Flow and Processing/Transaction Processing Pipeline.md +++ /dev/null @@ -1,370 +0,0 @@ -# Transaction Processing Pipeline - - -**Referenced Files in This Document** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp) -- [transaction.cpp](file://libraries/protocol/transaction.cpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the transaction processing pipeline in the VIZ node from reception to final application. It covers validation stages (syntax, signatures, authority), operation processing via evaluators, and state application. It also documents the evaluator registry mechanism, transaction object lifecycle, error handling, rollback, and performance optimizations such as batch-like pending transaction handling and caching via the database’s internal caches. - -## Project Structure -The transaction pipeline spans protocol-level transaction definitions, chain-level validation and application, and evaluator dispatch. Key areas: -- Protocol: transaction definition, validation, signature verification, authority computation -- Chain: database orchestration, validation flags, pending transactions, block application -- Evaluators: operation-specific logic and state mutations -- Registry: mapping operations to evaluators - -```mermaid -graph TB -subgraph "Protocol Layer" -P1["transaction.hpp
transaction.cpp"] -P2["operations.hpp"] -end -subgraph "Chain Layer" -C1["database.hpp"] -C2["transaction_object.hpp"] -C3["evaluator.hpp"] -C4["evaluator_registry.hpp"] -C5["chain_evaluator.cpp"] -end -P1 --> C1 -P2 --> C1 -C1 --> C3 -C3 --> C4 -C4 --> C5 -C1 --> C2 -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) - -## Core Components -- Transaction model and validation: syntax and operation validation, signature digest and verification, authority computation and verification -- Evaluator framework: base evaluator interface, typed evaluator implementation, and registry for dispatch -- Database orchestration: transaction validation entry points, pending transaction handling, block application, and state mutation hooks -- Transaction object: duplicate detection and expiration indexing - -Key responsibilities: -- Protocol layer validates transaction structure and operation semantics and verifies signatures and authorities -- Chain layer coordinates validation and application, manages pending state, and applies operations atomically -- Evaluators implement operation-specific logic and state transitions -- Registry ensures correct evaluator selection per operation type - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L240-L357) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) - -## Architecture Overview -End-to-end flow: -- Reception: client or plugin submits a signed transaction -- Validation: protocol-level checks (syntax, operations), signature verification, authority verification -- Application: database applies operations via evaluators, emitting notifications and virtual operations -- Persistence: successful transactions become eligible for inclusion in blocks - -```mermaid -sequenceDiagram -participant Client as "Client" -participant DB as "database.hpp" -participant Proto as "transaction.cpp" -participant Reg as "evaluator_registry.hpp" -participant Eval as "chain_evaluator.cpp" -Client->>DB : push_transaction(trx) -DB->>Proto : validate_transaction(trx) -Proto-->>DB : validation result -DB->>DB : _validate_transaction(trx, flags) -DB->>Reg : get_evaluator(op) -Reg-->>DB : evaluator instance -DB->>Eval : evaluator.apply(op) -Eval-->>DB : state changes -DB-->>Client : result -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L468-L476) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) - -## Detailed Component Analysis - -### Transaction Validation Stages -- Syntax and operation validation: transaction must contain at least one operation; each operation is validated -- Signature verification: computes signature digest and verifies signatures; enforces uniqueness -- Authority verification: computes required authorities and validates provided signatures/authorities against thresholds and recursion limits - -```mermaid -flowchart TD -Start(["Receive signed_transaction"]) --> Syntax["Validate transaction structure
and operations"] -Syntax --> SigDigest["Compute sig_digest(chain_id)"] -SigDigest --> VerifySigs["Verify signatures and detect duplicates"] -VerifySigs --> ComputeAuth["Compute required authorities"] -ComputeAuth --> VerifyAuth["Verify authorities vs provided keys/approvals"] -VerifyAuth --> Done(["Validation OK"]) -VerifyAuth --> |Fail| Error["Throw validation exception"] -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L45-L56) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L225-L237) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L240-L316) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L45-L56) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L225-L237) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L240-L316) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) - -### Evaluator Registry Mechanism -- Registry stores a vector of evaluators indexed by operation type tag -- Registration binds operation type to evaluator implementation -- Dispatch selects evaluator by operation’s static_variant index - -```mermaid -classDiagram -class evaluator_registry { -+register_evaluator(EvaluatorType, args...) -+get_evaluator(op) evaluator --_op_evaluators : vector> -} -class evaluator { -<> -+apply(op) -+get_type() int -} -class evaluator_impl { -+apply(op) -+get_type() int --db() database& -} -evaluator_registry --> evaluator : "holds" -evaluator_impl ..|> evaluator -``` - -**Diagram sources** -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) - -**Section sources** -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) - -### Operation Processing and State Application -- Database coordinates validation and application -- Pending transactions are stored and later applied in block context -- Virtual operations may be emitted during evaluation -- Notifications are emitted pre/post operation and post block - -```mermaid -sequenceDiagram -participant DB as "database.hpp" -participant Reg as "evaluator_registry.hpp" -participant Eval as "chain_evaluator.cpp" -participant Notif as "Notifications" -DB->>DB : apply_transaction(trx) -loop for each operation -DB->>Reg : get_evaluator(op) -Reg-->>DB : evaluator -DB->>Eval : evaluator.do_apply(op) -Eval-->>DB : state changes -DB->>Notif : notify_pre/post_apply_operation -end -DB->>Notif : notify_applied_block -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L468-L478) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L238-L263) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L468-L478) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L238-L263) - -### Transaction Object Lifecycle -- Creation: upon receipt, transactions may be recorded to prevent duplicates -- Expiration: transactions are tracked by expiration time -- Cleanup: expired entries are removed at block processing boundaries - -```mermaid -flowchart TD -A["Receive trx"] --> B["Store in transaction_index
with expiration"] -B --> C{"Expired?"} -C --> |No| D["Pending until block inclusion"] -C --> |Yes| E["Remove from index"] -D --> F["Applied in block -> remove"] -``` - -**Diagram sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) - -**Section sources** -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) - -### Error Handling and Rollback -- Validation throws exceptions on failures (syntax, signatures, authority) -- Authority verification uses assertions to enforce thresholds and detect unused approvals/signatures -- Database maintains sessions and undo history; on reindex or errors, state can be rolled back -- Pending transactions cache allows reapplication after popping blocks - -```mermaid -flowchart TD -Start(["Validation"]) --> Check["Syntax/Operations OK?"] -Check --> |No| Throw["Throw exception"] -Check --> |Yes| Sig["Signatures verified?"] -Sig --> |No| Throw -Sig --> Auth["Authority satisfied?"] -Auth --> |No| Throw -Auth --> Apply["Apply operations"] -Apply --> Err{"Exception during apply?"} -Err --> |Yes| Undo["Rollback via undo history"] -Err --> |No| Commit["Commit changes"] -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L466-L472) - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L466-L472) - -### Example: Transfer Operation Evaluation -- Demonstrates typical evaluator pattern: fetch accounts, validate balances, adjust assets, emit virtual operations if applicable - -```mermaid -sequenceDiagram -participant DB as "database.hpp" -participant Eval as "transfer_evaluator : : do_apply" -DB->>Eval : apply(transfer_operation) -Eval->>DB : get_account(from) -Eval->>DB : get_account(to) -Eval->>DB : adjust_balance(from, -amount) -Eval->>DB : adjust_balance(to, +amount) -Eval-->>DB : done -``` - -**Diagram sources** -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L857-L950) - -**Section sources** -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L857-L950) - -## Dependency Analysis -- Protocol depends on operations static variant to enumerate supported operations -- Database orchestrates validation and application and holds evaluator registry -- Evaluators depend on database for state queries and mutations -- Transaction object depends on protocol transaction types - -```mermaid -graph LR -OP["operations.hpp"] --> EV["evaluator.hpp"] -EV --> ER["evaluator_registry.hpp"] -ER --> CE["chain_evaluator.cpp"] -TR["transaction.cpp"] --> DB["database.hpp"] -DB --> CE -DB --> TO["transaction_object.hpp"] -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) - -## Performance Considerations -- Batch-like processing: pending transactions are accumulated and applied together during block processing, reducing repeated validations -- Caching: database caches frequently accessed state (accounts, validators, etc.) to avoid repeated lookups -- Validation flags: skip flags allow bypassing expensive checks during reindex or trusted contexts -- Indexing: transaction index supports duplicate detection and expiration cleanup - -Recommendations: -- Prefer local transaction submission with minimal signatures to reduce verification overhead -- Use appropriate skip flags only when safe (e.g., during reindex) -- Monitor shared memory growth and tune flush intervals for block production nodes - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L423-L423) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) - -## Troubleshooting Guide -Common issues and diagnostics: -- Transaction rejected due to missing or extra signatures: verify_authority enforces unused signature detection and throws explicit errors -- Insufficient authority: verify_authority enforces required active/master/regular thresholds and missing approvals -- Operation validation failure: transaction.validate triggers per-operation validation; inspect operation content and semantics -- Duplicate transaction: transaction_object prevents duplicate inclusion; check expiration and packing logic -- Pending transaction not applied: ensure node is not skipping transaction application or is not stalled on undo history - -Actions: -- Enable verbose logs around validation and application -- Temporarily disable skip flags for diagnosis -- Confirm authority getters return expected keys and weights -- Verify chain ID and reference block fields are correct - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L76-L92) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L105-L222) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction_object.hpp](file://libraries/chain/include/graphene/chain/transaction_object.hpp#L19-L49) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L200-L206) - -## Conclusion -The VIZ node implements a robust transaction pipeline with layered validation, a flexible evaluator registry, and careful state application. Protocol-level checks ensure syntactic correctness and cryptographic integrity, while authority verification enforces governance rules. The database coordinates validation and application, supports notifications and virtual operations, and leverages caching and indexing for performance. Proper use of skip flags, careful authority configuration, and monitoring of pending state help maintain reliability and throughput. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Design Patterns and Architectural Decisions.md b/.qoder/repowiki/en/content/Architecture Overview/Design Patterns and Architectural Decisions.md deleted file mode 100644 index c821194a29..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Design Patterns and Architectural Decisions.md +++ /dev/null @@ -1,387 +0,0 @@ -# Design Patterns and Architectural Decisions - - -**Referenced Files in This Document** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp) -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [main.cpp](file://programs/vizd/main.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Security and Monitoring](#security-and-monitoring) -9. [Scalability and Extensibility](#scalability-and-extensibility) -10. [Troubleshooting Guide](#troubleshooting-guide) -11. [Conclusion](#conclusion) - -## Introduction -This document explains the design patterns and architectural decisions that define the VIZ C++ node. It focuses on the MVC-like separation between data (database), control (plugins), and view (APIs), event-driven architecture using signals, factory and strategy patterns for evaluators, and a plugin-based architecture. It also analyzes trade-offs, performance characteristics, maintainability, extensibility, security, monitoring, and scalability considerations. - -## Project Structure -The system is organized around three primary layers: -- Data layer: chain database and object model -- Control layer: plugins that orchestrate lifecycle, validation, and dispatch -- View layer: APIs exposed by plugins - -```mermaid -graph TB -subgraph "Programs" -VIZD["vizd main()
initializes app and plugins"] -end -subgraph "Plugins" -ChainPlugin["Chain Plugin
accepts blocks/txns, exposes DB"] -P2P["P2P Plugin"] -WebServer["Webserver Plugin"] -end -subgraph "Chain Library" -DB["Database
fork DB, block log, indexes"] -EvalReg["Evaluator Registry
factory for evaluators"] -Ops["Operations Type
static_variant"] -end -subgraph "Network Library" -NetNode["Network Node
peer management, sync"] -end -VIZD --> ChainPlugin -VIZD --> P2P -VIZD --> WebServer -ChainPlugin --> DB -ChainPlugin --> EvalReg -EvalReg --> Ops -P2P --> NetNode -NetNode --> ChainPlugin -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) - -## Core Components -- Database: central state machine managing chain state, indexes, fork database, block log, and event emissions for operations and blocks. -- Evaluator system: strategy/factory for applying operations to state. -- Plugins: lifecycle-managed modules that expose APIs, integrate with the database, and coordinate with the network. -- Network node: peer-to-peer synchronization and message propagation. - -Key responsibilities: -- Data (Database): persistence, validation, indexing, and event signaling. -- Control (Plugins): initialization, startup/shutdown, block/txn acceptance, and API exposure. -- View (APIs): presentation of chain state via plugin-provided endpoints. - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) - -## Architecture Overview -The system follows an event-driven, plugin-based architecture: -- The database emits signals for pre/post operation application, applied blocks, and pending/applied transactions. -- Plugins subscribe to these signals to implement cross-cutting concerns (e.g., API indexing, history tracking). -- The evaluator registry acts as a factory mapping operation types to specialized evaluators. -- The main executable initializes the application and registers plugins. - -```mermaid -sequenceDiagram -participant App as "Application" -participant Chain as "Chain Plugin" -participant DB as "Database" -participant Reg as "Evaluator Registry" -participant Eval as "Concrete Evaluator" -App->>Chain : "accept_block()/accept_transaction()" -Chain->>DB : "validate_* / push_*" -DB->>DB : "pre_apply_operation signal" -DB->>Reg : "get_evaluator(op)" -Reg-->>DB : "evaluator instance" -DB->>Eval : "apply(op)" -Eval-->>DB : "state changes" -DB->>DB : "post_apply_operation signal" -DB->>DB : "applied_block/on_applied_transaction signals" -DB-->>Chain : "callbacks processed by plugins" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L96-L121) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L29-L37) - -## Detailed Component Analysis - -### MVC-like Separation: Data, Control, View -- Data (Database): encapsulates chain state, indexes, fork database, block log, and validation logic. It exposes getters, push operations, and signals for observers. -- Control (Plugins): manage lifecycle, accept blocks/transactions, and coordinate with the database and network. -- View (APIs): provided by plugins that expose endpoints backed by the database. - -```mermaid -graph LR -DB["Database"] -PL["Plugins"] -API["API Layer"] -PL --> DB -API --> DB -API --> PL -``` - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L111-L287) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) - -### Event-Driven Architecture with Signals -The database emits signals for: -- Pre/post operation application -- Applied block -- Pending/applied transactions - -Plugins subscribe to these signals to implement features like API indexing, history tracking, and metrics. - -```mermaid -sequenceDiagram -participant DB as "Database" -participant Sub as "Subscriber Plugin" -DB->>DB : "notify_pre_apply_operation(note)" -DB-->>Sub : "pre_apply_operation signal" -DB->>DB : "notify_post_apply_operation(note)" -DB-->>Sub : "post_apply_operation signal" -DB->>DB : "notify_applied_block(block)" -DB-->>Sub : "applied_block signal" -DB->>DB : "notify_on_applied_transaction(tx)" -DB-->>Sub : "on_applied_transaction signal" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L275) - -### Factory Pattern for Object Creation (Evaluator Registry) -The evaluator registry is a factory that: -- Initializes a vector sized by the number of operation types -- Registers evaluators by operation type tag -- Retrieves the appropriate evaluator for an operation - -```mermaid -classDiagram -class EvaluatorRegistry { -+register_evaluator(EvaluatorType, args...) -+get_evaluator(Operation) evaluator --_op_evaluators : vector> -} -class Evaluator { -<> -+apply(op) -+get_type() int -} -class EvaluatorImpl { -+apply(op) -+get_type() int --db() database& -} -EvaluatorRegistry --> Evaluator : "stores" -EvaluatorImpl ..|> Evaluator -``` - -**Diagram sources** -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) - -**Section sources** -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) - -### Strategy Pattern for Evaluation Strategies -Each operation type has a dedicated evaluator implementation. The strategy is selected dynamically by the registry based on the operation’s static variant index. This enables: -- Clear separation of concerns per operation -- Easy addition of new operations and evaluators -- Testability and isolation of evaluation logic - -```mermaid -flowchart TD -Start(["Receive Operation"]) --> GetTag["Get operation tag"] -GetTag --> Lookup["Lookup evaluator in registry"] -Lookup --> Found{"Evaluator found?"} -Found --> |Yes| Apply["Call evaluator.do_apply(op)"] -Found --> |No| Error["Assert/throw 'No registered evaluator'"] -Apply --> End(["State updated"]) -Error --> End -``` - -**Diagram sources** -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L23-L36) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -**Section sources** -- [chain_evaluator.hpp](file://libraries/chain/include/graphene/chain/chain_evaluator.hpp#L14-L79) -- [chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp#L52-L141) - -### Plugin-Based Architecture -The application uses a plugin framework to modularize functionality: -- Plugins declare dependencies and lifecycle hooks -- The main executable registers and starts plugins -- Plugins expose APIs and interact with the database - -```mermaid -sequenceDiagram -participant Main as "vizd main" -participant App as "AppBase Application" -participant Chain as "Chain Plugin" -participant DB as "Database" -Main->>App : "register_plugin()" -Main->>App : "initialize(...)" -App->>Chain : "plugin_initialize(options)" -App->>Chain : "plugin_startup()" -Chain->>DB : "db.open()/db.reindex()" -App-->>Main : "exec() loop" -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L36-L42) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L181) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L181) - -### Network Synchronization and Peer Coordination -The network node provides: -- Peer discovery and connection management -- Block and transaction propagation -- Sync protocols and bandwidth controls - -```mermaid -sequenceDiagram -participant Node as "Network Node" -participant Peer as "Peer" -participant Chain as "Chain Plugin" -Node->>Peer : "connect_to_endpoint()" -Node->>Peer : "broadcast(block/trx)" -Peer-->>Node : "handle_block()/handle_transaction()" -Node->>Chain : "delegate.handle_block()/handle_transaction()" -Chain-->>Node : "validation result" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L79-L88) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L96-L121) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L96-L121) - -## Dependency Analysis -- The database depends on the evaluator registry and operation types to apply state changes. -- Plugins depend on the database and network node to accept blocks/transactions and propagate them. -- The main executable orchestrates plugin registration and lifecycle. - -```mermaid -graph TD -Main["vizd main.cpp"] --> Chain["Chain Plugin"] -Main --> P2P["P2P Plugin"] -Chain --> DB["Database"] -Chain --> Reg["Evaluator Registry"] -Reg --> Ops["Operations"] -P2P --> Net["Network Node"] -Net --> Chain -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) -- [evaluator_registry.hpp](file://libraries/chain/include/graphene/chain/evaluator_registry.hpp#L8-L40) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) - -## Performance Considerations -- C++ choice: provides low-level control, predictable performance, and efficient memory usage, suitable for high-throughput blockchain operations. -- Shared memory database: chainbase-backed database with configurable shared memory sizing and incremental growth to reduce IO overhead. -- Signal-based notifications: minimal coupling between components; however, subscribers must avoid heavy work in callbacks to prevent write-lock contention. -- Single write thread option: optional serialization of write operations via the application’s io_service to simplify concurrency control at the cost of throughput. -- Evaluator strategy: static variant dispatch minimizes branching overhead; registering evaluators once reduces runtime lookup costs. - -Trade-offs: -- Performance vs. safety: skipping validations (e.g., signatures) can improve speed but risks chain integrity. -- Throughput vs. simplicity: single write thread simplifies correctness but may bottleneck writes under load. - -**Section sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L106-L121) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L56-L73) -- [database.cpp](file://libraries/chain/database.cpp#L198-L200) - -## Security and Monitoring -- Logging: centralized logging configuration via INI sections for console and file appenders; supports JSON console logging and rotation. -- Signal handling: signal guard sets up handlers for graceful shutdown and interruption. -- Validation: strict validation steps and hardfork-aware checks ensure consensus correctness. -- API exposure: plugins expose endpoints; monitor and rate-limit as needed at the webserver layer. - -Monitoring highlights: -- Logging configuration supports multiple appenders and loggers. -- Signals for applied operations and blocks enable real-time analytics. - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L167-L288) -- [database.cpp](file://libraries/chain/database.cpp#L134-L184) - -## Scalability and Extensibility -- Horizontal scaling: P2P network layer supports multiple peers and sync strategies; block and transaction propagation scales with peer count. -- Vertical scaling: shared memory database sizing and incremental growth support larger datasets; plugin architecture allows selective feature loading. -- Extensibility: new operations and evaluators can be added with minimal changes; plugins can subscribe to signals for cross-cutting features. - -Considerations: -- Shared memory limits: tune shared-file-size and increments to accommodate growth. -- Plugin selection: load only required plugins to reduce memory footprint. -- Network topology: optimize peer selection and bandwidth limits for large networks. - -**Section sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L183-L200) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L290-L295) - -## Troubleshooting Guide -Common areas to inspect: -- Database initialization and reindexing failures -- Plugin startup/shutdown errors -- Signal handler exceptions during shutdown -- Transaction/Block acceptance errors (time checks, validation flags) - -Actions: -- Review logs configured via INI sections -- Enable/disable specific validation steps for diagnostics -- Use wipe/replay utilities for corrupted states -- Verify plugin dependencies and signal subscriptions - -**Section sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L134-L146) -- [database.cpp](file://libraries/chain/database.cpp#L134-L184) -- [main.cpp](file://programs/vizd/main.cpp#L167-L288) - -## Conclusion -The VIZ C++ node employs a robust, event-driven, plugin-based architecture with clear separation of concerns. The database-centric design, combined with a factory/strategy evaluator system and strong plugin lifecycle management, yields a highly extensible and maintainable platform. Performance is optimized through C++, shared memory storage, and careful validation controls, while the P2P layer supports scalable horizontal growth. Security and observability are addressed through structured logging and signal handling, enabling reliable operations in production environments. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Custom Plugin Development.md b/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Custom Plugin Development.md deleted file mode 100644 index 14da93b61e..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Custom Plugin Development.md +++ /dev/null @@ -1,448 +0,0 @@ -# Custom Plugin Development - - -**Referenced Files in This Document** -- [newplugin.py](file://programs/util/newplugin.py) -- [plugin.md](file://documentation/plugin.md) -- [CMakeLists.txt](file://plugins/CMakeLists.txt) -- [config.ini](file://share/vizd/config/config.ini) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [account_history_plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [database_api_plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp) -- [testing.md](file://documentation/testing.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains how to develop custom plugins from scratch in the project’s plugin framework. It covers: -- The plugin template system and how to generate boilerplate code using the provided generator -- The plugin class structure, required methods, and interface implementation patterns -- Step-by-step tutorials for three plugin types: API plugins, database plugins, and network plugins -- The development workflow from template generation to deployment and testing -- Configuration options, command-line parameter handling, and integration with the main application -- Testing strategies, unit testing patterns, and integration testing approaches -- Practical examples of common plugin patterns and anti-patterns -- Guidelines for packaging, distribution, and version management -- Debugging techniques and common development issues - -## Project Structure -Plugins are organized under the plugins directory. Each plugin typically includes: -- A header file defining the plugin class and API declarations -- An implementation file implementing plugin lifecycle and API methods -- Optional API object headers for serialization and data structures -- A CMakeLists.txt to integrate the plugin into the build system -- A dedicated include/graphene/plugins// directory for public headers - -The top-level plugin registry is driven by a CMake script that discovers subdirectories and registers available plugins. Third-party plugins can be dropped into an external_plugins directory and built alongside internal ones. - -```mermaid -graph TB -subgraph "Build System" -PL_CMAKE["plugins/CMakeLists.txt"] -GEN["programs/util/newplugin.py"] -end -subgraph "Plugin Registry" -REG["Internal Plugins
CHAIN_INTERNAL_PLUGINS"] -EXT["External Plugins
external_plugins"] -end -subgraph "Example Plugins" -TEST_API["test_api_plugin.hpp/.cpp"] -WEB["webserver_plugin.hpp"] -P2P["p2p_plugin.hpp"] -MONGO["mongo_db_plugin.hpp"] -CHAIN["chain_plugin.hpp"] -JSONRPC["json_rpc_plugin.hpp"] -DBAPI["database_api_plugin.hpp"] -NB["network_broadcast_api_plugin.hpp"] -end -GEN --> |"Generates"| TEST_API -PL_CMAKE --> |"Scans subdirs"| REG -EXT --> |"Discovered by"| REG -TEST_API --> |"Requires"| JSONRPC -WEB --> |"Requires"| JSONRPC -P2P --> |"Requires"| CHAIN -MONGO --> |"Requires"| CHAIN -DBAPI --> |"Requires"| CHAIN -NB --> |"Requires"| CHAIN -NB --> |"Requires"| P2P -``` - -**Diagram sources** -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L1-L61) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L1-L62) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L1-L57) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L1-L100) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) -- [database_api_plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L1-L429) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L1-L91) - -**Section sources** -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [plugin.md](file://documentation/plugin.md#L1-L28) - -## Core Components -- Plugin template generator: A Python script that scaffolds a new plugin with standard files and boilerplate code. -- Plugin base class and lifecycle: Plugins derive from the application plugin base and implement initialization, startup, shutdown, and optional API registration. -- JSON-RPC integration: Many plugins depend on and register APIs with the JSON-RPC plugin to expose RPC endpoints. -- Chain integration: Plugins may subscribe to chain events (e.g., applied block signals) and access the database through the chain plugin. -- Configuration and CLI options: Plugins define program options and read configuration values during initialization. - -Key implementation patterns: -- Use APPBASE_PLUGIN_REQUIRES to declare dependencies on other plugins -- Implement plugin_initialize to parse options and register APIs -- Implement plugin_startup to connect signals and finalize initialization -- Implement plugin_shutdown to clean up resources -- Expose API methods via DECLARE_API and DEFINE_API macros - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L1-L100) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L1-L61) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) - -## Architecture Overview -The plugin architecture centers around appbase, with plugins registering themselves and APIs with the JSON-RPC dispatcher. Plugins commonly depend on the chain plugin for database access and on p2p for networking. - -```mermaid -graph TB -subgraph "Application Layer" -APP["Application"] -CFG["Config Options"] -end -subgraph "Core Plugins" -CHAIN["chain::plugin"] -JSONRPC["json_rpc::plugin"] -end -subgraph "Feature Plugins" -API["database_api::plugin"] -NB["network_broadcast_api::plugin"] -P2P["p2p::plugin"] -WS["webserver::plugin"] -TEST["test_api::plugin"] -MONGO["mongo_db::plugin"] -end -APP --> CHAIN -APP --> JSONRPC -API --> CHAIN -API --> JSONRPC -NB --> CHAIN -NB --> P2P -NB --> JSONRPC -P2P --> CHAIN -WS --> JSONRPC -TEST --> JSONRPC -MONGO --> CHAIN -CFG --> APP -``` - -**Diagram sources** -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L1-L100) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) -- [database_api_plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L1-L429) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L1-L91) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L1-L57) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L1-L62) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L1-L61) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) - -## Detailed Component Analysis - -### Plugin Template System -The template generator creates a complete plugin scaffold with: -- A plugin header declaring the plugin class and required methods -- An API header declaring the API class and FC_API method list -- Implementation files for both plugin and API -- A CMakeLists.txt fragment for library integration - -The generator accepts provider and plugin name arguments and writes files into libraries/plugins/. - -```mermaid -flowchart TD -Start(["Run newplugin.py"]) --> ParseArgs["Parse provider and plugin name"] -ParseArgs --> BuildCtx["Build context dict"] -BuildCtx --> IterateTemplates["Iterate template dictionary"] -IterateTemplates --> FormatContent["Format template content"] -FormatContent --> EnsureDir["Ensure output directory exists"] -EnsureDir --> WriteFiles["Write files to disk"] -WriteFiles --> Done(["Scaffold ready"]) -``` - -**Diagram sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L251) - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L1-L251) - -### Plugin Class Structure and Lifecycle -A typical plugin class follows this structure: -- Public plugin class deriving from the application plugin base -- Private implementation class encapsulating logic -- Required methods: plugin_name, plugin_initialize, plugin_startup, plugin_shutdown -- Optional: signal connections and API registration - -```mermaid -classDiagram -class PluginBase { -<> -+plugin_name() string -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -} -class MyPlugin { --my : PluginImpl -+plugin_name() string -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -} -class PluginImpl { --app : Application -+plugin_name() string -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+on_applied_block(b) -} -PluginBase <|-- MyPlugin -MyPlugin --> PluginImpl : "owns" -``` - -**Diagram sources** -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L27-L53) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L23) - -**Section sources** -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L1-L61) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) - -### API Plugin Pattern -API plugins expose RPC endpoints via the JSON-RPC plugin. They: -- Define argument/result structs -- Declare APIs with DECLARE_API and implement with DEFINE_API -- Register themselves during plugin_initialize using JSON_RPC_REGISTER_API - -```mermaid -sequenceDiagram -participant App as "Application" -participant Test as "test_api_plugin" -participant RPC as "json_rpc : : plugin" -App->>Test : plugin_initialize(options) -Test->>RPC : JSON_RPC_REGISTER_API("test_api") -App->>Test : plugin_startup() -Test->>RPC : add_api_method("test_api_a", ...) -Test->>RPC : add_api_method("test_api_b", ...) -Note over Test,App : API methods now callable via JSON-RPC -``` - -**Diagram sources** -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L35) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L109-L113) - -**Section sources** -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L23-L53) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) - -### Database Plugin Pattern -Database plugins typically: -- Require the chain plugin -- Subscribe to chain events (e.g., applied block) -- Access the database through the chain plugin’s database interface -- Optionally maintain their own indices or state - -```mermaid -sequenceDiagram -participant Chain as "chain : : plugin" -participant Mongo as "mongo_db : : plugin" -participant DB as "Database" -Chain-->>Mongo : applied_block(signed_block) -Mongo->>DB : read/write operations -Mongo-->>Chain : signal handling complete -``` - -**Diagram sources** -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) - -**Section sources** -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L1-L100) - -### Network Plugin Pattern -Network plugins: -- Depend on the chain plugin for blockchain data -- May broadcast blocks/transactions via the p2p plugin -- Often expose APIs for broadcasting and synchronization - -```mermaid -sequenceDiagram -participant NB as "network_broadcast_api : : plugin" -participant P2P as "p2p : : plugin" -participant Chain as "chain : : plugin" -NB->>P2P : broadcast_transaction(...) -NB->>P2P : broadcast_block(...) -P2P-->>NB : ack/nack -NB->>Chain : accept_transaction(...) (via chain plugin) -``` - -**Diagram sources** -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L47-L83) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L44-L46) - -**Section sources** -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L1-L91) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L1-L57) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L1-L100) - -## Dependency Analysis -Plugins declare dependencies using APPBASE_PLUGIN_REQUIRES. The JSON-RPC plugin is central for exposing APIs. The chain plugin provides database access and event signals. Network plugins typically depend on p2p and chain. - -```mermaid -graph LR -JSONRPC["json_rpc::plugin"] --> TEST["test_api::plugin"] -JSONRPC --> DBAPI["database_api::plugin"] -JSONRPC --> WS["webserver::plugin"] -CHAIN["chain::plugin"] --> DBAPI -CHAIN --> MONGO["mongo_db::plugin"] -CHAIN --> NB["network_broadcast_api::plugin"] -CHAIN --> P2P["p2p::plugin"] -P2P --> NB -``` - -**Diagram sources** -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L35) -- [database_api_plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L17-L19) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L49-L49) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L20) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L23-L23) - -**Section sources** -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L35) -- [database_api_plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L17-L19) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L49-L49) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L20) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L23-L23) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) - -## Performance Considerations -- Minimize database contention by using single-write-thread options and tuned lock retries -- Avoid heavy operations in signal handlers; defer to background tasks when possible -- Use appropriate indexing and caching strategies in database plugins -- Keep API methods efficient; avoid blocking operations in RPC handlers - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Plugin not loading: Verify plugin is enabled in configuration and discovered by the build system -- API not available: Ensure JSON-RPC registration occurs in plugin_initialize and that public-api is configured if needed -- Chain-dependent plugin failing: Confirm chain plugin is enabled and that replay may be required when toggling history-related plugins -- Lock contention: Adjust read/write wait retries and consider single-write-thread mode - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) -- [config.ini](file://share/vizd/config/config.ini#L13-L47) - -## Conclusion -The project provides a robust, extensible plugin framework. By leveraging the template generator, following the plugin lifecycle, and integrating with core plugins (JSON-RPC, chain, p2p), developers can build API, database, and network plugins efficiently. Proper configuration, testing, and performance tuning ensure reliable deployments. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Step-by-Step Tutorial: API Plugin -1. Generate the plugin scaffold: - - Run the generator with provider and plugin name - - Review and customize the generated files -2. Implement API methods: - - Define argument/result structs - - Declare APIs in the plugin header - - Implement methods in the plugin source -3. Register the API: - - Call JSON-RPC registration in plugin_initialize -4. Configure and run: - - Enable the plugin in configuration - - Start the node and test via JSON-RPC - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L251) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L23-L53) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L35) -- [plugin.md](file://documentation/plugin.md#L21-L28) - -### Step-by-Step Tutorial: Database Plugin -1. Generate the plugin scaffold -2. Add chain dependency and database access patterns -3. Subscribe to chain events (e.g., applied block) -4. Implement persistence logic and indexing -5. Register and enable the plugin - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L251) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L17-L19) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) - -### Step-by-Step Tutorial: Network Plugin -1. Generate the plugin scaffold -2. Add dependencies on chain and p2p -3. Implement broadcast methods and event handling -4. Integrate with network_broadcast_api if exposing RPC - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L251) -- [network_broadcast_api_plugin.hpp](file://plugins/network_broadcast_api/include/graphene/plugins/network_broadcast_api/network_broadcast_api_plugin.hpp#L49-L49) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L20) - -### Plugin Configuration and CLI -- Enable plugins via configuration entries -- Use public-api and api-user for access control -- Configure thread pools and endpoints for webserver plugins -- Adjust chain-related options affecting plugin behavior - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -### Testing Strategies -- Unit tests: Use the chain_test target and categorize tests -- Runtime configuration: Control logging and reporting via test harness options -- Code coverage: Enable coverage builds and generate HTML reports - -**Section sources** -- [testing.md](file://documentation/testing.md#L1-L43) - -### Packaging and Distribution -- Place third-party plugins under external_plugins and build with the same process -- Ensure each plugin includes a CMakeLists.txt and proper include layout -- Version management: Align plugin versions with the project’s release cycle - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L7-L7) -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Inter-Plugin Communication.md b/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Inter-Plugin Communication.md deleted file mode 100644 index a95daf87f5..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Inter-Plugin Communication.md +++ /dev/null @@ -1,483 +0,0 @@ -# Inter-Plugin Communication - - -**Referenced Files in This Document** -- [main.cpp](file://programs/vizd/main.cpp) -- [CMakeLists.txt](file://plugins/CMakeLists.txt) -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp) -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp) -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp) -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [api.cpp (database_api)](file://plugins/database_api/api.cpp) - - -## Update Summary -**Changes Made** -- Enhanced memory management coordination between account_history and operation_history plugins -- Added purging coordination mechanisms to prevent dangling references -- Improved signal handler management with proper cleanup procedures -- Updated plugin dependency graph to reflect enhanced coordination patterns - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Memory Management Coordination](#memory-management-coordination) -7. [Dependency Analysis](#dependency-analysis) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction -This document explains inter-plugin communication patterns and mechanisms in the codebase built on the appbase framework. It focuses on: -- How plugins declare and consume dependencies via appbase's dependency injection system -- Event-driven communication using Boost.Signals2 signals and slots -- Practical examples of signal emission and subscription across plugins -- How plugins expose APIs to other plugins and establish communication channels -- The plugin dependency graph and initialization order -- Best practices for loose coupling and avoiding circular dependencies -- Thread-safety considerations and synchronization mechanisms -- **Enhanced memory management coordination between plugins for efficient resource utilization** - -## Project Structure -The application initializes a set of plugins and wires them together through appbase. Plugins declare required dependencies, and the runtime ensures they are initialized in dependency order. The main entrypoint registers and starts selected plugins. - -```mermaid -graph TB -Main["programs/vizd/main.cpp
Registers and starts plugins"] --> Chain["plugins/chain/plugin.hpp
Provides chain database and signals"] -Main --> P2P["plugins/p2p/p2p_plugin.hpp
Requires chain"] -Main --> Web["plugins/webserver/webserver_plugin.hpp
Requires json_rpc"] -Main --> DBAPI["plugins/database_api/plugin.hpp
Requires chain and json_rpc"] -Main --> OpHist["plugins/operation_history/plugin.hpp
Requires chain and json_rpc"] -Main --> AccHist["plugins/account_history/plugin.hpp
Requires chain, operation_history, json_rpc"] -Chain --> DBAPI -Chain --> OpHist -Chain --> AccHist -Chain --> P2P -Web --> DBAPI -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugin.hpp (p2p_plugin.hpp)](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L21) -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L39) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L192) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L52-L58) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L66) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -## Core Components -- Plugin registration and startup: The main entrypoint registers all plugins and starts a configured subset. -- Dependency declaration: Plugins declare required plugins via APPBASE_PLUGIN_REQUIRES, enabling automatic initialization and injection. -- Signals and slots: Plugins emit and subscribe to Boost.Signals2 signals for event-driven communication. -- API exposure: Plugins expose read-only APIs (e.g., database queries) to other plugins and clients. -- **Memory management coordination: Enhanced coordination between plugins to prevent memory leaks and dangling references.** - -Key implementation references: -- Plugin registration and startup sequence - - [main.cpp](file://programs/vizd/main.cpp#L62-L90) - - [main.cpp](file://programs/vizd/main.cpp#L117-L122) -- Dependency declarations - - [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) - - [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) - - [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L53-L58) - - [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L60-L66) - - [plugin.hpp (p2p_plugin.hpp)](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L21) - - [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L39) -- Signals and slots - - [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) - - [api.cpp (database_api)](file://plugins/database_api/api.cpp#L35) -- **Memory management coordination** - - [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L107-L122) - - [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L94-L163) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L53-L58) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L60-L66) -- [plugin.hpp (p2p_plugin.hpp)](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L21) -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L39) -- [api.cpp (database_api)](file://plugins/database_api/api.cpp#L35) -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L107-L122) -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L94-L163) - -## Architecture Overview -The system composes plugins around the chain plugin as a central dependency provider. Other plugins depend on chain and/or json_rpc to expose APIs and coordinate events. **Enhanced memory management coordination ensures efficient resource utilization across plugins.** - -```mermaid -graph TB -subgraph "Runtime" -App["appbase::application"] -Chain["chain::plugin"] -JsonRpc["json_rpc::plugin"] -DBAPI["database_api::plugin"] -OpHist["operation_history::plugin"] -AccHist["account_history::plugin"] -P2P["p2p::p2p_plugin"] -Web["webserver::webserver_plugin"] -end -App --> Chain -App --> JsonRpc -App --> DBAPI -App --> OpHist -App --> AccHist -App --> P2P -App --> Web -DBAPI --> Chain -OpHist --> Chain -AccHist --> Chain -AccHist --> OpHist -P2P --> Chain -Web --> JsonRpc -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L53-L58) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L60-L66) -- [plugin.hpp (p2p_plugin.hpp)](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L21) -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L39) - -## Detailed Component Analysis - -### Chain Plugin: Central Dependency Provider and Signal Emitter -- Purpose: Provides the blockchain database and emits synchronization signals. -- Signals: - - on_sync: emitted when the blockchain is syncing/live; useful for plugins that optionally depend on chain state. -- Dependencies: - - Requires json_rpc::plugin for RPC transport. - -```mermaid -classDiagram -class ChainPlugin { -+on_sync : "signal" -+plugin_initialize(...) -+plugin_startup() -+accept_block(...) -+accept_transaction(...) -+db() : "database&" -} -``` - -**Diagram sources** -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L91) - -**Section sources** -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L91) - -### Database API Plugin: API Exposure and Callback Subscription -- Purpose: Exposes read-only database queries and subscriptions to clients and other plugins. -- Dependencies: - - Requires chain::plugin and json_rpc::plugin. -- Subscriptions: - - set_block_applied_callback: allows plugins to receive notifications when blocks are applied. - - set_subscribe_callback and set_pending_transaction_callback: for streaming updates. -- Example usage patterns: - - Subscribe to block-applied events from another plugin via the callback mechanism. - - Query chain state through the chain database accessor exposed by chain::plugin. - -```mermaid -classDiagram -class DatabaseAPIPlugin { -+set_block_applied_callback(cb) -+set_subscribe_callback(cb, clear_filter) -+set_pending_transaction_callback(cb) -+cancel_all_subscriptions() -+plugin_initialize(...) -+plugin_startup() -} -DatabaseAPIPlugin --> ChainPlugin : "requires" -DatabaseAPIPlugin --> JsonRpcPlugin : "requires" -``` - -**Diagram sources** -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L226) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) - -**Section sources** -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L226) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) - -### Operation History Plugin: Event Producer for Operations -- Purpose: Tracks operations and exposes APIs to query operations in blocks and transactions. -- Dependencies: - - Requires chain::plugin and json_rpc::plugin. -- Purging mechanism: - - Implements automatic purging based on history_count_blocks configuration. - - Provides get_min_keep_block() API for other plugins to coordinate purging. -- Typical usage: - - Used by account_history to maintain per-account histories. - -```mermaid -classDiagram -class OperationHistoryPlugin { -+get_ops_in_block(...) -+get_transaction(...) -+get_min_keep_block() : "uint32_t" -+purge_old_history() -+plugin_initialize(...) -+plugin_startup() -} -OperationHistoryPlugin --> ChainPlugin : "requires" -OperationHistoryPlugin --> JsonRpcPlugin : "requires" -``` - -**Diagram sources** -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L52-L83) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L53-L58) -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L99-L122) - -**Section sources** -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L52-L83) -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L99-L122) - -### Account History Plugin: Consumer of Operation History Events -- Purpose: Maintains per-account operation histories by subscribing to operation streams. -- Dependencies: - - Requires chain::plugin, operation_history::plugin, and json_rpc::plugin. -- Purging coordination: - - Coordinates with operation_history plugin to avoid dangling references. - - Uses get_min_keep_block() from operation_history plugin for aggressive purging. -- Typical usage: - - Subscribes to operation updates and records them for later retrieval. - -```mermaid -classDiagram -class AccountHistoryPlugin { -+get_account_history(...) -+tracked_accounts() -+purge_old_history() -+plugin_initialize(...) -+plugin_startup() -+plugin_shutdown() -} -AccountHistoryPlugin --> ChainPlugin : "requires" -AccountHistoryPlugin --> OperationHistoryPlugin : "requires" -AccountHistoryPlugin --> JsonRpcPlugin : "requires" -``` - -**Diagram sources** -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L93) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L60-L66) -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L94-L163) - -**Section sources** -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L93) -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L94-L163) - -### P2P Plugin: Broadcast and Event Propagation -- Purpose: Handles peer-to-peer networking and broadcasts blocks/transactions. -- Dependencies: - - Requires chain::plugin for block/transaction validation and context. -- Typical usage: - - Receives blocks from chain and broadcasts them to peers. - -```mermaid -classDiagram -class P2PPlugin { -+broadcast_block(...) -+broadcast_transaction(...) -+plugin_initialize(...) -+plugin_startup() -} -P2PPlugin --> ChainPlugin : "requires" -``` - -**Diagram sources** -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) - -**Section sources** -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) - -### Webserver Plugin: JSON-RPC Endpoint -- Purpose: Exposes HTTP/WS endpoints for JSON-RPC requests. -- Dependencies: - - Requires json_rpc::plugin for request routing and transport. -- Thread-safety note: - - The plugin documentation indicates handlers run on the appbase io_service thread, and callbacks can be invoked from any thread and are automatically propagated to the HTTP thread. - -```mermaid -classDiagram -class WebserverPlugin { -+plugin_initialize(...) -+plugin_startup() -+plugin_shutdown() -} -WebserverPlugin --> JsonRpcPlugin : "requires" -``` - -**Diagram sources** -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -**Section sources** -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -### Observer Pattern with Boost.Signals2 -- Signal emission: - - chain::plugin defines on_sync as a signal for synchronization events. -- Signal consumption: - - database_api::plugin subscribes to signals exposed by other components (e.g., block-applied signals) to react to blockchain events. -- Practical examples: - - chain::plugin emits on_sync during sync/live transitions. - - database_api::plugin uses a signal reference parameter to connect to block-applied events. - -```mermaid -sequenceDiagram -participant Chain as "chain : : plugin" -participant DB as "database_api : : plugin" -participant IO as "appbase io_service" -Chain->>Chain : "emit on_sync()" -Chain-->>IO : "signal event" -IO-->>DB : "invoke connected slot(s)" -DB->>DB : "handle block-applied callback" -``` - -**Diagram sources** -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) -- [api.cpp (database_api)](file://plugins/database_api/api.cpp#L35) - -**Section sources** -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) -- [api.cpp (database_api)](file://plugins/database_api/api.cpp#L35) - -## Memory Management Coordination - -### Enhanced Purging Coordination Mechanisms -The account_history and operation_history plugins now implement sophisticated memory management coordination to prevent memory leaks and dangling references. - -#### Operation History Purging -The operation_history plugin maintains a configurable history depth and provides purging capabilities: - -```mermaid -flowchart TD -A["Block Applied Event"] --> B["Check history_count_blocks"] -B --> C{"Need to purge?"} -C --> |Yes| D["Calculate min_keep_block"] -D --> E["Iterate through operation_index"] -E --> F["Remove old operations"] -F --> G["Complete purging"] -C --> |No| H["No action"] -``` - -**Diagram sources** -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L107-L122) - -#### Account History Coordination -The account_history plugin coordinates with operation_history to ensure consistent memory management: - -```mermaid -flowchart TD -A["Block Applied Event"] --> B["Calculate own min_keep_block"] -B --> C["Get operation_history min_keep_block"] -C --> D{"Compare values"} -D --> |operation_history > own| E["Use operation_history value"] -D --> |own >= operation_history| F["Use own value"] -E --> G["Purge old account history"] -F --> G["Purge old account history"] -G --> H["Update account range objects"] -``` - -**Diagram sources** -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L94-L163) - -#### Signal Handler Management -Both plugins implement proper signal handler management for graceful shutdown: - -**Section sources** -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L107-L122) -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L94-L163) -- [plugin.cpp (operation_history)](file://plugins/operation_history/plugin.cpp#L293-L299) -- [plugin.cpp (account_history)](file://plugins/account_history/plugin.cpp#L580-L586) - -## Dependency Analysis -Plugins declare dependencies using APPBASE_PLUGIN_REQUIRES. The runtime initializes plugins in dependency order and makes required plugins available to consumers. **Enhanced coordination ensures proper memory management across the plugin ecosystem.** - -```mermaid -graph LR -JsonRpc["json_rpc::plugin"] --> Chain["chain::plugin"] -JsonRpc --> DBAPI["database_api::plugin"] -JsonRpc --> Web["webserver::webserver_plugin"] -JsonRpc --> OpHist["operation_history::plugin"] -JsonRpc --> AccHist["account_history::plugin"] -JsonRpc --> PrivateMsg["private_message_plugin"] -JsonRpc --> TestAPI["test_api_plugin"] -Chain --> P2P["p2p::p2p_plugin"] -Chain --> OpHist -Chain --> AccHist -Chain --> DebugNode["debug_node::plugin"] -Chain --> RawBlock["raw_block::plugin"] -Chain --> BlockInfo["block_info::plugin"] -Chain --> Tags["tags::plugin"] -Chain --> Follow["follow::plugin"] -Chain --> validator["witness_plugin::witness_plugin"] -Chain --> CommitteeAPI["committee_api::committee_api"] -Chain --> InviteAPI["invite_api::invite_api"] -Chain --> PaidSubAPI["paid_subscription_api::paid_subscription_api"] -Chain --> CustomProtoAPI["custom_protocol_api::custom_protocol_api_plugin"] -OpHist -.-> AccHist -AccHist -.-> OpHist -``` - -**Diagram sources** -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L53-L58) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L60-L66) -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L39) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L21) - -**Section sources** -- [plugin.hpp (chain)](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugin.hpp (database_api)](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L187-L192) -- [plugin.hpp (operation_history)](file://plugins/operation_history/include/graphene/plugins/operation_history/plugin.hpp#L53-L58) -- [plugin.hpp (account_history)](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L60-L66) -- [plugin.hpp (webserver)](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L39) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L21) - -## Performance Considerations -- Prefer lightweight signals for event propagation to avoid heavy synchronous calls. -- Use asynchronous callbacks and appbase io_service threads for I/O-bound tasks (e.g., webserver). -- Minimize contention by keeping shared state protected and avoiding long-running work in signal handlers. -- Batch operations where possible to reduce overhead (e.g., bulk indexing in account_history). -- **Implement coordinated purging to prevent memory accumulation and improve performance.** -- **Use get_min_keep_block() API for consistent memory management across plugins.** - -## Troubleshooting Guide -- Initialization failures: - - Verify that all required plugins are registered and present in the dependency graph. - - Ensure APPBASE_PLUGIN_REQUIRES lists are correct and match actual dependencies. -- Signal not firing: - - Confirm that the emitting plugin actually emits the signal and that subscribers connect before the event occurs. - - Check that the signal lifetime exceeds the subscription duration. -- API not available: - - Ensure the plugin exposing the API is started and that dependent plugins are initialized in the correct order. -- **Memory issues:** - - Verify that both operation_history and account_history plugins are properly coordinating purging. - - Check that get_min_keep_block() values are consistent across plugins. - - Ensure signal handlers are properly disconnected during shutdown. - -## Conclusion -Inter-plugin communication in this codebase relies on a clean separation of concerns with enhanced memory management coordination: -- appbase manages dependency injection and initialization order -- Boost.Signals2 enables decoupled event propagation -- Plugins expose read-only APIs for others to consume -- The chain plugin acts as the central dependency provider -- **Enhanced coordination between operation_history and account_history prevents memory leaks and dangling references** -- **Proper signal handler management ensures graceful shutdown and resource cleanup** - -Following the patterns documented here helps maintain loose coupling, predictable initialization, robust event-driven workflows, and efficient memory utilization across the plugin ecosystem. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin API Design Patterns.md b/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin API Design Patterns.md deleted file mode 100644 index 4e7fe0c839..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin API Design Patterns.md +++ /dev/null @@ -1,327 +0,0 @@ -# Plugin API Design Patterns - - -**Referenced Files in This Document** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp) -- [api.cpp](file://plugins/database_api/api.cpp) -- [db_with.hpp](file://libraries/chain/include/graphene/chain/db_with.hpp) -- [history_object.hpp](file://plugins/account_history/include/graphene/plugins/account_history/history_object.hpp) -- [plugin.cpp](file://plugins/account_history/plugin.cpp) -- [follow_objects.hpp](file://plugins/follow/include/graphene/plugins/follow/follow_objects.hpp) -- [plugin.cpp](file://plugins/follow/plugin.cpp) -- [database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains plugin API design patterns and best practices for the VIZ blockchain node, focusing on how plugins access and interact with the blockchain database through the chainbase framework. It documents database access patterns (find, get, and index operations), demonstrates CRUD operations implemented by plugins, outlines API surface design principles, and covers error handling, validation, and performance considerations. Practical usage patterns and integration with other system components are also included. - -## Project Structure -The plugin system is organized around the appbase framework with plugins exposing JSON-RPC APIs backed by the chain database. The chain plugin owns the database and exposes template-based access helpers. Other plugins (e.g., database_api, account_history, follow) depend on the chain plugin and use the database to implement their APIs. - -```mermaid -graph TB -subgraph "Plugins" -CH["Chain Plugin
plugins/chain"] -DBAPI["Database API Plugin
plugins/database_api"] -AH["Account History Plugin
plugins/account_history"] -FOL["Follow Plugin
plugins/follow"] -end -subgraph "Core Chain" -DB["Chain Database
libraries/chain"] -CB["Chainbase Framework
thirdparty/chainbase"] -end -CH --> DB -DBAPI --> CH -AH --> CH -FOL --> CH -DB --> CB -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L181) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L1-L100) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L181) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) - -## Core Components -- Chain plugin: Provides the database handle and template-based accessors (has_index, get_index, find, get) to support type-safe object operations. It manages database lifecycle (open, reindex, replay, close) and exposes signals for synchronization. -- Database API plugin: Exposes read-only RPC queries against the chain database (blocks, transactions, globals, accounts, balances, authority/validations, database info). -- Account History plugin: Maintains per-account operation history and ranges, implementing CRUD-like operations to create, modify, and purge history entries. -- Follow plugin: Implements social features (followers, following, feed/blog caches) using database CRUD operations and custom operation interpretation. - -Key template-based database access patterns: -- has_index() -- get_index() -- find(key) -- find(object_id) -- get(key) -- get(object_id) - -These are thin wrappers around the underlying chainbase database and are exposed by the chain plugin for use by other plugins. - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L52-L86) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L181) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L13-L558) - -## Architecture Overview -The chain plugin owns the database and exposes a clean API surface to other plugins. Plugins requiring read/write access to the database use the chain plugin’s database handle and template-based helpers. Plugins that only need read access wrap their API calls with weak read locks to avoid blocking writers. - -```mermaid -sequenceDiagram -participant Client as "RPC Client" -participant DBAPI as "Database API Plugin" -participant CH as "Chain Plugin" -participant DB as "Chain Database" -Client->>DBAPI : "get_dynamic_global_properties" -DBAPI->>CH : "db()" -CH-->>DBAPI : "database reference" -DBAPI->>DB : "get(id)" -DB-->>DBAPI : "object reference" -DBAPI-->>Client : "API response" -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [api.cpp](file://plugins/database_api/api.cpp#L333-L347) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L83-L86) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L164-L164) - -## Detailed Component Analysis - -### Chain Plugin: Template-Based Accessors and Lifecycle -- Template accessors: - - has_index() checks for the existence of a multi-index type. - - get_index() returns a reference to the generic index for type T. - - find(key) and find(id) locate objects without throwing if not found. - - get(key) and get(id) retrieve objects and throw if missing. -- Database lifecycle: - - Open/reindex/replay/close with configurable shared memory sizing and flushing intervals. - - Options for single-write-thread mode, virtual operations skipping, and plugin hooks on push_transaction. - -```mermaid -classDiagram -class ChainPlugin { -+plugin_initialize(opts) -+plugin_startup() -+plugin_shutdown() -+accept_block(block, currently_syncing, skip) -+accept_transaction(trx) -+has_index() -+get_index() -+find(key) -+find(id) -+get(key) -+get(id) -+db() : Database -+on_sync : signal -} -class Database { -+open(data_dir, shm_dir, ...) -+reindex(data_dir, shm_dir, from, ...) -+push_block(...) -+push_transaction(...) -+get_index(...) -+find(...) -+get(...) -} -ChainPlugin --> Database : "owns and exposes" -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L424) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L558) - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L52-L86) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L424) - -### Database API Plugin: Read-Only Queries and Locking -- API surface includes blocks, transactions, globals, accounts, balances, authority/validations, and database info. -- Uses weak read locks around database queries to minimize contention with writers. -- Subscriptions and callbacks for block-applied events. - -```mermaid -sequenceDiagram -participant Client as "RPC Client" -participant DBAPI as "Database API Plugin" -participant DB as "Chain Database" -Client->>DBAPI : "get_accounts(names)" -DBAPI->>DB : "with_weak_read_lock(...)" -DBAPI->>DB : "get_index().indices().get()" -DBAPI->>DB : "find(name)" -DB-->>DBAPI : "account_api_object" -DBAPI-->>Client : "vector" -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [api.cpp](file://plugins/database_api/api.cpp#L371-L399) -- [api.cpp](file://plugins/database_api/api.cpp#L401-L425) - -**Section sources** -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [api.cpp](file://plugins/database_api/api.cpp#L371-L399) -- [api.cpp](file://plugins/database_api/api.cpp#L401-L425) - -### Account History Plugin: CRUD Operations on Blockchain Objects -- Creates account history entries and maintains account range boundaries. -- Modifies existing range objects when sequences change. -- Purges old history based on configured block window. -- Uses database.create, database.modify, and database.remove for CRUD. - -```mermaid -flowchart TD -Start(["Operation Applied"]) --> Impact["Determine impacted accounts"] -Impact --> Iterate{"For each impacted account"} -Iterate --> |Create| CreateHist["create"] -Iterate --> |Update Range| UpdateRange["modify"] -Iterate --> |Purge Old| Purge["Iterate by_block index
remove entries outside window"] -CreateHist --> End(["Done"]) -UpdateRange --> End -Purge --> End -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L51-L82) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L93-L126) - -**Section sources** -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L51-L82) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L93-L126) -- [history_object.hpp](file://plugins/account_history/include/graphene/plugins/account_history/history_object.hpp#L76-L119) - -### Follow Plugin: Index-Based Reads and Writes -- Implements social graph and feed/blog caches using multi-index containers. -- Reads with lower_bound and equality checks on composite indices. -- Writes with create and remove operations, enforcing limits via old-index trimming. - -```mermaid -sequenceDiagram -participant OP as "Operation" -participant VIS as "post_operation_visitor" -participant DB as "Chain Database" -OP->>VIS : "visit(content_operation)" -VIS->>DB : "get_content(author, permlink)" -VIS->>DB : "get_index().indices().get()" -VIS->>DB : "find(account)" -VIS->>DB : "create() / create()" -VIS->>DB : "trim old entries via by_old_* indices" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/follow/plugin.cpp#L114-L188) -- [follow_objects.hpp](file://plugins/follow/include/graphene/plugins/follow/follow_objects.hpp#L144-L226) - -**Section sources** -- [plugin.cpp](file://plugins/follow/plugin.cpp#L114-L188) -- [follow_objects.hpp](file://plugins/follow/include/graphene/plugins/follow/follow_objects.hpp#L144-L226) - -### Plugin API Surface Design Principles -- Clean separation of concerns: chain plugin owns DB, other plugins depend on it. -- Type-safe accessors via templates to avoid runtime errors and promote compile-time safety. -- Read-only APIs use weak read locks to reduce contention. -- Strong validation and assertions for limits and invariants. -- Signals for synchronization and event-driven integrations. - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L52-L86) -- [api.cpp](file://plugins/database_api/api.cpp#L150-L175) -- [plugin.cpp](file://plugins/follow/plugin.cpp#L350-L395) - -## Dependency Analysis -- The chain plugin depends on chainbase and exposes a database interface to other plugins. -- The database API plugin depends on the chain plugin and the JSON-RPC plugin. -- Account History and Follow plugins depend on the chain plugin and define their own multi-indexes. - -```mermaid -graph LR -CH["Chain Plugin"] --> DB["Chain Database"] -DBAPI["Database API Plugin"] --> CH -AH["Account History Plugin"] --> CH -FOL["Follow Plugin"] --> CH -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L42) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L191) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L491-L492) -- [plugin.cpp](file://plugins/follow/plugin.cpp#L319-L323) - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L42) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L191) -- [plugin.cpp](file://plugins/account_history/plugin.cpp#L491-L492) -- [plugin.cpp](file://plugins/follow/plugin.cpp#L319-L323) - -## Performance Considerations -- Shared memory sizing and growth: - - Configure initial shared memory size, increment size, and minimum free space thresholds. - - Periodic checks for free space to avoid stalls. -- Single write thread: - - Optional single-write-thread mode to serialize block/transaction pushes for stability. -- Virtual operations: - - Skip virtual operations to reduce memory pressure during sync. -- Read locks: - - Use weak read locks for read-only APIs to minimize writer contention. -- Index traversal limits: - - Enforce reasonable limits on returned rows to prevent excessive CPU/memory usage. -- Flush interval: - - Tune flush interval to balance durability and performance. - -Practical tips: -- Prefer index-based lookups (lower_bound, equal_range) for efficient scans. -- Use composite indices for multi-key queries. -- Limit pagination sizes and enforce client-side caps. -- Monitor free shared memory and adjust increments accordingly. - -**Section sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L183-L251) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L330-L346) -- [api.cpp](file://plugins/database_api/api.cpp#L436-L450) -- [api.cpp](file://plugins/database_api/api.cpp#L582-L591) - -## Troubleshooting Guide -Common issues and strategies: -- Database open failures: - - On revision mismatch or corruption, the chain plugin attempts to replay or resync automatically based on configuration flags. -- Exception propagation: - - Plugins should wrap API calls with weak read locks and handle exceptions gracefully. - - Internal exception macros and plugin exception handling ensure consistent logging and propagation. -- Operation validation and evaluation: - - Dedicated exception categories for validation and evaluation failures aid in diagnostics. -- Signal handling: - - Signal guard ensures proper cleanup and exception reporting during signal delivery. - -Best practices: -- Always validate inputs and enforce limits before querying the database. -- Use find vs get appropriately: find avoids exceptions for missing objects; get throws if not found. -- Log and rethrow plugin exceptions to preserve stack traces. - -**Section sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L348-L386) -- [database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L45-L62) -- [api.cpp](file://plugins/database_api/api.cpp#L150-L175) - -## Conclusion -The VIZ plugin system leverages a clean, template-based API surface to safely and efficiently access the blockchain database. Plugins implement CRUD operations using chainbase multi-indexes, expose read-only RPC APIs with weak read locks, and integrate through signals and custom operation interpreters. Robust error handling, validation, and performance tuning enable scalable and maintainable plugin development. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin Architecture.md b/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin Architecture.md deleted file mode 100644 index 442d21c298..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin Architecture.md +++ /dev/null @@ -1,902 +0,0 @@ -# Plugin Architecture - - -**Referenced Files in This Document** -- [main.cpp](file://programs/vizd/main.cpp) -- [newplugin.py](file://programs/util/newplugin.py) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp) -- [plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp) -- [plugin.hpp](file://plugins/follow/include/graphene/plugins/follow/plugin.hpp) -- [CMakeLists.txt](file://plugins/CMakeLists.txt) -- [database.cpp](file://libraries/chain/database.cpp) -- [node.cpp](file://libraries/network/node.cpp) -- [snapshot_plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp) -- [snapshot_plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [snapshot_types.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp) -- [snapshot_serializer.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) - - -## Update Summary -**Changes Made** -- Added comprehensive documentation for RAII session guard implementation in snapshot plugin connection handling -- Documented enhanced retry logic for client-side connection establishment addressing race conditions in P2P communication -- Updated snapshot plugin system with improved session management and connection reliability -- Enhanced documentation of anti-spam protection mechanisms with race condition prevention -- Added detailed coverage of session_guard class and its role in preventing race conditions - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Enhanced Lifecycle Management](#enhanced-lifecycle-management) -7. [Signal Handler Coordination](#signal-handler-coordination) -8. [Memory Leak Prevention](#memory-leak-prevention) -9. [Snapshot Plugin System](#snapshot-plugin-system) -10. [Custom Network Protocol](#custom-network-protocol) -11. [Anti-Spam Protection](#anti-spam-protection) -12. [Session Guard Implementation](#session-guard-implementation) -13. [Enhanced Retry Logic](#enhanced-retry-logic) -14. [Race Condition Prevention](#race-condition-prevention) -15. [Integration with Chain Plugin](#integration-with-chain-plugin) -16. [Dependency Analysis](#dependency-analysis) -17. [Performance Considerations](#performance-considerations) -18. [Troubleshooting Guide](#troubleshooting-guide) -19. [Conclusion](#conclusion) -20. [Appendices](#appendices) - -## Introduction -This document explains the plugin architecture of the node, focusing on the modular, plugin-based design enabled by the appbase framework. It covers how plugins are registered, initialized, started, and shut down with enhanced lifecycle management; how they interact with the chain database and each other; and how to develop custom plugins using the provided template generator. The architecture now includes improved shutdown procedures, signal handler coordination, memory leak prevention mechanisms, comprehensive snapshot plugin system with custom network protocols for efficient blockchain state synchronization, and robust session management with RAII-based race condition prevention. - -## Project Structure -The node organizes plugins under the plugins directory, with each plugin providing its own header and implementation files. The application entry point registers and initializes plugins, while a Python script generates boilerplate for new plugins. The enhanced lifecycle management ensures proper resource cleanup during shutdown. The new snapshot plugin extends this architecture with advanced state synchronization capabilities and sophisticated connection management. - -```mermaid -graph TB -subgraph "Application Entry Point" -A["programs/vizd/main.cpp
Registers plugins and starts app"] -end -subgraph "Core Plugins" -P1["plugins/chain
Chain plugin"] -P2["plugins/webserver
Webserver plugin"] -P3["plugins/p2p
P2P plugin"] -P4["plugins/snapshot
Snapshot plugin
Enhanced"] -end -subgraph "Feature Plugins" -P5["plugins/database_api
Database API plugin"] -P6["plugins/account_history
Account History plugin"] -P7["plugins/follow
Follow plugin"] -end -subgraph "Plugin Template Generator" -T["programs/util/newplugin.py
Generates plugin boilerplate"] -end -A --> P1 -A --> P2 -A --> P3 -A --> P4 -A --> P5 -A --> P6 -A --> P7 -T --> P1 -T --> P2 -T --> P3 -T --> P4 -T --> P5 -T --> P6 -T --> P7 -``` - -**Diagram sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) - -**Section sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) - -## Core Components -- Application bootstrap and plugin registration: The application entry point registers all built-in plugins and initializes the app with selected plugins. -- Chain plugin: Provides blockchain database access, block acceptance, transaction acceptance, and emits synchronization signals with enhanced shutdown handling. -- Webserver plugin: Exposes JSON-RPC endpoints and delegates routing to the JSON-RPC plugin with proper connection cleanup. -- P2P plugin: Depends on the chain plugin and broadcasts blocks/transactions with graceful shutdown support. -- Database API plugin: Depends on chain and JSON-RPC; provides read-only database queries and subscriptions. -- Account History plugin: Tracks per-account operation history and depends on chain and operation history. -- Follow plugin: Depends on chain and JSON-RPC; provides social graph APIs. -- **Snapshot plugin**: Enhanced - Provides DLT state snapshots, automatic snapshot creation, P2P synchronization, custom TCP protocol for efficient state distribution, and robust session management with RAII-based race condition prevention. -- Plugin template generator: Automates creation of new plugins with standardized structure and dependencies. - -**Section sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [webserver_plugin.cpp:329-331](file://plugins/webserver/webserver_plugin.cpp#L329-L331) -- [p2p_plugin.cpp:568-573](file://plugins/p2p/p2p_plugin.cpp#L568-L573) -- [plugin.hpp:179-409](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L409) -- [plugin.hpp:59-97](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L97) -- [plugin.hpp:23-70](file://plugins/follow/include/graphene/plugins/follow/plugin.hpp#L23-L70) -- [newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) -- [snapshot_plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) - -## Architecture Overview -The node uses appbase to manage plugins as independent, composable units with enhanced lifecycle management. Plugins declare their dependencies, receive lifecycle callbacks with proper shutdown handling, and can expose APIs and signals. The chain plugin owns the database and emits events; other plugins subscribe to chain events or depend on the chain plugin for read/write access with coordinated shutdown procedures. The enhanced snapshot plugin integrates seamlessly with this architecture through specialized callbacks for state management and sophisticated connection handling. - -```mermaid -graph TB -subgraph "Appbase Application" -APP["appbase::app()"] -end -subgraph "Core Plugins" -CHAIN["chain::plugin
owns database
enhanced shutdown"] -JSONRPC["json_rpc::plugin"] -SNAPSHOT["snapshot::plugin
Enhanced
DLT state management
RAII session guards"] -end -subgraph "Feature Plugins" -WS["webserver_plugin
proper cleanup"] -P2P["p2p_plugin
graceful shutdown"] -DBAPI["database_api::plugin"] -AH["account_history::plugin"] -FOLLOW["follow::plugin"] -end -APP --> CHAIN -APP --> JSONRPC -APP --> SNAPSHOT -APP --> WS -APP --> P2P -APP --> DBAPI -APP --> AH -APP --> FOLLOW -CHAIN --> JSONRPC -CHAIN --> SNAPSHOT -P2P --> CHAIN -DBAPI --> CHAIN -DBAPI --> JSONRPC -AH --> CHAIN -AH --> JSONRPC -FOLLOW --> CHAIN -FOLLOW --> JSONRPC -SNAPSHOT --> CHAIN -``` - -**Diagram sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [webserver_plugin.cpp:329-331](file://plugins/webserver/webserver_plugin.cpp#L329-L331) -- [p2p_plugin.cpp:568-573](file://plugins/p2p/p2p_plugin.cpp#L568-L573) -- [plugin.hpp:179-191](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L191) -- [plugin.hpp:59-65](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L65) -- [plugin.hpp:23-31](file://plugins/follow/include/graphene/plugins/follow/plugin.hpp#L23-L31) -- [snapshot_plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) - -## Detailed Component Analysis - -### Plugin Registration and Lifecycle -- Registration: The application registers each plugin via appbase::app().register_plugin(). -- Initialization: plugin_initialize parses options and prepares internal state. -- Startup: plugin_startup opens databases, subscribes to signals, and exposes APIs. -- Shutdown: plugin_shutdown closes resources and tears down state with proper cleanup. - -```mermaid -sequenceDiagram -participant Main as "vizd main.cpp" -participant App as "appbase : : app()" -participant Chain as "chain : : plugin" -participant Snapshot as "snapshot : : plugin" -participant WS as "webserver_plugin" -participant DB as "database_api : : plugin" -Main->>App : register_plugins() -App->>Chain : register_plugin() -App->>Snapshot : register_plugin() -App->>WS : register_plugin() -App->>DB : register_plugin() -Main->>App : initialize(argc, argv) -App->>Chain : plugin_initialize(options) -App->>Snapshot : plugin_initialize(options) -App->>WS : plugin_initialize(options) -App->>DB : plugin_initialize(options) -Main->>App : startup() -App->>Chain : plugin_startup() -App->>Snapshot : plugin_startup() -App->>WS : plugin_startup() -App->>DB : plugin_startup() -Main->>App : exec() -Note over Main,App : Runtime loop until shutdown -Main->>App : shutdown() -App->>Chain : plugin_shutdown() -App->>Snapshot : plugin_shutdown() -App->>WS : plugin_shutdown() -App->>DB : plugin_shutdown() -``` - -**Diagram sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [main.cpp:117-140](file://programs/vizd/main.cpp#L117-L140) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [webserver_plugin.cpp:329-331](file://plugins/webserver/webserver_plugin.cpp#L329-L331) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [snapshot_plugin.cpp:1956-1959](file://plugins/snapshot/plugin.cpp#L1956-L1959) - -**Section sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [main.cpp:117-140](file://programs/vizd/main.cpp#L117-L140) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [snapshot_plugin.cpp:1956-1959](file://plugins/snapshot/plugin.cpp#L1956-L1959) - -### Chain Plugin: Database Access and Signals -- Database access: Provides db() getters and convenience helpers for indices and objects. -- Block/transaction acceptance: Validates and applies blocks/transactions via the underlying database. -- Synchronization signal: Emits on_sync to notify other plugins when the chain is ready. -- Enhanced shutdown: Properly closes database connections during plugin_shutdown. -- **Snapshot integration**: Provides specialized callbacks for snapshot loading, creation, and P2P synchronization. - -```mermaid -classDiagram -class chain_plugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+accept_block(block, currently_syncing, skip) bool -+accept_transaction(trx) -+db() database -+on_sync signal -+snapshot_load_callback function -+snapshot_create_callback function -+snapshot_p2p_sync_callback function -} -class database { -+open(...) -+push_block(...) -+push_transaction(...) -+close() -} -chain_plugin --> database : "owns and uses" -``` - -**Diagram sources** -- [plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) - -**Section sources** -- [plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) - -### Inter-Plugin Communication: Observer Pattern with Boost.Signals2 -- Chain plugin emits on_sync when synchronized. -- Other plugins subscribe during startup to coordinate behavior after chain readiness. -- Example: database_api and account_history rely on chain readiness for state queries. -- **Snapshot plugin**: Integrates through specialized callbacks rather than signals for state management operations. - -```mermaid -sequenceDiagram -participant Chain as "chain : : plugin" -participant DB as "database_api : : plugin" -participant AH as "account_history : : plugin" -participant Snapshot as "snapshot : : plugin" -Chain->>Chain : on_sync() -Chain-->>DB : connected observers -Chain-->>AH : connected observers -Chain-->>Snapshot : snapshot callbacks -DB->>DB : initialize state after on_sync -AH->>AH : initialize history after on_sync -Snapshot->>Snapshot : load/create snapshot state -``` - -**Diagram sources** -- [plugin.hpp:90-90](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L90-L90) -- [plugin.hpp:195-199](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L195-L199) -- [plugin.hpp:76-77](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L76-L77) -- [snapshot_plugin.hpp:92-105](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L92-L105) - -**Section sources** -- [plugin.hpp:90-90](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L90-L90) -- [plugin.hpp:195-199](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L195-L199) -- [plugin.hpp:76-77](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L76-L77) -- [snapshot_plugin.hpp:92-105](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L92-L105) - -### Plugin Template System: newplugin.py -- Generates boilerplate for a new plugin including: - - CMakeLists.txt for library definition and linking - - Plugin header with appbase::plugin base class and lifecycle methods - - Plugin implementation with API factory registration and event subscription - - API header and implementation stubs -- The template demonstrates: - - Declaring plugin dependencies via APPBASE_PLUGIN_REQUIRES - - Registering an API factory during plugin startup - - Subscribing to chain events (e.g., applied_block) - -```mermaid -flowchart TD -Start(["Run newplugin.py provider name"]) --> CreateDirs["Create plugin directory under libraries/plugins/name"] -CreateDirs --> WriteCMake["Write CMakeLists.txt"] -CreateDirs --> WriteHeaders["Write plugin and API headers"] -CreateDirs --> WriteSources["Write plugin and API sources"] -WriteCMake --> Done(["Done"]) -WriteHeaders --> Done -WriteSources --> Done -``` - -**Diagram sources** -- [newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) - -**Section sources** -- [newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) - -### Practical Plugin Development Workflow -- Use the template generator to scaffold a new plugin. -- Implement plugin lifecycle methods and register API factories. -- Declare dependencies using APPBASE_PLUGIN_REQUIRES. -- Subscribe to chain signals or database events as needed. -- Integrate the plugin into the application's registration and initialization steps. - -```mermaid -flowchart TD -A["Generate plugin with newplugin.py"] --> B["Implement plugin.cpp"] -B --> C["Add API methods in plugin.hpp"] -C --> D["Register plugin in vizd main.cpp"] -D --> E["Initialize and startup via appbase"] -E --> F["Expose APIs and react to chain events"] -``` - -**Diagram sources** -- [newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.cpp:316-396](file://plugins/chain/plugin.cpp#L316-L396) - -**Section sources** -- [newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.cpp:316-396](file://plugins/chain/plugin.cpp#L316-L396) - -## Enhanced Lifecycle Management - -### Improved Shutdown Procedures -The enhanced plugin lifecycle management includes comprehensive shutdown procedures designed to prevent memory leaks and ensure proper resource cleanup: - -- **Chain Plugin Shutdown**: The chain plugin implements proper database closure in plugin_shutdown(), ensuring all database connections are properly closed. -- **Webserver Plugin Cleanup**: The webserver plugin implements thorough cleanup of HTTP and WebSocket servers, thread pools, and connection handlers. -- **P2P Plugin Graceful Shutdown**: The P2P plugin ensures proper network node closure, connection termination, and thread cleanup. -- **Snapshot Plugin Shutdown**: The snapshot plugin properly stops TCP servers, cancels async operations, and cleans up file handles. - -```mermaid -sequenceDiagram -participant App as "Application" -participant Chain as "Chain Plugin" -participant Snapshot as "Snapshot Plugin" -participant WS as "Webserver Plugin" -participant P2P as "P2P Plugin" -App->>Chain : plugin_shutdown() -Chain->>Chain : db.close() -App->>Snapshot : plugin_shutdown() -Snapshot->>Snapshot : stop_server() -Snapshot->>Snapshot : cancel_async_ops() -App->>WS : plugin_shutdown() -WS->>WS : stop_webserver() -WS->>WS : thread_pool.join_all() -WS->>WS : connections.cleanup() -App->>P2P : plugin_shutdown() -P2P->>P2P : node.close() -P2P->>P2P : p2p_thread.quit() -P2P->>P2P : node.reset() -``` - -**Diagram sources** -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [webserver_plugin.cpp:167-190](file://plugins/webserver/webserver_plugin.cpp#L167-L190) -- [p2p_plugin.cpp:568-573](file://plugins/p2p/p2p_plugin.cpp#L568-L573) -- [snapshot_plugin.cpp:1956-1959](file://plugins/snapshot/plugin.cpp#L1956-L1959) - -**Section sources** -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [webserver_plugin.cpp:167-190](file://plugins/webserver/webserver_plugin.cpp#L167-L190) -- [p2p_plugin.cpp:568-573](file://plugins/p2p/p2p_plugin.cpp#L568-L573) -- [snapshot_plugin.cpp:1956-1959](file://plugins/snapshot/plugin.cpp#L1956-L1959) - -### Signal Handler Coordination -The enhanced architecture includes sophisticated signal handler coordination mechanisms for graceful shutdown and resource cleanup: - -- **Signal Guard Implementation**: The database layer implements a signal_guard class that manages signal handlers for SIGHUP, SIGINT, and SIGTERM. -- **Interrupt Detection**: Signal handlers set interrupt flags that can be checked during long-running operations like blockchain reindexing. -- **Graceful Termination**: Operations check for interruption signals and terminate gracefully when detected. - -```mermaid -flowchart TD -A["Signal Received"] --> B["signal_guard.setup()"] -B --> C["Install signal handlers"] -C --> D["Set is_interrupted = true"] -D --> E["Operations check get_is_interrupted()"] -E --> F{"Interrupted?"} -F --> |Yes| G["Cancel operations"] -F --> |No| H["Continue normally"] -G --> I["Cleanup resources"] -H --> J["Complete operation"] -``` - -**Diagram sources** -- [database.cpp:134-180](file://libraries/chain/database.cpp#L134-L180) -- [database.cpp:270-329](file://libraries/chain/database.cpp#L270-L329) - -**Section sources** -- [database.cpp:134-180](file://libraries/chain/database.cpp#L134-L180) -- [database.cpp:270-329](file://libraries/chain/database.cpp#L270-L329) - -### Memory Leak Prevention -The enhanced lifecycle management includes several mechanisms to prevent memory leaks during plugin operations: - -- **Connection Cleanup**: Webserver plugin properly cleans up WebSocket and HTTP connections, ensuring no lingering references. -- **Thread Pool Management**: Proper thread pool shutdown with join_all() prevents thread leaks. -- **Network Node Cleanup**: P2P plugin ensures complete network node shutdown with proper resource deallocation. -- **Scoped Connections**: Plugins use scoped_connection objects that automatically disconnect when going out of scope. -- **Snapshot Resource Management**: Snapshot plugin implements proper cleanup of TCP sockets, file handles, and async operations. - -**Section sources** -- [webserver_plugin.cpp:167-190](file://plugins/webserver/webserver_plugin.cpp#L167-L190) -- [p2p_plugin.cpp:568-573](file://plugins/p2p/p2p_plugin.cpp#L568-L573) -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [snapshot_plugin.cpp:1427-1436](file://plugins/snapshot/plugin.cpp#L1427-L1436) - -## Snapshot Plugin System - -### Overview -The snapshot plugin provides comprehensive DLT (Distributed Ledger Technology) state management capabilities for VIZ blockchain nodes. It enables efficient state synchronization through automatic snapshot creation, loading from existing snapshots, and P2P synchronization between nodes using a custom TCP protocol. The enhanced version includes robust session management with RAII-based race condition prevention and sophisticated retry logic for reliable connection establishment. - -### Key Features -- **Automatic Snapshot Creation**: Creates snapshots at specific block heights or periodically -- **State Loading**: Loads blockchain state from snapshot files instead of replaying blocks -- **P2P Synchronization**: Downloads snapshots from trusted peers for rapid node bootstrap -- **Custom TCP Protocol**: Implements a binary protocol for efficient snapshot distribution -- **Anti-Spam Protection**: Built-in rate limiting and connection management -- **Security Controls**: Trust model with optional trusted-only serving -- **Enhanced Session Management**: RAII-based session guards prevent race conditions -- **Robust Connection Handling**: Retry logic addresses timing issues in P2P communication - -### Snapshot Formats and Storage -The snapshot plugin supports two file formats: -- **.vizjson**: Compressed JSON format with zlib compression -- **.json**: Plain JSON format for debugging and compatibility - -Snapshots are stored with naming convention: `snapshot-block-{block_number}.{extension}` - -**Section sources** -- [snapshot_plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) -- [snapshot_types.hpp:16-43](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp#L16-L43) -- [snapshot-plugin.md:1-164](file://documentation/snapshot-plugin.md#L1-L164) - -## Custom Network Protocol - -### Protocol Design -The snapshot plugin implements a custom binary protocol over TCP for efficient state synchronization: - -#### Wire Format -``` -[4 bytes: payload_size][4 bytes: msg_type][payload_bytes] -``` - -#### Message Types -| Type | Name | Description | -|------|------|-------------| -| 1 | `SNAPSHOT_INFO_REQUEST` | Empty payload. "What's your latest snapshot?" | -| 2 | `SNAPSHOT_INFO_REPLY` | `{block_num, block_id, checksum, compressed_size}` | -| 3 | `SNAPSHOT_DATA_REQUEST` | `{block_num, offset, chunk_size}` | -| 4 | `SNAPSHOT_DATA_REPLY` | `{offset, data_bytes, is_last}` | -| 5 | `SNAPSHOT_NOT_AVAILABLE` | No snapshot available | - -### Protocol Flow -1. **Info Request**: Client sends `SNAPSHOT_INFO_REQUEST` -2. **Info Reply**: Server responds with snapshot metadata -3. **Data Requests**: Client requests snapshot in chunks -4. **Data Replies**: Server streams snapshot data -5. **Verification**: Client verifies checksum before accepting - -```mermaid -sequenceDiagram -participant Client as "Client Node" -participant Server as "Server Node" -Client->>Server : SNAPSHOT_INFO_REQUEST -Server->>Client : SNAPSHOT_INFO_REPLY {block_num, checksum, size} -Client->>Server : SNAPSHOT_DATA_REQUEST {offset, chunk_size} -Server->>Client : SNAPSHOT_DATA_REPLY {data, is_last} -loop Multiple Chunks -Client->>Server : SNAPSHOT_DATA_REQUEST {offset, chunk_size} -Server->>Client : SNAPSHOT_DATA_REPLY {data, is_last} -end -Client->>Client : Verify Checksum -Client->>Client : Load Snapshot State -``` - -**Diagram sources** -- [snapshot_plugin.cpp:1249-1317](file://plugins/snapshot/plugin.cpp#L1249-L1317) -- [snapshot_plugin.cpp:1546-1617](file://plugins/snapshot/plugin.cpp#L1546-L1617) - -**Section sources** -- [snapshot_plugin.cpp:1249-1317](file://plugins/snapshot/plugin.cpp#L1249-L1317) -- [snapshot_plugin.cpp:1546-1617](file://plugins/snapshot/plugin.cpp#L1546-L1617) -- [snapshot-plugin.md:104-120](file://documentation/snapshot-plugin.md#L104-L120) - -## Anti-Spam Protection - -### Connection Management -The snapshot TCP server implements comprehensive anti-spam protection: - -#### Active Session Control -- **Single Active Session Per IP**: Prevents multiple concurrent downloads from the same IP -- **Connection Tracking**: Monitors active connections to enforce limits - -#### Rate Limiting -- **3 Connections Per Hour Per IP**: Prevents abuse through repeated connection attempts -- **Time Window Management**: Automatic cleanup of expired connection records -- **Dynamic Rate Adjustment**: Prevents unbounded memory growth in connection history - -#### Security Enforcement -- **Trusted Peer Bypass**: Anti-spam rules apply equally to all connections -- **Trust Model**: Separate from anti-spam enforcement for fairness -- **Connection Timeout**: 60-second timeout for idle connections - -```mermaid -flowchart TD -A["Incoming Connection"] --> B{"Trusted Only Mode?"} -B --> |Yes| C{"IP in Trusted List?"} -B --> |No| D["Accept Connection"] -C --> |Yes| E["Check Anti-Spam"] -C --> |No| F["Reject Connection"] -E --> G{"Max Concurrent Connections?"} -G --> |Yes| H["Reject - Too Many Active Sessions"] -G --> |No| I{"Rate Limit Exceeded?"} -I --> |Yes| J["Reject - Rate Limited"] -I --> |No| K["Accept Connection"] -``` - -**Diagram sources** -- [snapshot_plugin.cpp:1438-1544](file://plugins/snapshot/plugin.cpp#L1438-L1544) - -**Section sources** -- [snapshot_plugin.cpp:1438-1544](file://plugins/snapshot/plugin.cpp#L1438-L1544) -- [snapshot-plugin.md:96-103](file://documentation/snapshot-plugin.md#L96-L103) - -## Session Guard Implementation - -### RAII Session Guard Design -The snapshot plugin implements a sophisticated RAII session guard to prevent race conditions in connection handling: - -#### Session Guard Class -The `session_guard` class provides automatic cleanup of active session records: - -```cpp -struct session_guard { - snapshot_plugin::plugin_impl& self; - uint32_t ip; - bool released = false; - session_guard(snapshot_plugin::plugin_impl& s, uint32_t i) : self(s), ip(i) {} - ~session_guard() { release(); } - void release() { - if (!released) { - released = true; - fc::scoped_lock lock(self.sessions_mutex); - self.active_sessions.erase(ip); - } - } -} guard(*this, remote_ip); -``` - -#### Race Condition Prevention -- **Eager Cleanup**: Session is removed from `active_sessions` immediately when the guard goes out of scope -- **Prevents Duplicate Connections**: Ensures no race condition where a client reconnects before async fiber cleanup -- **Thread Safety**: Uses mutex protection for session management operations - -#### Integration with Connection Handling -The session guard is integrated into the `handle_connection` method: - -```mermaid -flowchart TD -A["handle_connection Called"] --> B["Create session_guard"] -B --> C["Process Connection Requests"] -C --> D{"Function Returns?"} -D --> |Yes| E["Guard destructor called"] -E --> F["Remove from active_sessions"] -F --> G["Cleanup Complete"] -D --> |No Exception| H["Guard destructor called"] -H --> F -``` - -**Diagram sources** -- [snapshot_plugin.cpp:1800-1820](file://plugins/snapshot/plugin.cpp#L1800-L1820) -- [snapshot_plugin.cpp:1780-1788](file://plugins/snapshot/plugin.cpp#L1780-L1788) - -**Section sources** -- [snapshot_plugin.cpp:1800-1820](file://plugins/snapshot/plugin.cpp#L1800-L1820) -- [snapshot_plugin.cpp:1780-1788](file://plugins/snapshot/plugin.cpp#L1780-L1788) - -## Enhanced Retry Logic - -### Client-Side Connection Retry Mechanism -The snapshot plugin implements sophisticated retry logic to address race conditions in P2P communication: - -#### Retry Strategy -- **Maximum 3 Retries**: Limits retry attempts to prevent infinite loops -- **2-Second Delays**: Allows time for server-side session cleanup -- **Exponential Backoff**: Gradual retry delays improve success probability -- **Resource Cleanup**: Proper cleanup between retry attempts - -#### Retry Implementation -The retry logic addresses timing issues where server-side cleanup may not complete: - -```mermaid -flowchart TD -A["Connect Attempt"] --> B{"Connection Success?"} -B --> |Yes| C["Connected Successfully"] -B --> |No| D{"Retry Attempts Left?"} -D --> |Yes| E["Close Socket"] -E --> F["Wait 2 Seconds"] -F --> G["Create New Socket"] -G --> A -D --> |No| H["Throw Exception"] -``` - -#### Specific Use Cases -- **Phase 1 to Phase 2 Transition**: Brief delay allows server to clean up Phase 1 session -- **Anti-Spam Duplicate Session Check**: Addresses timing window where duplicate session detection triggers -- **Async Fiber Cleanup**: Allows time for background fiber to complete cleanup operations - -**Diagram sources** -- [snapshot_plugin.cpp:2047-2064](file://plugins/snapshot/plugin.cpp#L2047-L2064) -- [snapshot_plugin.cpp:2036-2039](file://plugins/snapshot/plugin.cpp#L2036-L2039) - -**Section sources** -- [snapshot_plugin.cpp:2047-2064](file://plugins/snapshot/plugin.cpp#L2047-L2064) -- [snapshot_plugin.cpp:2036-2039](file://plugins/snapshot/plugin.cpp#L2036-L2039) - -## Race Condition Prevention - -### Comprehensive Race Condition Mitigation -The enhanced snapshot plugin addresses multiple race conditions through layered protection mechanisms: - -#### Connection Race Conditions -- **Duplicate Session Prevention**: Session guard ensures no overlap between connection attempts -- **Anti-Spam Race**: Mutex-protected session tracking prevents concurrent access issues -- **Cleanup Timing**: Proper sequencing of cleanup operations prevents timing gaps - -#### Session Management Race Conditions -- **Active Session Tracking**: Thread-safe tracking prevents concurrent modifications -- **Connection Count Synchronization**: Atomic operations ensure accurate connection counts -- **Resource Deallocation**: Proper cleanup order prevents dangling references - -#### P2P Communication Race Conditions -- **Phase Transition Delays**: Controlled timing prevents race between phases -- **Retry Logic Coordination**: Synchronized retry attempts with server cleanup -- **Timeout Management**: Coordinated timeouts prevent deadlocks - -```mermaid -sequenceDiagram -participant Client as "Client" -participant Server as "Server" -Client->>Server : Phase 1 Connection -Server->>Server : Session Created -Client->>Server : Close Phase 1 -Server->>Server : Async Cleanup Started -Client->>Server : Phase 2 Connection (Retry) -Note over Server : Session Guard prevents duplicate -Server->>Server : Cleanup Completes -Server->>Client : Connection Established -``` - -**Diagram sources** -- [snapshot_plugin.cpp:1720-1761](file://plugins/snapshot/plugin.cpp#L1720-L1761) -- [snapshot_plugin.cpp:1804-1820](file://plugins/snapshot/plugin.cpp#L1804-L1820) - -**Section sources** -- [snapshot_plugin.cpp:1720-1761](file://plugins/snapshot/plugin.cpp#L1720-L1761) -- [snapshot_plugin.cpp:1804-1820](file://plugins/snapshot/plugin.cpp#L1804-L1820) - -## Integration with Chain Plugin - -### Callback System -The snapshot plugin integrates with the chain plugin through a sophisticated callback system that coordinates state management during node startup: - -#### Available Callbacks -1. **snapshot_load_callback**: Loads state from snapshot file -2. **snapshot_create_callback**: Creates snapshot after full database load -3. **snapshot_p2p_sync_callback**: Downloads and loads snapshot from trusted peers - -#### Execution Timing -- **snapshot_load_callback**: Executed BEFORE `on_sync()` fires -- **snapshot_create_callback**: Executed AFTER full DB load, BEFORE `on_sync()` -- **snapshot_p2p_sync_callback**: Executed when state is empty, BEFORE `on_sync()` - -```mermaid -sequenceDiagram -participant Chain as "Chain Plugin" -participant Snapshot as "Snapshot Plugin" -Chain->>Chain : Initialize Plugins -Chain->>Snapshot : Set snapshot_load_callback -Chain->>Snapshot : Set snapshot_create_callback -Chain->>Snapshot : Set snapshot_p2p_sync_callback -Chain->>Chain : Load Database State -alt State Exists -Chain->>Chain : Start Normal Operation -else Empty State -alt P2P Sync Enabled -Chain->>Snapshot : Call snapshot_p2p_sync_callback -Snapshot->>Snapshot : Download Snapshot -Snapshot->>Chain : Load Snapshot State -else Create Snapshot -Chain->>Snapshot : Call snapshot_create_callback -Snapshot->>Snapshot : Create Snapshot -end -end -Chain->>Chain : Fire on_sync() -Chain->>Chain : Start P2P/validator -``` - -**Diagram sources** -- [snapshot_plugin.cpp:1872-1919](file://plugins/snapshot/plugin.cpp#L1872-L1919) -- [snapshot_plugin.hpp:92-105](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L92-L105) - -**Section sources** -- [snapshot_plugin.cpp:1872-1919](file://plugins/snapshot/plugin.cpp#L1872-L1919) -- [snapshot_plugin.hpp:92-105](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L92-L105) - -### Configuration Options -The snapshot plugin provides extensive configuration options for different deployment scenarios: - -#### Basic Configuration -- `snapshot-at-block`: Create snapshot at specific block number -- `snapshot-every-n-blocks`: Automatic periodic snapshots -- `snapshot-dir`: Directory for auto-generated snapshots -- `snapshot-max-age-days`: Age-based snapshot cleanup - -#### P2P Synchronization -- `allow-snapshot-serving`: Enable TCP snapshot serving -- `allow-snapshot-serving-only-trusted`: Restrict to trusted peers -- `snapshot-serve-endpoint`: TCP listen endpoint -- `trusted-snapshot-peer`: Trusted peer endpoints (repeatable) - -#### CLI Options -- `--snapshot `: Load state from snapshot file -- `--create-snapshot `: Create snapshot and exit -- `--sync-snapshot-from-trusted-peer true`: Download snapshot on empty state - -**Section sources** -- [snapshot_plugin.cpp:1767-1797](file://plugins/snapshot/plugin.cpp#L1767-L1797) -- [snapshot-plugin.md:142-164](file://documentation/snapshot-plugin.md#L142-L164) - -## Dependency Analysis -- Plugin discovery: The plugins/CMakeLists.txt iterates subdirectories and adds those with a CMakeLists.txt, enabling automatic inclusion of internal plugins. -- Plugin dependencies: - - chain::plugin is required by p2p, database_api, account_history, follow, and **snapshot**. - - json_rpc::plugin is required by webserver, database_api, and follow. - - account_history additionally requires operation_history internally. - - **snapshot::plugin requires chain::plugin for database access**. - -```mermaid -graph LR -CHAIN["chain::plugin"] --> P2P["p2p::plugin"] -CHAIN --> DBAPI["database_api::plugin"] -CHAIN --> AH["account_history::plugin"] -CHAIN --> FOLLOW["follow::plugin"] -CHAIN --> SNAPSHOT["snapshot::plugin"] -JSONRPC["json_rpc::plugin"] --> WS["webserver::plugin"] -JSONRPC --> DBAPI -JSONRPC --> FOLLOW -SNAPSHOT --> CHAIN -``` - -**Diagram sources** -- [p2p_plugin.cpp:531-566](file://plugins/p2p/p2p_plugin.cpp#L531-L566) -- [plugin.hpp:188-191](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [plugin.hpp:28-31](file://plugins/follow/include/graphene/plugins/follow/plugin.hpp#L28-L31) -- [webserver_plugin.cpp:314-327](file://plugins/webserver/webserver_plugin.cpp#L314-L327) -- [snapshot_plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L44) - -**Section sources** -- [CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [p2p_plugin.cpp:531-566](file://plugins/p2p/p2p_plugin.cpp#L531-L566) -- [plugin.hpp:188-191](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [plugin.hpp:28-31](file://plugins/follow/include/graphene/plugins/follow/plugin.hpp#L28-L31) -- [webserver_plugin.cpp:314-327](file://plugins/webserver/webserver_plugin.cpp#L314-L327) -- [snapshot_plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L44) - -## Performance Considerations -- Single write thread: The chain plugin supports a single-write-thread mode to serialize block/transaction application, which can simplify locking but may reduce throughput. -- Shared memory sizing: Configurable shared memory size and increments help manage storage growth during replay or sync. -- Skipping virtual operations: Option to skip virtual operations reduces memory overhead for plugins not requiring them. -- Flush intervals: Periodic flushing of state to disk can be tuned for durability vs. performance trade-offs. -- **Snapshot compression**: Zlib compression reduces snapshot file sizes by 50-70% compared to uncompressed JSON. -- **Chunked transfers**: 1MB chunk size balances memory usage and network efficiency. -- **Connection limits**: Maximum 5 concurrent connections prevents resource exhaustion. -- **Anti-spam controls**: Prevents abuse while maintaining reasonable throughput. -- **Session guard overhead**: Minimal performance impact with significant race condition prevention benefits. -- **Retry logic efficiency**: Controlled retries with exponential backoff optimize connection success rates. - -**Section sources** -- [plugin.cpp:281-346](file://plugins/chain/plugin.cpp#L281-L346) -- [plugin.cpp:300-304](file://plugins/chain/plugin.cpp#L300-L304) -- [snapshot_plugin.cpp:1694-1700](file://plugins/snapshot/plugin.cpp#L1694-L1700) -- [snapshot-plugin.md:96-103](file://documentation/snapshot-plugin.md#L96-L103) - -## Troubleshooting Guide -- Database errors on startup: The chain plugin catches database revision and block log exceptions, optionally triggering a replay or wipe and resync path. -- Logging configuration: The application loads logging configuration from the config file and applies appenders/loggers accordingly. -- Graceful shutdown: Plugins should release connections and close databases in plugin_shutdown with proper cleanup procedures. -- Signal handling: The enhanced signal handling system provides better control over shutdown procedures and resource cleanup. -- **Snapshot issues**: - - Verify snapshot file integrity using checksum verification - - Check snapshot directory permissions for read/write access - - Monitor TCP server logs for connection errors - - Validate trusted peer configurations for P2P sync - - **Check session guard logs for race condition prevention** - - **Monitor retry attempts for connection establishment issues** -- **Performance issues**: - - Monitor snapshot creation time and adjust chunk sizes - - Check network bandwidth for P2P synchronization - - Verify anti-spam configuration isn't blocking legitimate connections - - **Review session guard effectiveness in preventing race conditions** - -```mermaid -flowchart TD -Start(["Startup"]) --> OpenDB["Open chain database"] -OpenDB --> DBErr{"Exception?"} -DBErr --> |Yes| ReplayOpt{"Replay-if-corrupted?"} -ReplayOpt --> |Yes| Replay["Replay or Wipe + Reindex"] -ReplayOpt --> |No| Quit["Exit gracefully"] -DBErr --> |No| Ready["on_sync and continue"] -Ready --> SnapshotCheck{"Snapshot Configured?"} -SnapshotCheck --> |Yes| SnapshotOp{"Load/Create/P2P Sync"} -SnapshotCheck --> |No| Normal["Normal Operation"] -SnapshotOp --> SnapshotOK{"Snapshot Success?"} -SnapshotOK --> |Yes| Ready -SnapshotOK --> |No| Error["Log Error and Continue"] -Error --> Normal -Shutdown(["Shutdown Requested"]) --> SignalCheck{"Signal Detected?"} -SignalCheck --> |Yes| InterruptOps["Check operations for interruption"] -InterruptOps --> Cleanup["Cleanup resources properly"] -SignalCheck --> |No| NormalShutdown["Normal shutdown procedure"] -Cleanup --> Finalize["Finalize shutdown"] -NormalShutdown --> Finalize -``` - -**Diagram sources** -- [plugin.cpp:348-386](file://plugins/chain/plugin.cpp#L348-L386) -- [database.cpp:270-329](file://libraries/chain/database.cpp#L270-L329) -- [snapshot_plugin.cpp:1872-1919](file://plugins/snapshot/plugin.cpp#L1872-L1919) - -**Section sources** -- [plugin.cpp:348-386](file://plugins/chain/plugin.cpp#L348-L386) -- [main.cpp:131-137](file://programs/vizd/main.cpp#L131-L137) -- [database.cpp:270-329](file://libraries/chain/database.cpp#L270-L329) -- [snapshot_plugin.cpp:1872-1919](file://plugins/snapshot/plugin.cpp#L1872-L1919) - -## Conclusion -The enhanced plugin architecture leverages appbase to provide a clean separation of concerns with improved lifecycle management, enabling flexible feature addition and removal. Plugins declare dependencies, participate in a standardized lifecycle with proper shutdown procedures, and communicate via signals and API factories. The enhanced shutdown mechanisms, signal handler coordination, and memory leak prevention ensure robust, maintainable extensions to the node. - -**The new snapshot plugin system significantly enhances the architecture by providing:** -- Efficient DLT state management through automatic snapshot creation and loading -- Custom TCP protocol for optimized P2P synchronization -- Comprehensive anti-spam protection and security controls -- Seamless integration with the chain plugin through specialized callbacks -- **Robust session management with RAII-based race condition prevention** -- **Sophisticated retry logic addressing P2P communication timing issues** -- **Layered protection mechanisms preventing various race conditions** - -The template generator accelerates development while the chain plugin centralizes database access and synchronization events with proper resource cleanup. The enhanced snapshot plugin exemplifies best practices for extending node functionality with specialized protocols, robust error handling, comprehensive configuration options, and sophisticated concurrency control mechanisms. - -## Appendices -- Best practices for extending node functionality: - - Use APPBASE_PLUGIN_REQUIRES to declare explicit dependencies. - - Register API factories in plugin_startup and expose only necessary methods. - - Subscribe to chain signals (e.g., on_sync) to coordinate with other plugins. - - Implement proper plugin_shutdown methods for resource cleanup. - - Use scoped_connection objects for automatic disconnection. - - Handle signals appropriately using the signal_guard mechanism. - - Keep plugin responsibilities narrow and focused on specific domains. - - Use configuration options to tune performance and behavior. - - Ensure graceful shutdown procedures for all long-running operations. - - **For network plugins**: Implement proper connection management, anti-abuse controls, and race condition prevention. - - **For state management plugins**: Provide validation and recovery mechanisms with proper cleanup procedures. - - **For protocol plugins**: Design efficient wire formats, implement proper error handling, and consider concurrency implications. - - **For plugins with shared resources**: Implement RAII-based resource management to prevent race conditions. - - **For client-side plugins**: Implement retry logic with proper backoff strategies for reliable operation. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin Lifecycle and Registration.md b/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin Lifecycle and Registration.md deleted file mode 100644 index 2c3ab84a00..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/Plugin Architecture/Plugin Lifecycle and Registration.md +++ /dev/null @@ -1,445 +0,0 @@ -# Plugin Lifecycle and Registration - - -**Referenced Files in This Document** -- [main.cpp](file://programs/vizd/main.cpp) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [snapshot_plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp) -- [snapshot_plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [plugin.md](file://documentation/plugin.md) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) - - -## Update Summary -**Changes Made** -- Enhanced plugin initialization order requirements section to reflect new coordination mechanisms -- Added comprehensive coverage of snapshot plugin integration with chain plugin callbacks -- Updated deferred execution model documentation for snapshot loading and creation -- Expanded coordination between chain and snapshot plugins during startup -- Added detailed explanation of P2P snapshot sync callback mechanism -- Updated dependency analysis to include snapshot plugin requirements - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Enhanced Plugin Initialization Order Requirements](#enhanced-plugin-initialization-order-requirements) -7. [Deferred Execution Model for Snapshot Loading](#deferred-execution-model-for-snapshot-loading) -8. [Coordination Between Chain and Snapshot Plugins](#coordination-between-chain-and-snapshot-plugins) -9. [Dependency Analysis](#dependency-analysis) -10. [Performance Considerations](#performance-considerations) -11. [Troubleshooting Guide](#troubleshooting-guide) -12. [Conclusion](#conclusion) - -## Introduction -This document explains the plugin lifecycle and registration mechanisms in the application built on the appbase framework. It covers the three phases of plugin execution: initialization, startup, and shutdown. It also documents how plugins register themselves with the application, how dependencies are declared via the APPBASE_PLUGIN_REQUIRES macro, and how the application framework determines plugin loading order. The document has been updated to reflect enhanced plugin initialization order requirements, improved coordination between chain and snapshot plugins, and the deferred execution model for snapshot loading. - -## Project Structure -The application binary registers and initializes plugins in the main entry point. Plugins are organized under plugins// with a standard plugin interface that extends appbase::plugin. Dependencies between plugins are declared using APPBASE_PLUGIN_REQUIRES in each plugin's header. The snapshot plugin introduces a sophisticated callback system that coordinates with the chain plugin for deferred execution of snapshot operations. - -```mermaid -graph TB -A["programs/vizd/main.cpp
Entry point"] --> B["appbase::application
register_plugin()"] -B --> C["plugins/*/include/*.hpp
APPBASE_PLUGIN_REQUIRES(...)"] -C --> D["plugins/*/plugin.cpp
plugin_* methods"] -D --> E["Chain Plugin
Callback Registration"] -E --> F["Snapshot Plugin
Deferred Execution"] -``` - -**Diagram sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.hpp:21-42](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L42) -- [snapshot_plugin.hpp:55-89](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L89) - -**Section sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.md:1-28](file://documentation/plugin.md#L1-L28) - -## Core Components -- Application entry point and plugin registration: - - The application registers all plugins in the main entry point and then initializes the appbase application with a specific set of plugins. - - The application sets program options, initializes, starts up, and executes the event loop. - -- Plugin interface and lifecycle: - - Plugins derive from appbase::plugin and implement: - - plugin_initialize(options): parse configuration and prepare resources. - - plugin_startup(): open databases, bind services, start threads, and signal readiness. - - plugin_shutdown(): close resources and shut down gracefully. - -- Enhanced dependency management: - - Plugins declare dependencies using APPBASE_PLUGIN_REQUIRES in their header. - - The chain plugin requires json_rpc; snapshot plugin requires chain; p2p requires chain; webserver requires json_rpc. - - The appbase framework resolves dependencies and ensures required plugins are initialized before dependents. - -- Deferred execution coordination: - - The chain plugin provides callback mechanisms for snapshot operations. - - Snapshot plugin registers callbacks during initialization that execute during chain plugin startup. - - This ensures proper sequencing of snapshot loading, creation, and P2P sync operations. - -**Section sources** -- [main.cpp:108-160](file://programs/vizd/main.cpp#L108-L160) -- [plugin.hpp:21-42](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L42) -- [plugin.cpp:254-396](file://plugins/chain/plugin.cpp#L254-L396) -- [snapshot_plugin.hpp:55-89](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L89) - -## Architecture Overview -The application controls plugin lifecycle through appbase. Plugins are registered centrally, then initialized and started in dependency-aware order. The chain plugin typically initializes first because many plugins require it. The snapshot plugin introduces a callback-driven coordination mechanism that allows it to defer operations until the chain plugin is ready. - -```mermaid -sequenceDiagram -participant Main as "main.cpp" -participant App as "appbase : : application" -participant Chain as "chain : : plugin" -participant Snapshot as "snapshot : : snapshot_plugin" -participant P2P as "p2p : : p2p_plugin" -participant WS as "webserver : : webserver_plugin" -Main->>App : register_plugin(chain) -Main->>App : register_plugin(snapshot) -Main->>App : register_plugin(p2p) -Main->>App : register_plugin(webserver) -Main->>App : initialize(argc, argv) -App->>Chain : plugin_initialize(options) -App->>Snapshot : plugin_initialize(options) -App->>P2P : plugin_initialize(options) -App->>WS : plugin_initialize(options) -App->>Chain : plugin_startup() -Chain->>Snapshot : trigger_snapshot_load() -Chain->>Chain : on_sync() -App->>Snapshot : plugin_startup() -Snapshot->>Snapshot : start_server() -``` - -**Diagram sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [plugin.cpp:254-396](file://plugins/chain/plugin.cpp#L254-L396) -- [snapshot_plugin.cpp:3031-3093](file://plugins/snapshot/plugin.cpp#L3031-L3093) - -## Detailed Component Analysis - -### Plugin Registration in main.cpp -- Centralized registration: - - The application registers each plugin via appbase::app().register_plugin(). - - Registration occurs before initialize() so dependencies can be resolved. - -- Enhanced initialization invocation: - - After registration, initialize() is called with a variadic list of plugins to initialize. - - The application then calls startup() and enters the event loop via exec(). - -- Practical pattern: - - Keep registration in one place (register_plugins()) and pass the same set to initialize(). - -**Section sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [main.cpp:117-122](file://programs/vizd/main.cpp#L117-L122) -- [main.cpp:139-140](file://programs/vizd/main.cpp#L139-L140) - -### Plugin Lifecycle Phases - -#### plugin_initialize -- Purpose: - - Parse configuration options and prepare internal state. - - Set up paths, sizes, and flags required for later operations. - -- Enhanced behavior with snapshot coordination: - - The chain plugin prepares callback registration points for snapshot operations. - - The snapshot plugin registers callback functions that will execute during chain plugin startup. - - Dependencies are declared using APPBASE_PLUGIN_REQUIRES with enhanced coordination requirements. - -- Error handling: - - Exceptions during initialization should be allowed to propagate so the application can handle them gracefully. - -**Section sources** -- [plugin.cpp:254-314](file://plugins/chain/plugin.cpp#L254-L314) -- [snapshot_plugin.cpp:2821-2977](file://plugins/snapshot/plugin.cpp#L2821-L2977) - -#### plugin_startup -- Purpose: - - Open databases, bind services, start threads, and publish readiness signals. - - Perform actions that require dependencies to be ready. - -- Enhanced startup with deferred execution: - - The chain plugin triggers snapshot loading through registered callbacks. - - The snapshot plugin starts its TCP server and begins accepting connections. - - P2P snapshot sync callback is executed when state is empty and snapshot is available. - -- Startup timing: - - Occurs after plugin_initialize for all plugins and after all dependencies are initialized. - -**Section sources** -- [plugin.cpp:316-390](file://plugins/chain/plugin.cpp#L316-L390) -- [snapshot_plugin.cpp:3031-3093](file://plugins/snapshot/plugin.cpp#L3031-L3093) - -#### plugin_shutdown -- Purpose: - - Close databases, stop threads, and release resources. - - Ensure clean termination. - -- Enhanced shutdown coordination: - - The chain plugin ensures proper shutdown sequence. - - The snapshot plugin stops stalled sync detection and TCP server gracefully. - -**Section sources** -- [plugin.cpp:392-396](file://plugins/chain/plugin.cpp#L392-L396) -- [snapshot_plugin.cpp:3095-3100](file://plugins/snapshot/plugin.cpp#L3095-L3100) - -### Enhanced Plugin Initialization Order Requirements - -#### Dependency Resolution with Coordination -- Declaration: - - Plugins declare dependencies in their header using APPBASE_PLUGIN_REQUIRES((dep1)(dep2)...). - - The chain plugin requires json_rpc; snapshot plugin requires chain; p2p requires chain; webserver requires json_rpc. - -- Enhanced resolution: - - The appbase framework ensures dependencies are initialized before the dependent plugin. - - The chain plugin provides callback registration points that allow dependent plugins to coordinate operations. - - The snapshot plugin registers callbacks during initialization that execute during chain plugin startup. - -- Example declarations: - - chain plugin declares json_rpc as a requirement. - - snapshot plugin declares chain as a requirement. - - p2p plugin declares chain as a requirement. - - webserver plugin declares json_rpc as a requirement. - -**Section sources** -- [plugin.hpp:21-34](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L34) -- [snapshot_plugin.hpp:55-57](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L57) -- [p2p_plugin.hpp:18-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L20) -- [webserver_plugin.hpp:32-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L38) - -### Plugin Naming Conventions and Static name() Method -- Naming convention: - - Plugins define a constant or macro for their name (e.g., P2P_PLUGIN_NAME, WEBSERVER_PLUGIN_NAME). - - The static name() method returns the plugin's string identifier. - -- Implementation pattern: - - A static std::string is constructed once and returned by name(). - - This enables consistent identification and logging. - -**Section sources** -- [p2p_plugin.hpp:29-32](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L29-L32) -- [webserver_plugin.hpp:40-43](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L40-L43) - -### Plugin Registration Patterns and Loading Order -- Registration pattern: - - Call appbase::app().register_plugin() for each plugin. - - Pass the same set of plugins to initialize(). - -- Enhanced loading order: - - The appbase framework resolves dependencies first, then initializes others in the order provided to initialize<...>(). - - The chain plugin typically initializes first, followed by snapshot plugin, then dependent plugins like p2p and webserver. - - Callback registration ensures proper sequencing of operations across plugin boundaries. - -- Practical example: - - The application registers chain, snapshot, p2p, and webserver, then initializes them in that order with enhanced coordination. - -**Section sources** -- [main.cpp:62-90](file://programs/vizd/main.cpp#L62-L90) -- [main.cpp:117-122](file://programs/vizd/main.cpp#L117-L122) - -### Error Handling During Initialization and Graceful Shutdown -- Initialization errors: - - The chain plugin catches database-related exceptions during startup and either replays or exits depending on configuration. - - The snapshot plugin handles various error scenarios including peer connection failures and snapshot validation errors. - - Other plugins should throw or log errors during plugin_initialize to prevent startup. - -- Enhanced graceful shutdown: - - The chain plugin closes the database in plugin_shutdown. - - The snapshot plugin stops stalled sync detection and TCP server gracefully. - - Other plugins should stop threads and release resources in plugin_shutdown. - -**Section sources** -- [plugin.cpp:348-396](file://plugins/chain/plugin.cpp#L348-L396) -- [snapshot_plugin.cpp:3095-3100](file://plugins/snapshot/plugin.cpp#L3095-L3100) - -## Enhanced Plugin Initialization Order Requirements - -### Advanced Dependency Coordination -The enhanced plugin initialization order requirements ensure proper sequencing of operations across plugin boundaries, particularly for the snapshot plugin's coordination with the chain plugin. - -```mermaid -graph LR -JSON["json_rpc::plugin"] --> CHAIN["chain::plugin"] -CHAIN --> SNAPSHOT["snapshot::snapshot_plugin"] -CHAIN --> P2P["p2p::p2p_plugin"] -JSON --> WS["webserver::webserver_plugin"] -SNAPSHOT -.-> CHAIN -P2P --> CHAIN -WS --> JSON -``` - -**Diagram sources** -- [plugin.hpp:21-23](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L23) -- [snapshot_plugin.hpp:55-57](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L57) -- [p2p_plugin.hpp:18-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L20) -- [webserver_plugin.hpp:32-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L38) - -**Section sources** -- [plugin.hpp:21-34](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L34) -- [snapshot_plugin.hpp:55-57](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L57) -- [p2p_plugin.hpp:18-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L20) -- [webserver_plugin.hpp:32-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L38) - -## Deferred Execution Model for Snapshot Loading - -### Snapshot Operation Coordination -The snapshot plugin implements a sophisticated deferred execution model that coordinates with the chain plugin through callback mechanisms. - -```mermaid -sequenceDiagram -participant Chain as "Chain Plugin" -participant Snapshot as "Snapshot Plugin" -participant DB as "Database" -Chain->>Chain : plugin_initialize() -Chain->>Chain : prepare_callbacks() -Snapshot->>Snapshot : plugin_initialize() -Snapshot->>Chain : register_callbacks() -Snapshot->>Snapshot : parse_config() -Snapshot->>Snapshot : setup_callbacks() -Chain->>Chain : plugin_startup() -Chain->>Snapshot : trigger_snapshot_load() -Snapshot->>DB : load_snapshot() -DB-->>Snapshot : snapshot_loaded -Snapshot->>DB : initialize_hardforks() -Chain->>Chain : on_sync() -Chain->>Chain : plugin_startup() -Chain->>Snapshot : check_state() -alt State empty -Chain->>Snapshot : snapshot_p2p_sync_callback() -Snapshot->>Snapshot : download_snapshot_from_peers() -Snapshot->>DB : load_snapshot() -Snapshot->>DB : set_dlt_mode() -Snapshot->>DB : initialize_hardforks() -else State not empty -Chain->>Chain : normal_startup() -end -``` - -**Diagram sources** -- [snapshot_plugin.cpp:2947-3028](file://plugins/snapshot/plugin.cpp#L2947-L3028) -- [plugin.cpp:437-532](file://plugins/chain/plugin.cpp#L437-L532) - -### Deferred Execution Mechanisms -The deferred execution model ensures that snapshot operations occur at the appropriate time in the plugin lifecycle: - -- **Snapshot Loading**: Executed during chain plugin startup, before on_sync() fires, ensuring P2P sync starts from the snapshot head block. -- **Snapshot Creation**: Executed after full database load (including replay), but before on_sync(), preventing P2P/validator startup. -- **P2P Snapshot Sync**: Executed when state is empty (head_block_num == 0), before on_sync(), enabling bootstrap from trusted peers. - -**Section sources** -- [snapshot_plugin.cpp:2947-3028](file://plugins/snapshot/plugin.cpp#L2947-L3028) -- [plugin.cpp:437-532](file://plugins/chain/plugin.cpp#L437-L532) - -## Coordination Between Chain and Snapshot Plugins - -### Callback Registration System -The chain plugin provides a callback registration system that enables the snapshot plugin to coordinate operations at specific points in the chain plugin's lifecycle. - -```mermaid -graph TD -A["Chain Plugin
plugin_initialize()"] --> B["Register Callback Points"] -B --> C["snapshot_load_callback"] -B --> D["snapshot_create_callback"] -B --> E["snapshot_p2p_sync_callback"] -F["Snapshot Plugin
plugin_initialize()"] --> G["Register Callback Functions"] -G --> H["load_snapshot()"] -G --> I["create_snapshot()"] -G --> J["download_snapshot_from_peers()"] -K["Chain Plugin
plugin_startup()"] --> L["Execute Registered Callbacks"] -L --> M["trigger_snapshot_load()"] -L --> N["Check State for P2P Sync"] -N --> O["snapshot_p2p_sync_callback()"] -``` - -**Diagram sources** -- [plugin.hpp:97-110](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L97-L110) -- [snapshot_plugin.cpp:2947-3028](file://plugins/snapshot/plugin.cpp#L2947-L3028) - -### Enhanced Coordination Features -The coordination between chain and snapshot plugins includes several advanced features: - -- **Trigger Mechanism**: The chain plugin provides `trigger_snapshot_load()` to activate deferred snapshot loading when the snapshot plugin is ready. -- **State Awareness**: The chain plugin checks if state is empty (head_block_num == 0) before executing P2P snapshot sync. -- **Execution Timing**: Callbacks execute at precisely defined points in the plugin lifecycle to ensure proper sequencing. -- **Error Handling**: Comprehensive error handling for snapshot operations including peer connection failures and validation errors. - -**Section sources** -- [plugin.hpp:52-55](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L52-L55) -- [snapshot_plugin.cpp:3031-3093](file://plugins/snapshot/plugin.cpp#L3031-L3093) - -## Dependency Analysis -This section maps plugin dependencies and their impact on initialization order, with enhanced focus on the coordination between chain and snapshot plugins. - -```mermaid -graph LR -JSON["json_rpc::plugin"] --> CHAIN["chain::plugin"] -CHAIN --> SNAPSHOT["snapshot::snapshot_plugin"] -CHAIN --> P2P["p2p::p2p_plugin"] -JSON --> WS["webserver::webserver_plugin"] -SNAPSHOT -.-> CHAIN -P2P --> CHAIN -WS --> JSON -subgraph "Enhanced Coordination" -CHAIN -.-> SNAPSHOT -SNAPSHOT -.-> CHAIN -end -``` - -**Diagram sources** -- [plugin.hpp:21-23](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L23) -- [snapshot_plugin.hpp:55-57](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L57) -- [p2p_plugin.hpp:18-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L20) -- [webserver_plugin.hpp:32-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L38) - -**Section sources** -- [plugin.hpp:21-34](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L34) -- [snapshot_plugin.hpp:55-57](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L55-L57) -- [p2p_plugin.hpp:18-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L20) -- [webserver_plugin.hpp:32-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L38) - -## Performance Considerations -- Minimize heavy work in plugin_initialize; defer expensive operations to plugin_startup. -- Use asynchronous I/O and dedicated threads where appropriate (e.g., webserver runs in its own thread). -- Configure shared memory and flush intervals thoughtfully to balance safety and performance. -- The snapshot plugin's deferred execution model prevents blocking operations during critical startup phases. -- Anti-spam protections in the snapshot TCP server prevent resource exhaustion during high-load scenarios. - -## Troubleshooting Guide -- Plugin fails to start due to missing dependency: - - Ensure the required plugin is registered and appears before the dependent plugin in the initialize<> list or rely on the framework to resolve dependencies. - - Verify that callback registration occurs before the chain plugin attempts to trigger snapshot operations. - -- Database errors during startup: - - The chain plugin attempts to replay on revision mismatch or block log errors. Adjust configuration flags to force replay or resync if needed. - - The snapshot plugin handles various error scenarios including peer connection failures and snapshot validation errors. - -- Snapshot loading issues: - - Verify that snapshot plugin is registered before chain plugin to ensure proper callback registration. - - Check that snapshot file paths are accessible and contain valid snapshot data. - - Monitor snapshot loading logs for detailed error information. - -- P2P snapshot sync problems: - - Ensure trusted snapshot peers are properly configured and reachable. - - Verify that snapshot serving is enabled on source nodes and that anti-spam configurations are appropriate. - - Check network connectivity and firewall settings for snapshot TCP connections. - -- Logging configuration issues: - - Review logging options and ensure the configuration file sections are correctly formatted. - -- Common pitfalls: - - Forgetting to register a plugin leads to unresolved dependencies. - - Not implementing plugin_shutdown properly can leave resources open. - - Performing blocking operations in plugin_initialize can delay startup. - - Missing callback registration prevents proper coordination between plugins. - -**Section sources** -- [plugin.cpp:348-396](file://plugins/chain/plugin.cpp#L348-L396) -- [snapshot_plugin.cpp:3095-3100](file://plugins/snapshot/plugin.cpp#L3095-L3100) -- [plugin.md:11-28](file://documentation/plugin.md#L11-L28) - -## Conclusion -The appbase framework provides a robust lifecycle for plugins with enhanced coordination capabilities: register them centrally, declare dependencies with APPBASE_PLUGIN_REQUIRES, implement plugin_initialize, plugin_startup, and plugin_shutdown, and rely on the framework to enforce dependency ordering. The chain plugin typically initializes first, followed by dependent plugins such as snapshot, p2p, and webserver. The enhanced coordination between chain and snapshot plugins through callback mechanisms ensures proper sequencing of snapshot operations. The deferred execution model prevents blocking operations during critical startup phases and enables sophisticated features like P2P snapshot synchronization. Proper error handling during initialization and graceful shutdown ensure reliable operation, while the callback-driven architecture provides flexible coordination between plugins for complex operational scenarios. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Architecture Overview/System Overview.md b/.qoder/repowiki/en/content/Architecture Overview/System Overview.md deleted file mode 100644 index 207137cfff..0000000000 --- a/.qoder/repowiki/en/content/Architecture Overview/System Overview.md +++ /dev/null @@ -1,384 +0,0 @@ -# System Overview - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [main.cpp](file://programs/vizd/main.cpp) -- [CMakeLists.txt](file://libraries/CMakeLists.txt) -- [CMakeLists.txt](file://plugins/CMakeLists.txt) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [chain_api_properties.hpp](file://libraries/api/include/graphene/api/chain_api_properties.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document presents a system overview of the VIZ C++ Node, focusing on how the main vizd process orchestrates the entire stack: the application framework (appbase), the blockchain core (chain library), protocol definitions (protocol library), networking (network library), wallet functionality (wallet library), and the plugin system. It explains the modular design that enables flexible feature addition and removal through plugins, describes the observer pattern used for event-driven architecture, and illustrates the data flow from JSON-RPC requests through plugins to database operations. It also outlines system boundaries for peer-to-peer interactions, API request handling, and persistent state management. - -## Project Structure -The repository is organized around a layered architecture: -- Application entrypoint and plugin orchestration live under programs/vizd. -- Libraries are grouped by domain: protocol, chain, network, wallet, api, and utilities. -- Plugins are feature modules that integrate with the appbase framework and the chain library. - -```mermaid -graph TB -subgraph "Application Layer" -VIZD["vizd main()
programs/vizd/main.cpp"] -end -subgraph "Plugin Layer" -ChainP["Chain Plugin
plugins/chain/plugin.hpp"] -P2P["P2P Plugin
plugins/p2p/p2p_plugin.hpp"] -Web["Webserver Plugin
plugins/webserver/webserver_plugin.hpp"] -end -subgraph "Core Libraries" -Protocol["Protocol Library
libraries/protocol/*"] -ChainLib["Chain Library
libraries/chain/*"] -Network["Network Library
libraries/network/*"] -Wallet["Wallet Library
libraries/wallet/*"] -API["API Objects
libraries/api/*"] -end -VIZD --> ChainP -VIZD --> P2P -VIZD --> Web -ChainP --> ChainLib -ChainP --> Protocol -ChainP --> API -P2P --> Network -P2P --> ChainP -Web --> ChainP -ChainLib --> Protocol -ChainLib --> Network -ChainLib --> API -Wallet --> ChainLib -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -**Section sources** -- [README.md](file://README.md#L1-L53) -- [CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -## Core Components -- Application framework (appbase): Provides the plugin lifecycle, dependency injection, and runtime orchestration. The vizd entrypoint registers and initializes plugins, sets logging, and runs the event loop. -- Blockchain core (chain library): Implements the database, block validation, transaction processing, and event signals for observers. -- Protocol definitions (protocol library): Defines operations, transactions, blocks, and types used across the chain. -- Networking (network library): Offers a peer-to-peer node abstraction with delegate callbacks for block/transaction handling and synchronization. -- Wallet (wallet library): Provides wallet APIs and helpers for building and signing transactions. -- Plugin system: Feature modules that depend on appbase and optionally on the chain library, enabling modular extension. - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L287) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L60-L167) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) - -## Architecture Overview -The VIZ node follows an event-driven architecture: -- The vizd process initializes appbase, registers plugins, and starts the application loop. -- The chain plugin owns the blockchain state and emits signals for operations, blocks, and transactions. -- The P2P plugin consumes network events and forwards blocks/transactions to the chain. -- The webserver plugin exposes JSON-RPC endpoints that route to chain and API plugins. -- Plugins can subscribe to chain signals to react to state changes. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant Web as "Webserver Plugin" -participant Chain as "Chain Plugin" -participant DB as "Database" -participant Net as "P2P Plugin" -Client->>Web : "JSON-RPC Request" -Web->>Chain : "Dispatch to Chain API" -Chain->>DB : "Read/Write State" -DB-->>Chain : "Result" -Chain-->>Web : "Response" -Web-->>Client : "JSON-RPC Response" -Net->>Chain : "Accept Block" -Net->>Chain : "Accept Transaction" -Chain->>DB : "Apply Block/Transaction" -DB-->>Chain : "Applied" -Chain-->>Net : "Acknowledge" -``` - -**Diagram sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L287) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) - -## Detailed Component Analysis - -### Application Orchestration (vizd) -- Registers plugins including chain, p2p, webserver, and many others. -- Initializes logging and starts the appbase event loop. -- Ensures required plugins are available before startup. - -```mermaid -flowchart TD -Start(["Process Start"]) --> Register["Register Plugins"] -Register --> InitLogging["Initialize Logging Config"] -InitLogging --> Startup["Startup Plugins"] -Startup --> Exec["Run Event Loop"] -Exec --> Shutdown(["Shutdown"]) -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) - -### Plugin System and Dependencies -- Plugins declare dependencies using appbase macros. -- The chain plugin depends on json_rpc; webserver and p2p depend on json_rpc; p2p depends on chain. -- This ensures initialization order and decouples plugin lifecycles. - -```mermaid -classDiagram -class ChainPlugin { -+plugin_initialize() -+plugin_startup() -+accept_block() -+accept_transaction() -} -class P2PPlugin { -+broadcast_block() -+broadcast_transaction() -+plugin_initialize() -} -class WebserverPlugin { -+plugin_initialize() -+plugin_startup() -} -ChainPlugin --> WebserverPlugin : "uses JSON-RPC" -P2PPlugin --> ChainPlugin : "requires" -WebserverPlugin --> ChainPlugin : "uses JSON-RPC" -``` - -**Diagram sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -**Section sources** -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L42) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L38) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L38-L52) - -### Blockchain Core and Signals -- The database exposes signals for operation application, block application, pending/applied transactions, and plugin index registration. -- Plugins subscribe to these signals to implement features like history tracking, API indexing, and analytics. - -```mermaid -classDiagram -class Database { -+pre_apply_operation -+post_apply_operation -+applied_block -+on_pending_transaction -+on_applied_transaction -+_plugin_index_signal -} -class ChainPlugin { -+plugin_initialize() -+accept_block() -+accept_transaction() -} -Database --> ChainPlugin : "emits signals" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L287) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L287) - -### Protocol Definitions -- Operations are defined as a static variant covering on-chain actions and virtual operations. -- This type system underpins transaction validation and operation dispatch. - -```mermaid -classDiagram -class Operation { -<> -} -class Operations { -+transfer_operation -+account_update_operation -+proposal_create_operation -+virtual_operations... -} -Operations --> Operation : "static_variant" -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -### Networking and Peer Interactions -- The node delegate interface defines callbacks for handling blocks, transactions, and synchronization. -- The node manages peer connections, broadcasting, and sync status reporting. - -```mermaid -classDiagram -class NodeDelegate { -+has_item() -+handle_block() -+handle_transaction() -+get_item() -+get_blockchain_synopsis() -+sync_status() -+connection_count_changed() -} -class Node { -+set_node_delegate() -+listen_on_endpoint() -+connect_to_endpoint() -+broadcast() -+sync_from() -} -Node --> NodeDelegate : "calls back" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L60-L167) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L60-L167) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) - -### Wallet Integration -- The wallet API provides transaction building, signing, and proposal workflows. -- It integrates with remote node APIs and chain state for account and balance queries. - -```mermaid -classDiagram -class WalletAPI { -+begin_builder_transaction() -+add_operation_to_builder_transaction() -+sign_builder_transaction() -+propose_builder_transaction() -+get_account() -+get_dynamic_global_properties() -} -WalletAPI --> Database : "queries" -``` - -**Diagram sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -**Section sources** -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -### API Properties and State Exposure -- API objects encapsulate chain state for consumption by clients. -- Example: chain_api_properties mirrors chain configuration exposed via APIs. - -```mermaid -classDiagram -class ChainAPIProperties { -+account_creation_fee -+maximum_block_size -+create_account_delegation_ratio -+... (other fields) -} -ChainAPIProperties --> Database : "constructed from" -``` - -**Diagram sources** -- [chain_api_properties.hpp](file://libraries/api/include/graphene/api/chain_api_properties.hpp#L11-L44) - -**Section sources** -- [chain_api_properties.hpp](file://libraries/api/include/graphene/api/chain_api_properties.hpp#L11-L44) - -## Dependency Analysis -- The plugin layer depends on appbase and optionally on the chain library. -- The chain library depends on protocol definitions and network abstractions. -- The webserver and p2p plugins depend on json_rpc for request routing. -- The wallet library depends on chain and protocol types. - -```mermaid -graph LR -AppBase["appbase"] --> Plugins["Plugins"] -Plugins --> ChainLib["Chain Library"] -Plugins --> Protocol["Protocol Library"] -Plugins --> Network["Network Library"] -Webserver["Webserver Plugin"] --> JsonRPC["JSON-RPC"] -P2P["P2P Plugin"] --> ChainLib -ChainLib --> Protocol -ChainLib --> Network -Wallet["Wallet Library"] --> ChainLib -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -## Performance Considerations -- Signal-based event handling avoids tight coupling and supports efficient plugin reactions to chain state changes. -- The plugin model enables selective feature activation, reducing overhead when unnecessary modules are disabled. -- Proper logging configuration and network bandwidth limits help maintain responsiveness under load. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Verify plugin registration and initialization order in the main entrypoint. -- Confirm logging configuration is loaded and appropriate appenders/loggers are defined. -- Monitor network delegate callbacks for block/transaction acceptance and sync progress. -- Subscribe to chain signals to diagnose operation/application timing and failures. - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L117-L158) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L252-L287) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L60-L167) - -## Conclusion -The VIZ C++ Node is a modular, event-driven system centered on appbase and the chain library. The plugin architecture cleanly separates concerns across networking, API exposure, and specialized features, while the observer pattern enables responsive, decoupled reactions to blockchain state changes. Together, these components form a robust foundation for a production-grade blockchain node with flexible feature management and clear system boundaries. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Chain Plugin.md b/.qoder/repowiki/en/content/Chain Plugin.md deleted file mode 100644 index ca73407da8..0000000000 --- a/.qoder/repowiki/en/content/Chain Plugin.md +++ /dev/null @@ -1,815 +0,0 @@ -# Chain Plugin - - -**Referenced Files in This Document** -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp) -- [plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [application.cpp](file://thirdparty/appbase/application.cpp) -- [README.md](file://README.md) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) -- [console_appender.cpp](file://thirdparty/fc/src/log/console_appender.cpp) -- [console_defines.h](file://thirdparty/fc/src/log/console_defines.h) -- [logger_config.cpp](file://thirdparty/fc/src/log/logger_config.cpp) -- [main.cpp](file://programs/vizd/main.cpp) - - -## Update Summary -**Changes Made** -- Added comprehensive automatic recovery system from shared memory corruption with new --auto-recover-from-snapshot flag -- Implemented new --snapshot-auto-latest command-line flag for automatic snapshot discovery -- Enhanced database error handling with shared_memory_corruption_exception for robust error reporting -- Improved chain plugin startup sequence with conditional on_sync() callback invocation to prevent conflicts during automatic recovery scenarios -- Added immediate auto-recovery mechanism that triggers during block processing when corruption is detected - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Enhanced Logging System](#enhanced-logging-system) -7. [Automatic Recovery System](#automatic-recovery-system) -8. [Dependency Analysis](#dependency-analysis) -9. [Performance Considerations](#performance-considerations) -10. [Troubleshooting Guide](#troubleshooting-guide) -11. [Conclusion](#conclusion) - -## Introduction -The Chain Plugin is the core component responsible for managing the blockchain state, accepting blocks and transactions, maintaining database consistency, and coordinating with other plugins in the VIZ node. It integrates tightly with the underlying database layer and provides APIs for block acceptance, transaction processing, and state queries. Recent enhancements focus on improved plugin coordination, deferred execution support for snapshot loading, comprehensive recovery system integration with DLT block log capabilities, expanded snapshot management infrastructure with consistent data directory usage, enhanced logging system with visual differentiation for better debugging experience, and most importantly, a comprehensive automatic recovery system that can detect and recover from shared memory corruption scenarios. - -## Project Structure -The Chain Plugin resides under the `plugins/chain` directory and interfaces with the `libraries/chain` database implementation. The plugin exposes a clean interface for other plugins and the application to interact with the blockchain state, with enhanced deferred execution support and comprehensive recovery capabilities. The data directory path has been standardized to use 'state' for improved organizational clarity. - -```mermaid -graph TB -subgraph "Plugins Layer" -ChainPlugin["Chain Plugin
plugins/chain"] -JSONRPC["JSON-RPC Plugin
plugins/json_rpc"] -SnapshotPlugin["Snapshot Plugin
plugins/snapshot"] -end -subgraph "Chain Library" -Database["Database Implementation
libraries/chain/database"] -ForkDB["Fork Database
libraries/chain/fork_database"] -BlockLog["Block Log
libraries/chain/block_log"] -DLBlockLog["DLT Block Log
libraries/chain/dlt_block_log"] -end -subgraph "Application" -App["Application
appbase"] -end -App --> JSONRPC -JSONRPC --> ChainPlugin -ChainPlugin --> Database -Database --> ForkDB -Database --> BlockLog -Database --> DLBlockLog -ChainPlugin --> SnapshotPlugin -``` - -**Diagram sources** -- [plugin.cpp:183-649](file://plugins/chain/plugin.cpp#L183-L649) -- [database.cpp:351-544](file://libraries/chain/database.cpp#L351-L544) -- [plugin.cpp:3031-3118](file://plugins/snapshot/plugin.cpp#L3031-L3118) - -**Section sources** -- [plugin.cpp:1-694](file://plugins/chain/plugin.cpp#L1-L694) -- [database.cpp:1-6314](file://libraries/chain/database.cpp#L1-L6314) - -## Core Components -The Chain Plugin consists of two primary parts: -- The plugin class that manages lifecycle, configuration, and external interfaces -- The database wrapper that handles block acceptance, transaction processing, and state management - -Key responsibilities include: -- Managing shared memory configuration and growth policies with updated default directory structure using 'state' -- Handling snapshot loading and recovery modes with enhanced deferred execution support -- Coordinating block and transaction acceptance with plugin synchronization -- Providing state queries and database accessors -- Supporting DLT (Dynamic Ledger Technology) block logging with comprehensive replay capabilities -- Implementing advanced recovery procedures with automatic snapshot detection and restoration -- Integrating with comprehensive snapshot management infrastructure including automatic discovery, rotation, and serving capabilities -- **Enhanced** Providing visual feedback through color-coded console logging for improved debugging experience -- **New** Implementing comprehensive automatic recovery system from shared memory corruption with immediate detection and restoration capabilities - -**Updated** Enhanced plugin coordination with deferred execution support allows seamless integration between chain and snapshot plugins, enabling flexible startup sequences and improved error recovery mechanisms. The default shared memory directory has been changed from 'blockchain' to 'state' for better organizational clarity and consistency across data directory usage. The enhanced logging system now provides visual differentiation through ANSI escape codes for better console output readability. The new automatic recovery system provides robust protection against shared memory corruption scenarios with immediate detection and restoration capabilities. - -**Section sources** -- [plugin.hpp:21-124](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L124) -- [plugin.cpp:21-93](file://plugins/chain/plugin.cpp#L21-L93) -- [database.cpp:351-544](file://libraries/chain/database.cpp#L351-L544) - -## Architecture Overview -The Chain Plugin follows a layered architecture with clear separation of concerns and enhanced plugin coordination: - -```mermaid -sequenceDiagram -participant App as "Application" -participant Chain as "Chain Plugin" -participant DB as "Database" -participant Fork as "Fork Database" -participant Log as "Block Log" -participant Snapshot as "Snapshot Plugin" -App->>Chain : set_program_options() -Chain->>Chain : parse CLI/config options -Chain->>Chain : validate snapshot_path -Chain->>DB : open()/open_from_snapshot() -DB->>Fork : initialize fork database -DB->>Log : initialize block log -Chain->>Snapshot : register callbacks -Note over Chain,Snapsho : Deferred Execution Support -Chain->>Chain : check pending_snapshot_load -alt snapshot plugin ready -Chain->>Chain : trigger_snapshot_load() -Chain->>DB : load snapshot via callback -else snapshot plugin not ready -Chain->>Chain : set pending_snapshot_load -Chain->>Snapshot : plugin_startup() -Snapshot->>Chain : trigger_snapshot_load() -end -Chain->>App : on_sync signal -App->>Chain : accept_block(block) -Chain->>Chain : check_time_in_block() -Chain->>DB : validate_block() -alt single_write_thread -Chain->>Chain : post to io_service -Chain->>DB : push_block() -else -Chain->>DB : push_block() -end -DB->>Fork : update fork database -DB->>Log : append to block log -DB-->>Chain : result -Chain-->>App : accepted -App->>Chain : accept_transaction(trx) -Chain->>DB : validate_transaction() -alt single_write_thread -Chain->>Chain : post to io_service -Chain->>DB : push_transaction() -else -Chain->>DB : push_transaction() -end -``` - -**Diagram sources** -- [plugin.cpp:103-183](file://plugins/chain/plugin.cpp#L103-L183) -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) -- [plugin.cpp:650-666](file://plugins/chain/plugin.cpp#L650-L666) - -**Section sources** -- [plugin.cpp:197-272](file://plugins/chain/plugin.cpp#L197-L272) -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) - -## Detailed Component Analysis - -### Chain Plugin Class -The plugin class serves as the main interface for blockchain operations and configuration management with enhanced plugin coordination and deferred execution support. - -```mermaid -classDiagram -class Plugin { -+set_program_options(cli, cfg) -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+accept_block(block, currently_syncing, skip) -+accept_transaction(trx) -+block_is_on_preferred_chain(block_id) -+check_time_in_block(block) -+db() database& -+on_sync signal -+snapshot_load_callback function -+snapshot_create_callback function -+snapshot_p2p_sync_callback function -+trigger_snapshot_load function -} -class PluginImpl { -+shared_memory_size uint64_t -+shared_memory_dir path -+replay bool -+resync bool -+readonly bool -+check_locks bool -+validate_invariants bool -+flush_interval uint32_t -+loaded_checkpoints map -+allow_future_time uint32_t -+read_wait_micro uint64_t -+max_read_wait_retries uint32_t -+write_wait_micro uint64_t -+max_write_wait_retries uint32_t -+inc_shared_memory_size size_t -+min_free_shared_memory_size size_t -+enable_plugins_on_push_transaction bool -+block_num_check_free_size uint32_t -+skip_virtual_ops bool -+snapshot_path string -+replay_from_snapshot bool -+auto_recover_from_snapshot bool -+db database -+single_write_thread bool -+sync_start_logged bool -+pending_snapshot_load bool -+check_time_in_block(block) -+accept_block(block, currently_syncing, skip) bool -+accept_transaction(trx) -+wipe_db(data_dir, wipe_block_log) -+replay_db(data_dir, force_replay) -+do_snapshot_load(data_dir, is_recovery) -+trigger_snapshot_load() -+attempt_auto_recovery() -} -Plugin --> PluginImpl : "owns" -``` - -**Diagram sources** -- [plugin.hpp:21-124](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L124) -- [plugin.cpp:21-93](file://plugins/chain/plugin.cpp#L21-L93) - -#### Configuration Options -The plugin supports extensive configuration through command-line and configuration file options with enhanced recovery and coordination capabilities: - -| Option | Type | Description | Default | -|--------|------|-------------|---------| -| shared-file-dir | path | Location of shared memory files (absolute path or relative to application data dir) | **state** | -| shared-file-size | size | Initial shared memory size | 2G | -| inc-shared-file-size | size | Memory growth increment | 2G | -| min-free-shared-file-size | size | Minimum free space threshold | 500M | -| block-num-check-free-size | uint32_t | Check free space every N blocks | 1000 | -| checkpoint | pairs | Enforced checkpoints | none | -| flush-state-interval | uint32_t | Flush interval | 10000 | -| read-wait-micro | uint64_t | Read lock timeout | db default | -| max-read-wait-retries | uint32_t | Read retry attempts | db default | -| write-wait-micro | uint64_t | Write lock timeout | db default | -| max-write-wait-retries | uint32_t | Write retry attempts | db default | -| single-write-thread | bool | Single thread mode | false | -| clear-votes-before-block | uint32_t | Clear votes before block | 0 | -| skip-virtual-ops | bool | Skip virtual ops | false | -| enable-plugins-on-push-transaction | bool | Enable plugins on tx | false | -| dlt-block-log-max-blocks | uint32_t | DLT log size | 100000 | -| **snapshot** | string | Load state from snapshot file | empty | -| **snapshot-auto-latest** | bool | Auto-find latest snapshot in snapshot-dir | false | -| **replay-from-snapshot** | bool | Snapshot + dlt_block_log replay | false | -| **snapshot-dir** | string | Directory for auto-generated snapshots | empty | -| **auto-recover-from-snapshot** | bool | Automatically recover from corruption | true | - -**Updated** Enhanced plugin coordination with deferred execution support for snapshot operations, allowing flexible startup sequences between chain and snapshot plugins. The default shared-file-dir has been changed from 'blockchain' to 'state' for improved organizational clarity and consistency across data directory usage. Enhanced logging system provides visual feedback through color-coded console output. **New** The --auto-recover-from-snapshot flag enables automatic recovery from shared memory corruption by importing the latest available snapshot and replaying DLT block log data. - -**Section sources** -- [plugin.cpp:197-272](file://plugins/chain/plugin.cpp#L197-L272) -- [plugin.cpp:274-386](file://plugins/chain/plugin.cpp#L274-L386) - -### Database Operations -The database layer provides comprehensive blockchain state management with enhanced recovery and DLT block log integration: - -```mermaid -flowchart TD -Start([Startup]) --> CheckResync{"Resync requested?"} -CheckResync --> |Yes| WipeDB["Wipe database and block log"] -CheckResync --> |No| OpenDB["Open existing database"] -OpenDB --> CheckHead{"Head block exists?"} -CheckHead --> |No| CheckSnapshot{"Snapshot available?"} -CheckHead --> |Yes| CheckReplay{"Need replay?"} -CheckSnapshot --> |Yes| ValidateSnapshotPath["Validate snapshot_path"] -ValidateSnapshotPath --> LoadSnapshot["Load snapshot state"] -CheckSnapshot --> |No| InitGenesis["Initialize genesis"] -LoadSnapshot --> InitHardforks["Initialize hardforks"] -InitHardforks --> ReplayDLT{"DLT log available?"} -ReplayDLT --> |Yes| ReindexDLT["Reindex from DLT log"] -ReplayDLT --> |No| StartSync["Start synchronization"] -CheckReplay --> |Yes| ReplayBlocks["Replay blockchain"] -CheckReplay --> |No| StartSync -ReplayBlocks --> StartSync -InitGenesis --> StartSync -StartSync --> Ready([Ready]) -``` - -**Diagram sources** -- [plugin.cpp:388-649](file://plugins/chain/plugin.cpp#L388-L649) -- [database.cpp:351-544](file://libraries/chain/database.cpp#L351-L544) -- [plugin.cpp:650-666](file://plugins/chain/plugin.cpp#L650-L666) - -#### Block Processing Pipeline -The block processing pipeline handles validation, application, and persistence with enhanced error handling and plugin coordination: - -```mermaid -sequenceDiagram -participant Chain as "Chain Plugin" -participant DB as "Database" -participant Fork as "Fork DB" -participant Log as "Block Log" -Chain->>DB : validate_block() -DB->>DB : _validate_block() -DB->>DB : validate_block_header() -DB->>DB : apply_transactions() -loop For each transaction -DB->>DB : apply_transaction() -DB->>DB : evaluate_operations() -end -DB->>DB : update_global_properties() -DB->>DB : update_witness_schedule() -DB->>DB : update_last_irreversible_block() -DB->>Fork : update fork database -DB->>Log : append to block log -DB-->>Chain : block applied -``` - -**Diagram sources** -- [database.cpp:4253-4323](file://libraries/chain/database.cpp#L4253-L4323) -- [database.cpp:4314-4323](file://libraries/chain/database.cpp#L4314-L4323) - -**Section sources** -- [database.cpp:351-544](file://libraries/chain/database.cpp#L351-L544) -- [database.cpp:4253-4323](file://libraries/chain/database.cpp#L4253-L4323) - -### Transaction Processing -Transaction processing involves validation, evaluation, and application within the block context: - -```mermaid -flowchart TD -TxIn([Transaction Input]) --> ValidateTx["Validate Transaction"] -ValidateTx --> CheckSig{"Authority Check"} -CheckSig --> |Pass| EvaluateOps["Evaluate Operations"] -CheckSig --> |Fail| RejectTx["Reject Transaction"] -EvaluateOps --> ApplyTx["Apply to State"] -ApplyTx --> CheckOps{"Operation Validation"} -CheckOps --> |Pass| StoreTx["Store Transaction"] -CheckOps --> |Fail| Rollback["Rollback Changes"] -StoreTx --> Done([Transaction Complete]) -RejectTx --> Done -Rollback --> Done -``` - -**Diagram sources** -- [database.cpp:4253-4323](file://libraries/chain/database.cpp#L4253-L4323) - -**Section sources** -- [database.cpp:4253-4323](file://libraries/chain/database.cpp#L4253-L4323) - -### Enhanced Plugin Coordination and Deferred Execution -Recent improvements focus on sophisticated plugin coordination mechanisms with deferred execution support: - -**Updated** Enhanced plugin coordination includes: -- Deferred snapshot loading when snapshot plugin isn't ready during chain startup -- Automatic callback registration and triggering between chain and snapshot plugins -- Comprehensive recovery system integration with DLT block log replay capabilities -- Improved error handling and fallback mechanisms for snapshot operations -- **New** Immediate auto-recovery mechanism that can trigger during block processing when corruption is detected - -```mermaid -flowchart TD -SnapshotInit["Snapshot Initialization"] --> CheckCallback{"snapshot_load_callback registered?"} -CheckCallback --> |Yes| ValidatePath["Validate snapshot_path"] -CheckCallback --> |No| CheckArgs{"snapshot args present?"} -CheckArgs --> |Yes| SetPending["Set pending_snapshot_load = true"] -CheckArgs --> |No| NormalStartup["Normal startup"] -SetPending --> WaitReady["Wait for snapshot plugin ready"] -WaitReady --> CheckReady{"snapshot plugin ready?"} -CheckReady --> |Yes| TriggerLoad["Call trigger_snapshot_load()"] -CheckReady --> |No| WaitReady -TriggerLoad --> ValidatePath -ValidatePath --> LoadSnapshot["Load snapshot state"] -LoadSnapshot --> InitHardforks["Initialize hardforks"] -InitHardforks --> ReplayDLT{"DLT log available?"} -ReplayDLT --> |Yes| ReindexDLT["Reindex from DLT log"] -ReplayDLT --> |No| StartSync["Start synchronization"] -``` - -**Diagram sources** -- [plugin.cpp:420-475](file://plugins/chain/plugin.cpp#L420-L475) -- [plugin.cpp:650-666](file://plugins/chain/plugin.cpp#L650-L666) -- [plugin.cpp:3031-3042](file://plugins/snapshot/plugin.cpp#L3031-L3042) - -**Section sources** -- [plugin.cpp:420-475](file://plugins/chain/plugin.cpp#L420-L475) -- [plugin.cpp:650-666](file://plugins/chain/plugin.cpp#L650-L666) -- [plugin.cpp:3031-3042](file://plugins/snapshot/plugin.cpp#L3031-L3042) - -### Comprehensive Recovery System Integration -The enhanced recovery system provides robust snapshot-based restoration with DLT block log integration: - -**Updated** Advanced recovery capabilities include: -- Automatic snapshot detection and loading with path validation -- DLT block log replay for incremental recovery from corrupted states -- Emergency consensus mode support for network recovery scenarios -- Comprehensive error reporting and fallback mechanisms -- **New** Immediate auto-recovery from shared memory corruption during block processing - -```mermaid -flowchart TD -RecoveryStart["Recovery Mode"] --> CheckSnapshot{"Snapshot available?"} -CheckSnapshot --> |Yes| LoadSnapshot["Load snapshot state"] -CheckSnapshot --> |No| CheckDLT{"DLT log available?"} -LoadSnapshot --> InitHardforks["Initialize hardforks"] -InitHardforks --> CheckDLT -CheckDLT --> |Yes| ReplayDLT["Replay DLT block log"] -CheckDLT --> |No| Fallback["Fallback to normal sync"] -ReplayDLT --> StartSync["Start synchronization"] -StartSync --> RecoveryComplete["Recovery Complete"] -Fallback --> RecoveryComplete -``` - -**Diagram sources** -- [plugin.cpp:566-649](file://plugins/chain/plugin.cpp#L566-L649) -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) - -**Section sources** -- [plugin.cpp:566-649](file://plugins/chain/plugin.cpp#L566-L649) -- [database.cpp:438-544](file://libraries/chain/database.cpp#L438-L544) - -### Enhanced Snapshot Management Infrastructure -**Updated** The snapshot plugin now provides comprehensive snapshot management capabilities: - -- **Automatic Discovery**: `--snapshot-auto-latest` with `--snapshot-dir` for finding the latest snapshot -- **Periodic Snapshots**: `--snapshot-every-n-blocks` for automated snapshot creation -- **Snapshot Rotation**: `--snapshot-max-age-days` for automatic cleanup of old snapshots -- **Snapshot Serving**: `--allow-snapshot-serving` with trust model and anti-spam protection -- **Trusted Peers**: `--trusted-snapshot-peer` for P2P snapshot synchronization -- **Stalled Sync Detection**: Automatic detection and recovery from stalled synchronization - -```mermaid -flowchart TD -SnapshotConfig["Snapshot Configuration"] --> AutoDiscover["Auto-Discovery"] -AutoDiscover --> LatestSnap["Latest Snapshot Detection"] -LatestSnap --> ValidatePath["Validate Path"] -ValidatePath --> LoadState["Load Snapshot State"] -SnapshotConfig --> Periodic["Periodic Snapshots"] -Periodic --> EveryNBlocks["Every N Blocks"] -EveryNBlocks --> CreateSnap["Create Snapshot"] -CreateSnap --> RotateAges["Age-Based Rotation"] -RotateAges --> CleanupOld["Cleanup Old Snapshots"] -SnapshotConfig --> Serving["Snapshot Serving"] -Serving --> TrustModel["Trust Model"] -TrustModel --> PublicServing["Public Serving"] -TrustModel --> TrustedOnly["Trusted Only Serving"] -PublicServing --> AntiSpam["Anti-Spam Protection"] -TrustedOnly --> AntiSpam -AntiSpam --> RateLimiting["Rate Limiting"] -AntiSpam --> SessionLimits["Session Limits"] -AntiSpam --> ConnectionTimeout["Connection Timeout"] -SnapshotConfig --> P2PSync["P2P Sync"] -P2PSync --> TrustedPeers["Trusted Peers"] -TrustedPeers --> DownloadSnap["Download Snapshot"] -DownloadSnap --> LoadState -``` - -**Diagram sources** -- [plugin.cpp:344-382](file://plugins/chain/plugin.cpp#L344-L382) -- [plugin.cpp:2817-2861](file://plugins/snapshot/plugin.cpp#L2817-L2861) -- [plugin.cpp:2908-2920](file://plugins/snapshot/plugin.cpp#L2908-L2920) - -**Section sources** -- [plugin.cpp:344-382](file://plugins/chain/plugin.cpp#L344-L382) -- [plugin.cpp:2817-2861](file://plugins/snapshot/plugin.cpp#L2817-L2861) -- [plugin.cpp:2908-2920](file://plugins/snapshot/plugin.cpp#L2908-L2920) - -## Enhanced Logging System - -**Updated** The Chain Plugin now features an enhanced logging system with visual differentiation for improved debugging experience: - -### Color-Coded Console Output -The logging system uses ANSI escape codes to provide visual feedback: - -- **Green Color** (`\033[92m`): Used for sync mode start and completion messages -- **Brown/Yellow Color** (`\033[93m`): Used for periodic sync progress notifications -- **Default Colors**: Maintained for other log levels (debug, warn, error) - -### Sync Mode Status Messages -The enhanced logging provides clear visual indicators for different sync states: - -```mermaid -flowchart TD -SyncStart["Sync Mode Started"] --> GreenStart["\033[92m>>> Syncing Blockchain started from block #${n} (head: ${head})\033[0m"] -SyncProgress["Sync Progress"] --> YellowProgress["\033[93mSyncing Blockchain --- Got block: #${n} time: ${t} producer: ${p}\033[0m"] -SyncEnd["Sync Mode Ended"] --> GreenEnd["\033[92mSync mode ended: received normal block #${n} (head: ${head}), sync_start_logged reset\033[0m"] -``` - -**Diagram sources** -- [plugin.cpp:105-121](file://plugins/chain/plugin.cpp#L105-L121) - -### Logging Framework Enhancements -The underlying logging framework supports comprehensive color configuration: - -- **Default Configuration**: Debug (green), Warn (brown), Error (red) -- **Application Override**: Error level now uses cyan instead of red for better visual hierarchy -- **Platform Support**: Windows and Unix terminal color support through ANSI escape codes -- **File Output**: Color codes are stripped when logging to files to prevent garbled output - -### Visual Differentiation Benefits -The enhanced logging system improves debugging experience through: - -- **Immediate Visual Feedback**: Sync start and completion clearly highlighted in green -- **Progress Indicators**: Periodic sync progress shown in yellow/brown for easy scanning -- **Consistent Color Scheme**: Maintains visual hierarchy with appropriate colors for different log levels -- **Cross-Platform Compatibility**: ANSI escape codes work across different terminal environments - -**Section sources** -- [plugin.cpp:105-121](file://plugins/chain/plugin.cpp#L105-L121) -- [console_appender.cpp:71-84](file://thirdparty/fc/src/log/console_appender.cpp#L71-L84) -- [console_defines.h:146-188](file://thirdparty/fc/src/log/console_defines.h#L146-L188) -- [logger_config.cpp:69-89](file://thirdparty/fc/src/log/logger_config.cpp#L69-L89) -- [main.cpp:234-250](file://programs/vizd/main.cpp#L234-L250) - -## Automatic Recovery System - -**New** The Chain Plugin now implements a comprehensive automatic recovery system designed to detect and recover from shared memory corruption scenarios without manual intervention. This system provides multiple layers of protection and recovery mechanisms. - -### Recovery System Architecture - -```mermaid -flowchart TD -CorruptionDetected["Shared Memory Corruption Detected"] --> CheckAutoRecover{"--auto-recover-from-snapshot enabled?"} -CheckAutoRecover --> |Yes| FindLatestSnapshot["Find Latest Available Snapshot"] -CheckAutoRecover --> |No| ManualRecovery["Manual Recovery Required"] -FindLatestSnapshot --> CheckSnapshotExists{"Snapshot Found?"} -CheckSnapshotExists --> |Yes| CloseDatabase["Close Corrupted Database"] -CheckSnapshotExists --> |No| FallbackReplay["Fallback to Replay Mode"] -CloseDatabase --> SetSnapshotPath["Set Snapshot Path"] -SetSnapshotPath --> LoadSnapshot["Load Snapshot State"] -LoadSnapshot --> InitializeHardforks["Initialize Hardforks"] -InitializeHardforks --> ReplayDLT["Replay DLT Block Log"] -ReplayDLT --> ResumeNode["Resume Node Operation"] -ManualRecovery --> ExitNode["Exit Node with Error Message"] -FallbackReplay --> ReplayBlockchain["Replay Blockchain from Scratch"] -ReplayBlockchain --> ResumeNode -``` - -**Diagram sources** -- [plugin.cpp:757-816](file://plugins/chain/plugin.cpp#L757-L816) -- [plugin.cpp:547-600](file://plugins/chain/plugin.cpp#L547-L600) - -### Recovery Triggers - -The automatic recovery system can be triggered in multiple scenarios: - -1. **Startup Phase Recovery**: When the database fails to open due to shared memory corruption -2. **Runtime Recovery**: During block processing when shared memory corruption is detected -3. **Configuration-Based Recovery**: When the `--auto-recover-from-snapshot` flag is enabled - -### Recovery Process Implementation - -The recovery process follows a systematic approach: - -```mermaid -sequenceDiagram -participant Chain as "Chain Plugin" -participant DB as "Database" -participant Snapshot as "Snapshot Plugin" -Chain->>DB : Attempt to open database -DB-->>Chain : Throws shared_memory_corruption_exception -Chain->>Chain : check auto_recover_from_snapshot flag -alt Auto-recovery enabled -Chain->>Chain : find_latest_snapshot() -Chain->>Chain : snapshot_path = latest_snapshot -Chain->>Chain : do_snapshot_load(data_dir, is_recovery=true) -Chain->>DB : open_from_snapshot() -DB->>Snapshot : snapshot_load_callback() -Snapshot-->>DB : Load snapshot state -DB-->>Chain : Recovery complete -Chain->>Chain : Resume normal operation -else Auto-recovery disabled -Chain->>Chain : Log error and exit -end -``` - -**Diagram sources** -- [plugin.cpp:757-816](file://plugins/chain/plugin.cpp#L757-L816) -- [database_exceptions.hpp:122](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L122) - -### Database Exception Handling - -The system leverages a dedicated exception type for shared memory corruption detection: - -```mermaid -classDiagram -class shared_memory_corruption_exception { -+inherits chain_exception -+code : 4140000 -+message : "shared memory corruption detected" -} -class chain_exception { -+inherits fc : : exception -+base exception for all chain-related errors -} -shared_memory_corruption_exception --> chain_exception : "inherits" -``` - -**Diagram sources** -- [database_exceptions.hpp:122](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L122) - -### Command-Line Configuration - -The automatic recovery system introduces new command-line flags: - -| Flag | Type | Description | Default | -|------|------|-------------|---------| -| `--auto-recover-from-snapshot` | boolean | Automatically recover from shared memory corruption by importing latest snapshot | true | -| `--snapshot-auto-latest` | boolean | Auto-discover latest snapshot in snapshot-dir for recovery scenarios | false | - -### Recovery Validation and Safety - -The system includes multiple safety mechanisms: - -- **Snapshot Validation**: Ensures recovered snapshots are valid and compatible -- **Database Integrity Checks**: Verifies recovered state before resuming operations -- **DLT Block Log Replay**: Applies incremental updates from DLT log for complete state consistency -- **Graceful Degradation**: Falls back to traditional replay mode if snapshot recovery fails - -### Recovery Monitoring and Reporting - -The system provides comprehensive logging for recovery operations: - -- **Recovery Initiation**: Logs when automatic recovery is triggered -- **Snapshot Detection**: Reports found snapshot path and block number -- **Recovery Progress**: Tracks recovery stages and completion status -- **Error Handling**: Provides detailed error messages for recovery failures - -**Section sources** -- [plugin.cpp:757-816](file://plugins/chain/plugin.cpp#L757-L816) -- [plugin.cpp:547-600](file://plugins/chain/plugin.cpp#L547-L600) -- [database_exceptions.hpp:122](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L122) - -## Dependency Analysis -The Chain Plugin has well-defined dependencies and integration points with enhanced plugin coordination: - -```mermaid -graph TB -subgraph "External Dependencies" -AppBase["AppBase Framework"] -Boost["Boost Libraries"] -OpenSSL["OpenSSL Crypto"] -FC["FC Utilities"] -end -subgraph "Internal Dependencies" -ChainBase["ChainBase Database"] -Protocol["Protocol Definitions"] -Network["Network Layer"] -Wallet["Wallet Interface"] -SnapshotPlugin["Snapshot Plugin"] -end -ChainPlugin --> AppBase -ChainPlugin --> Boost -ChainPlugin --> OpenSSL -ChainPlugin --> FC -ChainPlugin --> ChainBase -ChainPlugin --> Protocol -ChainPlugin --> Network -ChainPlugin --> Wallet -ChainPlugin --> SnapshotPlugin -``` - -**Diagram sources** -- [plugin.cpp:1-12](file://plugins/chain/plugin.cpp#L1-L12) -- [database.cpp:1-10](file://libraries/chain/database.cpp#L1-L10) - -### Integration Points -The plugin integrates with several other components with enhanced coordination: -- JSON-RPC plugin for API exposure -- Snapshot plugin for state recovery with deferred execution support -- P2P plugin for block propagation -- Validator Plugin for block production -- Database plugin for state persistence - -**Updated** Enhanced integration with snapshot plugin includes sophisticated deferred execution mechanisms, automatic callback registration, and comprehensive recovery system coordination. The default shared memory directory has been changed from 'blockchain' to 'state' for better organizational structure and consistent data directory usage. The enhanced logging system provides visual feedback for better debugging experience. **New** The automatic recovery system provides seamless protection against shared memory corruption with immediate detection and restoration capabilities. - -**Section sources** -- [plugin.hpp:23-24](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L23-L24) -- [plugin.cpp:92-105](file://plugins/chain/plugin.cpp#L92-L105) - -## Performance Considerations -The Chain Plugin implements several performance optimizations with enhanced plugin coordination: - -### Shared Memory Management -- Configurable shared memory size with automatic growth -- Minimum free space thresholds to prevent fragmentation -- Periodic flushing to balance performance and safety - -### Concurrency Control -- Optional single-thread mode for deterministic processing -- Configurable read/write lock timeouts and retry limits -- Asynchronous processing through io_service for non-blocking operations - -### Storage Optimization -- DLT (Dynamic Ledger Technology) block logging for recovery scenarios -- Checkpoint enforcement for validation acceleration -- Efficient fork database management for chain reorganization - -### Enhanced Plugin Coordination Performance -**Updated** Optimized plugin coordination includes: -- Deferred execution support to avoid blocking during plugin initialization -- Efficient callback registration and triggering mechanisms -- Reduced redundant operations through intelligent state checking -- Optimized snapshot loading with automatic path validation -- Improved snapshot serving performance with trust model and anti-spam protection -- **Enhanced** Color-coded logging reduces visual scanning time for important sync events -- **New** Automatic recovery system minimizes downtime during corruption scenarios - -### Logging Performance Considerations -**Updated** The enhanced logging system maintains performance through: -- Minimal overhead for color code insertion -- Efficient ANSI escape code handling -- Platform-specific optimization for Windows and Unix terminals -- Stripping of color codes for file output to prevent performance degradation - -### Recovery System Performance Impact -**New** The automatic recovery system is designed to minimize performance impact: -- Fast snapshot discovery using optimized file system scanning -- Incremental DLT block log replay to reduce recovery time -- Graceful degradation to traditional replay mode if needed -- Background recovery operations to avoid blocking normal node operations - -**Section sources** -- [plugin.cpp:24-51](file://plugins/chain/plugin.cpp#L24-L51) -- [plugin.cpp:398-418](file://plugins/chain/plugin.cpp#L398-L418) - -## Troubleshooting Guide - -### Common Startup Issues -1. **Database Corruption**: The plugin automatically attempts to replay the blockchain when corruption is detected -2. **Missing State**: Uses snapshot recovery mode when available with enhanced deferred execution support -3. **Lock Conflicts**: Configurable lock timeouts and retry mechanisms -4. **Plugin Coordination Issues**: Enhanced error reporting for snapshot plugin initialization delays -5. ****New** Shared Memory Corruption**: Automatic recovery system provides immediate detection and restoration - -### Recovery Procedures -- Use `--replay-blockchain` to force blockchain replay -- Use `--resync-blockchain` to wipe and rebuild from scratch -- Use `--replay-from-snapshot` for recovery from corrupted state with DLT block log replay -- **Updated** Use `--snapshot-auto-latest` with proper `--snapshot-dir` configuration for automatic snapshot discovery -- **New** Enable `--auto-recover-from-snapshot` to automatically recover from corruption scenarios - -### Monitoring and Diagnostics -- Enable `--check-locks` for lock validation debugging -- Use `--validate-database-invariants` for state consistency checks -- Monitor shared memory usage and growth patterns -- **Updated** Enable verbose logging for snapshot plugin coordination failures -- Monitor snapshot serving metrics and trust model compliance -- **Enhanced** Use color-coded logs to quickly identify sync mode status and progress -- **New** Monitor automatic recovery system logs for corruption detection and restoration events - -### Enhanced Plugin Coordination Troubleshooting -**Updated** Specific troubleshooting for plugin coordination issues: -- Verify snapshot plugin is loaded before chain plugin for optimal performance -- Check deferred execution logs for snapshot loading delays -- Ensure proper callback registration between chain and snapshot plugins -- Monitor plugin startup order and initialization sequences -- Validate snapshot file compatibility and path accessibility -- Check snapshot directory permissions and disk space availability - -### Recovery System Troubleshooting -**Updated** Specific troubleshooting for recovery system issues: -- Verify DLT block log availability for incremental recovery -- Check snapshot file integrity and compatibility -- Monitor hardfork initialization during recovery processes -- Validate emergency consensus mode settings for network recovery scenarios -- Test snapshot serving configuration with trust model and anti-spam settings -- **New** Verify `--auto-recover-from-snapshot` flag is properly configured -- **New** Check snapshot directory permissions for automatic recovery access -- **New** Monitor recovery logs for corruption detection and restoration success - -### Snapshot Management Troubleshooting -**Updated** Specific troubleshooting for snapshot management issues: -- Verify snapshot directory exists and is writable -- Check snapshot rotation configuration with `--snapshot-max-age-days` -- Validate periodic snapshot creation with `--snapshot-every-n-blocks` -- Test snapshot serving configuration with `--allow-snapshot-serving` -- Configure trusted peers properly with `--trusted-snapshot-peer` -- Monitor snapshot P2P sync performance and reliability - -### Enhanced Logging Troubleshooting -**Updated** Specific troubleshooting for logging issues: -- Verify terminal supports ANSI escape codes for color output -- Check logging configuration for proper color scheme setup -- Ensure color codes are properly formatted in log messages -- Test color output in different terminal environments -- Verify color codes are stripped when logging to files -- Check for proper terminal reset sequences after color output - -### Sync Mode Troubleshooting -**Updated** Specific troubleshooting for sync mode logging: -- Verify green color appears for sync start and completion messages -- Check yellow/brown color for periodic sync progress notifications -- Ensure sync mode status messages appear only during active synchronization -- Verify sync mode completion messages reset properly when normal blocks arrive -- Check that sync mode guard variables work correctly to prevent duplicate messages - -### Automatic Recovery System Troubleshooting -**New** Specific troubleshooting for automatic recovery system issues: -- Verify `--auto-recover-from-snapshot` flag is enabled in configuration -- Check snapshot directory accessibility for automatic recovery operations -- Monitor recovery logs for corruption detection and restoration attempts -- Verify snapshot plugin is properly configured for recovery callbacks -- Test recovery system by simulating shared memory corruption scenarios -- Check DLT block log availability for incremental recovery after snapshot restoration - -**Section sources** -- [plugin.cpp:562-601](file://plugins/chain/plugin.cpp#L562-L601) -- [plugin.cpp:251-271](file://plugins/chain/plugin.cpp#L251-L271) - -## Conclusion -The Chain Plugin provides a robust foundation for blockchain state management in the VIZ node. Its modular design, comprehensive configuration options, and efficient database operations make it suitable for production deployments while maintaining flexibility for development and testing scenarios. Recent enhancements focus on improved plugin coordination with deferred execution support, comprehensive recovery system integration with DLT block log capabilities, and sophisticated snapshot loading mechanisms. The plugin's integration with snapshot technology, emergency consensus mode, and advanced recovery procedures provides strong operational resilience and enhanced error handling capabilities with improved plugin coordination and seamless user experience. - -**Updated** The default shared-memory directory has been changed from 'blockchain' to 'state' for better organizational clarity, ensuring consistency across data directory usage in plugin initialization and snapshot plugin deferred loading functionality. The comprehensive snapshot management infrastructure provides powerful automation capabilities including automatic discovery, periodic creation, rotation, serving, and P2P synchronization with trust models and anti-spam protection. The enhanced logging system significantly improves debugging experience through color-coded console output, providing immediate visual feedback for sync mode status and progress indicators with green color for important completion messages and yellow/brown for periodic progress notifications. - -**New** The comprehensive automatic recovery system represents a major advancement in operational resilience, providing seamless protection against shared memory corruption scenarios. The system automatically detects corruption during both startup and runtime, immediately initiates recovery procedures, and restores node operation with minimal downtime. This system includes sophisticated snapshot discovery, validation, and restoration mechanisms, along with incremental DLT block log replay for complete state consistency. The recovery system is fully configurable and can be enabled or disabled based on operational requirements, providing operators with control over their recovery strategy while ensuring maximum uptime and data integrity. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Configuration Management/Build Configuration.md b/.qoder/repowiki/en/content/Configuration Management/Build Configuration.md deleted file mode 100644 index f8f2d77d00..0000000000 --- a/.qoder/repowiki/en/content/Configuration Management/Build Configuration.md +++ /dev/null @@ -1,508 +0,0 @@ -# Build Configuration - - -**Referenced Files in This Document** -- [.gitmodules](file://.gitmodules) -- [thirdparty/fc/.gitmodules](file://thirdparty/fc/.gitmodules) -- [thirdparty/fc/CMakeLists.txt](file://thirdparty/fc/CMakeLists.txt) -- [thirdparty/chainbase/CMakeLists.txt](file://thirdparty/chainbase/CMakeLists.txt) -- [thirdparty/appbase/CMakeLists.txt](file://thirdparty/appbase/CMakeLists.txt) -- [CMakeLists.txt](file://CMakeLists.txt) -- [building.md](file://documentation/building.md) -- [.travis.yml](file://.travis.yml) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt) -- [programs/CMakeLists.txt](file://programs/CMakeLists.txt) -- [thirdparty/CMakeLists.txt](file://thirdparty/CMakeLists.txt) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt) -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) - - -## Update Summary -**Changes Made** -- Updated shared library configuration documentation to reflect consistent static library builds across all platforms -- Added comprehensive coverage of the BUILD_SHARED_LIBRARIES=OFF setting and its implications -- Enhanced platform-specific build behavior documentation for Linux, macOS, and Windows -- Updated Docker configuration references to show consistent static linking approach -- Revised build script documentation to highlight the unified static library approach - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the build configuration for VIZ CPP Node, focusing on the CMake build system, available build options, compiler flags, feature toggles, cross-platform compilation, dependency management, and third-party library integration. It also covers build variants (development, production, low-memory, testnet), environment variable requirements, toolchain configuration, and CI integration via Docker and GitHub Actions. Practical examples and troubleshooting guidance are included to help you build reliably across platforms. - -**Updated** Enhanced with comprehensive documentation of the unified static library approach across all platforms, ensuring consistent build behavior and simplified deployment. - -## Project Structure -The repository is organized around a top-level CMake project that orchestrates three major subtrees: -- thirdparty: Internal vendored libraries (appbase, fc, chainbase) with specified branch management -- libraries: Core libraries (api, chain, protocol, network, time, utilities, wallet) -- plugins: Optional plugin modules (e.g., chain, p2p, webserver, mongo_db) -- programs: Executables (vizd, cli_wallet, js_operation_serializer, size_checker, util) - -```mermaid -graph TB -Root["Top-level CMakeLists.txt"] -TP["thirdparty/CMakeLists.txt"] -LIB["libraries/CMakeLists.txt"] -PLG["plugins/CMakeLists.txt"] -PRG["programs/CMakeLists.txt"] -Submods[".gitmodules
Branch Specifications"] -Root --> TP -Root --> LIB -Root --> PLG -Root --> PRG -TP --> Submods -``` - -**Diagram sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) - -**Section sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) - -## Core Components -Key build options and toggles configured at the top-level CMake: -- CMAKE_BUILD_TYPE: Selects Release or Debug profiles -- BUILD_SHARED_LIBRARIES: Controls static vs shared library builds (UNIFIED STATIC APPROACH) -- BUILD_TESTNET: Enables testnet configuration via preprocessor defines -- LOW_MEMORY_NODE: Enables low-memory node configuration via preprocessor defines -- CHAINBASE_CHECK_LOCKING: Enables chainbase locking checks via preprocessor defines -- ENABLE_MONGO_PLUGIN: Enables MongoDB plugin and related preprocessor defines -- USE_PCH: Enables precompiled headers via cotire when set -- FULL_STATIC_BUILD: Forces static linking flags on Windows and Linux -- ENABLE_INSTALLER: Enables CPack packaging configuration (optional) - -**Updated** The BUILD_SHARED_LIBRARIES option is now consistently set to OFF across all build environments to ensure uniform static library behavior. - -Compiler and toolchain behavior: -- Minimum compiler versions enforced for GCC and Clang -- Platform-specific flags for MSVC, MinGW, Apple, and Linux -- Optional ccache launchers for compile and link stages -- Coverage instrumentation toggle -- Git revision injection for version metadata - -Platform specifics: -- Windows: Boost static linkage, MSVC/MinGW flags, TCL detection, static-linking options -- macOS: Shared libraries enabled by default, static linking available via --static flag -- Linux: Static libraries enabled by default through build scripts, static linking available via --static flag -- All platforms: Unified approach to ensure consistent deployment characteristics - -**Section sources** -- [CMakeLists.txt:1-271](file://CMakeLists.txt#L1-L271) -- [building.md:3-212](file://documentation/building.md#L3-L212) - -## Architecture Overview -The build system composes the final executable by linking together internal libraries and plugins, then installs the runtime artifacts. The vizd executable links against appbase, graphene core libraries, and enabled plugins. - -```mermaid -graph TB -subgraph "Build Targets" -VIZD["vizd (executable)"] -LIBS["Core Libraries
api, chain, protocol, network, time, utilities, wallet"] -PLUGINS["Plugins
chain, p2p, webserver, ..."] -THIRDPARTY["Third-party
appbase, fc, chainbase"] -SUBMODULES["Submodule Dependencies
Branch Specifications"] -end -VIZD --> LIBS -VIZD --> PLUGINS -VIZD --> THIRDPARTY -THIRDPARTY --> SUBMODULES -``` - -**Diagram sources** -- [programs/vizd/CMakeLists.txt:16-49](file://programs/vizd/CMakeLists.txt#L16-L49) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) - -## Detailed Component Analysis - -### Top-Level CMake Configuration -Highlights: -- Enforces minimum compiler versions for GCC and Clang -- Configures module paths for custom Find modules and Git version generation -- Sets export of compile_commands.json for tooling support -- Defines Boost components and static-linking preference -- Provides feature toggles (BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, ENABLE_MONGO_PLUGIN) -- Adds platform-specific compiler/linker flags and optional ccache -- Adds subdirectories for thirdparty, libraries, plugins, and programs -- Supports CPack packaging when enabled - -**Updated** The BUILD_SHARED_LIBRARIES option is now set to OFF by default, ensuring consistent static library behavior across all platforms and build methods. - -Common build invocations: -- Release build: cmake -DCMAKE_BUILD_TYPE=Release .. -- Low-memory node: cmake -DLowMemoryNode=TRUE .. -- Testnet build: cmake -DBUILD_TESTNET=TRUE .. -- Enable MongoDB plugin: cmake -DENABLE_MONGO_PLUGIN=TRUE .. -- Static build (all platforms): cmake -DBUILD_SHARED_LIBRARIES=OFF .. - -Environment variables: -- BOOST_ROOT (Windows): points to Boost installation -- TCL_ROOT (Windows): points to TCL include directory -- OPENSSL_ROOT_DIR (macOS): points to OpenSSL installation -- CCACHE: enables transparent caching of compilation/linking - -Toolchains and generators: -- Ninja generator benefits from color diagnostics on Clang -- Visual Studio and MinGW toolchains supported per documentation - -**Section sources** -- [CMakeLists.txt:1-271](file://CMakeLists.txt#L1-L271) -- [building.md:3-212](file://documentation/building.md#L3-L212) - -### Build Variants and Feature Toggles -- Debug vs Release: controlled by CMAKE_BUILD_TYPE; debug adds a debug macro and may enable coverage instrumentation when toggled -- Low-memory node: reduces storage footprint by disabling non-consensus data fields -- Testnet: switches to testnet configuration and seeds -- Chainbase locking checks: enables additional synchronization assertions -- MongoDB plugin: compiles and links the mongo_db plugin with appropriate preprocessor defines -- Static vs shared libraries: BUILD_SHARED_LIBRARIES controls library type (UNIFIED STATIC APPROACH) -- Full static build: FULL_STATIC_BUILD forces static linking flags on supported platforms - -**Updated** All build variants now consistently use static libraries (BUILD_SHARED_LIBRARIES=OFF) to ensure uniform deployment characteristics across environments. - -Preprocessor defines injected at configure time: -- BUILD_TESTNET, IS_LOW_MEM, CHAINBASE_CHECK_LOCKING, MONGODB_PLUGIN_BUILT - -**Section sources** -- [CMakeLists.txt:56-89](file://CMakeLists.txt#L56-L89) -- [CMakeLists.txt:196-208](file://CMakeLists.txt#L196-L208) - -### Cross-Platform Compilation -- Linux: - - C++ standard set to C++14 - - Threading and realtime libraries linked conditionally - - Optional static linking flags - - Ninja generator gains color diagnostics - - **Static libraries enabled by default through build scripts** -- macOS: - - Uses libc++ and shared libraries by default - - Static linking available via --static flag in build-mac.sh - - Optional TCMalloc discovery via gperftools -- Windows: - - MSVC flags: disables safe-seh, ensures debug info in Debug - - MinGW flags: C++11, permissiveness, SSE4.2, big object support, optimized debug flags - - TCL detection and adjusted library naming - - **Static libraries enabled by default through build scripts** - -**Updated** All platforms now use a unified approach with static libraries enabled by default, ensuring consistent behavior and simplified deployment. - -Dependencies: -- Boost 1.71+ required across all thirdparty libraries (appbase, chainbase, fc) -- Special handling for Boost 1.53 on Windows -- OpenSSL (via find_package or explicit root on macOS) -- Readline on Unix-like systems -- Optional: MongoDB C/C++ drivers when enabling the plugin - -**Section sources** -- [CMakeLists.txt:91-202](file://CMakeLists.txt#L91-L202) -- [building.md:25-212](file://documentation/building.md#L25-L212) -- [thirdparty/fc/CMakeLists.txt:115-130](file://thirdparty/fc/CMakeLists.txt#L115-L130) -- [thirdparty/chainbase/CMakeLists.txt](file://thirdparty/chainbase/CMakeLists.txt#L28) -- [thirdparty/appbase/CMakeLists.txt](file://thirdparty/appbase/CMakeLists.txt#L21) - -### Enhanced Submodule Management and Dependency Resolution - -#### Third-Party Submodule Branch Specifications -The repository now maintains explicit branch specifications for thirdparty submodules to ensure consistent dependency resolution: - -- fc submodule: Branch `update` for enhanced functionality -- chainbase submodule: Branch `lib-boost-1.71` for Boost 1.71 compatibility -- appbase submodule: Branch `lib-boost-1.71` for Boost 1.71 compatibility - -These branch specifications ensure that all thirdparty dependencies align with the required Boost 1.71 version and receive the latest updates for their respective functionality. - -#### fc Submodule Vendor Dependencies -The fc library manages its own vendor dependencies through nested submodules: -- websocketpp: WebSocket protocol implementation -- diff-match-patch-cpp-stl: Text comparison utilities -- secp256k1-zkp: Cryptographic elliptic curve implementation - -These dependencies are automatically managed during the fc build process and integrated into the final library. - -#### Streamlined Docker Configuration -The Docker build system has been streamlined to support multiple deployment variants with consistent static library approach: -- Production builds with Release configuration and static linking -- Testnet builds with testnet-specific configuration and static linking -- Low-memory builds optimized for resource-constrained environments with static linking -- MongoDB-enabled builds with database integration support and static linking - -**Updated** All Docker configurations now consistently use BUILD_SHARED_LIBRARIES=FALSE to ensure uniform behavior across containerized deployments. - -**Section sources** -- [.gitmodules:1-13](file://.gitmodules#L1-L13) -- [thirdparty/fc/.gitmodules:1-10](file://thirdparty/fc/.gitmodules#L1-L10) -- [thirdparty/fc/CMakeLists.txt:51-101](file://thirdparty/fc/CMakeLists.txt#L51-L101) -- [thirdparty/chainbase/CMakeLists.txt](file://thirdparty/chainbase/CMakeLists.txt#L28) -- [thirdparty/appbase/CMakeLists.txt](file://thirdparty/appbase/CMakeLists.txt#L21) - -### Dependency Management and Third-Party Integration -- Boost: 1.71+ required across all thirdparty libraries (appbase, chainbase, fc) -- OpenSSL: found automatically or via OPENSSL_ROOT_DIR on macOS -- Readline: optional on Unix-like systems -- gperftools: optional TCMalloc usage on Unix-like systems -- MongoDB: optional; when enabled, the mongo_c driver and mongo_cxx driver are built and linked - -**Updated** All thirdparty libraries now enforce Boost 1.71+ requirement, with specific branch targeting for compatibility and feature alignment. - -**Section sources** -- [CMakeLists.txt:97-104](file://CMakeLists.txt#L97-L104) -- [CMakeLists.txt:160-183](file://CMakeLists.txt#L160-L183) -- [CMakeLists.txt:106-110](file://CMakeLists.txt#L106-L110) -- [programs/vizd/CMakeLists.txt:10-14](file://programs/vizd/CMakeLists.txt#L10-L14) -- [thirdparty/fc/CMakeLists.txt:115-130](file://thirdparty/fc/CMakeLists.txt#L115-L130) -- [thirdparty/chainbase/CMakeLists.txt](file://thirdparty/chainbase/CMakeLists.txt#L28) -- [thirdparty/appbase/CMakeLists.txt](file://thirdparty/appbase/CMakeLists.txt#L21) - -### Executable Linkage (vizd) -The vizd executable links against: -- appbase and fc -- Core graphene libraries and plugins -- Optional MongoDB library when enabled -- Platform-specific libraries (readline on macOS/Unix, tcmalloc if found) - -Installation: -- Runtime, library, and archive targets are installed under standard prefixes - -**Section sources** -- [programs/vizd/CMakeLists.txt:1-58](file://programs/vizd/CMakeLists.txt#L1-L58) - -### Continuous Integration and Packaging -- Travis CI: - - Builds multiple Docker images for production, test, testnet, lowmem, and mongo variants - - Matrix builds tag/branch-aware image names - - Pushes images to registry on success -- GitHub Actions: - - Builds production and testnet Docker images on master branch pushes - - Uses Docker's build-push action with credentials from secrets - -**Updated** Docker configurations have been streamlined to support the enhanced submodule management and improved dependency resolution with consistent static library approach. - -Dockerfiles: -- Production: Release build with shared libs disabled, minimal flags -- Testnet: Same as production plus BUILD_TESTNET -- Low-memory: Same as production plus LOW_MEMORY_NODE -- Mongo: Installs MongoDB C/C++ drivers and enables ENABLE_MONGO_PLUGIN - -**Section sources** -- [.travis.yml:1-46](file://.travis.yml#L1-L46) -- [.github/workflows/docker-main.yml:1-41](file://.github/workflows/docker-main.yml#L1-L41) -- [share/vizd/docker/Dockerfile-testnet:46-54](file://share/vizd/docker/Dockerfile-testnet#L46-L54) -- [share/vizd/docker/Dockerfile-lowmem:45-53](file://share/vizd/docker/Dockerfile-lowmem#L45-L53) -- [share/vizd/docker/Dockerfile-mongo:74-82](file://share/vizd/docker/Dockerfile-mongo#L74-L82) - -## Dependency Analysis -The build system composes targets in a layered fashion. The top-level CMake orchestrates thirdparty, libraries, plugins, and programs. The vizd executable depends on core libraries and selected plugins. - -```mermaid -graph LR -Root["Root CMakeLists.txt"] -TP["thirdparty"] -LIB["libraries"] -PLG["plugins"] -PRG["programs"] -Submods[".gitmodules
Branch Specifications"] -Root --> TP -Root --> LIB -Root --> PLG -Root --> PRG -TP --> Submods -PRG --> VIZD["vizd"] -VIZD --> Core["Core Libraries"] -VIZD --> Plugins["Enabled Plugins"] -VIZD --> Third["Third-party Libraries"] -Third --> Boost["Boost 1.71+"] -Third --> OpenSSL["OpenSSL"] -Third --> Crypto["secp256k1-zkp"] -``` - -**Diagram sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [programs/vizd/CMakeLists.txt:16-49](file://programs/vizd/CMakeLists.txt#L16-L49) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) -- [thirdparty/fc/CMakeLists.txt:115-130](file://thirdparty/fc/CMakeLists.txt#L115-L130) - -**Section sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [programs/vizd/CMakeLists.txt:16-49](file://programs/vizd/CMakeLists.txt#L16-L49) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) - -## Performance Considerations -- Compiler flags: - - MinGW sets C++11, permissiveness, SSE4.2, and big object support for large translation units - - Linux sets fno-builtin-memcmp for performance-sensitive comparisons - - Ninja with Clang enables color diagnostics for readability -- Caching: - - ccache is detected and used globally for compile and link steps when available -- Static linking: - - FULL_STATIC_BUILD toggles static linking flags on Windows and Linux to reduce runtime dependencies - - **Unified static library approach reduces deployment complexity and improves portability** -- Coverage: - - ENABLE_COVERAGE_TESTING injects coverage flags for analysis workflows -- PCH: - - USE_PCH enables precompiled headers via cotire to speed up rebuilds - -**Updated** The unified static library approach simplifies performance optimization by eliminating shared library dependency issues across different environments. - -**Section sources** -- [CMakeLists.txt:147-156](file://CMakeLists.txt#L147-L156) -- [CMakeLists.txt:186-188](file://CMakeLists.txt#L186-L188) -- [CMakeLists.txt:190-194](file://CMakeLists.txt#L190-L194) -- [CMakeLists.txt:106-110](file://CMakeLists.txt#L106-L110) -- [CMakeLists.txt:204-208](file://CMakeLists.txt#L204-L208) -- [CMakeLists.txt:29-31](file://CMakeLists.txt#L29-L31) - -## Troubleshooting Guide -Common issues and resolutions: -- Boost version mismatch: - - Ubuntu 14.04 Boost in repos is too old; use Boost 1.71 manually installed - - Ubuntu 16.04 modern Boost packages are sufficient - - All thirdparty libraries now require Boost 1.71+ -- macOS Boost/OpenSSL paths: - - Set BOOST_ROOT and OPENSSL_ROOT_DIR to Homebrew locations when using non-system Boost/OpenSSL -- Windows toolchain: - - Ensure TCL_ROOT is set for TCL include path; MSVC flags disable safe-seh and enable debug info in Debug -- MinGW large object sizes: - - Debug builds use increased optimization level to avoid assembler "file too big" errors -- Coverage builds: - - ENABLE_COVERAGE_TESTING requires compatible toolchain and gcov/gcovr availability -- Plugin dependencies: - - Enabling ENABLE_MONGO_PLUGIN requires MongoDB C/C++ drivers; Dockerfile-mongo demonstrates the process -- Submodule branch conflicts: - - Ensure thirdparty submodules are checked out from the correct branches (lib-boost-1.71 for chainbase/appbase, update for fc) -- **Static library deployment issues**: - - **All builds now use static libraries by default, eliminating shared library dependency problems** - - **If encountering runtime linking issues, verify the unified static library approach is being used** - -**Updated** Added troubleshooting guidance for the unified static library approach and deployment-related issues. - -**Section sources** -- [building.md:76-137](file://documentation/building.md#L76-L137) -- [building.md:138-199](file://documentation/building.md#L138-L199) -- [CMakeLists.txt:91-156](file://CMakeLists.txt#L91-L156) -- [CMakeLists.txt:204-208](file://CMakeLists.txt#L204-L208) -- [thirdparty/fc/CMakeLists.txt:115-130](file://thirdparty/fc/CMakeLists.txt#L115-L130) -- [thirdparty/chainbase/CMakeLists.txt](file://thirdparty/chainbase/CMakeLists.txt#L28) -- [thirdparty/appbase/CMakeLists.txt](file://thirdparty/appbase/CMakeLists.txt#L21) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) - -## Conclusion -The VIZ CPP Node build system is designed for portability and flexibility across Linux, macOS, and Windows. It exposes a concise set of CMake options to tailor builds for development, production, testnet, and specialized configurations like low-memory nodes and MongoDB-enabled deployments. The enhanced submodule management ensures consistent dependency resolution with Boost 1.71+ across all thirdparty libraries, while streamlined Docker configurations support automated CI/CD workflows. **The unified static library approach across all platforms eliminates shared library dependency issues and ensures consistent deployment characteristics.** CI pipelines automate reproducible builds using Docker, ensuring consistent outcomes across environments. - -**Updated** The build system now includes enhanced submodule management with branch specifications, improved dependency resolution through Boost 1.71+ enforcement, and a unified static library approach that simplifies deployment across all supported platforms. - -## Appendices - -### Practical Build Scenarios -- Development build (Linux/macOS): - - Configure with Release profile and desired feature toggles - - Example: cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=TRUE .. - - **Static libraries enabled by default for consistent behavior** -- Production deployment (Linux): - - Use Dockerfile-production for a Release build with static linking and minimal flags - - Alternatively, cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBRARIES=OFF .. - - **All production builds use static libraries for simplified deployment** -- Low-memory node: - - cmake -DCMAKE_BUILD_TYPE=Release -DLOW_MEMORY_NODE=TRUE .. - - **Static libraries enabled for optimal performance** -- Testnet node: - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=TRUE .. - - **Consistent static library approach across all testnet deployments** -- MongoDB-enabled node: - - Build Dockerfile-mongo or cmake -DENABLE_MONGO_PLUGIN=TRUE .. with drivers installed - - **Static libraries ensure reliable MongoDB plugin deployment** -- Windows (MSVC): - - Ensure Boost and TCL roots are set; use Visual Studio generator - - **Static libraries enabled by default through build scripts** -- Windows (MinGW): - - cmake -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" .. - - **Static libraries enabled by default through build scripts** -- Submodule management: - - Ensure thirdparty submodules are properly initialized: git submodule update --init --recursive - - Verify branch specifications match required versions - -**Updated** Added comprehensive coverage of the unified static library approach across all build scenarios and platforms. - -### Environment Variables Reference -- BOOST_ROOT: Path to Boost installation (Windows) -- TCL_ROOT: Path to TCL include directory (Windows) -- OPENSSL_ROOT_DIR: Path to OpenSSL installation (macOS) -- CCACHE: Presence enables transparent compile/link caching - -### CI Integration Notes -- Travis CI builds multiple Docker images for production, test, testnet, lowmem, and mongo variants -- GitHub Actions builds production and testnet images on master branch pushes - -**Updated** Docker configurations have been streamlined to support the enhanced submodule management and improved dependency resolution with consistent static library approach. - -**Section sources** -- [share/vizd/docker/Dockerfile-testnet:46-54](file://share/vizd/docker/Dockerfile-testnet#L46-L54) -- [share/vizd/docker/Dockerfile-lowmem:45-53](file://share/vizd/docker/Dockerfile-lowmem#L45-L53) -- [share/vizd/docker/Dockerfile-mongo:74-82](file://share/vizd/docker/Dockerfile-mongo#L74-L82) -- [.travis.yml:12-42](file://.travis.yml#L12-L42) -- [.github/workflows/docker-main.yml:11-41](file://.github/workflows/docker-main.yml#L11-L41) -- [.gitmodules:1-13](file://.gitmodules#L1-L13) - -### Platform-Specific Build Behavior - -#### Linux Build Scripts -- **Default behavior**: Static libraries enabled (SHARED_LIBS="OFF") -- **Command-line option**: --static flag enables static linking -- **Build type**: Release by default with optional Debug -- **Feature toggles**: Low memory, testnet, MongoDB plugin support - -#### macOS Build Scripts -- **Default behavior**: Shared libraries enabled (SHARED_LIBS="ON") -- **Command-line option**: --static flag switches to static linking -- **Build type**: Release by default with optional Debug -- **Feature toggles**: Low memory, testnet support - -#### Windows Build Scripts -- **Default behavior**: Static libraries enabled (BUILD_SHARED_LIBRARIES=OFF) -- **Build type**: Release by default with optional Debug -- **Feature toggles**: Low memory, testnet, MongoDB plugin support - -**Updated** Platform-specific behaviors now reflect the unified static library approach, with macOS and Windows maintaining backward compatibility through command-line options. - -**Section sources** -- [build-linux.sh:39](file://build-linux.sh#L39) -- [build-mac.sh:35](file://build-mac.sh#L35) -- [build-mingw.bat:99](file://build-mingw.bat#L99) -- [build-msvc.bat:91](file://build-msvc.bat#L91) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Configuration Management/Configuration Management.md b/.qoder/repowiki/en/content/Configuration Management/Configuration Management.md deleted file mode 100644 index f6121ac762..0000000000 --- a/.qoder/repowiki/en/content/Configuration Management/Configuration Management.md +++ /dev/null @@ -1,662 +0,0 @@ -# Configuration Management - - -**Referenced Files in This Document** -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_witness.ini](file://share/vizd/config/config_witness.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [vizd.sh](file://share/vizd/vizd.sh) -- [main.cpp](file://programs/vizd/main.cpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [building.md](file://documentation/building.md) -- [testnet.md](file://documentation/testnet.md) -- [plugin.md](file://documentation/plugin.md) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) -- [plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [application.cpp](file://thirdparty/appbase/application.cpp) - - -## Update Summary -**Changes Made** -- Updated snapshot directory default behavior section to reflect new data-directory-based default location -- Added documentation for enhanced snapshot configuration including trusted-snapshot-peer integration -- Updated snapshot configuration examples to show new default behavior and trusted peer options -- Enhanced troubleshooting guidance for snapshot-related configuration issues - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the configuration management system for VIZ CPP Node. It explains configuration file structure, runtime parameters, environment variable overrides, node types (full node, validator node, low-memory node), network configuration, plugin activation, performance tuning, logging, Docker-specific configuration, build-time options, and troubleshooting guidance. Practical deployment scenarios (production, testnet, development) are included. - -## Project Structure -The configuration system centers around: -- A primary configuration file template for mainnet -- Testnet-specific configuration -- validator-specific configuration -- MongoDB-enabled configuration variants -- Dockerfiles for production, testnet, and low-memory builds -- A container entrypoint script that supports environment variable overrides -- The node binary's program options and logging configuration loader - -```mermaid -graph TB -cfg_main["Config Template
share/vizd/config/config.ini"] -cfg_test["Testnet Config
share/vizd/config/config_testnet.ini"] -cfg_wit["validator Config
share/vizd/config/config_witness.ini"] -cfg_mongo["Mongo Config
share/vizd/config/config_mongo.ini"] -cfg_dbg["Debug Config
share/vizd/config/config_debug.ini"] -cfg_dbg_mongo["Debug+Mongo Config
share/vizd/config/config_debug_mongo.ini"] -df_prod["Dockerfile-production
share/vizd/docker/Dockerfile-production"] -df_test["Dockerfile-testnet
share/vizd/docker/Dockerfile-testnet"] -df_lowmem["Dockerfile-lowmem
share/vizd/docker/Dockerfile-lowmem"] -entry["Container Entrypoint
share/vizd/vizd.sh"] -node["Node Binary
programs/vizd/main.cpp"] -cfg_main --> node -cfg_test --> node -cfg_wit --> node -cfg_mongo --> node -cfg_dbg --> node -cfg_dbg_mongo --> node -df_prod --> entry -df_test --> entry -df_lowmem --> entry -entry --> node -``` - -**Diagram sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_debug_mongo.ini:1-135](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [Dockerfile-production:1-88](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet:1-88](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [Dockerfile-lowmem:1-82](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- [main.cpp:106-158](file://programs/vizd/main.cpp#L106-L158) - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_debug_mongo.ini:1-135](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [Dockerfile-production:1-88](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet:1-88](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [Dockerfile-lowmem:1-82](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- [main.cpp:106-158](file://programs/vizd/main.cpp#L106-L158) - -## Core Components -- Configuration file format: INI-style with sections for logging appenders and loggers. -- Runtime parameters: Passed via program options and loaded from configuration. -- Environment variable overrides: Container entrypoint sets CLI flags based on environment variables. -- Plugin activation: Controlled via configuration entries. -- Logging configuration: Program options and INI sections define appenders and logger routing. - -Key configuration categories: -- Network endpoints and connectivity -- RPC endpoints (HTTP/WebSocket) -- Lock/wait tuning for database operations -- Shared memory sizing and growth policy -- Plugin list and activation -- validator production controls -- Logging configuration -- **Snapshot configuration** (updated with new default behavior) - -**Section sources** -- [main.cpp:167-191](file://programs/vizd/main.cpp#L167-L191) -- [main.cpp:194-289](file://programs/vizd/main.cpp#L194-L289) -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) - -## Architecture Overview -The configuration pipeline integrates configuration files, program options, and environment variables to initialize the node. - -```mermaid -sequenceDiagram -participant Entrypoint as "Container Entrypoint
vizd.sh" -participant Env as "Environment Variables" -participant Node as "Node Binary
main.cpp" -participant Config as "INI Loader
load_config_sections/load_logging_config" -Entrypoint->>Env : Read VIZD_* variables -Entrypoint->>Node : Pass CLI flags (--p2p-endpoint, --rpc-endpoint,
--p2p-seed-node, --validator, --private-key) -Node->>Node : Parse program options -Node->>Config : Load logging config from config path -Config-->>Node : fc : : logging_config -Node->>Node : Configure logging and startup -``` - -**Diagram sources** -- [vizd.sh:13-81](file://share/vizd/vizd.sh#L13-L81) -- [main.cpp:112-139](file://programs/vizd/main.cpp#L112-L139) -- [main.cpp:194-289](file://programs/vizd/main.cpp#L194-L289) - -## Detailed Component Analysis - -### Configuration File Structure and Sections -- Logging appenders: - - Console appenders - - File appenders -- Loggers: - - Route loggers to specific appenders with levels - -These sections are parsed by the node to configure logging at startup. - -**Section sources** -- [main.cpp:167-191](file://programs/vizd/main.cpp#L167-L191) -- [main.cpp:211-289](file://programs/vizd/main.cpp#L211-L289) -- [config.ini:118-136](file://share/vizd/config/config.ini#L118-L136) - -### Runtime Parameters and Program Options -Program options include logging configuration and standard node options. The node parses configuration files and applies logging settings accordingly. - -**Section sources** -- [main.cpp:112-139](file://programs/vizd/main.cpp#L112-L139) -- [main.cpp:167-191](file://programs/vizd/main.cpp#L167-L191) - -### Environment Variable Overrides (Docker) -The container entrypoint supports the following environment variables: -- VIZD_SEED_NODES: Comma-separated seed nodes -- VIZD_WITNESS_NAME: validator name to operate -- VIZD_PRIVATE_KEY: Private key for signing -- VIZD_RPC_ENDPOINT: Override RPC endpoint -- VIZD_P2P_ENDPOINT: Override P2P endpoint -- VIZD_EXTRA_OPTS: Additional arguments appended to node invocation - -These variables override defaults and inject CLI flags at runtime. - -**Section sources** -- [vizd.sh:17-37](file://share/vizd/vizd.sh#L17-L37) -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) -- [vizd.sh:74-81](file://share/vizd/vizd.sh#L74-L81) - -### Node Types and Their Configuration Requirements -- Full node (mainnet): - - Uses the main configuration template with default plugin sets suitable for full synchronization and API exposure. -- Testnet: - - Includes testnet-specific defaults and enables stale production for continuous block production. -- validator node: - - Activates validator and witness_api plugins, binds RPC endpoints to localhost by default, and includes validator credentials. -- Low-memory node: - - Built with a dedicated flag to reduce memory footprint; Dockerfile demonstrates enabling this build-time option. - -```mermaid -flowchart TD -Start(["Select Node Type"]) --> Full["Full Node
Mainnet"] -Start --> Testnet["Testnet Node"] -Start --> validator["validator Node"] -Start --> LowMem["Low-Memory Node"] -Full --> FullCfg["Use config.ini"] -Testnet --> TestCfg["Use config_testnet.ini"] -validator --> WitCfg["Use config_witness.ini"] -LowMem --> LowMemFlag["Build with LOW_MEMORY_NODE=TRUE"] -``` - -**Diagram sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- [Dockerfile-lowmem:45-51](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) -- [building.md:11-15](file://documentation/building.md#L11-L15) - -**Section sources** -- [config.ini:73-85](file://share/vizd/config/config.ini#L73-L85) -- [config_testnet.ini:69-73](file://share/vizd/config/config_testnet.ini#L69-L73) -- [config_witness.ini:72-84](file://share/vizd/config/config_witness.ini#L72-L84) -- [building.md:11-15](file://documentation/building.md#L11-L15) -- [Dockerfile-lowmem:45-51](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) - -### Network Configuration -Network settings include: -- P2P endpoint binding and maximum connections -- Seed nodes for initial connectivity -- Checkpoint enforcement for block safety -- Peer connection and sync behavior governed by network constants - -```mermaid -flowchart TD -NetCfg["Network Settings"] --> P2PEndpoint["P2P Endpoint"] -NetCfg --> MaxConns["Max Incoming Connections"] -NetCfg --> SeedNodes["Seed Nodes"] -NetCfg --> Checkpoints["Block Checkpoints"] -P2PEndpoint --> PeerConn["Peer Connection Logic"] -MaxConns --> PeerConn -SeedNodes --> PeerConn -PeerConn --> Sync["Sync Behavior
network config constants"] -``` - -**Diagram sources** -- [config.ini:1-16](file://share/vizd/config/config.ini#L1-L16) -- [config_testnet.ini:1-11](file://share/vizd/config/config_testnet.ini#L1-L11) -- [config_witness.ini:1-15](file://share/vizd/config/config_witness.ini#L1-L15) -- [config.hpp:54-56](file://libraries/network/include/graphene/network/config.hpp#L54-L56) -- [config.hpp:105-106](file://libraries/network/include/graphene/network/config.hpp#L105-L106) - -**Section sources** -- [config.ini:1-16](file://share/vizd/config/config.ini#L1-L16) -- [config_testnet.ini:1-11](file://share/vizd/config/config_testnet.ini#L1-L11) -- [config_witness.ini:1-15](file://share/vizd/config/config_witness.ini#L1-L15) -- [config.hpp:54-56](file://libraries/network/include/graphene/network/config.hpp#L54-L56) -- [config.hpp:105-106](file://libraries/network/include/graphene/network/config.hpp#L105-L106) - -### Plugin Activation and Configuration -Plugins are activated via configuration entries. The node registers all built-in plugins and loads the configured set at startup. Some plugins maintain persistent state and may require replay when toggled. - -- Example plugin lists appear in the configuration templates. -- The plugin documentation outlines enabling/disabling and replay requirements. - -**Section sources** -- [config.ini:73-85](file://share/vizd/config/config.ini#L73-L85) -- [config_testnet.ini:69-73](file://share/vizd/config/config_testnet.ini#L69-L73) -- [config_witness.ini:72-84](file://share/vizd/config/config_witness.ini#L72-L84) -- [plugin.md:14-18](file://documentation/plugin.md#L14-L18) - -### Performance Tuning Parameters -Key tunables include: -- Thread pool size for RPC clients -- Read/write lock wait durations and retries -- Single-threaded write mode for database operations -- Skipping plugin notifications on push transactions -- Shared memory size, minimum free space, increment, and free-space check frequency - -These parameters influence throughput and stability under load. - -**Section sources** -- [config.ini:17-72](file://share/vizd/config/config.ini#L17-L72) -- [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) - -### Logging Configuration -Logging is configured via INI sections for appenders and loggers. The node exposes program options to define console/file appenders and logger routing. - -- Console and file appenders -- Logger levels and appender assignments - -**Section sources** -- [config.ini:118-136](file://share/vizd/config/config.ini#L118-L136) -- [main.cpp:167-191](file://programs/vizd/main.cpp#L167-L191) -- [main.cpp:211-289](file://programs/vizd/main.cpp#L211-L289) - -### Snapshot Configuration and Default Directory Behavior - -**Updated** The snapshot plugin now uses a data-directory-based default location instead of the current working directory fallback: - -#### Default Directory Behavior -- **New default**: When `snapshot-dir` is not explicitly configured, the system now defaults to `/snapshots` instead of the current working directory -- **Data directory resolution**: The data directory is determined by the application's data_dir() method, typically located at `/var/lib/vizd` for production containers -- **Automatic creation**: The default snapshot directory is automatically created if it doesn't exist - -#### Enhanced Trusted Peer Integration -The snapshot plugin now includes comprehensive trusted peer configuration options: - -- **Trusted snapshot peers**: List of seed nodes that can serve snapshots to this node -- **Snapshot serving restrictions**: Control who can download snapshots from this node -- **Anti-spam protection**: Built-in rate limiting and connection management for snapshot serving -- **DLT mode support**: Optimized for distributed ledger technology nodes without full block history - -#### Configuration Examples - -**Basic snapshot configuration**: -```ini -plugin = snapshot -snapshot-dir = /var/lib/vizd/snapshots -snapshot-every-n-blocks = 2400 -snapshot-max-age-days = 2 -``` - -**Trusted peer configuration**: -```ini -plugin = snapshot -sync-snapshot-from-trusted-peer = true -trusted-snapshot-peer = 185.45.192.155:8092 -trusted-snapshot-peer = 62.109.17.82:8092 -snapshot-dir = /var/lib/vizd/snapshots -``` - -**Snapshot serving configuration**: -```ini -plugin = snapshot -allow-snapshot-serving = true -allow-snapshot-serving-only-trusted = false -snapshot-serve-endpoint = 0.0.0.0:8092 -snapshot-dir = /var/lib/vizd/snapshots -``` - -**Section sources** -- [plugin.cpp:2974-2983](file://plugins/snapshot/plugin.cpp#L2974-L2983) -- [plugin.cpp:3044-3050](file://plugins/snapshot/plugin.cpp#L3044-L3050) -- [plugin.cpp:3070-3090](file://plugins/snapshot/plugin.cpp#L3070-L3090) -- [plugin.cpp:352-355](file://plugins/chain/plugin.cpp#L352-L355) -- [application.cpp:298-300](file://thirdparty/appbase/application.cpp#L298-L300) -- [snapshot-plugin.md:1-365](file://documentation/snapshot-plugin.md#L1-L365) - -### Docker-Specific Configuration -- Production image: - - Copies main configuration and seednodes into /etc/vizd - - Exposes RPC and P2P ports - - Mounts persistent volumes for data and config -- Testnet image: - - Uses testnet configuration and snapshot - - Enables testnet build flag -- Low-memory image: - - Enables low-memory build flag - -```mermaid -graph TB -subgraph "Docker Images" -prod["Production Image"] -test["Testnet Image"] -lowmem["Low-Memory Image"] -end -prod --> vol1["Volumes:
/var/lib/vizd, /etc/vizd"] -test --> vol1 -lowmem --> vol1 -prod --> ports["Expose:
8090, 8091, 2001"] -test --> ports -lowmem --> ports -``` - -**Diagram sources** -- [Dockerfile-production:74-87](file://share/vizd/docker/Dockerfile-production#L74-L87) -- [Dockerfile-testnet:75-87](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [Dockerfile-lowmem:68-81](file://share/vizd/docker/Dockerfile-lowmem#L68-L81) - -**Section sources** -- [Dockerfile-production:74-87](file://share/vizd/docker/Dockerfile-production#L74-L87) -- [Dockerfile-testnet:75-87](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [Dockerfile-lowmem:68-81](file://share/vizd/docker/Dockerfile-lowmem#L68-L81) - -### Build-Time Configuration Options -Build-time flags and toggles: -- LOW_MEMORY_NODE: Builds a consensus-only low-memory node -- BUILD_TESTNET: Enables testnet-specific defaults -- CHAINBASE_CHECK_LOCKING: Debugging toggle -- ENABLE_MONGO_PLUGIN: Feature toggle for MongoDB plugin - -These are set via CMake flags in Dockerfiles and documented in the building guide. - -**Section sources** -- [building.md:11-15](file://documentation/building.md#L11-L15) -- [Dockerfile-production:46-52](file://share/vizd/docker/Dockerfile-production#L46-L52) -- [Dockerfile-testnet:46-52](file://share/vizd/docker/Dockerfile-testnet#L46-L52) -- [Dockerfile-lowmem:45-51](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) - -### Practical Configuration Scenarios - -- Production deployment (mainnet) - - Use the main configuration template and production Docker image. - - Persist data via mounted volumes. - - Optionally override endpoints and seed nodes via environment variables. - - **Updated**: Snapshot directory will default to `/var/lib/vizd/snapshots` if not explicitly configured. - -- Testnet setup - - Use the testnet Docker image and configuration. - - The testnet image enables testnet build flags and uses a testnet snapshot. - - **Updated**: Trusted snapshot peers are pre-configured for testnet bootstrap. - -- Development environment - - Use the debug configuration templates to enable additional plugins and adjust logging. - - Optionally enable MongoDB plugin configuration. - -- validator node - - Use the validator configuration template and bind RPC endpoints to localhost. - - Provide validator name and private key via environment variables. - - **Updated**: Snapshot configuration supports validator-aware deferral to prevent missed production slots. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [Dockerfile-production:74-87](file://share/vizd/docker/Dockerfile-production#L74-L87) -- [Dockerfile-testnet:75-87](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [testnet.md:21-37](file://documentation/testnet.md#L21-L37) - -## Dependency Analysis -Configuration dependencies and interactions: -- The node binary depends on the configuration loader to parse logging and runtime settings. -- Docker entrypoint depends on environment variables to inject CLI flags. -- Network behavior is influenced by both configuration and compiled-in network constants. -- **Updated**: Snapshot configuration depends on the application's data_dir() method for default directory resolution. - -```mermaid -graph LR -ConfigINI["INI Config Files"] --> Loader["INI Loader
load_config_sections/load_logging_config"] -Loader --> NodeMain["Node Main
main.cpp"] -Entrypoint["Entrypoint Script
vizd.sh"] --> NodeMain -NodeMain --> Logging["Logging System"] -NodeMain --> Network["Network Constants
config.hpp"] -NodeMain --> DataDir["Data Directory
appbase::app().data_dir()"] -DataDir --> SnapshotDefault["Snapshot Default Dir
/snapshots"] -``` - -**Diagram sources** -- [main.cpp:194-289](file://programs/vizd/main.cpp#L194-L289) -- [vizd.sh:13-81](file://share/vizd/vizd.sh#L13-L81) -- [config.hpp:54-56](file://libraries/network/include/graphene/network/config.hpp#L54-L56) -- [application.cpp:298-300](file://thirdparty/appbase/application.cpp#L298-L300) - -**Section sources** -- [main.cpp:194-289](file://programs/vizd/main.cpp#L194-L289) -- [vizd.sh:13-81](file://share/vizd/vizd.sh#L13-L81) -- [config.hpp:54-56](file://libraries/network/include/graphene/network/config.hpp#L54-L56) -- [application.cpp:298-300](file://thirdparty/appbase/application.cpp#L298-L300) - -## Performance Considerations -- Tune read/write lock wait parameters to balance latency and contention. -- Use single-threaded writes to reduce database lock contention. -- Adjust shared memory size and growth thresholds to minimize resizing overhead. -- Limit plugin notifications on push transactions to improve responsiveness. -- Select appropriate node type (low-memory) for constrained environments. -- **Updated**: Configure snapshot directory on fast storage for optimal snapshot creation and loading performance. - -## Troubleshooting Guide - -### validator Configuration Issues - -**Updated** The validator configuration defaults have been updated to improve reliability and participation calculations: - -- **enable-stale-production default changed**: The default value for `enable-stale-production` has been changed from `false` to `true`. This means validator nodes will now automatically continue producing blocks even when the chain appears stale, improving network resilience during network partitions or temporary forks. - -- **required-participation calculation**: The `required-participation` parameter now uses the formula `33 * CHAIN_1_PERCENT` instead of a hardcoded percentage. With `CHAIN_1_PERCENT` equal to 100 (representing 1% in the 10000-point scale), this calculates to 3300, which represents 33% participation threshold. - -- **validator production failures**: If validator production is failing, check the participation threshold calculation. The system now requires at least 33% of validators to be participating for block production to continue. - -### Snapshot Configuration Issues - -**Updated** Common snapshot configuration issues and validation techniques: - -- **Snapshot directory default behavior** - - Verify that the data directory is properly mounted in Docker containers - - Check that `/var/lib/vizd/snapshots` exists and has proper permissions - - Use explicit `snapshot-dir` configuration when the default location is not suitable - -- **Trusted peer configuration** - - Verify that trusted snapshot peers are reachable and serving snapshots - - Check firewall rules for port 8092 (snapshot serving) - - Use `test-trusted-seeds` option to diagnose connectivity issues - -- **Snapshot auto-discovery** - - Ensure snapshot files follow the naming convention `snapshot-block-NNNNN.vizjson` - - Verify that snapshot files are readable and not corrupted - - Check that the snapshot directory contains valid snapshot files - -- **Snapshot serving issues** - - Verify that `allow-snapshot-serving` is properly configured - - Check anti-spam settings if clients are being rate-limited - - Monitor snapshot serving logs for connection errors - -Common configuration issues and validation techniques: -- Logging misconfiguration - - Verify INI sections for appenders and loggers. - - Confirm program options for logging are recognized by the node. -- RPC/P2P endpoint conflicts - - Ensure endpoints are reachable and not blocked by firewalls. - - Validate environment variable overrides for endpoints. -- Plugin activation problems - - Confirm plugin entries in configuration. - - Review plugin documentation for replay requirements when toggling stateful plugins. -- Memory and shared file sizing - - Adjust shared file size and growth increments based on observed free space checks. -- Docker volume and permissions - - Ensure persistent volumes are mounted and owned by the node user. - - Confirm snapshot extraction occurs when present. -- validator production issues - - Verify validator name and private key are correctly configured. - - Check participation threshold calculations using the CHAIN_1_PERCENT constant. - - Monitor for "low participation" errors indicating below-threshold validator participation. -- **Updated**: Snapshot configuration issues - - Verify snapshot directory permissions and disk space - - Check snapshot file integrity and naming conventions - - Validate trusted peer connectivity and snapshot availability - -**Section sources** -- [main.cpp:167-191](file://programs/vizd/main.cpp#L167-L191) -- [main.cpp:211-289](file://programs/vizd/main.cpp#L211-L289) -- [config.ini:118-136](file://share/vizd/config/config.ini#L118-L136) -- [plugin.md:14-18](file://documentation/plugin.md#L14-L18) -- [vizd.sh:44-53](file://share/vizd/vizd.sh#L44-L53) -- [validator.cpp:125-130](file://plugins/validator/validator.cpp#L125-L130) -- [config_testnet.hpp:57-59](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L57-L59) -- [config.hpp:57-59](file://libraries/protocol/include/graphene/protocol/config.hpp#L57-L59) -- [plugin.cpp:2974-2983](file://plugins/snapshot/plugin.cpp#L2974-L2983) -- [plugin.cpp:3044-3050](file://plugins/snapshot/plugin.cpp#L3044-L3050) -- [plugin.cpp:3070-3090](file://plugins/snapshot/plugin.cpp#L3070-L3090) - -## Conclusion -VIZ CPP Node offers a flexible configuration system combining INI-based settings, program options, and environment variable overrides. Different node types and deployment modes are supported through configuration templates and Docker images. Proper tuning of performance and logging parameters ensures reliable operation across production, testnet, and development environments. **Updated**: The snapshot configuration system now provides enhanced default behavior with data-directory-based snapshot directories and comprehensive trusted peer integration for improved bootstrap and distribution capabilities. - -## Appendices - -### Appendix A: Configuration Keys Reference -- Network - - p2p-endpoint - - p2p-max-connections - - p2p-seed-node - - checkpoint -- RPC - - webserver-thread-pool-size - - webserver-http-endpoint - - webserver-ws-endpoint -- Database locks - - read-wait-micro - - max-read-wait-retries - - write-wait-micro - - max-write-wait-retries - - single-write-thread - - enable-plugins-on-push-transaction -- Shared memory - - shared-file-size - - min-free-shared-file-size - - inc-shared-file-size - - block-num-check-free-size -- Plugins - - plugin (repeatable) -- validator - - enable-stale-production (default: true) - - required-participation (default: 33% calculated as 33 * CHAIN_1_PERCENT) - - validator - - private-key -- Logging - - log.console_appender.* - - log.file_appender.* - - logger.* -- **Updated** Snapshot - - snapshot-at-block (default: 0) - - snapshot-every-n-blocks (default: 0) - - snapshot-dir (default: `/snapshots`) - - snapshot-max-age-days (default: 0) - - allow-snapshot-serving (default: false) - - allow-snapshot-serving-only-trusted (default: false) - - snapshot-serve-endpoint (default: 0.0.0.0:8092) - - trusted-snapshot-peer (repeatable) - - sync-snapshot-from-trusted-peer (default: false) - - enable-stalled-sync-detection (default: false) - - stalled-sync-timeout-minutes (default: 5) - - test-trusted-seeds (default: false) - - dlt-block-log-max-blocks (default: 100000) - -**Updated** validator configuration defaults now use improved defaults for better network reliability and accurate participation calculations. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_debug_mongo.ini:1-135](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [validator.cpp:125-130](file://plugins/validator/validator.cpp#L125-L130) -- [config_testnet.hpp:57-59](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L57-L59) -- [config.hpp:57-59](file://libraries/protocol/include/graphene/protocol/config.hpp#L57-L59) -- [plugin.cpp:2974-2983](file://plugins/snapshot/plugin.cpp#L2974-L2983) -- [plugin.cpp:3044-3050](file://plugins/snapshot/plugin.cpp#L3044-L3050) -- [plugin.cpp:3070-3090](file://plugins/snapshot/plugin.cpp#L3070-L3090) - -### Appendix B: Docker Environment Variables -- VIZD_SEED_NODES -- VIZD_WITNESS_NAME -- VIZD_PRIVATE_KEY -- VIZD_RPC_ENDPOINT -- VIZD_P2P_ENDPOINT -- VIZD_EXTRA_OPTS - -**Section sources** -- [vizd.sh:17-37](file://share/vizd/vizd.sh#L17-L37) -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) -- [vizd.sh:74-81](file://share/vizd/vizd.sh#L74-L81) - -### Appendix C: validator Participation Calculation Details - -**New** The validator participation calculation system now uses the CHAIN_1_PERCENT constant for precise percentage calculations: - -- **CHAIN_1_PERCENT definition**: Defined as `CHAIN_100_PERCENT/100` where `CHAIN_100_PERCENT` equals 10000 -- **Participation threshold**: Default required participation is 33% (3300/10000) -- **Calculation method**: `required-participation = 33 * CHAIN_1_PERCENT` -- **Precision**: Uses 10000-point scale for accurate percentage representation - -This change ensures more accurate participation calculations and consistent behavior across mainnet and testnet configurations. - -**Section sources** -- [validator.cpp:125-130](file://plugins/validator/validator.cpp#L125-L130) -- [config_testnet.hpp:57-59](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L57-L59) -- [config.hpp:57-59](file://libraries/protocol/include/graphene/protocol/config.hpp#L57-L59) - -### Appendix D: Snapshot Configuration Best Practices - -**New** Best practices for snapshot configuration: - -- **Default directory behavior**: The system now defaults to `/snapshots` instead of current working directory -- **Volume mounting**: Ensure `/var/lib/vizd` volume is properly mounted in Docker containers -- **Trusted peer selection**: Choose reliable snapshot peers with good uptime and bandwidth -- **Anti-spam configuration**: Use `allow-snapshot-serving-only-trusted` for private networks -- **Monitoring**: Regularly check snapshot directory disk usage and cleanup old snapshots -- **Security**: Restrict snapshot serving to trusted peers in production environments -- **Performance**: Store snapshots on fast storage devices for optimal loading performance - -**Section sources** -- [plugin.cpp:2974-2983](file://plugins/snapshot/plugin.cpp#L2974-L2983) -- [plugin.cpp:3044-3050](file://plugins/snapshot/plugin.cpp#L3044-L3050) -- [plugin.cpp:3070-3090](file://plugins/snapshot/plugin.cpp#L3070-L3090) -- [snapshot-plugin.md:1-365](file://documentation/snapshot-plugin.md#L1-L365) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Configuration Management/Docker Configuration.md b/.qoder/repowiki/en/content/Configuration Management/Docker Configuration.md deleted file mode 100644 index fa03236047..0000000000 --- a/.qoder/repowiki/en/content/Configuration Management/Docker Configuration.md +++ /dev/null @@ -1,470 +0,0 @@ -# Docker Configuration - - -**Referenced Files in This Document** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [vizd.sh](file://share/vizd/vizd.sh) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini) -- [docker-main.yml](file://.github/workflows/docker-main.yml) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [README.md](file://README.md) - - -## Update Summary -**Changes Made** -- Updated compression library dependencies section to reflect libbz2-dev, liblzma-dev, libzstd-dev, and zlib1g-dev additions -- Added documentation for sed command pattern fixes for test subdirectory removal -- Updated base image information to reflect newer phusion/baseimage:noble-1.0.3 variants -- Enhanced dependency analysis to include compression library requirements -- Updated troubleshooting section with compression-related issues - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive Docker configuration guidance for deploying the VIZ C++ Node in containers. It covers available Docker images (production, testnet, low-memory, and MongoDB variants), environment variables, persistent storage via volumes, network exposure, and operational patterns. It also documents image customization, base image selection, security considerations, monitoring and logging, and update procedures. - -## Project Structure -The Docker assets and configuration are organized under share/vizd/docker and share/vizd/config. The runtime bootstrap script is located at share/vizd/vizd.sh. Automated builds are defined in GitHub Actions workflows. - -```mermaid -graph TB -subgraph "Docker Build Artifacts" -DFProd["Dockerfile-production"] -DFTnet["Dockerfile-testnet"] -DFLowMem["Dockerfile-lowmem"] -DFMongo["Dockerfile-mongo"] -end -subgraph "Runtime Configurations" -CfgProd["config.ini"] -CfgTnet["config_testnet.ini"] -CfgMongo["config_mongo.ini"] -CfgDebug["config_debug.ini"] -CfgDebugMongo["config_debug_mongo.ini"] -end -Script["vizd.sh"] -DFProd --> Script -DFTnet --> Script -DFLowMem --> Script -DFMongo --> Script -DFProd --> CfgProd -DFTnet --> CfgTnet -DFMongo --> CfgMongo -DFLowMem --> CfgProd -``` - -**Diagram sources** -- [Dockerfile-production:1-103](file://share/vizd/docker/Dockerfile-production#L1-L103) -- [Dockerfile-testnet:1-103](file://share/vizd/docker/Dockerfile-testnet#L1-L103) -- [Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) -- [Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114) -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) - -**Section sources** -- [Dockerfile-production:1-103](file://share/vizd/docker/Dockerfile-production#L1-L103) -- [Dockerfile-testnet:1-103](file://share/vizd/docker/Dockerfile-testnet#L1-L103) -- [Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) -- [Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114) -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) - -## Core Components -- Production image: Built from Dockerfile-production, targeting the mainnet with standard configuration and exposed ports for RPC and P2P. -- Testnet image: Built from Dockerfile-testnet, configured for testnet with pre-set testnet endpoints and plugins. -- Low-memory image: Built from Dockerfile-lowmem, optimized for constrained environments with reduced memory footprint. -- MongoDB-enabled image: Built from Dockerfile-mongo, including MongoDB drivers and enabling the mongo_db plugin with a default connection URI. - -Key runtime behavior is orchestrated by the entrypoint script (vizd.sh), which sets up user permissions, applies environment overrides, initializes optional cached blockchain data, and starts the node with appropriate endpoints and arguments. - -**Section sources** -- [Dockerfile-production:81-103](file://share/vizd/docker/Dockerfile-production#L81-L103) -- [Dockerfile-testnet:82-103](file://share/vizd/docker/Dockerfile-testnet#L82-L103) -- [Dockerfile-lowmem:63-85](file://share/vizd/docker/Dockerfile-lowmem#L63-L85) -- [Dockerfile-mongo:92-114](file://share/vizd/docker/Dockerfile-mongo#L92-L114) -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) - -## Architecture Overview -The container architecture consists of: -- Base image: phusion/baseimage:noble-1.0.3 variants for Debian-based environments. -- Build stage: installs build dependencies including compression libraries (libbz2-dev, liblzma-dev, libzstd-dev, zlib1g-dev), clones repository, initializes submodules, removes test subdirectories, builds release binaries, and installs artifacts. -- Runtime stage: creates a non-root user, prepares cache and config directories, exposes RPC and P2P ports, defines persistent volumes, and starts the node via an init service. - -```mermaid -graph TB -Base["phusion/baseimage:noble-1.0.3
Debian-based runtime"] -Builder["Builder Stage
Install deps, build, install"] -Runtime["Runtime Stage
User, cache, config, expose ports, volumes"] -Builder --> Base -Base --> Runtime -``` - -**Diagram sources** -- [Dockerfile-production:1-103](file://share/vizd/docker/Dockerfile-production#L1-L103) -- [Dockerfile-testnet:1-103](file://share/vizd/docker/Dockerfile-testnet#L1-L103) -- [Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) -- [Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114) - -## Detailed Component Analysis - -### Docker Images and Variants -- Production image - - Purpose: Run on the main VIZ network. - - Build parameters: standard build with MongoDB disabled. - - Exposed ports: RPC HTTP (8090), RPC WS (8091), P2P (2001). - - Persistent volumes: /var/lib/vizd (blockchain data), /etc/vizd (configuration). - - Entrypoint script: sets defaults and starts node. - - Reference: [Dockerfile-production:1-103](file://share/vizd/docker/Dockerfile-production#L1-L103), [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98), [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) - -- Testnet image - - Purpose: Run on the testnet. - - Build parameters: BUILD_TESTNET enabled, MongoDB disabled. - - Exposed ports: same as production. - - Persistent volumes: same as production. - - Entrypoint script: same behavior, with testnet-specific defaults. - - Reference: [Dockerfile-testnet:1-103](file://share/vizd/docker/Dockerfile-testnet#L1-L103), [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) - -- Low-memory image - - Purpose: Constrained environments. - - Build parameters: LOW_MEMORY_NODE enabled, MongoDB disabled. - - Exposed ports: same as production. - - Persistent volumes: same as production. - - Reference: [Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) - -- MongoDB-enabled image - - Purpose: Enable historical indexing and analytics via MongoDB. - - Build parameters: ENABLE_MONGO_PLUGIN enabled, installs MongoDB C/C++ drivers. - - Exposed ports: same as production. - - Persistent volumes: same as production. - - MongoDB URI: configured in the MongoDB-enabled config. - - Reference: [Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114), [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) - -```mermaid -flowchart TD -Start(["Image Build"]) --> DetectVariant{"Variant?"} -DetectVariant --> |Production| ProdBuild["cmake -DENABLE_MONGO_PLUGIN=FALSE
Install artifacts"] -DetectVariant --> |Testnet| TnetBuild["cmake -DBUILD_TESTNET=TRUE
Install artifacts"] -DetectVariant --> |Low-Memory| LowMemBuild["cmake -DLOW_MEMORY_NODE=TRUE
Install artifacts"] -DetectVariant --> |MongoDB| MongoBuild["Install MongoDB drivers
cmake -DENABLE_MONGO_PLUGIN=TRUE
Install artifacts"] -ProdBuild --> RuntimeStage["Prepare user, cache, config
Expose ports, define volumes"] -TnetBuild --> RuntimeStage -LowMemBuild --> RuntimeStage -MongoBuild --> RuntimeStage -RuntimeStage --> Entrypoint["Start via vizd.sh"] -``` - -**Diagram sources** -- [Dockerfile-production:55-74](file://share/vizd/docker/Dockerfile-production#L55-L74) -- [Dockerfile-testnet:55-74](file://share/vizd/docker/Dockerfile-testnet#L55-L74) -- [Dockerfile-lowmem:41-60](file://share/vizd/docker/Dockerfile-lowmem#L41-L60) -- [Dockerfile-mongo:62-90](file://share/vizd/docker/Dockerfile-mongo#L62-L90) -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) - -**Section sources** -- [Dockerfile-production:1-103](file://share/vizd/docker/Dockerfile-production#L1-L103) -- [Dockerfile-testnet:1-103](file://share/vizd/docker/Dockerfile-testnet#L1-L103) -- [Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) -- [Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114) - -### Environment Variables -The container supports the following environment variables to customize runtime behavior: -- VIZD_SEED_NODES: Space-delimited list of seed nodes to connect to at startup. Overrides default seed list. -- VIZD_WITNESS_NAME: Name of the validator to operate when block production is enabled. -- VIZD_PRIVATE_KEY: Private key for signing blocks (when operating a validator). -- VIZD_RPC_ENDPOINT: RPC endpoint binding (default: 0.0.0.0:8090). -- VIZD_P2P_ENDPOINT: P2P endpoint binding (default: 0.0.0.0:2001). -- VIZD_EXTRA_OPTS: Additional command-line options appended to the node invocation. - -Behavior is implemented in the entrypoint script, which constructs arguments, copies the packaged config into the data directory, and starts the node with the chosen endpoints and optional cached blockchain initialization. - -**Section sources** -- [vizd.sh:13-97](file://share/vizd/vizd.sh#L13-L97) -- [config.ini:16-20](file://share/vizd/config/config.ini#L16-L20) -- [config_testnet.ini:16-20](file://share/vizd/config/config_testnet.ini#L16-L20) - -### Volume Mounting Strategies -Persistent data storage relies on two primary volumes: -- /var/lib/vizd: Contains blockchain data (including the blockchain directory and cache). -- /etc/vizd: Contains configuration files and seednode lists. - -Mounting strategy recommendations: -- Bind mounts for durability and backups: map /var/lib/vizd to a host directory for long-term persistence. -- Config override: mount a host config.ini into /etc/vizd/config.ini to customize RPC endpoints, plugins, and logging without rebuilding images. -- Seednodes override: mount a custom seednodes file into /etc/vizd/seednodes to tailor connectivity. - -Exposed ports: -- RPC HTTP: 8090/tcp -- RPC WS: 8091/tcp -- P2P: 2001/tcp - -These are defined in the Dockerfiles and used by the entrypoint script. - -**Section sources** -- [Dockerfile-production:94-103](file://share/vizd/docker/Dockerfile-production#L94-L103) -- [Dockerfile-testnet:94-103](file://share/vizd/docker/Dockerfile-testnet#L94-L103) -- [Dockerfile-lowmem:76-85](file://share/vizd/docker/Dockerfile-lowmem#L76-L85) -- [Dockerfile-mongo:105-114](file://share/vizd/docker/Dockerfile-mongo#L105-L114) -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) - -### Network Configuration -- Default RPC endpoints are configurable via environment variables; otherwise, defaults bind to 0.0.0.0 on ports 8090 (HTTP) and 8091 (WS). -- P2P endpoint defaults to 0.0.0.0:2001. -- Port exposure is defined per image; ensure firewall and orchestration platforms allow inbound connections on these ports. -- Seed nodes are either taken from the packaged seednodes file or overridden via VIZD_SEED_NODES. - -**Section sources** -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) -- [config.ini:1-20](file://share/vizd/config/config.ini#L1-L20) -- [config_testnet.ini:1-20](file://share/vizd/config/config_testnet.ini#L1-L20) - -### Image Customization and Base Image Selection -- Base image: phusion/baseimage:noble-1.0.3 variants are used across all images, providing a modern Debian-based runtime environment. -- Build customization: - - Production: standard build with MongoDB disabled. - - Testnet: enables BUILD_TESTNET. - - Low-memory: enables LOW_MEMORY_NODE. - - MongoDB: installs MongoDB C/C++ drivers and enables ENABLE_MONGO_PLUGIN. -- Debug configurations: separate debug configs are provided for development and MongoDB-enabled debug setups. - -**Section sources** -- [Dockerfile-production:1-103](file://share/vizd/docker/Dockerfile-production#L1-L103) -- [Dockerfile-testnet:1-103](file://share/vizd/docker/Dockerfile-testnet#L1-L103) -- [Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) -- [Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114) -- [config_debug.ini:1-135](file://share/vizd/config/config_debug.ini#L1-L135) -- [config_debug_mongo.ini:1-135](file://share/vizd/config/config_debug_mongo.ini#L1-L135) - -### Security Considerations -- Non-root execution: the runtime stage creates a dedicated non-root user and sets ownership on cache and data directories. -- Minimal attack surface: images exclude unnecessary packages post-build and rely on minimal base images. -- Secrets handling: private keys and sensitive configuration should be mounted from secure volumes or managed via secret stores in orchestrators. -- Network exposure: restrict inbound access to RPC and P2P ports using firewalls and reverse proxies as appropriate. - -**Section sources** -- [Dockerfile-production:84-87](file://share/vizd/docker/Dockerfile-production#L84-L87) -- [Dockerfile-testnet:85-88](file://share/vizd/docker/Dockerfile-testnet#L85-L88) -- [Dockerfile-lowmem:66-69](file://share/vizd/docker/Dockerfile-lowmem#L66-L69) -- [Dockerfile-mongo:95-98](file://share/vizd/docker/Dockerfile-mongo#L95-L98) - -### Monitoring and Logging -- Logging configuration is defined in the packaged config files. Logs are written to files under the data directory according to the configured appenders and loggers. -- For production deployments, consider mounting a writable logs directory or integrating with container-native logging stacks. -- Health checks and metrics are not defined in the images; deploy external monitoring and alerting as needed. - -**Section sources** -- [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- [config_testnet.ini:113-132](file://share/vizd/config/config_testnet.ini#L113-L132) -- [config_mongo.ini:116-135](file://share/vizd/config/config_mongo.ini#L116-L135) -- [config_debug.ini:107-135](file://share/vizd/config/config_debug.ini#L107-L135) -- [config_debug_mongo.ini:116-135](file://share/vizd/config/config_debug_mongo.ini#L116-L135) - -### Practical Deployment Patterns -- Standalone container - - Run the production image and map ports 8090, 8091, and 2001. - - Override RPC/P2P endpoints via environment variables if needed. - - Persist data via a bind mount to /var/lib/vizd. - - Reference: [README.md:21-29](file://README.md#L21-L29), [Dockerfile-production:94-103](file://share/vizd/docker/Dockerfile-production#L94-L103) - -- Testnet node - - Use the testnet image tag and adjust seed nodes if required. - - Reference: [README.md:16-20](file://README.md#L16-L20), [Dockerfile-testnet:94-103](file://share/vizd/docker/Dockerfile-testnet#L94-L103) - -- validator node - - Set VIZD_WITNESS_NAME and VIZD_PRIVATE_KEY to operate a validator. - - Reference: [vizd.sh:31-37](file://share/vizd/vizd.sh#L31-L37) - -- MongoDB analytics node - - Use the MongoDB-enabled image and configure mongodb-uri in the mounted config. - - Reference: [Dockerfile-mongo:105-114](file://share/vizd/docker/Dockerfile-mongo#L105-L114), [config_mongo.ini:71-72](file://share/vizd/config/config_mongo.ini#L71-L72) - -- Development environment - - Use debug configurations and enable debug plugins for development and testing. - - Reference: [config_debug.ini:69-135](file://share/vizd/config/config_debug.ini#L69-L135), [config_debug_mongo.ini:69-135](file://share/vizd/config/config_debug_mongo.ini#L69-L135) - -**Section sources** -- [README.md:12-53](file://README.md#L12-L53) -- [Dockerfile-production:94-103](file://share/vizd/docker/Dockerfile-production#L94-L103) -- [Dockerfile-testnet:94-103](file://share/vizd/docker/Dockerfile-testnet#L94-L103) -- [Dockerfile-mongo:105-114](file://share/vizd/docker/Dockerfile-mongo#L105-L114) -- [vizd.sh:31-37](file://share/vizd/vizd.sh#L31-L37) -- [config_debug.ini:69-135](file://share/vizd/config/config_debug.ini#L69-L135) -- [config_debug_mongo.ini:69-135](file://share/vizd/config/config_debug_mongo.ini#L69-L135) - -### Container Orchestration and Multi-Container Scenarios -- Single-node deployment: run one container with mapped volumes and ports. -- Multi-node deployment: run multiple containers with distinct data directories and optionally different seed nodes. -- MongoDB stack: when using the MongoDB-enabled image, deploy a MongoDB instance alongside the node container and configure the mongodb-uri accordingly. -- Reverse proxy: front RPC endpoints with a reverse proxy for TLS termination and rate limiting. - -### Docker Registry Usage, Versioning, and Updates -- Official image repository: vizblockchain/vizd on Docker Hub. -- Tags: - - latest: production image built from master. - - testnet: testnet image built from master. -- Automated builds: - - Master branch pushes trigger builds for both production and testnet images. - - Pull requests trigger testnet image builds with ref-based tagging. -- Update procedure: - - Pull the target tag. - - Stop the running container. - - Recreate the container with updated image and preserved volumes. - -**Section sources** -- [README.md:14-20](file://README.md#L14-L20) -- [docker-main.yml:1-53](file://.github/workflows/docker-main.yml#L1-L53) -- [docker-pr-build.yml:1-30](file://.github/workflows/docker-pr-build.yml#L1-L30) - -## Dependency Analysis -The runtime depends on: -- Entrypoint script for argument construction and startup. -- Configuration files for RPC endpoints, plugins, and logging. -- Persistent volumes for blockchain data and configuration. -- Compression libraries (libbz2-dev, liblzma-dev, libzstd-dev, zlib1g-dev) for efficient blockchain data processing. - -**Updated** Added compression library dependencies to support modern blockchain data compression formats. - -```mermaid -graph LR -DFProd["Dockerfile-production"] --> Script["vizd.sh"] -DFTnet["Dockerfile-testnet"] --> Script -DFLowMem["Dockerfile-lowmem"] --> Script -DFMongo["Dockerfile-mongo"] --> Script -Script --> CfgProd["config.ini"] -Script --> CfgTnet["config_testnet.ini"] -Script --> CfgMongo["config_mongo.ini"] -``` - -**Diagram sources** -- [Dockerfile-production:94-96](file://share/vizd/docker/Dockerfile-production#L94-L96) -- [Dockerfile-testnet:95-97](file://share/vizd/docker/Dockerfile-testnet#L95-L97) -- [Dockerfile-mongo:106-108](file://share/vizd/docker/Dockerfile-mongo#L106-L108) -- [vizd.sh:39-42](file://share/vizd/vizd.sh#L39-L42) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) - -**Section sources** -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) - -## Performance Considerations -- Shared memory sizing: tune shared-file-size and related parameters in the configuration to balance memory usage and performance. -- Plugin selection: disable unused plugins to reduce overhead. -- Single write thread: the configuration encourages single-write-thread to mitigate lock contention. -- Low-memory variant: use the low-memory image when running on constrained hardware. -- Compression library optimization: modern compression libraries (libbz2, liblzma, libzstd, zlib) provide efficient blockchain data compression and decompression. - -**Updated** Added compression library optimization considerations for improved blockchain data processing performance. - -## Troubleshooting Guide -Common issues and resolutions: -- Ports already in use - - Ensure host ports 8090, 8091, and 2001 are free or remap to different host ports. - - Verify container port exposure matches published ports. - - References: [Dockerfile-production:94-103](file://share/vizd/docker/Dockerfile-production#L94-L103), [Dockerfile-testnet:94-103](file://share/vizd/docker/Dockerfile-testnet#L94-L103) - -- Permission denied on data directory - - Confirm the non-root user owns /var/lib/vizd after initial run. - - Reference: [Dockerfile-production:84-87](file://share/vizd/docker/Dockerfile-production#L84-L87) - -- No connectivity to peers - - Override seed nodes via VIZD_SEED_NODES or mount a custom seednodes file. - - Reference: [vizd.sh:17-29](file://share/vizd/vizd.sh#L17-L29), [README.md:31-39](file://README.md#L31-L39) - -- Blockchain initialization delays - - Cached blockchain data may be unpacked on first run; allow time for decompression. - - Reference: [vizd.sh:44-53](file://share/vizd/vizd.sh#L44-L53) - -- MongoDB plugin connectivity - - Ensure mongodb-uri is reachable from the container network and credentials are correct. - - Reference: [config_mongo.ini:71-72](file://share/vizd/config/config_mongo.ini#L71-L72), [Dockerfile-mongo:105-108](file://share/vizd/docker/Dockerfile-mongo#L105-L108) - -- Compression library issues - - Verify compression libraries (libbz2, liblzma, libzstd, zlib) are properly installed and accessible. - - Check for compatibility between compression formats and blockchain data versions. - - Reference: [Dockerfile-production:32-41](file://share/vizd/docker/Dockerfile-production#L32-L41), [Dockerfile-testnet:32-41](file://share/vizd/docker/Dockerfile-testnet#L32-L41) - -**Updated** Added compression library troubleshooting section to address potential issues with blockchain data compression. - -**Section sources** -- [Dockerfile-production:84-103](file://share/vizd/docker/Dockerfile-production#L84-L103) -- [Dockerfile-testnet:85-103](file://share/vizd/docker/Dockerfile-testnet#L85-L103) -- [vizd.sh:17-53](file://share/vizd/vizd.sh#L17-L53) -- [README.md:31-39](file://README.md#L31-L39) -- [config_mongo.ini:71-72](file://share/vizd/config/config_mongo.ini#L71-L72) - -## Conclusion -The VIZ C++ Node provides multiple Docker images tailored for production, testnet, low-memory, and MongoDB-enabled deployments. By leveraging environment variables, persistent volumes, and the packaged configurations, operators can quickly deploy reliable and secure nodes. Automated CI builds maintain official images, and the modular design allows for flexible customization and operational patterns. - -## Appendices - -### Environment Variable Reference -- VIZD_SEED_NODES: Seed nodes to connect to. -- VIZD_WITNESS_NAME: validator name for block production. -- VIZD_PRIVATE_KEY: Private key for signing blocks. -- VIZD_RPC_ENDPOINT: RPC endpoint binding. -- VIZD_P2P_ENDPOINT: P2P endpoint binding. -- VIZD_EXTRA_OPTS: Additional CLI options. - -**Section sources** -- [vizd.sh:17-97](file://share/vizd/vizd.sh#L17-L97) - -### Ports Reference -- RPC HTTP: 8090 -- RPC WS: 8091 -- P2P: 2001 - -**Section sources** -- [Dockerfile-production:94-103](file://share/vizd/docker/Dockerfile-production#L94-L103) -- [Dockerfile-testnet:94-103](file://share/vizd/docker/Dockerfile-testnet#L94-L103) -- [Dockerfile-lowmem:76-85](file://share/vizd/docker/Dockerfile-lowmem#L76-L85) -- [Dockerfile-mongo:105-114](file://share/vizd/docker/Dockerfile-mongo#L105-L114) - -### Compression Library Dependencies -The Docker images now include modern compression library dependencies: -- libbz2-dev: Bzip2 compression support for blockchain data -- liblzma-dev: LZMA compression support for efficient data compression -- libzstd-dev: Zstandard compression for high-speed compression/decompression -- zlib1g-dev: Standard zlib compression library for compatibility - -These libraries are essential for processing modern blockchain data formats and provide optimal compression ratios and performance. - -**Section sources** -- [Dockerfile-production:32-41](file://share/vizd/docker/Dockerfile-production#L32-L41) -- [Dockerfile-testnet:32-41](file://share/vizd/docker/Dockerfile-testnet#L32-L41) -- [Dockerfile-lowmem:19-29](file://share/vizd/docker/Dockerfile-lowmem#L19-L29) -- [Dockerfile-mongo:19-29](file://share/vizd/docker/Dockerfile-mongo#L19-L29) - -### Sed Command Pattern Fixes -All Dockerfiles now include sed command pattern fixes to remove test subdirectories during the build process: -- `sed -i '/add_subdirectory(tests)/d' thirdparty/fc/CMakeLists.txt` - -This ensures that test subdirectories are excluded from compilation, reducing build time and image size while maintaining functionality. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L58) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L58) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L44) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L73) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Configuration Management/Network Configuration.md b/.qoder/repowiki/en/content/Configuration Management/Network Configuration.md deleted file mode 100644 index 44835079c0..0000000000 --- a/.qoder/repowiki/en/content/Configuration Management/Network Configuration.md +++ /dev/null @@ -1,652 +0,0 @@ -# Network Configuration - - -**Referenced Files in This Document** -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [stcp_socket.cpp](file://libraries/network/stcp_socket.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_witness.ini](file://share/vizd/config/config_witness.ini) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp) -- [plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp) -- [plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) - - -## Update Summary -**Changes Made** -- Added comprehensive documentation for the new P2P stale sync detection feature -- Documented the p2p-stale-sync-detection configuration option (default: false) -- Documented the p2p-stale-sync-timeout-seconds configuration option (default: 120) -- Updated all configuration template examples to include the new stale sync detection options -- Enhanced troubleshooting section with stale sync detection guidance -- Updated practical deployment examples to include stale sync detection configuration - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive network configuration guidance for the VIZ CPP Node peer-to-peer (P2P) networking stack. It covers peer connection settings, seed node configuration, network discovery mechanisms, listen address and port configuration, firewall considerations, security settings, connection limits, performance tuning, bandwidth management, and monitoring. Practical examples are included for private networks, testnets, and mainnet-like deployments. - -**Updated** The configuration system now includes a sophisticated stale sync detection mechanism that automatically recovers from network stalls by resetting sync from the last irreversible block and reconnecting seed peers when no blocks are received for the configured timeout period. - -## Project Structure -The P2P networking is implemented in the network library and integrated via the P2P plugin. Configuration is primarily driven by command-line options and configuration files, with enhanced integration through the snapshot plugin for trusted peer management and stale sync detection capabilities. - -```mermaid -graph TB -subgraph "Application Layer" -P2PPlugin["P2P Plugin
p2p_plugin.cpp"] -SnapshotPlugin["Snapshot Plugin
plugin.cpp"] -ChainPlugin["Chain Plugin
plugin.cpp"] -StaleSync["Stale Sync Detection
p2p-stale-sync-detection"] -TimeoutConfig["Timeout Configuration
p2p-stale-sync-timeout-seconds"] -end -subgraph "Network Library" -Node["Node
node.cpp"] -PeerConn["PeerConnection
peer_connection.cpp"] -STCP["STCP Socket
stcp_socket.cpp"] -Config["Network Config Constants
config.hpp"] -Msg["Message Types
message.hpp"] -PeerDB["Peer Database
peer_database.hpp"] -end -subgraph "Configuration" -CfgIni["config.ini"] -TestCfg["config_testnet.ini"] -WitCfg["config_witness.ini"] -DebugCfg["config_debug.ini"] -MongoCfg["config_mongo.ini"] -StockCfg["config_stock_exchange.ini"] -TrustedPeers["Trusted Peer IPs
_trusted_peer_ips"] -SoftBan["Soft-Ban Duration
TRUSTED_SOFT_BAN_DURATION_SEC"] -end -P2PPlugin --> Node -Node --> PeerConn -PeerConn --> STCP -Node --> Config -Node --> Msg -Node --> PeerDB -P2PPlugin --> CfgIni -P2PPlugin --> TestCfg -P2PPlugin --> WitCfg -P2PPlugin --> DebugCfg -P2PPlugin --> MongoCfg -P2PPlugin --> StockCfg -P2PPlugin --> StaleSync -P2PPlugin --> TimeoutConfig -SnapshotPlugin --> TrustedPeers -SnapshotPlugin --> SoftBan -Node --> TrustedPeers -``` - -**Diagram sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [plugin.cpp:714-715](file://plugins/snapshot/plugin.cpp#L714-L715) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) -- [node.cpp:592-600](file://libraries/network/node.cpp#L592-L600) - -**Section sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [plugin.cpp:714-715](file://plugins/snapshot/plugin.cpp#L714-L715) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -## Core Components -- P2P Plugin: Parses CLI and config options, initializes the Node, sets advanced parameters, and starts listening/connecting. -- Node: Manages P2P lifecycle, connection orchestration, sync loops, rate limiting, bandwidth monitoring, trusted peer soft-ban management, and stale sync detection. -- PeerConnection: Encapsulates per-peer state, encryption handshake, send queues, and inventory tracking. -- STCP Socket: Provides encrypted transport using ephemeral ECDH key exchange and AES-based stream cipher. -- Configuration Constants: Defines protocol version, ports, defaults, timeouts, limits, and performance parameters. -- Message Types: Defines the wire-level message header and serialization. -- Peer Database: Tracks potential peers, connection attempts, and outcomes. -- **Stale Sync Detection**: New component that monitors block reception timing and automatically recovers from network stalls. -- **Trusted Peer System**: Enhanced component that manages trusted peer endpoints and reduces soft-ban duration for improved network bootstrapping. - -Key configuration entry points: -- CLI options: p2p-endpoint, p2p-max-connections, p2p-seed-node, p2p-force-validate, **p2p-stale-sync-detection**, **p2p-stale-sync-timeout-seconds**. -- Config file keys: p2p-endpoint, p2p-max-connections, p2p-seed-node, loggers, **p2p-stale-sync-detection**, **p2p-stale-sync-timeout-seconds**, **trusted-snapshot-peer**. -- **Stale Sync Configuration**: Two new configuration options for detecting and recovering from network stalls. -- **Trusted Peer Configuration**: Multiple trusted-snapshot-peer entries for reduced soft-ban duration. - -**Updated** Configuration now includes stale sync detection with two new configuration options and enhanced trusted peer support with reduced soft-ban duration. - -**Section sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [plugin.cpp:714-715](file://plugins/snapshot/plugin.cpp#L714-L715) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -## Architecture Overview -High-level P2P flow from plugin initialization to network operation, including stale sync detection and trusted peer integration. - -```mermaid -sequenceDiagram -participant CLI as "CLI/Config" -participant P2P as "P2P Plugin" -participant Node as "Node" -participant Snapshot as "Snapshot Plugin" -participant Seed as "Seed Peers" -participant StaleSync as "Stale Sync Monitor" -CLI->>P2P : Parse options (p2p-endpoint, p2p-max-connections, p2p-seed-node, p2p-stale-sync-detection, p2p-stale-sync-timeout-seconds) -P2P->>Node : Construct node(user_agent) -P2P->>Node : load_configuration(p2p_dir) -P2P->>Node : set_node_delegate(...) -alt p2p-endpoint configured -P2P->>Node : listen_on_endpoint(endpoint, wait_if_busy=true) -end -P2P->>Node : add_node(seed) -P2P->>Node : connect_to_endpoint(seed) -P2P->>Node : set_advanced_node_parameters(max_connections) -P2P->>Node : listen_to_p2p_network() -P2P->>Node : connect_to_p2p_network() -P2P->>Node : sync_from(head, synopsis) -Note over Snapshot,Node : Trusted Peer Registration -Snapshot->>P2P : get_trusted_snapshot_peers() -P2P->>Node : set_trusted_peer_endpoints(trusted_eps) -Note over StaleSync,Node : Stale Sync Detection Setup -StaleSync->>P2P : Initialize stale sync monitor -Node->>Seed : Connect and handshake -Node-->>P2P : Status callbacks (connection_count_changed, sync_status) -``` - -**Diagram sources** -- [p2p_plugin.cpp:689-706](file://plugins/p2p/p2p_plugin.cpp#L689-L706) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) - -## Detailed Component Analysis - -### Peer Connection Settings and Security -- Encryption and Handshake: - - Ephemeral ECDH key exchange performed during connection establishment. - - Shared secret used to derive AES cipher keys for bidirectional encryption. - - Transport ensures confidentiality and integrity for all P2P messages. -- Connection States and Lifecycle: - - PeerConnection tracks direction, negotiation status, and connection states. - - Queued message transmission with backpressure and maximum queue size enforcement. - - Automatic closure on excessive queued message size. -- Inventory and Throttling: - - Per-peer inventory lists with time-based expiry. - - Transaction and block inventory limits to prevent memory pressure. -- Firewall and NAT Awareness: - - Node determines firewall status and tracks publicly visible listening endpoint. - - Peer records include inbound/outbound ports for NAT traversal hints. -- **Soft-Ban Management**: - - **Default duration**: 1 hour (3600 seconds) for regular peers. - - **Trusted peers**: Reduced to 5 minutes (300 seconds) soft-ban duration. - - Automatic soft-ban duration adjustment based on peer trust status. - -```mermaid -classDiagram -class PeerConnection { -+direction -+negotiation_status -+inventory tracking -+send_message() -+send_item() -+close_connection() -+get_remote_endpoint() -} -class STCP_Socket { -+connect_to() -+accept() -+readsome() -+writesome() -} -class Node { -+listen_on_endpoint() -+connect_to_endpoint() -+set_advanced_node_parameters() -+get_actual_listening_endpoint() -+set_trusted_peer_endpoints() -+is_trusted_peer() -+get_soft_ban_duration() -} -class StaleSyncDetection { -+_stale_sync_enabled -+_stale_sync_timeout_seconds -+_last_block_received_time -+stale_sync_check_task() -} -class TrustedPeerSystem { -+_trusted_peer_ips -+SOFT_BAN_DURATION_SEC = 3600 -+TRUSTED_SOFT_BAN_DURATION_SEC = 300 -} -PeerConnection --> STCP_Socket : "encrypted transport" -Node --> PeerConnection : "manages" -Node --> StaleSyncDetection : "uses" -Node --> TrustedPeerSystem : "uses" -``` - -**Diagram sources** -- [peer_connection.cpp:68-162](file://libraries/network/peer_connection.cpp#L68-L162) -- [stcp_socket.cpp:37-92](file://libraries/network/stcp_socket.cpp#L37-L92) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [node.cpp:592-600](file://libraries/network/node.cpp#L592-L600) - -**Section sources** -- [peer_connection.cpp:68-162](file://libraries/network/peer_connection.cpp#L68-L162) -- [stcp_socket.cpp:37-92](file://libraries/network/stcp_socket.cpp#L37-L92) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [node.cpp:592-600](file://libraries/network/node.cpp#L592-L600) - -### Stale Sync Detection System -- **Stale Sync Detection Configuration**: - - Enabled via `p2p-stale-sync-detection = true` in configuration files. - - Timeout configured via `p2p-stale-sync-timeout-seconds = 120` (default: 120 seconds = 2 minutes). - - Monitors block reception timing across all connected peers. -- **Automatic Recovery Mechanism**: - - Background task checks every 30 seconds for network stalls. - - When no blocks received for the configured timeout, automatically triggers recovery actions. - - Three sequential recovery actions: reset sync from LIB, resync with connected peers, reconnect seed peers. -- **Recovery Actions**: - - **Reset sync from LIB**: Sync start point moved to last irreversible block (safe fork-proof position). - - **Resync with connected peers**: Fresh synchronization requests sent to all currently connected peers. - - **Reconnect seed peers**: All seed nodes from configuration are reconnected. -- **Complementary to Snapshot Plugin**: - - P2P stale detection is lightweight and doesn't require snapshot downloads. - - Works alongside snapshot plugin's stalled sync detection for comprehensive recovery. - - Faster recovery compared to snapshot-based approach. - -```mermaid -flowchart TD -Start(["Stale Sync Detection Startup"]) --> CheckEnabled{"p2p-stale-sync-detection enabled?"} -CheckEnabled --> |No| End(["Disabled"]) -CheckEnabled --> |Yes| InitTimer["Initialize last_block_received_time"] -InitTimer --> ScheduleTask["Schedule 30-second check task"] -ScheduleTask --> Monitor["Monitor block reception"] -Monitor --> CheckStall{"Elapsed > timeout?"} -CheckStall --> |No| Wait["Wait 30 seconds"] -CheckStall --> |Yes| ResetSync["Reset sync from LIB"] -ResetSync --> ResyncPeers["Resync with connected peers"] -ResyncPeers --> ReconnectSeeds["Reconnect all seed peers"] -ReconnectSeeds --> ResetTimer["Reset last_block_received_time"] -ResetTimer --> Wait -Wait --> Monitor -``` - -**Diagram sources** -- [p2p_plugin.cpp:585-649](file://plugins/p2p/p2p_plugin.cpp#L585-L649) -- [p2p_plugin.cpp:812-820](file://plugins/p2p/p2p_plugin.cpp#L812-L820) - -**Section sources** -- [p2p_plugin.cpp:585-649](file://plugins/p2p/p2p_plugin.cpp#L585-L649) -- [p2p_plugin.cpp:812-820](file://plugins/p2p/p2p_plugin.cpp#L812-L820) - -### Trusted Peer Support System -- **Trusted Peer Configuration**: - - Configured via multiple `trusted-snapshot-peer` entries in config.ini. - - Supports IP:port format for each trusted peer endpoint. - - Automatically parsed and registered during P2P plugin initialization. -- **Automatic Integration**: - - P2P plugin automatically queries snapshot plugin for trusted peer endpoints. - - Trusted peer IPs are converted to 32-bit integers for O(1) lookup performance. - - Registered trusted peers receive reduced soft-ban duration (5 minutes vs 1 hour). -- **Soft-Ban Duration Management**: - - Default soft-ban: 3600 seconds (1 hour) for regular peers. - - Trusted peers: 300 seconds (5 minutes) soft-ban duration. - - Dynamic duration calculation based on peer trust status. -- **Logging and Monitoring**: - - Logs registration of trusted peers with count and duration information. - - Clear distinction between regular and trusted peer soft-ban behavior. - -```mermaid -flowchart TD -Start(["P2P Plugin Startup"]) --> LoadSnapshot["Load Snapshot Plugin"] -LoadSnapshot --> CheckTrusted["Check trusted-snapshot-peer Config"] -CheckTrusted --> HasPeers{"Any trusted peers?"} -HasPeers --> |Yes| GetEndpoints["Get trusted endpoints from snapshot plugin"] -GetEndpoints --> ConvertIP["Convert to 32-bit IP addresses"] -ConvertIP --> Register["Register with Node.set_trusted_peer_endpoints()"] -Register --> LogInfo["Log trusted peer registration"] -HasPeers --> |No| Continue["Continue with normal operation"] -LogInfo --> Continue -Continue --> End(["Ready"]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) - -**Section sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [plugin.cpp:714-715](file://plugins/snapshot/plugin.cpp#L714-L715) - -### Seed Node Configuration and Discovery -- Seed Nodes: - - Configured via CLI option p2p-seed-node and/or config file key p2p-seed-node. - - Multiple seeds supported; each is added and connected to during startup. - - Integrated directly into config.ini without external file management. -- Peer Database: - - Tracks potential peers, last seen time, disposition, and connection attempt counts. - - Used to manage retry/backoff and selection of peers for outbound connections. - -```mermaid -flowchart TD -Start(["Startup"]) --> LoadCfg["Load CLI and Config"] -LoadCfg --> ParseSeeds["Parse p2p-seed-node(s)"] -ParseSeeds --> AddNodes["node.add_node(seed)"] -AddNodes --> Connect["node.connect_to_endpoint(seed)"] -Connect --> Loop["Connect Loop and Retry"] -Loop --> PeerDB["Update peer_database entries"] -PeerDB --> Sync["Start sync_from(head)"] -Sync --> End(["Ready"]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:497-521](file://plugins/p2p/p2p_plugin.cpp#L497-L521) -- [node.cpp:780-785](file://libraries/network/node.cpp#L780-L785) -- [peer_database.hpp:47-71](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) - -**Section sources** -- [p2p_plugin.cpp:497-521](file://plugins/p2p/p2p_plugin.cpp#L497-L521) -- [peer_database.hpp:47-71](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) - -### Listen Address, Port, and Endpoint Management -- Listen Endpoint: - - Configured via CLI p2p-endpoint or config key p2p-endpoint. - - Node binds to the specified IP:port; supports waiting if port is busy. - - Actual listening endpoint is recorded and exposed for diagnostics. -- Ports: - - Standardized default P2P port is 2001 for mainnet configurations. - - Testnet uses 4243 as specified in config_testnet.ini. - - Users can override port in config or CLI. - -**Updated** Mainnet now uses standardized port 2001 instead of legacy 4243. - -```mermaid -sequenceDiagram -participant P2P as "P2P Plugin" -participant Node as "Node" -P2P->>Node : listen_on_endpoint(endpoint, wait_if_busy=true) -Node-->>P2P : get_actual_listening_endpoint() -P2P-->>P2P : Log actual endpoint -``` - -**Diagram sources** -- [p2p_plugin.cpp:537-540](file://plugins/p2p/p2p_plugin.cpp#L537-L540) -- [node.cpp:786-792](file://libraries/network/node.cpp#L786-L792) - -**Section sources** -- [p2p_plugin.cpp:487-495](file://plugins/p2p/p2p_plugin.cpp#L487-L495) -- [node.cpp:786-792](file://libraries/network/node.cpp#L786-L792) -- [config.hpp:52-56](file://libraries/network/include/graphene/network/config.hpp#L52-L56) - -### Firewall and NAT Considerations -- Firewall Detection: - - Node periodically sends firewall check messages and tracks state. - - Publicly visible listening endpoint is recorded when known. -- Inbound vs Outbound: - - Inbound connections are accepted on the bound endpoint. - - Outbound connections are initiated to seed nodes and peers discovered via inventory. - -**Section sources** -- [node.cpp:441-445](file://libraries/network/node.cpp#L441-L445) -- [peer_connection.cpp:169-206](file://libraries/network/peer_connection.cpp#L169-L206) - -### Security Settings and Authentication -- Transport Security: - - STCP socket performs ECDH key exchange and AES encryption for all traffic. -- Node Identity: - - Node configuration includes a node identity keypair; used in hello/user data. -- Authentication: - - No explicit node authentication or TLS certificate verification is implemented in the referenced code; encryption is provided by STCP. - -**Section sources** -- [stcp_socket.cpp:49-66](file://libraries/network/stcp_socket.cpp#L49-L66) -- [node.cpp:223-239](file://libraries/network/node.cpp#L223-L239) - -### Connection Limits and Timeouts -- Connection Limits: - - Desired and maximum connections are configurable via advanced parameters. - - Node enforces acceptance policy based on current connection count. -- Timeouts: - - Handshake inactivity timeout and disconnect timeout constants are defined. - - Inactivity-based disconnection is enforced during operation. - -```mermaid -flowchart TD -A["Incoming Connection"] --> B{"Connections < max?"} -B --> |Yes| Accept["Accept and add to active"] -B --> |No| Reject["Reject inbound"] -Accept --> C["Monitor activity"] -C --> D{"Idle beyond timeout?"} -D --> |Yes| Close["Close connection"] -D --> |No| C -``` - -**Diagram sources** -- [config.hpp:48-50](file://libraries/network/include/graphene/network/config.hpp#L48-L50) -- [node.cpp:638-640](file://libraries/network/node.cpp#L638-L640) - -**Section sources** -- [config.hpp:48-50](file://libraries/network/include/graphene/network/config.hpp#L48-L50) -- [node.cpp:518-526](file://libraries/network/node.cpp#L518-L526) - -### Bandwidth Management and Rate Limiting -- Rate Limiting: - - Node maintains a rate limiting group for outbound traffic. -- Bandwidth Monitoring: - - Rolling averages for read/write speeds are tracked and updated periodically. -- Message Queue Backpressure: - - Maximum queued message size enforced per-peer; oversized queues trigger closure. - -**Section sources** -- [node.cpp:548-567](file://libraries/network/node.cpp#L548-L567) -- [peer_connection.cpp:314-325](file://libraries/network/peer_connection.cpp#L314-L325) - -### Network Performance Tuning Parameters -- Defaults and Limits: - - Default desired and maximum connections, retry intervals, and message sizes are defined. - - Transaction rate limit and inventory retention windows are tunable via constants. -- Sync Behavior: - - Prefetch thresholds and batch sizes influence sync throughput. -- **Stale Sync Optimization**: - - Configurable timeout (default: 120 seconds) prevents unnecessary recovery actions. - - Automatic recovery from temporary network partitions and peer disconnections. - - Complementary to snapshot plugin for comprehensive network stall recovery. -- **Soft-Ban Optimization**: - - Trusted peers benefit from reduced soft-ban duration for faster recovery. - - Improved network bootstrapping and sync performance. - -**Section sources** -- [config.hpp:55-106](file://libraries/network/include/graphene/network/config.hpp#L55-L106) -- [node.cpp:592-600](file://libraries/network/node.cpp#L592-L600) - -### Practical Deployment Examples - -#### Private Network -- Configure a private listen endpoint and a small set of seed nodes. -- Adjust p2p-max-connections for expected peer count. -- Disable external exposure by binding to localhost or internal subnet. -- Enable stale sync detection for automatic recovery from network partitions. - -Example keys: -- p2p-endpoint = 10.0.0.5:2001 -- p2p-max-connections = 10 -- p2p-seed-node = 10.0.0.10:2001 -- **p2p-stale-sync-detection = true** -- **p2p-stale-sync-timeout-seconds = 120** - -**Updated** Using standardized port 2001 instead of legacy 4243 and added stale sync detection configuration. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) - -#### Testnet -- Use testnet-specific defaults and endpoints. -- Enable validator participation and adjust required participation as needed. -- Configure stale sync detection with appropriate timeout for testnet conditions. - -Example keys: -- p2p-endpoint = 0.0.0.0:4243 -- p2p-seed-node = (testnet seed IPs) -- enable-stale-production = true -- **p2p-stale-sync-detection = true** -- **p2p-stale-sync-timeout-seconds = 120** - -**Section sources** -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) - -#### Mainnet-like Deployment -- Bind to public IP and port 2001; ensure firewall/NAT traversal is configured. -- Increase p2p-max-connections for high-throughput nodes. -- Monitor bandwidth and tune rate limiting. -- Enable stale sync detection with conservative timeout settings. - -Example keys: -- p2p-endpoint = 0.0.0.0:2001 -- p2p-max-connections = 200 -- p2p-seed-node = (mainnet seed IPs) -- **p2p-stale-sync-detection = true** -- **p2p-stale-sync-timeout-seconds = 180** - -**Updated** Mainnet now standardized to port 2001 for consistent deployment and added stale sync detection configuration. - -#### Trusted Peer Deployment -- Configure trusted-snapshot-peer entries for reliable network bootstrapping. -- Set sync-snapshot-from-trusted-peer to true for automatic snapshot-based bootstrapping. -- Benefit from reduced soft-ban duration (5 minutes vs 1 hour) for trusted peers. -- Enable stale sync detection for automatic recovery from network stalls. - -Example keys: -- p2p-endpoint = 0.0.0.0:2001 -- p2p-max-connections = 200 -- p2p-seed-node = (mainnet seed IPs) -- sync-snapshot-from-trusted-peer = true -- trusted-snapshot-peer = 185.45.192.155:8092 -- trusted-snapshot-peer = 62.109.17.82:8092 -- **p2p-stale-sync-detection = true** -- **p2p-stale-sync-timeout-seconds = 120** - -**Updated** Added trusted peer configuration for enhanced network bootstrapping and reduced soft-ban duration, plus stale sync detection configuration. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config.hpp:52-56](file://libraries/network/include/graphene/network/config.hpp#L52-L56) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -## Dependency Analysis -```mermaid -graph LR -P2P["plugins/p2p/p2p_plugin.cpp"] --> N["libraries/network/node.cpp"] -N --> PC["libraries/network/peer_connection.cpp"] -PC --> STCP["libraries/network/stcp_socket.cpp"] -N --> CFG["libraries/network/include/graphene/network/config.hpp"] -N --> MSG["libraries/network/include/graphene/network/message.hpp"] -N --> PDB["libraries/network/include/graphene/network/peer_database.hpp"] -P2P --> C1["share/vizd/config/config.ini"] -P2P --> C2["share/vizd/config/config_testnet.ini"] -P2P --> C3["share/vizd/config/config_witness.ini"] -P2P --> C4["share/vizd/config/config_debug.ini"] -P2P --> C5["share/vizd/config/config_mongo.ini"] -P2P --> C6["share/vizd/config/config_stock_exchange.ini"] -Snapshot["plugins/snapshot/plugin.cpp"] --> TrustedPeers["_trusted_peer_ips"] -N --> TrustedPeers -N --> StaleSync["Stale Sync Detection"] -``` - -**Diagram sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [plugin.cpp:714-715](file://plugins/snapshot/plugin.cpp#L714-L715) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -**Section sources** -- [p2p_plugin.cpp:689-698](file://plugins/p2p/p2p_plugin.cpp#L689-L698) -- [node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) -- [plugin.cpp:714-715](file://plugins/snapshot/plugin.cpp#L714-L715) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) - -## Performance Considerations -- Tune p2p-max-connections to balance throughput and resource usage. -- Monitor bandwidth metrics and adjust rate limiting if necessary. -- Prefer outbound connections to stable, high-bandwidth peers. -- Keep inventory sizes reasonable to avoid memory pressure during floods. -- **Stale Sync Benefits**: - - Automatic recovery from temporary network partitions and peer disconnections. - - Configurable timeout prevents unnecessary recovery actions during normal operation. - - Lightweight recovery mechanism that doesn't require snapshot downloads. - - Complementary to snapshot plugin for comprehensive network stall handling. -- **Trusted Peer Benefits**: - - Reduced soft-ban duration (5 minutes vs 1 hour) for faster recovery from transient errors. - - Improved network bootstrapping performance with trusted snapshot peers. - - Enhanced sync reliability during network partitions or peer unavailability. - -## Troubleshooting Guide -Common issues and remedies: -- Connection Issues: - - Verify p2p-endpoint bind address and port availability. - - Ensure firewall allows inbound connections on the configured port. - - Confirm seed nodes are reachable and not rate-limited. -- Latency Problems: - - Reduce p2p-max-connections to lower contention. - - Monitor bandwidth metrics and adjust rate limiting. -- Peer Discovery Failures: - - Check peer_database entries for repeated failures. - - Increase retry delays and verify network connectivity. -- **Stale Sync Detection Issues**: - - Verify p2p-stale-sync-detection is set to true in configuration. - - Check that p2p-stale-sync-timeout-seconds is appropriately configured (default: 120 seconds). - - Monitor logs for stale sync detection messages and recovery actions. - - Ensure block reception is occurring normally before relying on stale sync recovery. -- **Trusted Peer Issues**: - - Verify trusted-snapshot-peer configuration entries are valid IP:port pairs. - - Check that snapshot plugin is properly loaded and reporting trusted peers. - - Monitor logs for trusted peer registration success messages. - - Ensure soft-ban duration is correctly applied (5 minutes vs 1 hour). - -Operational hooks: -- Node delegates connection_count_changed and sync_status for monitoring. -- Logs for P2P subsystem are configurable via logging appenders. -- **Stale Sync Logging**: Automatic detection and recovery actions are logged with timing information. -- **Trusted Peer Logging**: Automatic registration and soft-ban duration logging. - -**Section sources** -- [p2p_plugin.cpp:403-405](file://plugins/p2p/p2p_plugin.cpp#L403-L405) -- [config.ini:112-136](file://share/vizd/config/config.ini#L112-L136) -- [node.cpp:5259-5262](file://libraries/network/node.cpp#L5259-L5262) - -## Conclusion -The VIZ CPP Node P2P stack provides a robust, encrypted transport with configurable connection limits, bandwidth monitoring, and seed-driven discovery. The recent additions of stale sync detection significantly enhance network reliability by automatically recovering from network stalls through three sequential recovery actions: resetting sync from the last irreversible block, resynchronizing with connected peers, and reconnecting seed peers. The trusted peer support system further enhances network bootstrapping capabilities with reduced soft-ban duration (5 minutes vs 1 hour) and automatic integration with the snapshot plugin. The standardization of port 2001 for mainnet deployments and integration of seed node management through config.ini simplify configuration and improve consistency. The stale sync detection feature provides a lightweight, automatic recovery mechanism that complements the snapshot plugin's more comprehensive recovery approach. Correctly setting listen endpoints, ports, connection caps, trusted peer configurations, and stale sync detection parameters, combined with appropriate firewall and NAT configuration, enables reliable operation across private, testnet, and mainnet environments. Monitoring and tuning of rate limits, inventory sizes, trusted peer benefits, and stale sync detection further improves resilience under load. - -## Appendices - -### Configuration Options Summary -- p2p-endpoint: Local IP:port to listen for P2P connections (standardized to 2001 for mainnet). -- p2p-max-connections: Maximum number of simultaneous connections. -- p2p-seed-node: Remote peer IP:port to bootstrap discovery (configured in config.ini). -- p2p-force-validate: Force validation of all transactions. -- **p2p-stale-sync-detection**: Enable automatic recovery from network stalls (default: false). -- **p2p-stale-sync-timeout-seconds**: Timeout in seconds before stale sync detection triggers recovery (default: 120). -- **trusted-snapshot-peer**: Trusted peer IP:port for reduced soft-ban duration and enhanced bootstrapping. -- **sync-snapshot-from-trusted-peer**: Enable automatic snapshot-based bootstrapping from trusted peers. - -**Updated** Mainnet now uses standardized port 2001 and integrated seed node configuration, plus new stale sync detection system with two configuration options and enhanced trusted peer support system. - -**Section sources** -- [p2p_plugin.cpp:467-482](file://plugins/p2p/p2p_plugin.cpp#L467-L482) -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini:1-107](file://share/vizd/config/config_witness.ini#L1-L107) -- [config.ini:96-101](file://share/vizd/config/config.ini#L96-L101) -- [node.cpp:592-600](file://libraries/network/node.cpp#L592-L600) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Configuration Management/Node Configuration.md b/.qoder/repowiki/en/content/Configuration Management/Node Configuration.md deleted file mode 100644 index 18915da869..0000000000 --- a/.qoder/repowiki/en/content/Configuration Management/Node Configuration.md +++ /dev/null @@ -1,465 +0,0 @@ -# Node Configuration - - -**Referenced Files in This Document** -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_witness.ini](file://share/vizd/config/config_witness.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini) -- [main.cpp](file://programs/vizd/main.cpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [witness_plugin.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive guidance for configuring a VIZ CPP Node. It explains the configuration file structure, available parameters, defaults, and acceptable ranges. It also covers different node types (full node, validator node, low-memory node, testnet node), essential settings (database location, plugin activation, network parameters, performance tuning), authentication and API access controls, security configurations, and practical deployment examples. Finally, it includes validation tips, syntax guidance, and organizational best practices for configuration files. - -## Project Structure -The configuration system centers around a primary configuration file and several prebuilt templates for different deployment profiles. The node binary loads plugins and applies logging configuration from the same file. - -```mermaid -graph TB -cfg_main["Config: config.ini"] -cfg_test["Config: config_testnet.ini"] -cfg_wit["Config: config_witness.ini"] -cfg_mongo["Config: config_mongo.ini"] -cfg_debug["Config: config_debug.ini"] -cfg_debug_mongo["Config: config_debug_mongo.ini"] -cfg_stock["Config: config_stock_exchange.ini"] -bin_main["Binary: programs/vizd/main.cpp"] -plug_web["Plugin: webserver_plugin.hpp"] -plug_p2p["Plugin: p2p_plugin.hpp"] -plug_chain["Plugin: chain_plugin.hpp"] -plug_wit["Plugin: witness_plugin.hpp"] -bin_main --> plug_web -bin_main --> plug_p2p -bin_main --> plug_chain -bin_main --> plug_wit -cfg_main --> bin_main -cfg_test --> bin_main -cfg_wit --> bin_main -cfg_mongo --> bin_main -cfg_debug --> bin_main -cfg_debug_mongo --> bin_main -cfg_stock --> bin_main -``` - -**Diagram sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L1-L114) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [witness_plugin.hpp](file://plugins/validator/include/graphene/plugins/validator/witness_plugin.hpp#L34-L65) - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) - -## Core Components -This section enumerates the most important configuration parameters grouped by category, with purpose, default value, and acceptable ranges where applicable. - -- Network and P2P - - p2p-endpoint: IP:PORT for P2P listener. Default varies by template; see [config.ini](file://share/vizd/config/config.ini#L2-L2), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L2-L2), [config_witness.ini](file://share/vizd/config/config_witness.ini#L2-L2). - - p2p-max-connections: Integer; default unset (plugin-specific behavior). See [config.ini](file://share/vizd/config/config.ini#L5-L5). - - p2p-seed-node: Repeatable; default unset. See [config.ini](file://share/vizd/config/config.ini#L8-L8). - - checkpoint: Repeatable; pairs [BLOCK_NUM,BLOCK_ID]; default unset. See [config.ini](file://share/vizd/config/config.ini#L11-L11). - -- Webserver and RPC - - webserver-thread-pool-size: Integer; default 2. See [config.ini](file://share/vizd/config/config.ini#L14-L14). - - webserver-http-endpoint: IP:PORT; default 0.0.0.0:8090. See [config.ini](file://share/vizd/config/config.ini#L17-L17). - - webserver-ws-endpoint: IP:PORT; default 0.0.0.0:8091. See [config.ini](file://share/vizd/config/config.ini#L20-L20). - -- Locking and Concurrency - - read-wait-micro: Integer microseconds; default 500000. See [config.ini](file://share/vizd/config/config.ini#L23-L23). - - max-read-wait-retries: Integer retries; default 2. See [config.ini](file://share/vizd/config/config.ini#L27-L27). - - write-wait-micro: Integer microseconds; default 500000. See [config.ini](file://share/vizd/config/config.ini#L30-L30). - - max-write-wait-retries: Integer retries; default 3. See [config.ini](file://share/vizd/config/config.ini#L34-L34). - - single-write-thread: Boolean; default true. See [config.ini](file://share/vizd/config/config.ini#L40-L40). - - enable-plugins-on-push-transaction: Boolean; default false. See [config.ini](file://share/vizd/config/config.ini#L47-L47). - -- Shared Memory and Disk - - shared-file-size: Size string; default 2G. See [config.ini](file://share/vizd/config/config.ini#L54-L54). - - min-free-shared-file-size: Size string; default 500M. See [config.ini](file://share/vizd/config/config.ini#L58-L58). - - inc-shared-file-size: Size string; default 2G. See [config.ini](file://share/vizd/config/config.ini#L62-L62). - - block-num-check-free-size: Integer blocks; default 1000. See [config.ini](file://share/vizd/config/config.ini#L67-L67). - -- Plugin Activation - - plugin: Repeatable; list of plugin names. Defaults vary by template. See [config.ini](file://share/vizd/config/config.ini#L69-L73), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L73), [config_witness.ini](file://share/vizd/config/config_witness.ini#L68-L68). - -- History and Tracking - - clear-votes-before-block: Integer block number; default 0. See [config.ini](file://share/vizd/config/config.ini#L76-L76). - - skip-virtual-ops: Boolean; default false. See [config.ini](file://share/vizd/config/config.ini#L79-L79). - - track-account-range: JSON pair ["from","to"]; default unset. See [config.ini](file://share/vizd/config/config.ini#L82-L82). - - history-whitelist-ops: List of operation types; default unset. See [config.ini](file://share/vizd/config/config.ini#L85-L85). - - history-blacklist-ops: List of operation types; default unset. See [config.ini](file://share/vizd/config/config.ini#L88-L88). - - history-start-block: Integer block number; default unset. See [config.ini](file://share/vizd/config/config.ini#L91-L91). - - follow-max-feed-size: Integer; default 500. See [config.ini](file://share/vizd/config/config.ini#L94-L94). - - pm-account-range: JSON pair ["from","to"]; default unset. See [config.ini](file://share/vizd/config/config.ini#L97-L97). - -- validator Production - - enable-stale-production: Boolean; default false (production), true (testnet). See [config.ini](file://share/vizd/config/config.ini#L100-L100), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L100-L100). - - required-participation: Integer percentage (0–99); default unset (plugin default). See [config.ini](file://share/vizd/config/config.ini#L103-L103), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L103-L103). - - validator: String name; default unset (non-validator), "committee" (testnet). See [config.ini](file://share/vizd/config/config.ini#L106-L106), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L106-L106). - - private-key: WIF key; default unset (non-validator), testnet committee key shown. See [config.ini](file://share/vizd/config/config.ini#L109-L109), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L111-L111). - -- MongoDB (when enabled) - - mongodb-uri: URI string; default unset. See [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L72-L72), [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L72-L72). - -- Logging Configuration - - log.console_appender..stream: Stream target; default std_error. See [config.ini](file://share/vizd/config/config.ini#L113-L113), [main.cpp](file://programs/vizd/main.cpp#L167-L191). - - log.file_appender..filename: Path; default logs/p2p/p2p.log. See [config.ini](file://share/vizd/config/config.ini#L117-L117), [main.cpp](file://programs/vizd/main.cpp#L252-L268). - - logger..level: Level string; default warn. See [main.cpp](file://programs/vizd/main.cpp#L167-L191). - - logger..appenders: Comma-separated appender names; default stderr. See [main.cpp](file://programs/vizd/main.cpp#L167-L191). - -Notes on defaults and ranges: -- Numeric parameters without explicit ranges should be validated against typical hardware constraints and plugin expectations. -- Boolean parameters accept common variants supported by the underlying configuration parser. -- Paths for file appenders are resolved relative to the config file location unless absolute. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L1-L114) -- [main.cpp](file://programs/vizd/main.cpp#L167-L191) - -## Architecture Overview -The node binary initializes plugins and applies logging configuration from the selected configuration file. The webserver plugin exposes HTTP and WebSocket endpoints. The P2P plugin manages peer connections. The chain plugin coordinates block acceptance and transaction processing. The Validator Plugin participates in block production when configured. - -```mermaid -sequenceDiagram -participant CLI as "CLI" -participant Bin as "vizd main.cpp" -participant Log as "Logging Loader" -participant WS as "Webserver Plugin" -participant P2P as "P2P Plugin" -participant Chain as "Chain Plugin" -participant Wit as "Validator Plugin" -CLI->>Bin : Start process with config path -Bin->>Log : Load logging config from config file -Log-->>Bin : Apply logging configuration -Bin->>WS : Initialize webserver plugin -Bin->>P2P : Initialize p2p plugin -Bin->>Chain : Initialize chain plugin -Bin->>Wit : Initialize Validator Plugin (if enabled) -Bin->>Bin : Startup and exec loop -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [witness_plugin.hpp](file://plugins/validator/include/graphene/plugins/validator/witness_plugin.hpp#L34-L65) - -## Detailed Component Analysis - -### Node Types and Templates -- Full node (mainnet) - - Typical characteristics: Public P2P endpoint, broad plugin set, production RPC endpoints, validator disabled by default. - - Reference template: [config.ini](file://share/vizd/config/config.ini#L1-L130) -- Testnet node - - Characteristics: enable-stale-production=true, required-participation=0, validator="committee", private-key for committee. - - Reference template: [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- validator node - - Characteristics: webserver endpoints bound to localhost, validator and private-key configured, skip-virtual-ops=true. - - Reference template: [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- Low-memory node - - Build-time flag: LOW_MEMORY_NODE=TRUE via CMake; exposed via Dockerfile-lowmem. - - Reference: [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L48-L48) -- MongoDB-enabled node - - Characteristics: plugin list includes mongo_db, mongodb-uri configured. - - Reference templates: [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135), [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- Stock exchange node profile - - Characteristics: optimized plugin set for market data, localhost RPC, stricter history handling. - - Reference template: [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L1-L114) - -Practical selection guidance: -- Choose config_testnet.ini for development and testing. -- Use config_witness.ini when operating a validating validator with private keys. -- Use config_mongo.ini when integrating external analytics or historical archiving. -- Use config_stock_exchange.ini for market data consumers requiring minimal overhead. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L1-L114) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L48-L48) - -### Essential Settings: Database Location -- Shared memory sizing parameters control the size of the shared database file and growth thresholds. These influence disk usage and performance. - - shared-file-size, min-free-shared-file-size, inc-shared-file-size, block-num-check-free-size. -- References: - - [config.ini](file://share/vizd/config/config.ini#L54-L67) - - [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L54-L67) - - [config_witness.ini](file://share/vizd/config/config_witness.ini#L53-L66) - - [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L54-L67) - - [config_debug.ini](file://share/vizd/config/config_debug.ini#L54-L67) - - [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L54-L67) - - [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L54-L67) - -Operational notes: -- Adjust shared memory parameters to match expected chain growth and available disk space. -- Monitor free space checks to avoid excessive resizing during runtime. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L54-L67) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L54-L67) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L53-L66) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L54-L67) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L54-L67) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L54-L67) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L54-L67) - -### Plugin Activation -- The plugin directive enables or disables functionality. Different templates activate distinct sets of plugins tailored to their role. -- Examples: - - Full node: [config.ini](file://share/vizd/config/config.ini#L69-L73) - - Testnet: [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L73) - - validator: [config_witness.ini](file://share/vizd/config/config_witness.ini#L68-L68) - - MongoDB: [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L69), [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L69-L69) - - Stock exchange: [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L69-L69) - -Validation tip: -- Ensure required plugins are present for your deployment (e.g., chain, p2p, webserver, json_rpc, database_api). - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L69-L73) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L73) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L68-L68) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L69) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L69-L69) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L69-L69) - -### Network Parameters -- P2P endpoint and seed nodes define connectivity. - - p2p-endpoint: [config.ini](file://share/vizd/config/config.ini#L2-L2), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L2-L2), [config_witness.ini](file://share/vizd/config/config_witness.ini#L2-L2) - - p2p-seed-node: [config.ini](file://share/vizd/config/config.ini#L8-L8) -- RPC endpoints: - - webserver-http-endpoint: [config.ini](file://share/vizd/config/config.ini#L17-L17) - - webserver-ws-endpoint: [config.ini](file://share/vizd/config/config.ini#L20-L20) - -Security note: -- Bind RPC to localhost for validator nodes to prevent external exposure: [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L20). - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L20) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L20) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L20) - -### Performance Tuning Options -- Concurrency and locking: - - single-write-thread: [config.ini](file://share/vizd/config/config.ini#L40-L40) - - enable-plugins-on-push-transaction: [config.ini](file://share/vizd/config/config.ini#L47-L47) - - read-wait-micro, max-read-wait-retries: [config.ini](file://share/vizd/config/config.ini#L23-L27) - - write-wait-micro, max-write-wait-retries: [config.ini](file://share/vizd/config/config.ini#L30-L34) -- Shared memory growth: - - shared-file-size, min-free-shared-file-size, inc-shared-file-size, block-num-check-free-size: [config.ini](file://share/vizd/config/config.ini#L54-L67) - -Recommendations: -- Keep single-write-thread enabled for high-throughput RPC workloads to reduce lock contention. -- Tune retries and wait intervals based on observed lock acquisition failures. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L23-L67) - -### Authentication, API Access Controls, and Security -- RPC endpoints: - - HTTP: [config.ini](file://share/vizd/config/config.ini#L17-L17) - - WebSocket: [config.ini](file://share/vizd/config/config.ini#L20-L20) -- Binding to localhost for validator nodes: - - [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L20) -- validator credentials: - - validator and private-key: [config_witness.ini](file://share/vizd/config/config_witness.ini#L83-L86), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L106-L111) -- Logging security: - - Console and file appenders: [config.ini](file://share/vizd/config/config.ini#L112-L130) - - Program options for logging: [main.cpp](file://programs/vizd/main.cpp#L167-L191) - -Best practices: -- Restrict RPC access to trusted networks or bind to localhost for validator nodes. -- Rotate private keys and store them securely; avoid committing secrets to repositories. -- Use file appenders for persistent logs and monitor log rotation externally if needed. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L17-L130) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L86) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L106-L111) -- [main.cpp](file://programs/vizd/main.cpp#L167-L191) - -### Practical Configuration Scenarios -- Full node for public API: - - Use [config.ini](file://share/vizd/config/config.ini#L1-L130) with public RPC endpoints and broad plugin set. -- Testnet validator: - - Use [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) with enable-stale-production and committee validator settings. -- validator operator: - - Use [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) with localhost RPC and configured validator/private-key. -- Low-memory deployment: - - Build with LOW_MEMORY_NODE=TRUE via [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L48-L48). -- MongoDB integration: - - Use [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) or [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) and ensure mongodb-uri is reachable. -- Stock exchange consumer: - - Use [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L1-L114) for minimal overhead and focused plugins. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini#L1-L114) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L48-L48) - -### Parameter Validation and Syntax -- Configuration file syntax: - - Key-value pairs and repeatable directives (e.g., plugin, p2p-seed-node, checkpoint). - - Logging sections use dotted prefixes: log.console_appender.*, log.file_appender.*, logger.*. -- Validation steps: - - Ensure numeric values are within reasonable bounds for your hardware. - - Verify plugin names match available plugins. - - Confirm file paths for log appenders are writable. - - For validator nodes, confirm validator and private-key are set consistently. -- Example references: - - Logging program options: [main.cpp](file://programs/vizd/main.cpp#L167-L191) - - Config parsing and logging loader: [main.cpp](file://programs/vizd/main.cpp#L194-L288) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L167-L191) -- [main.cpp](file://programs/vizd/main.cpp#L194-L288) - -### Configuration Organization, Backup, and Version Management -- Organization: - - Keep a base template (e.g., config.ini) and environment-specific overlays. - - Group related settings (network, RPC, plugins, logging) together. -- Backup: - - Back up the entire configuration directory and rotate logs. -- Version management: - - Track changes to configuration files alongside code releases. - - Use environment variables or separate files for secrets (e.g., private-key) while keeping non-secret settings under version control. - -[No sources needed since this section provides general guidance] - -## Dependency Analysis -The node binary registers and initializes plugins, which in turn depend on each other. The webserver plugin requires the JSON-RPC plugin. The P2P plugin depends on the chain plugin. The Validator Plugin depends on both chain and P2P. - -```mermaid -graph LR -Main["vizd main.cpp"] -Web["webserver_plugin.hpp"] -P2P["p2p_plugin.hpp"] -Chain["chain_plugin.hpp"] -Wit["witness_plugin.hpp"] -Main --> Web -Main --> P2P -Main --> Chain -Main --> Wit -Web --> Chain -P2P --> Chain -Wit --> Chain -Wit --> P2P -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L38-L43) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L21) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L23-L24) -- [witness_plugin.hpp](file://plugins/validator/include/graphene/plugins/validator/witness_plugin.hpp#L36-L37) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [chain_plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [witness_plugin.hpp](file://plugins/validator/include/graphene/plugins/validator/witness_plugin.hpp#L34-L65) - -## Performance Considerations -- Single write thread: Reduces lock contention for database writes; recommended for high RPC throughput. -- Plugin notifications on push: Disabling can improve performance by avoiding extra indexing work for transactions not included in the next block. -- Shared memory sizing: Proper sizing prevents frequent reallocations and reduces latency spikes. -- Logging verbosity: Lower log levels reduce I/O overhead in production. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Unable to acquire READ/WRITE lock: - - Increase retries or adjust wait microseconds; review single-write-thread setting. - - References: [config.ini](file://share/vizd/config/config.ini#L23-L34), [config.ini](file://share/vizd/config/config.ini#L40-L47) -- Insufficient disk space for shared memory: - - Increase min-free-shared-file-size and inc-shared-file-size; monitor growth. - - References: [config.ini](file://share/vizd/config/config.ini#L58-L62) -- validator not producing blocks: - - Verify validator name and private-key; ensure enable-stale-production and required-participation are appropriate for the network. - - References: [config_witness.ini](file://share/vizd/config/config_witness.ini#L83-L86), [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L100-L103) -- RPC endpoints unreachable: - - Confirm binding address and port; for validator nodes, ensure localhost binding is intended. - - References: [config.ini](file://share/vizd/config/config.ini#L17-L20), [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L20) -- Logging misconfiguration: - - Validate dotted section names and file paths; ensure appenders are writable. - - References: [main.cpp](file://programs/vizd/main.cpp#L167-L191), [main.cpp](file://programs/vizd/main.cpp#L211-L288) - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L23-L67) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L83-L86) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L100-L103) -- [main.cpp](file://programs/vizd/main.cpp#L167-L191) -- [main.cpp](file://programs/vizd/main.cpp#L211-L288) - -## Conclusion -A well-tuned VIZ CPP Node configuration balances performance, security, and operational needs. Select the appropriate template for your deployment type, validate parameters carefully, and apply robust logging and backup practices. Use the provided references to align your configuration with the node’s plugin architecture and runtime behavior. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Appendix A: Configuration File Syntax Quick Reference -- Key-value pairs: key = value -- Repeatable directives: plugin = ..., p2p-seed-node = ... -- Logging sections: - - log.console_appender..stream - - log.file_appender..filename - - logger..level - - logger..appenders - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L167-L191) -- [config.ini](file://share/vizd/config/config.ini#L112-L130) - -### Appendix B: Build-Time Flags for Low-Memory Nodes -- LOW_MEMORY_NODE=TRUE via CMake; Dockerfile demonstrates usage. -- Reference: [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L48-L48) - -**Section sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L48-L48) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Contributing and Development.md b/.qoder/repowiki/en/content/Contributing and Development.md deleted file mode 100644 index 1c0b1c909d..0000000000 --- a/.qoder/repowiki/en/content/Contributing and Development.md +++ /dev/null @@ -1,353 +0,0 @@ -# Contributing and Development - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md) -- [documentation/building.md](file://documentation/building.md) -- [documentation/testing.md](file://documentation/testing.md) -- [documentation/plugin.md](file://documentation/plugin.md) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [LICENSE.md](file://LICENSE.md) -- [.travis.yml](file://.travis.yml) -- [programs/util/newplugin.py](file://programs/util/newplugin.py) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive contributing and development guidance for VIZ CPP Node. It covers the development workflow, code style and commit conventions, review processes, contribution lifecycle from issue to merge, quality standards (testing, documentation, performance), plugin development contribution process, community contribution opportunities, and practical examples of common contribution scenarios. It also outlines the relationship between contributions and project governance, licensing, and intellectual property considerations, and offers guidance for new contributors to get started. - -## Project Structure -The repository is organized around a CMake-based build system, layered libraries (chain, protocol, network, utilities, wallet), plugins, and documentation. Key areas for contributors: -- Libraries: core blockchain logic, protocol definitions, network messaging, utilities, and wallet APIs -- Plugins: built-in and external plugin ecosystem -- Documentation: build instructions, testing, plugin development, and guidelines -- Tools: scripts for building helpers, CLI utilities, and plugin scaffolding - -```mermaid -graph TB -A["Root"] --> B["libraries"] -A --> C["plugins"] -A --> D["documentation"] -A --> E["programs"] -A --> F["share"] -A --> G[".travis.yml"] -B --> B1["chain"] -B --> B2["protocol"] -B --> B3["network"] -B --> B4["utilities"] -B --> B5["wallet"] -C --> C1["built-in plugins"] -C --> C2["external plugins"] -D --> D1["building.md"] -D --> D2["testing.md"] -D --> D3["plugin.md"] -D --> D4["debug_node_plugin.md"] -D --> D5["git_guildelines.md"] -E --> E1["util/newplugin.py"] -E --> E2["util/test_block_log.cpp"] -E --> E3["util/schema_test.cpp"] -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) - -**Section sources** -- [README.md](file://README.md#L1-L53) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) - -## Core Components -- Build and CI: CMake-based build with Docker-based CI matrix covering multiple configurations -- Testing: unit tests via a dedicated target, runtime configuration options, and coverage generation -- Plugin system: built-in and external plugin support, registration, and API exposure -- Developer tools: plugin scaffolding script, schema and block log utilities for testing and validation - -Key references: -- Build options and platform-specific instructions -- Test categories and runtime configuration -- Plugin registration and enabling -- Debug node plugin usage and API surface -- Git branching, PR, and review policies -- Licensing and contribution terms - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) -- [LICENSE.md](file://LICENSE.md#L1-L26) -- [.travis.yml](file://.travis.yml#L1-L46) - -## Architecture Overview -The development and contribution workflow integrates build, test, and release automation with explicit branching and review policies. Contributors develop on feature branches, submit PRs, and adhere to code quality gates enforced by CI. - -```mermaid -graph TB -Dev["Developer"] --> BR["Feature Branch
develop-based"] -BR --> PR["Pull Request
to develop/master"] -PR --> CI["CI Pipeline
.travis.yml matrix"] -CI --> |Pass| Merge["Merge to develop/master"] -Merge --> Release["Release Tagging
vMajor.Hardfork.Release"] -Release --> Images["Docker Images
latest/testnet variants"] -``` - -**Diagram sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L8-L24) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L49-L76) -- [.travis.yml](file://.travis.yml#L12-L46) - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) -- [.travis.yml](file://.travis.yml#L1-L46) - -## Detailed Component Analysis - -### Development Workflow and Contribution Lifecycle -- Issue identification: Use repository issues to track work; non-issue patches still require an issue for documentation and traceability -- Branching: Feature branches originate from develop; naming convention for issue-related branches is issue-number-hyphen-shorthand; non-issue branches use YYYYMMDD-shortname -- Pull requests: All changes enter via PRs; automated testing is mandatory; maintainers enforce review and approval policies -- Merging: Master merges are single-commit PRs after feature consolidation; develop merges require passing tests and approvals - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Repo as "Repository" -participant CI as "CI Pipeline" -participant Maint as "Maintainers" -Dev->>Repo : Create feature branch from develop -Dev->>Repo : Commit changes and push -Dev->>Repo : Open PR to develop/master -Repo->>CI : Trigger tests -CI-->>Repo : Test results -alt Tests pass -Maint->>Repo : Code review and approval -Maint->>Repo : Merge PR -else Tests fail or review comments -Dev->>Repo : Update branch and address feedback -end -``` - -**Diagram sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L25-L48) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L49-L76) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L93-L111) - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) - -### Code Style Guidelines -- Commit messages: Reference related issues to create a documentation trail; ensure messages clearly explain the change and its rationale -- PRs: Include a summary of changes, rationale, and any special instructions for reviewers -- Branch hygiene: Keep commits focused; avoid mixing unrelated changes; rebase to resolve conflicts before resubmission - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L70-L76) - -### Review Processes and Governance -- Review requirements: At least two developers must review and approve changes; for releases and consensus-breaking changes, stricter requirements apply -- Author participation: Authors may review their own work; however, at least one independent reviewer is required -- Enforcement: PRs must pass automated tests; manual checks ensure adherence to style and correctness - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L93-L111) - -### Testing Requirements and Quality Standards -- Unit tests: Build and run via a dedicated target; tests are categorized (basic, block, operation, serialization, etc.) -- Runtime configuration: Control verbosity and filtering via runtime options -- Coverage: Enable coverage collection and generate HTML reports using lcov -- Validation utilities: Use provided tools to validate schema and block log behavior during development - -```mermaid -flowchart TD -Start(["Run Tests"]) --> Build["Build tests target"] -Build --> RunAll["Execute tests binary"] -RunAll --> Categories{"Select category/filter"} -Categories --> Report["Generate report"] -Report --> Coverage{"Coverage requested?"} -Coverage --> |Yes| Capture["Capture coverage data"] -Coverage --> |No| End(["Done"]) -Capture --> HTML["Generate HTML report"] -HTML --> End -``` - -**Diagram sources** -- [documentation/testing.md](file://documentation/testing.md#L1-L43) - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) - -### Documentation Expectations -- Build and usage docs: Follow the documented procedures for building and running the node -- Plugin docs: Use the plugin guide and debug plugin documentation as references for extending functionality -- Contribution docs: Adhere to the git guidelines and ensure PRs link to relevant issues - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) - -### Performance Criteria -- Build types: Prefer Release builds for production and benchmarking; use Debug with coverage for development and profiling -- Low-memory builds: Consider low-memory node builds for resource-constrained environments (e.g., validators) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L3-L16) - -### Plugin Development Contribution Process -- Template usage: Use the plugin scaffolding script to generate boilerplate for new plugins -- Registration and enabling: Configure plugins via configuration options; enable public APIs as needed -- Integration guidelines: Follow plugin registration patterns and integrate with the application’s plugin lifecycle - -```mermaid -flowchart TD -A["Choose provider/name"] --> B["Run newplugin.py"] -B --> C["Generated files in libraries/plugins/"] -C --> D["Add to CMake and build"] -D --> E["Enable plugin in config"] -E --> F["Expose public API if needed"] -F --> G["Test and iterate"] -``` - -**Diagram sources** -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [documentation/plugin.md](file://documentation/plugin.md#L14-L28) - -**Section sources** -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) - -### Community Contribution Opportunities -- Bug reports: Use repository issues to report reproducible bugs with clear steps and environment details -- Feature requests: Propose enhancements via issues; include motivation, acceptance criteria, and potential impact -- Documentation improvements: Submit PRs for docs; ensure clarity and completeness -- Code contributions: Follow the workflow outlined above; ensure tests and documentation accompany changes - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L49-L76) - -### Practical Examples of Common Contribution Scenarios -- Fixing a bug: - - Create a branch from develop - - Add or update unit tests to reproduce the issue - - Implement the fix and verify with tests - - Submit a PR referencing the issue -- Adding a feature: - - Open an issue to track the feature - - Develop on a feature branch; add tests and documentation - - Submit a PR; iterate based on feedback -- Improving documentation: - - Edit relevant documentation files - - Submit a PR with a concise description of changes - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L25-L48) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) - -### Licensing and Intellectual Property Considerations -- License: The repository uses the MIT License for VIZ-Blockchain-contributed code -- Dependencies: Third-party components remain under their respective licenses -- Contributions: By submitting code, contributors agree to license their work under the repository’s license - -**Section sources** -- [LICENSE.md](file://LICENSE.md#L1-L26) - -### Getting Started for New Contributors -- Set up the build environment per platform-specific instructions -- Explore the documentation for building, testing, and plugin development -- Pick an issue labeled as good first issue or similar; start with small tasks -- Follow the branching and PR workflow described above - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) - -## Dependency Analysis -The project relies on a CMake-based build system with Dockerized CI. The CI matrix builds multiple Docker images, ensuring compatibility across configurations. - -```mermaid -graph TB -CI[".travis.yml"] --> DF1["Dockerfile"] -CI --> DF2["share/vizd/docker/Dockerfile-test"] -CI --> DF3["share/vizd/docker/Dockerfile-testnet"] -CI --> DF4["share/vizd/docker/Dockerfile-lowmem"] -CI --> DF5["share/vizd/docker/Dockerfile-mongo"] -DF1 --> IMG1["Images"] -DF2 --> IMG2["Images"] -DF3 --> IMG3["Images"] -DF4 --> IMG4["Images"] -DF5 --> IMG5["Images"] -``` - -**Diagram sources** -- [.travis.yml](file://.travis.yml#L12-L18) -- [.travis.yml](file://.travis.yml#L22-L46) - -**Section sources** -- [.travis.yml](file://.travis.yml#L1-L46) - -## Performance Considerations -- Build type selection impacts performance and debugging capabilities -- Low-memory builds reduce storage and RAM usage for consensus roles -- Use coverage builds judiciously for profiling and optimization efforts - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L3-L16) - -## Troubleshooting Guide -- Build failures: Verify platform prerequisites and CMake options; consult platform-specific sections -- Test failures: Use runtime configuration to narrow down failing categories; inspect coverage output -- Plugin issues: Confirm plugin registration and configuration; validate API exposure settings -- CI failures: Review Docker build logs and ensure environment parity - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L212) -- [documentation/testing.md](file://documentation/testing.md#L16-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L14-L28) -- [.travis.yml](file://.travis.yml#L22-L46) - -## Conclusion -Contributions to VIZ CPP Node are governed by clear branching, PR, and review policies, supported by robust testing and CI infrastructure. Contributors should follow the documented workflow, ensure tests and documentation accompany changes, and leverage the plugin system and developer tools to deliver high-quality contributions. - -## Appendices -- Quick links to key documents: - - Build instructions: [documentation/building.md](file://documentation/building.md#L1-L212) - - Testing guide: [documentation/testing.md](file://documentation/testing.md#L1-L43) - - Plugin development: [documentation/plugin.md](file://documentation/plugin.md#L1-L28) - - Debug node plugin: [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) - - Git guidelines: [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) - - License: [LICENSE.md](file://LICENSE.md#L1-L26) - - CI configuration: [.travis.yml](file://.travis.yml#L1-L46) - - Plugin scaffolding: [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) - - Schema and block log utilities: [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57), [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Core Libraries/Core Libraries.md b/.qoder/repowiki/en/content/Core Libraries/Core Libraries.md deleted file mode 100644 index 69b97b12b0..0000000000 --- a/.qoder/repowiki/en/content/Core Libraries/Core Libraries.md +++ /dev/null @@ -1,1122 +0,0 @@ -# Core Libraries - - -**Referenced Files in This Document** -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [libraries/chain/database.cpp](file://libraries/chain/database.cpp) -- [libraries/chain/include/graphene/chain/evaluator.hpp](file://libraries/chain/include/graphene/chain/evaluator.hpp) -- [libraries/chain/include/graphene/chain/chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp) -- [libraries/chain/include/graphene/chain/db_with.hpp](file://libraries/chain/include/graphene/chain/db_with.hpp) -- [libraries/chain/include/graphene/chain/global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp) -- [libraries/chain/include/graphene/chain/fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [libraries/chain/fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [libraries/protocol/include/graphene/protocol/transaction.hpp](file://libraries/protocol/include/graphene/protocol/transaction.hpp) -- [libraries/protocol/transaction.cpp](file://libraries/protocol/transaction.cpp) -- [libraries/protocol/include/graphene/protocol/types.hpp](file://libraries/protocol/include/graphene/protocol/types.hpp) -- [libraries/protocol/operations.cpp](file://libraries/protocol/operations.cpp) -- [libraries/protocol/include/graphene/protocol/chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp) -- [libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp) -- [libraries/protocol/include/graphene/protocol/config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [libraries/network/node.cpp](file://libraries/network/node.cpp) -- [libraries/network/include/graphene/network/peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [libraries/network/peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp) -- [libraries/wallet/wallet.cpp](file://libraries/wallet/wallet.cpp) -- [libraries/wallet/include/graphene/wallet/api_documentation.hpp](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [plugins/validator/validator.cpp](file://plugins/validator/validator.cpp) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp) - - -## Update Summary -**Changes Made** -- Enhanced validator scheduling system documentation with emergency mode integration -- Added comprehensive peer connection management for emergency consensus -- Updated validator scheduling with hybrid schedule implementation during emergency mode -- Expanded emergency consensus activation and deactivation logic -- Added peer soft-banning mechanism for emergency fork management -- Updated fork database tie-breaking with deterministic hash-based selection - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Blockchain Operations and Data Types](#blockchain-operations-and-data-types) -7. [Protocol Specifications](#protocol-specifications) -8. [Emergency Consensus Mode](#emergency-consensus-mode) -9. [Peer Connection Management](#peer-connection-management) -10. [DNS Nameserver Helper Functionality](#dns-nameserver-helper-functionality) -11. [Postponed Transactions Processing](#postponed-transactions-processing) -12. [Dependency Analysis](#dependency-analysis) -13. [Performance Considerations](#performance-considerations) -14. [Troubleshooting Guide](#troubleshooting-guide) -15. [Conclusion](#conclusion) - -## Introduction -This document explains the VIZ CPP Node core libraries that form the foundation of the blockchain node. The four main library categories are: -- Chain library: blockchain state management, validation, and consensus -- Protocol library: transaction and operation definitions and cryptographic signing -- Network library: peer-to-peer communication and synchronization -- Wallet library: transaction signing and key management - -These libraries interact closely: the Chain library validates and applies operations, the Protocol library defines operations and transactions, the Network library propagates blocks and transactions across peers, and the Wallet library signs transactions before they are broadcast. The system now includes enhanced emergency consensus mode with integrated validator scheduling and improved peer connection management for maintaining network stability during critical situations. - -**Updated** Enhanced documentation now includes comprehensive coverage of emergency consensus mode, hybrid validator scheduling, peer connection management, blockchain operations, data types, protocol specifications, DNS nameserver helper functionality, and accurate postponed transactions processing with corrected logging behavior. - -## Project Structure -The core libraries are organized under the libraries/ directory, with each library providing focused capabilities: -- libraries/chain: state machine, evaluators, database, fork management, block processing, emergency consensus -- libraries/protocol: operations, transactions, signing, types, chain constants, emergency mode configuration -- libraries/network: P2P node, peer connections, message handling, synchronization, emergency peer management -- libraries/wallet: transaction builder, signing, key management, APIs, DNS nameserver helpers - -Plugins integrate these libraries into a full node via the appbase framework. The main entry point initializes plugins and starts the node. Emergency consensus mode adds new components for validator scheduling and peer management. - -```mermaid -graph TB -subgraph "Core Libraries" -CHAIN["Chain Library
database.hpp, evaluator.hpp, chain_objects.hpp
db_with.hpp
global_property_object.hpp
fork_database.hpp"] -PROTO["Protocol Library
operations.hpp, transaction.hpp, types.hpp
config.hpp"] -NET["Network Library
node.hpp
peer_connection.hpp"] -WALLET["Wallet Library
wallet.hpp, api_documentation.hpp
DNS Nameserver Helpers"] -end -subgraph "Plugins" -PL_CHAIN["plugins/chain/plugin.hpp"] -PL_P2P["plugins/p2p/p2p_plugin.hpp"] -PL_WITNESS["plugins/validator/validator.hpp"] -end -subgraph "Emergency Consensus Components" -EMERGENCY_MODE["Emergency Consensus
Mode Activation
Deactivation Logic"] -HYBRID_SCHED["Hybrid validator Schedule
Real + Committee Slots"] -PEER_MANAGEMENT["Peer Connection
Management & Soft-Banning"] -END -MAIN["programs/vizd/main.cpp"] -MAIN --> PL_CHAIN -MAIN --> PL_P2P -MAIN --> PL_WITNESS -PL_CHAIN --> CHAIN -PL_P2P --> NET -PL_WITNESS --> CHAIN -CHAIN --> PROTO -CHAIN --> EMERGENCY_MODE -NET --> PEER_MANAGEMENT -NET --> PROTO -WALLET --> PROTO -``` - -**Diagram sources** -- [programs/vizd/main.cpp:106-140](file://programs/vizd/main.cpp#L106-L140) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-46](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L46) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-46](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L46) -- [plugins/validator/validator.cpp:170-198](file://plugins/validator/validator.cpp#L170-L198) -- [libraries/chain/include/graphene/chain/database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [libraries/network/include/graphene/network/node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:96-1067](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) - -**Section sources** -- [programs/vizd/main.cpp:62-91](file://programs/vizd/main.cpp#L62-L91) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-46](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L46) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-46](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L46) -- [plugins/validator/validator.cpp:170-198](file://plugins/validator/validator.cpp#L170-L198) - -## Core Components -This section introduces the primary responsibilities and key classes of each library. - -- Chain Library - - database: central state machine managing blockchain objects, fork database, block log, and applying operations - - evaluator: pluggable operation handlers that mutate state according to protocol rules - - chain_objects: persistent object model (accounts, content, escrow, vesting routes, etc.) - - db_with: pending transaction processing, postponed transactions handling, and restoration logic - - global_property_object: dynamic chain properties including emergency consensus state - - fork_database: fork management with emergency mode tie-breaking and state tracking - - Responsibilities: block validation, transaction validation, state transitions, hardfork handling, validator scheduling, emergency consensus management - -- Protocol Library - - operations: static_variant of all supported operations (transfers, governance, content, etc.) - - transaction: structure with operations, expiration, reference block, and cryptographic signing - - types: comprehensive data type definitions including cryptographic keys, asset types, and authority structures - - config: emergency consensus constants and validator scheduling parameters - - Responsibilities: define canonical operation semantics, transaction signing and verification, authority checks, emergency mode configuration - -- Network Library - - node: P2P node with peer connections, message propagation, sync protocol, and broadcasting - - peer_connection: individual peer connections with emergency mode soft-banning and fork management - - Responsibilities: block and transaction propagation, peer discovery, sync from peers, bandwidth limits, emergency peer management - -- Wallet Library - - wallet_api: transaction builder, signing, key management, proposal creation, account operations, DNS nameserver helpers - - api_documentation: method descriptions and help system for wallet operations - - DNS Nameserver Helpers: validation, extraction, and management of DNS records in account metadata - - Responsibilities: construct transactions, sign with private keys, manage encrypted key storage, expose APIs, handle DNS metadata - -**Updated** Enhanced with comprehensive emergency consensus mode integration, hybrid validator scheduling, peer connection management, and DNS nameserver helper functionality. - -**Section sources** -- [libraries/chain/include/graphene/chain/database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [libraries/chain/include/graphene/chain/evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [libraries/chain/include/graphene/chain/chain_objects.hpp:20-200](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L200) -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) -- [libraries/protocol/include/graphene/protocol/operations.hpp:13-102](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/protocol/include/graphene/protocol/transaction.hpp:12-101](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [libraries/protocol/include/graphene/protocol/types.hpp:75-207](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L207) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [libraries/network/include/graphene/network/node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:96-1067](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) -- [libraries/wallet/include/graphene/wallet/api_documentation.hpp:37-75](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L37-L75) - -## Architecture Overview -The libraries integrate through explicit interfaces and signals. The Chain library exposes a database interface and signals for operation application. The Protocol library defines the canonical operation types and transaction structures. The Network library consumes blocks and transactions from the Chain library and broadcasts them to peers. The Wallet library constructs and signs transactions using the Protocol library and sends them to the Chain library via the P2P plugin. The DNS nameserver helper functionality extends the wallet library to manage DNS metadata within account JSON metadata. The db_with module handles postponed transactions processing with accurate counting and logging. Emergency consensus mode adds new components for validator scheduling and peer management. - -```mermaid -graph TB -WALLET["Wallet API
wallet.hpp
DNS Nameserver Helpers"] -API_DOC["API Documentation
api_documentation.hpp"] -PROTO["Protocol
transaction.hpp, operations.hpp, types.hpp, config.hpp"] -CHAIN["Chain Database
database.hpp"] -EVAL["Evaluators
evaluator.hpp"] -CHAIN_OBJ["Chain Objects
chain_objects.hpp"] -DB_WITH["Postponed Transactions
db_with.hpp"] -EMERGENCY_MODE["Emergency Consensus
Mode Management"] -HYBRID_SCHED["Hybrid validator Schedule
Real + Committee Slots"] -PEER_CONN["Peer Connections
Soft-Banning & Fork Management"] -NET["Network Node
node.hpp"] -PL_CHAIN["Chain Plugin
plugin.hpp"] -PL_P2P["P2P Plugin
p2p_plugin.hpp"] -PL_WITNESS["Validator Plugin
validator.hpp"] -DNS_HELPERS["DNS Nameserver Helpers
ns_validate_*
ns_create_metadata
ns_set_records"] -WITNESS_PLUGIN["Validator Plugin
Emergency Key Loading
Block Production"] -FORK_DB["Fork Database
Emergency Mode Tie-Breaking"] -WALLET --> API_DOC -WALLET --> PROTO -WALLET --> DNS_HELPERS -WALLET --> PL_P2P -PL_P2P --> NET -PL_CHAIN --> CHAIN -PL_WITNESS --> WITNESS_PLUGIN -CHAIN --> EVAL -CHAIN --> CHAIN_OBJ -CHAIN --> DB_WITH -CHAIN --> EMERGENCY_MODE -EMERGENCY_MODE --> HYBRID_SCHED -EMERGENCY_MODE --> PEER_CONN -EMERGENCY_MODE --> FORK_DB -NET --> PROTO -NET --> PEER_CONN -PROTO --> CHAIN -WITNESS_PLUGIN --> CHAIN -``` - -**Diagram sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:1310-1420](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1310-L1420) -- [libraries/wallet/include/graphene/wallet/api_documentation.hpp:43-75](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L43-L75) -- [libraries/protocol/include/graphene/protocol/transaction.hpp:12-101](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [libraries/protocol/include/graphene/protocol/operations.hpp:13-102](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/protocol/include/graphene/protocol/types.hpp:75-207](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L207) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [libraries/chain/include/graphene/chain/database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [libraries/chain/include/graphene/chain/evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [libraries/chain/include/graphene/chain/chain_objects.hpp:20-200](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L200) -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) -- [libraries/network/include/graphene/network/node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-46](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L46) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-46](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L46) -- [plugins/validator/validator.cpp:170-198](file://plugins/validator/validator.cpp#L170-L198) - -## Detailed Component Analysis - -### Chain Library -The Chain library is the core state machine. It manages: -- Blockchain state: persistent objects, indexes, and undo history -- Validation pipeline: block and transaction validation with configurable skip flags -- Fork management: fork database and branch selection with emergency mode tie-breaking -- Operation application: dispatch to evaluators and emit notifications -- Hardfork handling: versioning and activation logic -- Postponed transactions: accurate counting and processing with proper logging -- Emergency consensus: automatic activation/deactivation based on network health -- validator scheduling: hybrid schedule during emergency mode with real and committee validators - -Key classes and responsibilities: -- database: open/reindex, push/pop blocks, push transactions, notify signals, hardfork control, emergency mode management -- evaluator: base class for operation-specific logic -- chain_objects: multi-index containers for persistent state -- db_with: pending transaction restoration, postponed transaction processing, execution limits -- global_property_object: dynamic chain properties including emergency consensus state -- fork_database: fork management with emergency mode tie-breaking and state tracking - -```mermaid -classDiagram -class database { -+open(data_dir, shared_mem_dir, ...) -+reindex(data_dir, shared_mem_dir, from_block_num, ...) -+push_block(signed_block, skip_flags) -+push_transaction(signed_transaction, skip_flags) -+validate_block(...) -+validate_transaction(...) -+pre_apply_operation -+post_apply_operation -+applied_block -+on_pending_transaction -+on_applied_transaction -+is_known_transaction(id) -+has_hardfork(hf) -+get_dynamic_global_properties() -+get_witness_schedule_object() -+update_median_witness_props() -+check_block_post_validation_chain() -} -class evaluator { -<> -+apply(op) -+get_type() -} -class evaluator_impl { --database& _db -+apply(op) -+get_type() -} -class chain_objects { -<> -} -class pending_transactions_restorer { -+pending_transactions_restorer(db, skip, pending_txs) -+~pending_transactions_restorer() -+process_popped_tx() -+process_pending_tx() -} -class global_property_object { -+emergency_consensus_active : bool -+emergency_consensus_start_block : uint32_t -} -class fork_database { -+set_emergency_mode(active) -+is_emergency_mode() bool -+_emergency_consensus_active : bool -} -database --> evaluator : "dispatches operations" -evaluator <|-- evaluator_impl : "implements" -database --> chain_objects : "manages" -database --> pending_transactions_restorer : "uses" -database --> global_property_object : "manages" -database --> fork_database : "uses" -``` - -**Diagram sources** -- [libraries/chain/include/graphene/chain/database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [libraries/chain/include/graphene/chain/evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [libraries/chain/include/graphene/chain/chain_objects.hpp:20-200](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L200) -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) - -**Section sources** -- [libraries/chain/include/graphene/chain/database.hpp:36-561](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) -- [libraries/chain/database.cpp:198-200](file://libraries/chain/database.cpp#L198-L200) -- [libraries/chain/include/graphene/chain/evaluator.hpp:11-45](file://libraries/chain/include/graphene/chain/evaluator.hpp#L11-L45) -- [libraries/chain/include/graphene/chain/chain_objects.hpp:20-200](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L20-L200) -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) - -### Protocol Library -The Protocol library defines the canonical operation types and transaction structures: -- operations: static_variant of all operations (transfers, governance, content, etc.) -- transaction: operations, expiration, reference block, and signing/verification helpers -- types: comprehensive data type definitions including cryptographic keys, asset types, and authority structures -- Authority and sign_state: required authorities and signature verification -- config: emergency consensus constants and validator scheduling parameters - -```mermaid -classDiagram -class operations { -<> -+transfer_operation -+account_update_operation -+proposal_create_operation -+content_reward_operation -+... -} -class transaction { -+vector~operation~ operations -+time_point_sec expiration -+extensions_type extensions -+validate() -+id() -+sig_digest(chain_id) -} -class signed_transaction { -+vector~signature_type~ signatures -+sign(private_key, chain_id) -+verify_authority(...) -+get_required_signatures(...) -} -class types { -<> -+public_key_type -+extended_public_key_type -+asset -+price -+authority -+... -} -class config { -<> -+CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC -+CHAIN_EMERGENCY_WITNESS_ACCOUNT -+CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY -+CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS -} -operations --> transaction : "composed in" -signed_transaction --> transaction : "extends" -types --> operations : "used by" -config --> database : "used by" -``` - -**Diagram sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp:13-102](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/protocol/include/graphene/protocol/transaction.hpp:12-101](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [libraries/protocol/include/graphene/protocol/types.hpp:75-207](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L207) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) - -**Section sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp:13-102](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/protocol/include/graphene/protocol/transaction.hpp:12-101](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [libraries/protocol/include/graphene/protocol/types.hpp:75-207](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L207) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) -- [libraries/protocol/transaction.cpp:30-200](file://libraries/protocol/transaction.cpp#L30-L200) - -### Network Library -The Network library provides peer-to-peer connectivity with enhanced emergency mode support: -- node: P2P node with delegate interface, peer connections, message propagation, sync protocol -- peer_connection: individual peer connections with emergency mode soft-banning and fork management -- Broadcasting: blocks and transactions to peers -- Sync: blockchain synopsis, block requests, and peer synchronization - -```mermaid -sequenceDiagram -participant NET as "Network Node" -participant PEER as "Peer Connection" -participant CHAIN as "Chain Plugin" -participant DB as "Database" -NET->>PEER : "connect_to_endpoint()" -NET->>PEER : "sync_from(current_head_block, hard_fork_block_numbers)" -PEER->>PEER : "fork_rejected_until soft-ban check" -PEER-->>NET : "block_message" -NET->>CHAIN : "accept_block(block)" -CHAIN->>DB : "push_block(block)" -DB->>DB : "emergency mode tie-breaking" -DB-->>CHAIN : "applied_block signal" -CHAIN-->>NET : "sync_status(item_type, count)" -``` - -**Diagram sources** -- [libraries/network/include/graphene/network/node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:44-46](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L44-L46) - -**Section sources** -- [libraries/network/include/graphene/network/node.hpp:190-304](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/network/node.cpp:1-200](file://libraries/network/node.cpp#L1-L200) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [libraries/network/peer_connection.cpp:150-349](file://libraries/network/peer_connection.cpp#L150-L349) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:44-46](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L44-L46) - -### Wallet Library -The Wallet library provides transaction construction and signing: -- wallet_api: builder APIs, signing, key management, proposal creation, account operations, DNS nameserver helpers -- api_documentation: method descriptions and help system for wallet operations -- DNS Nameserver Helpers: comprehensive DNS metadata management functionality -- Signing: uses Protocol transaction structures and private keys -- Integration: communicates with the node via plugins and remote APIs - -```mermaid -flowchart TD -Start(["Begin Builder Transaction"]) --> AddOp["Add Operation to Builder"] -AddOp --> Preview["Preview Builder Transaction"] -Preview --> Sign{"Sign?"} -Sign --> |Yes| SignTx["Sign Builder Transaction"] -Sign --> |No| End(["End"]) -SignTx --> Broadcast{"Broadcast?"} -Broadcast --> |Yes| Send["Send to Chain Plugin"] -Broadcast --> |No| End -Send --> End -``` - -**Diagram sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:132-180](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L132-L180) -- [libraries/wallet/include/graphene/wallet/api_documentation.hpp:43-75](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L43-L75) - -**Section sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:96-1067](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L96-L1067) -- [libraries/wallet/wallet.cpp:1-200](file://libraries/wallet/wallet.cpp#L1-L200) -- [libraries/wallet/include/graphene/wallet/api_documentation.hpp:37-75](file://libraries/wallet/include/graphene/wallet/api_documentation.hpp#L37-L75) - -### DNS Nameserver Helper Functionality -The wallet library now includes comprehensive DNS nameserver helper functionality for managing DNS records within VIZ account metadata. This functionality enables: - -- **Validation Functions**: IPv4 address validation, SHA256 hash validation, TTL validation, and SSL TXT record format validation -- **Metadata Creation**: Generation of DNS metadata JSON with A records and SSL hash TXT records -- **Extraction Functions**: Retrieval of A records, SSL hashes, and TTL values from account metadata -- **Management Operations**: Setting and removing DNS records while preserving other metadata fields - -Key data structures: -- `ns_record`: Represents a single DNS record tuple [type, value] -- `ns_metadata_options`: Configuration options for DNS metadata (A records, SSL hash, TTL) -- `ns_summary`: Extracted DNS metadata summary from account JSON -- `ns_validation_result`: Validation results with error reporting - -```mermaid -classDiagram -class ns_metadata_options { -+vector~string~ a_records -+optional~string~ ssl_hash -+uint32_t ttl -} -class ns_summary { -+vector~string~ a_records -+optional~string~ ssl_hash -+uint32_t ttl -+bool has_ns_data -} -class ns_validation_result { -+bool is_valid -+vector~string~ errors -} -class wallet_api { -+ns_validate_ipv4(ipv4) -+ns_validate_sha256_hash(hash) -+ns_validate_ttl(ttl) -+ns_validate_ssl_txt_record(txt) -+ns_validate_metadata(options) -+ns_create_metadata(options) -+ns_get_summary(account_name) -+ns_extract_a_records(account_name) -+ns_extract_ssl_hash(account_name) -+ns_extract_ttl(account_name) -+ns_set_records(account_name, options, broadcast) -+ns_remove_records(account_name, broadcast) -} -ns_metadata_options --> ns_summary : "creates" -ns_validation_result --> ns_metadata_options : "validates" -wallet_api --> ns_metadata_options : "uses" -wallet_api --> ns_summary : "returns" -``` - -**Diagram sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:24-62](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L24-L62) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:1310-1420](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1310-L1420) -- [libraries/wallet/wallet.cpp:2577-2884](file://libraries/wallet/wallet.cpp#L2577-L2884) - -**Section sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:24-62](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L24-L62) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:1310-1420](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1310-L1420) -- [libraries/wallet/wallet.cpp:2577-2884](file://libraries/wallet/wallet.cpp#L2577-L2884) - -### Postponed Transactions Processing -The Chain library includes sophisticated postponed transactions processing with accurate counting and logging. This system handles transactions that cannot be included in a block due to size constraints or execution limits. - -Key components: -- `pending_transactions_restorer`: Manages restoration of pending transactions during block production -- `postponed_tx_count`: Accurate counter for transactions postponed due to block size limits -- Execution limits: Configurable time-based limits for processing pending transactions -- Logging: Prevents false 'Postponed' messages for skipped known transactions - -```mermaid -classDiagram -class pending_transactions_restorer { -+pending_transactions_restorer(database&, uint32_t, vector~signed_transaction~) -+~pending_transactions_restorer() -+process_popped_tx() -+process_pending_tx() -} -class database { -+push_transaction(signed_transaction, uint32_t) -+_push_transaction(signed_transaction, uint32_t) -+clear_pending() -+is_known_transaction(transaction_id_type) -} -class postponed_transactions_processing { -+postponed_tx_count : uint64_t -+CHAIN_BLOCK_GENERATION_POSTPONED_TX_LIMIT : 5 -+CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT : 200ms -+process_transactions_with_size_limits() -+accurate_logging_behavior() -} -pending_transactions_restorer --> database : "restores" -postponed_transactions_processing --> database : "uses" -``` - -**Diagram sources** -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/database.cpp:1165-1202](file://libraries/chain/database.cpp#L1165-L1202) -- [libraries/chain/database.cpp:549-555](file://libraries/chain/database.cpp#L549-L555) - -**Section sources** -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/database.cpp:1165-1202](file://libraries/chain/database.cpp#L1165-L1202) -- [libraries/chain/database.cpp:549-555](file://libraries/chain/database.cpp#L549-L555) - -### Typical Operations: Transaction Processing and Block Validation - -#### Transaction Processing Flow -- Wallet builds and signs a transaction using Protocol structures -- P2P plugin broadcasts the signed transaction to peers -- Chain plugin receives and validates the transaction via the Chain library -- Chain library applies the transaction's operations through evaluators -- Database emits notifications for pre/post application and applied block - -```mermaid -sequenceDiagram -participant WALLET as "Wallet API" -participant PROTO as "Protocol" -participant P2P as "P2P Plugin" -participant CHAIN as "Chain Plugin" -participant DB as "Database" -participant EVAL as "Evaluators" -WALLET->>PROTO : "Build signed_transaction" -WALLET->>P2P : "broadcast_transaction(tx)" -P2P->>CHAIN : "accept_transaction(tx)" -CHAIN->>DB : "validate_transaction(tx)" -DB->>EVAL : "apply operations" -EVAL-->>DB : "state changes" -DB-->>CHAIN : "on_applied_transaction signal" -CHAIN-->>P2P : "transaction propagated" -``` - -**Diagram sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:132-180](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L132-L180) -- [libraries/protocol/include/graphene/protocol/transaction.hpp:57-101](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L57-L101) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:46-46](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L46-L46) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:46-46](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L46-L46) -- [libraries/chain/include/graphene/chain/database.hpp:200-275](file://libraries/chain/include/graphene/chain/database.hpp#L200-L275) - -#### Block Validation Flow -- Network receives a block from peers -- Chain plugin accepts the block and validates it -- Database validates block header, extensions, and applies block-level operations -- Database updates global properties, validator schedules, and emits applied_block signal - -```mermaid -sequenceDiagram -participant NET as "Network Node" -participant CHAIN as "Chain Plugin" -participant DB as "Database" -NET->>CHAIN : "accept_block(block)" -CHAIN->>DB : "validate_block(block)" -DB->>DB : "_validate_block(next_block)" -DB->>DB : "apply_block(next_block)" -DB-->>CHAIN : "applied_block signal" -``` - -**Diagram sources** -- [libraries/network/include/graphene/network/node.hpp:79-80](file://libraries/network/include/graphene/network/node.hpp#L79-L80) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:44-44](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L44-L44) -- [libraries/chain/include/graphene/chain/database.hpp:194-226](file://libraries/chain/include/graphene/chain/database.hpp#L194-L226) - -## Blockchain Operations and Data Types - -### Comprehensive Operation Coverage -The protocol library now provides extensive operation documentation covering all blockchain operations: - -#### Core Operations -- **Account Management**: account_create_operation, account_update_operation, account_metadata_operation -- **Token Operations**: transfer_operation, transfer_to_vesting_operation, withdraw_vesting_operation -- **Governance**: witness_update_operation, chain_properties_update_operation, proposal operations -- **Content Operations**: content_operation, delete_content_operation, vote_operation -- **Escrow Operations**: escrow_transfer_operation, escrow_approve_operation, escrow_dispute_operation, escrow_release_operation -- **Virtual Operations**: author_reward_operation, curation_reward_operation, content_reward_operation - -#### Advanced Operations -- **Committee Operations**: Various committee request and approval operations -- **Award Operations**: Award creation and distribution operations -- **Paid Subscription Operations**: Subscription management and billing operations -- **Account Sale Operations**: Account marketplace operations -- **Hardfork Operations**: System upgrade and maintenance operations - -#### Data Type Definitions -The types.hpp file provides comprehensive data type coverage: - -- **Cryptographic Types**: - - public_key_type, extended_public_key_type, extended_private_key_type - - signature_type, chain_id_type -- **Asset Types**: - - asset, price, share_type for token and share management -- **Authority Structures**: - - authority, weight_type for multi-signature requirements -- **Name Types**: - - account_name_type for account identification - -**Section sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp:13-102](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [libraries/protocol/include/graphene/protocol/chain_operations.hpp:11-800](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L800) -- [libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp:11-329](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) -- [libraries/protocol/include/graphene/protocol/types.hpp:75-207](file://libraries/protocol/include/graphene/protocol/types.hpp#L75-L207) -- [libraries/protocol/operations.cpp:17-52](file://libraries/protocol/operations.cpp#L17-L52) - -### Operation Categorization and Classification -Operations are systematically categorized for better understanding and implementation: - -#### Operation Categories -- **Regular Operations**: Standard blockchain operations that affect state -- **Virtual Operations**: System-generated operations for rewards and maintenance -- **Data Operations**: Operations carrying raw data payloads -- **Governance Operations**: Operations affecting chain parameters and governance - -#### Operation Properties -Each operation includes validation rules, authority requirements, and extension mechanisms for future enhancements. - -**Section sources** -- [libraries/protocol/operations.cpp:17-52](file://libraries/protocol/operations.cpp#L17-L52) -- [libraries/protocol/include/graphene/protocol/operations.hpp:104-113](file://libraries/protocol/include/graphene/protocol/operations.hpp#L104-L113) - -## Protocol Specifications - -### Detailed Operation Documentation -The protocol now includes comprehensive documentation for all operation types: - -#### Operation Structure -Each operation follows a standardized structure: -- Base class inheritance from base_operation or virtual_operation -- validate() method for input validation -- get_required_*_authorities() methods for authority determination -- Extension support for future compatibility - -#### Authority Requirements -Operations specify required authorities: -- Active authorities for standard operations -- Master authorities for sensitive operations -- Regular authorities for metadata operations -- Custom authorities for specialized operations - -#### Virtual Operations -Virtual operations represent system events: -- Reward distributions (author_reward_operation, curation_reward_operation) -- Maintenance operations (hardfork_operation, shutdown_witness_operation) -- State transitions (fill_vesting_withdraw_operation) - -**Section sources** -- [libraries/protocol/include/graphene/protocol/chain_operations.hpp:11-800](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp#L11-L800) -- [libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp:11-329](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp#L11-L329) - -### Transaction Structure and Validation -Transactions follow a strict validation pipeline: -- Operation composition and ordering -- Expiration handling and reference block validation -- Signature verification and authority checking -- Extension processing and custom operation support - -**Section sources** -- [libraries/protocol/include/graphene/protocol/transaction.hpp:12-101](file://libraries/protocol/include/graphene/protocol/transaction.hpp#L12-L101) -- [libraries/protocol/transaction.cpp:30-200](file://libraries/protocol/transaction.cpp#L30-L200) - -## Emergency Consensus Mode - -The VIZ blockchain now includes a comprehensive emergency consensus mode designed to maintain network stability during prolonged network stalls or validator failures. This system automatically activates when no blocks are produced for a specified timeout period and ensures continuous block production through committee validators. - -### Emergency Consensus Activation Logic - -Emergency mode is triggered when the time since the last irreversible block exceeds the configured timeout threshold: - -```mermaid -flowchart TD -Start(["Block Applied"]) --> CheckLIB["Check Last Irreversible Block Time"] -CheckLIB --> CalcTime["Calculate Time Since LIB"] -CalcTime --> Timeout{"Timeout Exceeded?
> CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC"} -Timeout --> |No| Normal["Normal Operation"] -Timeout --> |Yes| Activate["Activate Emergency Mode"] -Activate --> CreateWitness["Create/Update Emergency validator"] -CreateWitness --> ResetPenalties["Reset validator Penalties"] -ResetPenalties --> OverrideSchedule["Override validator Schedule"] -OverrideSchedule --> NotifyForkDB["Notify Fork Database"] -NotifyForkDB --> LogActivation["Log Emergency Mode Activation"] -``` - -**Diagram sources** -- [libraries/chain/database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-112](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L112) - -### Hybrid validator Scheduling System - -During emergency mode, the validator scheduling system operates as a hybrid between real validators and committee validators: - -- **Real validator Slots**: Maintained for validators with valid signing keys -- **Committee Slots**: Filled by the emergency validator account for offline or unavailable validators -- **Full Schedule Expansion**: The schedule expands to include all committee slots during emergency - -The hybrid schedule ensures that: -- Real validators keep their scheduled slots -- Offline validators are replaced by committee validators -- The full CHAIN_MAX_WITNESSES schedule is maintained for consistent block production -- Committee validators maintain neutral voting positions aligned with current hardfork state - -```mermaid -sequenceDiagram -participant SCHED as "validator Scheduler" -participant REAL as "Real validators" -participant COMMITTEE as "Committee validators" -participant DATABASE as "Database" -SCHED->>DATABASE : "Get Current Schedule" -DATABASE-->>SCHED : "wso.current_shuffled_witnesses" -SCHED->>SCHED : "Iterate Full Schedule (MAX_WITNESSES)" -loop For Each Slot -SCHED->>REAL : "Check validator Availability" -alt validator Available -SCHED->>SCHED : "Keep Real validator Slot" -else validator Unavailable -SCHED->>COMMITTEE : "Assign Emergency validator" -SCHED->>SCHED : "Replace with Committee Slot" -end -end -SCHED->>DATABASE : "Expand Schedule to MAX_WITNESSES" -DATABASE-->>SCHED : "Updated Schedule" -``` - -**Diagram sources** -- [libraries/chain/database.cpp:2047-2143](file://libraries/chain/database.cpp#L2047-L2143) -- [libraries/protocol/include/graphene/protocol/config.hpp:115-116](file://libraries/protocol/include/graphene/protocol/config.hpp#L115-L116) - -### Emergency Mode Exit Conditions - -Emergency mode automatically deactivates when: -- The last irreversible block advances beyond the emergency start block -- 21 consecutive blocks are produced by the emergency validator (full round completion) -- Network conditions return to normal with sufficient validator participation - -The exit process restores normal operations: -- Disables emergency consensus flag -- Resets fork database emergency mode state -- Removes emergency validator from schedule -- Restores normal validator participation requirements - -### Emergency Consensus Configuration - -Key configuration parameters: -- `CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC`: 3600 seconds (1 hour) timeout for emergency activation -- `CHAIN_EMERGENCY_WITNESS_ACCOUNT`: "committee" account for emergency block production -- `CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY`: Public key for emergency validator signature verification -- `CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS`: 21 blocks for automatic emergency mode exit - -**Section sources** -- [libraries/chain/database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [libraries/chain/database.cpp:2047-2143](file://libraries/chain/database.cpp#L2047-L2143) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/protocol/include/graphene/protocol/config.hpp:110-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L124) - -## Peer Connection Management - -The network library includes enhanced peer connection management specifically designed for emergency consensus mode. These improvements ensure network stability and prevent fork propagation during critical situations. - -### Emergency Mode Peer Soft-Banning - -During emergency consensus mode, the network implements a soft-banning mechanism to prevent peers from propagating losing forks: - -- **fork_rejected_until**: Timestamp-based soft-ban for peers that lose forks during emergency mode -- **Prevents Fork Propagation**: Peers with losing forks are temporarily ignored -- **Maintains Network Stability**: Reduces confusion and prevents split-brain scenarios -- **Automatic Recovery**: Soft-bans expire naturally as emergency mode progresses - -### Peer Connection State Management - -Enhanced peer connection states for emergency mode: -- **Soft-Ban Enforcement**: Peers with fork_rejected_until timestamps are ignored for block propagation -- **Emergency Fork Detection**: Improved detection of emergency-mode fork conflicts -- **Connection Rejection Handling**: Better handling of connection rejections during emergency periods -- **Firewall Check Integration**: Enhanced firewall detection with emergency mode awareness - -```mermaid -classDiagram -class peer_connection { -+fork_rejected_until : time_point -+soft_ban_check() -+emergency_fork_handling() -+connection_rejection_handling() -} -class node { -+emergency_mode_active : bool -+peer_soft_ban_management() -+fork_conflict_resolution() -} -class fork_database { -+emergency_mode_tie_breaking() -+deterministic_hash_selection() -} -peer_connection --> node : "interacts with" -node --> fork_database : "uses for tie-breaking" -``` - -**Diagram sources** -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [libraries/chain/fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) - -### Emergency Mode Fork Database Tie-Breaking - -The fork database implements deterministic hash-based tie-breaking during emergency mode: - -- **Hash Comparison**: When multiple blocks compete at the same height, compare block_id hashes -- **Consistent Selection**: Lower block_id hash always wins, ensuring network convergence -- **Deterministic Behavior**: All nodes make identical decisions regardless of P2P arrival order -- **Emergency Mode Activation**: Tie-breaking only active during emergency consensus mode - -This mechanism ensures that even if multiple emergency producers create competing blocks simultaneously, the network will converge to a single chain based on deterministic hash comparison. - -**Section sources** -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [libraries/network/peer_connection.cpp:150-349](file://libraries/network/peer_connection.cpp#L150-L349) -- [libraries/chain/fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) - -## DNS Nameserver Helper Functionality - -The wallet library now includes comprehensive DNS nameserver helper functionality that extends blockchain metadata management capabilities with DNS record support for VIZ accounts. - -### DNS Metadata Structure -DNS nameserver helpers manage DNS records stored within account JSON metadata. The metadata structure supports: - -- **NS Array**: Contains DNS record tuples with type and value pairs -- **TTL Value**: Time-to-live for DNS records in seconds -- **SSL Hash**: Optional SHA256 hash for SSL certificate verification - -### Validation Functions -The DNS helpers provide comprehensive validation for DNS metadata: - -- **IPv4 Validation**: Validates IPv4 address format with proper octet ranges -- **SHA256 Hash Validation**: Ensures 64-character hexadecimal hash format -- **TTL Validation**: Requires positive integer values for TTL -- **SSL TXT Record Validation**: Validates "ssl=" format - -### Extraction and Management Operations -The DNS helpers support complete DNS metadata lifecycle management: - -- **Metadata Creation**: Generates DNS metadata JSON with A records and SSL hash TXT records -- **Summary Extraction**: Retrieves complete DNS metadata summary from account JSON -- **Record Extraction**: Extracts specific DNS record types (A records, SSL hashes, TTL values) -- **Record Management**: Sets and removes DNS records while preserving other metadata fields - -### Practical Usage Examples - -#### Setting DNS Records -```cpp -// Configure DNS metadata options -ns_metadata_options options; -options.a_records = {"188.120.231.153", "192.168.1.100"}; -options.ssl_hash = "a1b2c3d4e5f67890123456789012345678901234567890123456789012345678"; -options.ttl = 28800; // 8 hours - -// Validate metadata -auto validation = wallet.ns_validate_metadata(options); -if (validation.is_valid) { - // Set DNS records for account - auto tx = wallet.ns_set_records("myaccount", options, true); -} -``` - -#### Extracting DNS Information -```cpp -// Extract A records -auto a_records = wallet.ns_extract_a_records("myaccount"); -for (const auto& ip : a_records) { - std::cout << "A record: " << ip << std::endl; -} - -// Extract SSL hash -auto ssl_hash = wallet.ns_extract_ssl_hash("myaccount"); -if (ssl_hash) { - std::cout << "SSL hash: " << *ssl_hash << std::endl; -} - -// Extract TTL -auto ttl = wallet.ns_extract_ttl("myaccount"); -std::cout << "TTL: " << ttl << " seconds" << std::endl; -``` - -#### Removing DNS Records -```cpp -// Remove DNS records while preserving other metadata -auto tx = wallet.ns_remove_records("myaccount", true); -``` - -**Section sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:24-62](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L24-L62) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:1310-1420](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L1310-L1420) -- [libraries/wallet/wallet.cpp:2577-2884](file://libraries/wallet/wallet.cpp#L2577-L2884) - -## Postponed Transactions Processing - -The Chain library implements sophisticated postponed transactions processing to handle transactions that cannot be included in a block due to size constraints or execution limits. This system ensures accurate counting and appropriate logging behavior. - -### Postponed Transaction Counting Logic -The system maintains an accurate `postponed_tx_count` variable that tracks transactions postponed due to block size limits: - -- **Size Limit Checking**: Each transaction is evaluated against the maximum block size -- **Counter Increment**: Only transactions that exceed size limits increment the counter -- **Limit Enforcement**: Processing stops when the configured limit is reached -- **Accurate Reporting**: Log messages reflect the actual number of postponed transactions - -### Execution Limits and Performance -The system implements time-based execution limits to prevent excessive processing time: - -- **Execution Window**: Configurable time limit (default 200ms) for processing pending transactions -- **Graceful Degradation**: When time limit is reached, remaining transactions are postponed -- **Known Transaction Filtering**: Skipped known transactions prevent false logging messages - -### Logging Behavior Improvements -Recent improvements ensure accurate logging behavior: - -- **False Positive Prevention**: Known transactions are skipped without generating 'Postponed' messages -- **Accurate Counting**: Only truly postponed transactions contribute to the counter -- **Performance Monitoring**: Proper logging helps monitor block production efficiency - -```mermaid -sequenceDiagram -participant DB as "Database" -participant RESTORER as "pending_transactions_restorer" -participant EXECUTOR as "Transaction Executor" -DB->>RESTORER : "without_pending_transactions()" -RESTORER->>EXECUTOR : "process_popped_tx()" -loop For each popped transaction -EXECUTOR->>DB : "is_known_transaction(tx.id())" -alt Known transaction -EXECUTOR-->>RESTORER : "Skip (no log message)" -else Unknown transaction -EXECUTOR->>DB : "_push_transaction(tx)" -alt Can be applied -EXECUTOR-->>RESTORER : "Applied (count++)" -else Size limit exceeded -EXECUTOR-->>RESTORER : "Postponed (count++)" -end -end -end -RESTORER->>EXECUTOR : "process_pending_tx()" -alt Time limit reached -EXECUTOR-->>RESTORER : "Graceful degradation" -else Continue processing -EXECUTOR-->>RESTORER : "Process remaining" -end -RESTORER-->>DB : "Log postponed transactions" -``` - -**Diagram sources** -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/database.cpp:1165-1202](file://libraries/chain/database.cpp#L1165-L1202) -- [libraries/chain/database.cpp:549-555](file://libraries/chain/database.cpp#L549-L555) - -**Section sources** -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/database.cpp:1165-1202](file://libraries/chain/database.cpp#L1165-L1202) -- [libraries/chain/database.cpp:549-555](file://libraries/chain/database.cpp#L549-L555) - -## Dependency Analysis -The libraries exhibit layered dependencies with enhanced emergency consensus integration: -- Chain depends on Protocol for operation types, transaction structures, and emergency mode configuration -- Network depends on Protocol for message serialization, types, and emergency peer management -- Wallet depends on Protocol for transaction construction and signing, plus includes DNS helpers -- Plugins depend on Chain for database access, on Network for P2P operations, and on Emergency Mode for validator scheduling -- db_with module depends on Chain database for transaction processing and logging -- Emergency consensus components depend on all core libraries for coordinated operation - -```mermaid -graph LR -WALLET["Wallet"] --> PROTO["Protocol"] -WALLET --> DNS_HELPERS["DNS Nameserver Helpers"] -NET["Network"] --> PROTO -NET --> PEER_CONN["Peer Connection
Emergency Management"] -CHAIN["Chain"] --> PROTO -CHAIN --> DB_WITH["Postponed Transactions"] -CHAIN --> EMERGENCY_MODE["Emergency Mode
Components"] -EMERGENCY_MODE --> HYBRID_SCHED["Hybrid Schedule"] -EMERGENCY_MODE --> FORK_DB["Fork Database
Tie-Breaking"] -PL_P2P["P2P Plugin"] --> NET -PL_CHAIN["Chain Plugin"] --> CHAIN -PL_WITNESS["Validator Plugin"] --> CHAIN -MAIN["Main Entry"] --> PL_CHAIN -MAIN --> PL_P2P -MAIN --> PL_WITNESS -``` - -**Diagram sources** -- [libraries/wallet/include/graphene/wallet/wallet.hpp:18-21](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L18-L21) -- [libraries/protocol/include/graphene/protocol/operations.hpp:3-6](file://libraries/protocol/include/graphene/protocol/operations.hpp#L3-L6) -- [libraries/network/include/graphene/network/node.hpp:26-30](file://libraries/network/include/graphene/network/node.hpp#L26-L30) -- [libraries/chain/include/graphene/chain/database.hpp:8-8](file://libraries/chain/include/graphene/chain/database.hpp#L8-L8) -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:3-3](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L3-L3) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:7-7](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L7-L7) -- [plugins/validator/validator.cpp:170-198](file://plugins/validator/validator.cpp#L170-L198) -- [programs/vizd/main.cpp:106-140](file://programs/vizd/main.cpp#L106-L140) - -**Section sources** -- [programs/vizd/main.cpp:106-140](file://programs/vizd/main.cpp#L106-L140) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:7-7](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L7-L7) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:3-3](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L3-L3) -- [plugins/validator/validator.cpp:170-198](file://plugins/validator/validator.cpp#L170-L198) -- [libraries/chain/include/graphene/chain/database.hpp:8-8](file://libraries/chain/include/graphene/chain/database.hpp#L8-L8) -- [libraries/network/include/graphene/network/node.hpp:26-30](file://libraries/network/include/graphene/network/node.hpp#L26-L30) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:18-21](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L18-L21) -- [libraries/chain/include/graphene/chain/db_with.hpp:37-100](file://libraries/chain/include/graphene/chain/db_with.hpp#L37-L100) -- [libraries/chain/include/graphene/chain/global_property_object.hpp:134-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L134-L146) -- [libraries/chain/include/graphene/chain/fork_database.hpp:110-138](file://libraries/chain/include/graphene/chain/fork_database.hpp#L110-L138) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) - -## Performance Considerations -- Database tuning: shared memory sizing, flush intervals, and checkpoints reduce I/O overhead -- Validation skipping flags: during reindex or trusted scenarios, selective validation can accelerate startup -- Network bandwidth: rate limiting and propagation tracking help manage traffic -- Wallet caching: minimal caching assumptions favor local APIs with fast node connections -- Operation processing: efficient static_variant dispatch and lazy evaluation optimize performance -- DNS validation: lightweight validation functions minimize overhead for DNS metadata operations -- Postponed transactions: accurate counting prevents unnecessary processing and improves block production efficiency -- Execution limits: configurable time limits prevent excessive processing time during block production -- Emergency consensus: automatic activation reduces manual intervention overhead during network failures -- Peer soft-banning: prevents wasted bandwidth on fork propagation during emergency periods -- Hybrid scheduling: maintains consistent block production rates during emergency mode - -## Troubleshooting Guide -Common issues and diagnostics: -- Validation failures: inspect skip flags and hardfork versions; use validation steps to narrow down failure points -- Authority verification errors: ensure required signatures and approvals match operation requirements -- Network sync stalls: check peer counts, sync status callbacks, and bandwidth limits -- Wallet signing problems: verify chain ID, key derivation, and memo encryption -- Operation classification errors: verify operation type and category using is_virtual_operation and is_data_operation functions -- DNS metadata errors: validate DNS records using ns_validate_metadata and check for proper JSON formatting -- SSL hash validation failures: ensure 64-character hexadecimal format for SSL certificate hashes -- TTL validation errors: verify positive integer values for DNS record TTL settings -- Postponed transactions issues: check block size limits, execution time limits, and known transaction filtering -- Logging accuracy: verify postponed transaction counters and avoid false 'Postponed' messages for skipped known transactions -- Emergency mode activation: verify timeout thresholds and emergency validator configuration -- Hybrid schedule issues: check validator availability and schedule expansion during emergency mode -- Peer soft-banning: monitor fork_rejected_until timestamps and emergency peer connection management -- Fork database tie-breaking: ensure deterministic hash comparison during emergency mode conflicts - -**Section sources** -- [libraries/chain/include/graphene/chain/database.hpp:56-73](file://libraries/chain/include/graphene/chain/database.hpp#L56-L73) -- [libraries/protocol/transaction.cpp:94-200](file://libraries/protocol/transaction.cpp#L94-L200) -- [libraries/network/include/graphene/network/node.hpp:143-148](file://libraries/network/include/graphene/network/node.hpp#L143-L148) -- [libraries/wallet/include/graphene/wallet/wallet.hpp:311-331](file://libraries/wallet/include/graphene/wallet/wallet.hpp#L311-L331) -- [libraries/protocol/operations.cpp:17-52](file://libraries/protocol/operations.cpp#L17-L52) -- [libraries/wallet/wallet.cpp:2640-2673](file://libraries/wallet/wallet.cpp#L2640-L2673) -- [libraries/chain/database.cpp:1165-1202](file://libraries/chain/database.cpp#L1165-L1202) -- [libraries/chain/database.cpp:549-555](file://libraries/chain/database.cpp#L549-L555) -- [libraries/chain/database.cpp:4334-4438](file://libraries/chain/database.cpp#L4334-L4438) -- [libraries/chain/database.cpp:2047-2143](file://libraries/chain/database.cpp#L2047-L2143) -- [libraries/network/include/graphene/network/peer_connection.hpp:276-277](file://libraries/network/include/graphene/network/peer_connection.hpp#L276-L277) -- [libraries/chain/fork_database.cpp:80-87](file://libraries/chain/fork_database.cpp#L80-L87) - -## Conclusion -The VIZ CPP Node core libraries form a cohesive architecture with enhanced emergency consensus capabilities: Protocol defines canonical operations and transactions, Chain manages state and validation with emergency mode integration, Network enables peer synchronization with emergency peer management, and Wallet provides signing and key management. The enhanced documentation now provides comprehensive coverage of emergency consensus mode, hybrid validator scheduling, peer connection management, blockchain operations, data types, protocol specifications, DNS nameserver helper functionality, and accurate postponed transactions processing with corrected logging behavior, supporting robust transaction processing, block validation, peer coordination, and emergency network stability essential to a production blockchain node. - -**Updated** Enhanced documentation provides expanded coverage of emergency consensus mode, hybrid validator scheduling, peer connection management, blockchain operations, data types, protocol specifications, DNS nameserver helper functionality, and accurate postponed transactions processing with corrected logging behavior, making it easier for developers to understand and work with the VIZ blockchain protocol, manage emergency network conditions, and implement DNS records within account metadata. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Core Libraries/Emergency Consensus System.md b/.qoder/repowiki/en/content/Core Libraries/Emergency Consensus System.md deleted file mode 100644 index b22f2019a7..0000000000 --- a/.qoder/repowiki/en/content/Core Libraries/Emergency Consensus System.md +++ /dev/null @@ -1,648 +0,0 @@ -# Emergency Consensus System - - -**Referenced Files in This Document** -- [database.cpp](file://libraries/chain/database.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [12.hf](file://libraries/chain/hardfork.d/12.hf) -- [chainbase.cpp](file://thirdparty/chainbase/src/chainbase.cpp) -- [chainbase.hpp](file://thirdparty/chainbase/include/chainbase/chainbase.hpp) - - -## Update Summary -**Changes Made** -- Enhanced emergency recovery mechanisms with new emergency threshold constants and improved network resilience during critical failure scenarios -- Updated emergency consensus activation with comprehensive LIB availability validation and deterministic synchronization detection -- Improved emergency exit conditions with refined real validator recovery validation using 75% threshold -- Enhanced emergency mode flag management across fork database and Validator Plugin integration -- Strengthened emergency validator management with comprehensive penalty reset and schedule override logic -- Added enhanced memory management protection through operation guards during emergency mode operations - -## Table of Contents -1. [Introduction](#introduction) -2. [System Architecture](#system-architecture) -3. [Core Components](#core-components) -4. [Enhanced Emergency Consensus Activation](#enhanced-emergency-consensus-activation) -5. [Automatic Schedule Recovery](#automatic-schedule-recovery) -6. [Emergency Hybrid Schedule Override](#emergency-hybrid-schedule-override) -7. [Refined Emergency Exit Conditions](#refined-emergency-exit-conditions) -8. [Redesigned Emergency LIB Computation](#redesigned-emergency-lib-computation) -9. [Network Behavior](#network-behavior) -10. [Configuration and Constants](#configuration-and-constants) -11. [Comprehensive Concurrency Protection](#comprehensive-concurrency-protection) -12. [Implementation Details](#implementation-details) -13. [Troubleshooting Guide](#troubleshooting-guide) -14. [Conclusion](#conclusion) - -## Introduction - -The Emergency Consensus System is a critical safety mechanism implemented in the VIZ blockchain to maintain network continuity during extended periods of network stall or validator failure. This system automatically activates when the blockchain stops producing blocks for a predetermined timeout period, ensuring the network remains functional even when regular validator production is compromised. - -The system operates as a three-state safety enforcement mechanism, providing automatic recovery capabilities that prevent network paralysis during emergencies. It maintains consensus integrity while allowing the network to recover from various failure scenarios including validator failures, network partitions, or other catastrophic events. - -**Updated** Enhanced with comprehensive emergency consensus constants and configuration options including CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC, CHAIN_EMERGENCY_WITNESS_ACCOUNT, CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS, CHAIN_IRREVERSIBLE_THRESHOLD, and CHAIN_MAX_WITNESSES * 10 threshold that establishes the foundation for emergency consensus mode activation and operation with deterministic synchronization detection during replay, reindex, and live sync scenarios. - -## System Architecture - -The Emergency Consensus System is built on a distributed architecture that integrates multiple components working together to maintain blockchain functionality: - -```mermaid -graph TB -subgraph "Consensus Layer" -DB[Database Engine] -WS[validator Schedule] -DGP[Dynamic Global Properties] -END -subgraph "Emergency Components" -EW[Emergency validator] -FD[Fork Database] -WC[Validator Plugin] -OG[Operation Guards] -END -subgraph "Network Layer" -P2P[P2P Network] -BP[Block Production] -END -subgraph "Safety Mechanisms" -HC[Hardfork Control] -TM[Timeout Monitor] -EC[Emergency Checker] -MEM[Memory Manager] -ERR[Error Handler] -SD[Deterministic Sync Detector] -SR[Schedule Recovery] -HO[Hybrid Override] -IR[Irreversible Threshold] -END -DB --> WS -DB --> DGP -DB --> FD -DB --> OG -WS --> EW -DGP --> EC -EC --> HC -EC --> TM -EC --> SD -EC --> SR -EC --> HO -EC --> IR -EC --> MEM -EC --> ERR -WC --> BP -BP --> P2P -EC -.-> DB -TM -.-> DB -SD -.-> DB -SR -.-> DB -HO -.-> DB -IR -.-> DB -MEM -.-> DB -ERR -.-> DB -HC -.-> DB -OG -.-> DB -``` - -**Diagram sources** -- [database.cpp:4863-5004](file://libraries/chain/database.cpp#L4863-L5004) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) -- [validator.cpp:422-427](file://plugins/validator/validator.cpp#L422-L427) -- [database.cpp:1556](file://libraries/chain/database.cpp#L1556) -- [chainbase.hpp:1097-1115](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1097-L1115) - -The architecture consists of several key layers: - -- **Consensus Layer**: Core blockchain state management and validator scheduling -- **Emergency Components**: Specialized emergency validator and fork database modifications with operation guards -- **Network Layer**: Peer-to-peer communication and block propagation -- **Safety Mechanisms**: Hardfork coordination, timeout monitoring, deterministic synchronization detection, memory management, error handling, automatic schedule recovery, hybrid schedule override, and irreversible threshold validation - -## Core Components - -### Dynamic Global Properties - -The emergency consensus state is maintained through the dynamic global properties object, which tracks critical consensus parameters: - -```mermaid -classDiagram -class dynamic_global_property_object { -+uint32_t head_block_number -+block_id_type head_block_id -+time_point_sec time -+account_name_type current_witness -+uint32_t last_irreversible_block_num -+bool emergency_consensus_active -+uint32_t emergency_consensus_start_block -+uint32_t last_irreversible_block_ref_num -+uint32_t last_irreversible_block_ref_prefix -} -class witness_object { -+account_name_type owner -+public_key_type signing_key -+version running_version -+hardfork_version hardfork_version_vote -+time_point_sec hardfork_time_vote -+uint32_t total_missed -+uint64_t last_aslot -} -class witness_schedule_object { -+fc : : uint128_t current_virtual_time -+uint32_t next_shuffle_block_num -+account_name_type[] current_shuffled_witnesses -+uint8_t num_scheduled_witnesses -+version majority_version -} -dynamic_global_property_object --> witness_schedule_object : "references" -witness_schedule_object --> witness_object : "contains" -``` - -**Diagram sources** -- [global_property_object.hpp:24-146](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L146) -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) - -### Enhanced Emergency validator Implementation - -The emergency validator serves as the automated consensus producer during emergency conditions with comprehensive management: - -| Property | Value | Description | -|----------|-------|-------------| -| Account Name | `committee` | Emergency validator account identifier | -| Public Key | `VIZ75CRHVHPwYiUESy1bgN3KhVFbZCQQRA9jT6TnpzKAmpxMPD6Xv` | Block signing key | -| Role | Automated Producer | Produces blocks when network is stalled | -| Schedule Priority | Top | Takes precedence over all other validators | -| Version Synchronization | Automatic | Matches current binary version | -| Hardfork Alignment | Current Status | Votes for currently applied hardfork | -| Penalty Management | Reset | All penalties cleared during emergency | - -**Section sources** -- [config.hpp:114-124](file://libraries/protocol/include/graphene/protocol/config.hpp#L114-L124) -- [witness_objects.hpp:47-61](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L47-L61) - -## Enhanced Emergency Consensus Activation - -### Deterministic Synchronization Detection - -The emergency consensus activation is now protected by a deterministic synchronization detection mechanism that prevents false activations during node replay, reindex, or live sync scenarios: - -```mermaid -flowchart TD -Start([Block Applied]) --> CheckHF{"Hardfork 12 Active?"} -CheckHF --> |No| Normal[Normal Operation] -CheckHF --> |Yes| CheckActive{"Emergency Active?"} -CheckActive --> |Yes| Normal -CheckActive --> |No| CheckLIB["Check LIB Availability"] -CheckLIB --> CheckEmpty{"Is Block Log Empty?"} -CheckEmpty --> |Yes| SkipCheck["Skip Emergency Check"] -CheckEmpty --> |No| CalcTime["Calculate Time Since LIB"] -CalcTime --> CheckTimeout{"Seconds Since LIB ≥ 3600?"} -CheckTimeout --> |No| Normal -CheckTimeout --> |Yes| Activate["Activate Emergency Mode"] -Activate --> CreateWitness["Create/Update Emergency validator Object"] -CreateWitness --> ResetPenalties["Reset All validator Penalties"] -ResetPenalties --> OverrideSchedule["Override Schedule with Emergency validator"] -OverrideSchedule --> NotifyFork["Notify Fork Database"] -NotifyFork --> LogEvent["Log Emergency Activation"] -LogEvent --> Normal -SkipCheck --> Normal -Normal --> End([End]) -``` - -**Diagram sources** -- [database.cpp:4863-5004](file://libraries/chain/database.cpp#L4863-L5004) -- [config.hpp:110-128](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L128) - -### Enhanced Activation Triggers with Deterministic Synchronization Detection - -The system now implements comprehensive validation with deterministic synchronization detection: - -1. **LIB Availability Validation**: Uses block_log to verify LIB timestamp before activation -2. **Timeout Threshold**: 3,600 seconds (1 hour) since last irreversible block -3. **Hardfork Activation**: Requires CHAIN_HARDFORK_12 to be active -4. **Network Stall Detection**: No blocks produced within timeout period -5. **Snapshot Compatibility**: Handles DLT mode scenarios with proper LIB availability checking -6. **Error Prevention**: Skips emergency check when LIB timestamp cannot be determined -7. **Deterministic Behavior**: Same results on replay as original application - -**Section sources** -- [database.cpp:4863-5004](file://libraries/chain/database.cpp#L4863-L5004) -- [database.cpp:4887-4906](file://libraries/chain/database.cpp#L4887-L4906) - -## Automatic Schedule Recovery - -### Startup Schedule Repair Mechanism - -The system now includes comprehensive automatic schedule recovery that repairs broken validator schedules during node startup: - -```mermaid -sequenceDiagram -participant DB as Database -participant WSO as validator Schedule -participant DGP as Dynamic Global Properties -DB->>DB : Node Startup -DB->>DGP : Load DGP Object -DB->>WSO : Load validator Schedule -DB->>DB : Check for Empty Slots -alt Empty Slots Found -DB->>DGP : Activate Emergency Mode -DB->>WSO : Override All Slots with Committee -DB->>DB : Log Recovery -else Valid Schedule -DB->>DB : Restore Emergency Mode Flag -end -DB->>DB : Continue Normal Operation -``` - -**Diagram sources** -- [database.cpp:303-357](file://libraries/chain/database.cpp#L303-L357) - -### Comprehensive Schedule Repair Logic - -The automatic schedule recovery system addresses several critical scenarios: - -- **Crash Recovery**: Repairs schedules that became corrupted when nodes shut down during emergency mode -- **Empty Slot Detection**: Identifies validator schedules with null validator names in shuffled positions -- **Emergency Mode Restoration**: Reactivates emergency mode when broken schedules are detected -- **Complete Override**: Fills all schedule slots with emergency validator to ensure network stability -- **Next Shuffle Adjustment**: Updates next shuffle block number to ensure immediate schedule override - -**Section sources** -- [database.cpp:303-357](file://libraries/chain/database.cpp#L303-L357) - -## Emergency Hybrid Schedule Override - -### Dynamic Schedule Adjustment Logic - -The emergency system now implements sophisticated hybrid schedule override that dynamically adjusts validator assignments based on real validator availability: - -```mermaid -flowchart TD -Start([Schedule Update]) --> CheckHF{"Hardfork 12 Active?"} -CheckHF --> |No| Normal[Normal Operation] -CheckHF --> |Yes| CheckEmergency{"Emergency Active?"} -CheckEmergency --> |No| Normal -CheckEmergency --> |Yes| ScanSchedule["Scan Current Shuffled Schedule"] -ScanSchedule --> CountSlots["Count Real vs Committee Slots"] -CountSlots --> CheckAvailability{"Real validators Available?"} -CheckAvailability --> |Yes| FillCommittee["Fill Empty Slots with Committee"] -CheckAvailability --> |No| AllCommittee["All Slots = Committee"] -FillCommittee --> ExpandSchedule["Expand to Max validators"] -AllCommittee --> ExpandSchedule -ExpandSchedule --> UpdateNextShuffle["Update Next Shuffle Block"] -UpdateNextShuffle --> SyncCommittee["Sync Committee Props"] -SyncCommittee --> LogHybrid["Log Hybrid Schedule"] -LogHybrid --> End([End]) -Normal --> End -``` - -**Diagram sources** -- [database.cpp:2561-2591](file://libraries/chain/database.cpp#L2561-L2591) - -### Advanced Hybrid Schedule Features - -The emergency hybrid schedule override provides sophisticated validator management: - -- **Real validator Detection**: Identifies available real validators vs. empty/invalid slots -- **Dynamic Allocation**: Fills empty slots with emergency validator automatically -- **Schedule Expansion**: Expands schedule to include all 21 validators for proper rotation -- **Next Shuffle Optimization**: Adjusts next shuffle block to ensure immediate override -- **Committee Synchronization**: Keeps emergency validator properties synchronized with current state -- **Threshold-Based Logic**: Uses 75% threshold for emergency exit conditions - -**Section sources** -- [database.cpp:2561-2591](file://libraries/chain/database.cpp#L2561-L2591) -- [database.cpp:2596-2612](file://libraries/chain/database.cpp#L2596-L2612) - -## Refined Emergency Exit Conditions - -### Intelligent Automatic Deactivation - -The emergency consensus mode deactivates automatically when intelligent conditions are met: - -```mermaid -flowchart TD -Start([Emergency Active]) --> MonitorLIB["Monitor LIB Progress"] -MonitorLIB --> CheckProgress{"LIB > Start Block?"} -CheckProgress --> |No| Continue["Continue Emergency Mode"] -CheckProgress --> |Yes| CheckRecovery["Check Real validator Recovery"] -CheckRecovery --> CountReal["Count Real validator Slots"] -CountReal --> CheckThreshold{"Real validators ≥ 75%?"} -CheckThreshold --> |No| Continue -CheckThreshold --> |Yes| Deactivate["Deactivate Emergency Mode"] -Deactivate --> ClearFlag["Clear Emergency Flag"] -ClearFlag --> NotifyFork["Notify Fork Database"] -NotifyFork --> LogExit["Log Exit Condition Met"] -LogExit --> Continue -Continue --> End([End]) -``` - -**Diagram sources** -- [database.cpp:2614-2631](file://libraries/chain/database.cpp#L2614-L2631) - -### Advanced Exit Criteria with Enhanced Monitoring - -The system evaluates several sophisticated conditions for emergency mode exit: - -1. **LIB Advancement**: Last Irreversible Block number exceeds start block -2. **Network Recovery**: 75% of schedule slots are real validators (not committee) -3. **Automatic Trigger**: 21 consecutive blocks produced by emergency validator -4. **Manual Intervention**: System administrator override possible -5. **Real-time Monitoring**: Continuous LIB progress tracking during emergency -6. **Deterministic Synchronization**: Prevents premature exit during replay scenarios -7. **Consensus Validation**: Ensures network stability before deactivation - -**Section sources** -- [database.cpp:2614-2631](file://libraries/chain/database.cpp#L2614-L2631) -- [config.hpp:125-128](file://libraries/protocol/include/graphene/protocol/config.hpp#L125-L128) - -## Redesigned Emergency LIB Computation - -### Normal LIB Advancement During Emergency - -The emergency system now implements redesigned LIB computation that advances normally using all validators including committee: - -```mermaid -sequenceDiagram -participant DB as Database -participant WSO as validator Schedule -participant DPO as Dynamic Properties -DB->>DB : Update Last Irreversible Block -DB->>WSO : Get Scheduled validators -WSO-->>DB : Committee + Real validators -DB->>DB : Calculate Support Threshold -DB->>DB : Find Median Support -alt Emergency Mode -DB->>DB : Cap at Head-1 for Safety -DB->>DPO : Commit New LIB -else Normal Mode -DB->>DPO : Commit New LIB -end -DB->>DB : Update Block Log -``` - -**Diagram sources** -- [database.cpp:5473-5545](file://libraries/chain/database.cpp#L5473-L5545) - -### Enhanced LIB Computation Logic - -The redesigned emergency LIB computation provides: - -- **Normal Advancement**: LIB advances using all validators in schedule (including committee) -- **Safety Cap**: Caps LIB at head-1 during emergency to preserve undo protection -- **Median Calculation**: Uses validator support thresholds to determine LIB safely -- **Emergency Protection**: Prevents permanent state corruption during crashes -- **Seamless Transition**: Allows normal LIB computation to resume after emergency exit - -**Section sources** -- [database.cpp:5473-5545](file://libraries/chain/database.cpp#L5473-L5545) -- [database.cpp:5515-5529](file://libraries/chain/database.cpp#L5515-L5529) - -## Network Behavior - -### Enhanced Peer Connection Management - -During emergency mode, the system implements special peer connection handling with enhanced stability measures and deterministic synchronization: - -| Scenario | Action | Rationale | -|----------|--------|-----------| -| Multiple Emergency Producers | Prefer lower block ID hash | Prevents network splits | -| Cascade Disconnections | Prevention measures | Maintains network stability | -| Block Propagation | Normal P2P behavior | Ensures consensus continuity | -| Fork Collisions | Deterministic resolution | Reduces network fragmentation | -| Replay Scenarios | Deterministic handling | Prevents false activations | - -### Comprehensive validator Participation Override - -The emergency system bypasses normal validator participation requirements with enhanced error handling and deterministic synchronization: - -- **Participation Rate Checks**: Automatically enabled during emergency -- **Stale Block Production**: Allowed without penalties -- **Production Scheduling**: Emergency validator takes precedence -- **Conflict Resolution**: Enhanced tie-breaking algorithms -- **Schedule Updates**: Hybrid schedule during emergency mode -- **Deterministic Sync Detection**: Prevents immediate participation during replay -- **Penalty Management**: Comprehensive reset of all validator penalties - -**Section sources** -- [validator.cpp:422-427](file://plugins/validator/validator.cpp#L422-L427) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) - -## Configuration and Constants - -### Enhanced Emergency Consensus Parameters - -The system uses comprehensive configurable constants with enhanced monitoring and deterministic synchronization: - -| Parameter | Value | Unit | Description | -|-----------|-------|------|-------------| -| CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC | 3600 | Seconds | Timeout threshold | -| CHAIN_EMERGENCY_WITNESS_ACCOUNT | "committee" | Account | Emergency producer | -| CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY | VIZ75CR... | Key | Block signing key | -| CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS | 21 | Blocks | Consecutive blocks to exit | -| CHAIN_IRREVERSIBLE_THRESHOLD | 75% | Percent | Recovery threshold | -| CHAIN_MAX_WITNESSES | 21 | validators | Total validator count | -| CHAIN_MAX_WITNESSES * 10 | 210 | Blocks | Deterministic sync threshold | - -### Hardfork Configuration with Enhanced Protection - -The emergency consensus requires specific hardfork activation with comprehensive deterministic synchronization protection: - -- **Hardfork Version**: 12 -- **Activation Time**: 1776620500 (Unix timestamp) -- **Protocol Version**: 3.1.0 -- **Required Nodes**: Majority consensus for activation -- **Deterministic Sync Detection**: Prevents false activations during replay -- **Emergency Activation**: Requires both hardfork and sync detection validation - -**Section sources** -- [config.hpp:110-128](file://libraries/protocol/include/graphene/protocol/config.hpp#L110-L128) -- [12.hf:1-7](file://libraries/chain/hardfork.d/12.hf#L1-L7) - -## Comprehensive Concurrency Protection - -### Advanced Operation Guard Implementation - -The system now implements comprehensive concurrency protection through operation guards that ensure thread-safe emergency mode operations: - -```mermaid -classDiagram -class operation_guard { -+database& _db -+bool _active -+operation_guard(database& db) -+~operation_guard() -+void release() -+operation_guard(operation_guard&& other) -} -class database { -+void enter_operation() -+void exit_operation() -+operation_guard make_operation_guard() -+bool _resize_in_progress -+uint32_t _active_operations -} -class chainbase { -+void begin_resize_barrier() -+void end_resize_barrier() -+void with_read_lock() -+void with_write_lock() -} -operation_guard --> database : "manages" -database --> chainbase : "extends" -``` - -**Diagram sources** -- [chainbase.hpp:1097-1115](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1097-L1115) -- [database.cpp:1721](file://libraries/chain/database.cpp#L1721) -- [database.cpp:1556](file://libraries/chain/database.cpp#L1556) - -### Enhanced Memory Management with Operation Guards - -The enhanced memory management system includes comprehensive operation guard integration: - -- **Pre-resize Protection**: Operation guards prevent concurrent access during memory resizing -- **Thread Safety**: All emergency mode operations are protected by operation guards -- **Concurrent Access Control**: Prevents race conditions during emergency activation -- **Resource Management**: Automatic cleanup of operation guards on scope exit -- **Exception Safety**: Operation guards are properly cleaned up on exceptions - -**Section sources** -- [chainbase.hpp:1097-1115](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1097-L1115) -- [database.cpp:1721](file://libraries/chain/database.cpp#L1721) -- [database.cpp:1556](file://libraries/chain/database.cpp#L1556) - -## Implementation Details - -### Enhanced Database Integration - -The emergency consensus system integrates deeply with the blockchain database with comprehensive error handling and deterministic synchronization: - -```mermaid -classDiagram -class database { -+bool has_hardfork(uint32_t) -+void update_global_dynamic_data() -+void update_signing_witness() -+void update_last_irreversible_block() -+void check_block_post_validation_chain() -+void process_hardforks() -+bool _resize(uint32_t block_num) -+void check_free_memory(bool skip_print, uint32_t current_block_num) -+operation_guard make_operation_guard() -+void _node_startup_time -+bool _enable_emergency_mode -} -class emergency_consensus_system { -+bool emergency_consensus_active -+uint32_t emergency_consensus_start_block -+void activate_emergency_mode() -+void deactivate_emergency_mode() -+bool check_timeout_conditions() -+bool check_deterministic_sync_detection() -+void repair_schedule_on_startup() -+void apply_hybrid_schedule_override() -} -database --> emergency_consensus_system : "manages" -``` - -**Diagram sources** -- [database.cpp:4863-5004](file://libraries/chain/database.cpp#L4863-L5004) -- [database.hpp:37-612](file://libraries/chain/include/graphene/chain/database.hpp#L37-L612) - -### Advanced Error Handling with Deterministic Synchronization Protection - -The system implements comprehensive error handling throughout the consensus process with enhanced deterministic synchronization protection: - -- **LIB Availability Checks**: Validates LIB timestamp before emergency activation -- **Snapshot Compatibility**: Handles DLT mode scenarios gracefully -- **Memory Management Errors**: Provides detailed logging for memory operations -- **Fork Database Exceptions**: Enhanced error reporting for fork operations -- **validator Creation Failures**: Comprehensive error handling for emergency validator setup -- **Operation Guard Protection**: Thread-safe emergency mode operations -- **Deterministic Behavior**: Same results on replay as original application - -**Section sources** -- [database.cpp:4863-5004](file://libraries/chain/database.cpp#L4863-L5004) -- [database.cpp:4887-4906](file://libraries/chain/database.cpp#L4887-L4906) - -## Troubleshooting Guide - -### Enhanced Common Issues - -| Issue | Symptoms | Solution | -|-------|----------|----------| -| Emergency Mode Not Activating | No automatic blocks produced | Verify hardfork 12 activation, LIB availability, and sync detection | -| Emergency Mode Stuck | Cannot exit emergency mode | Check LIB advancement, memory management logs, and sync detection validation | -| Network Instability | Frequent disconnections | Review fork database settings, memory usage, and deterministic sync detection | -| validator Production Failures | Emergency validator cannot produce blocks | Verify emergency key configuration, memory allocation, and operation guard protection | -| Memory Issues | Low free memory warnings | Check memory management configuration, resize logs, and operation guard usage | -| Replay Scenarios | Delayed emergency activation | Verify replay detection and ensure CHAIN_MAX_WITNESSES * 10 threshold is observed | -| False Activations | Premature emergency activation | Check deterministic sync detection and LIB timestamp availability | -| Snapshot Restores | Deadlock during emergency activation | Verify DLT mode handling and LIB timestamp validation | -| Broken Schedules | Empty validator slots after crash | Check automatic schedule recovery and emergency mode flags | -| Hybrid Schedule Issues | Incorrect validator assignments | Verify hybrid schedule override logic and real validator detection | - -### Advanced Diagnostic Commands - -To troubleshoot emergency consensus issues with enhanced monitoring: - -1. **Check Emergency Status**: Verify `emergency_consensus_active` flag and start block -2. **Monitor Sync Detection**: Check replay/reindex detection and large block gap validation -3. **Monitor LIB Progress**: Track irreversible block advancement and timestamp -4. **Validate Timeout Logs**: Check activation/deactivation timestamps and LIB availability -5. **Validate Deterministic Sync**: Ensure sync detection passes during replay scenarios -6. **Check Operation Guards**: Monitor thread safety and concurrent access protection -7. **Validate validator Configuration**: Ensure emergency validator exists with correct key and schedule -8. **Monitor Memory Usage**: Check free, reserved, and maximum memory states with operation guard protection -9. **Check Schedule Recovery**: Verify automatic schedule repair and emergency mode restoration -10. **Validate Hybrid Override**: Monitor dynamic validator assignment during emergency - -### Performance Considerations - -- **Memory Usage**: Emergency mode may increase fork database size with detailed logging and operation guard overhead -- **Network Bandwidth**: Additional block propagation during emergency with enhanced monitoring -- **CPU Load**: Extra processing for emergency block validation with deterministic sync detection -- **Storage Impact**: Extended fork database retention during emergencies with better memory management -- **Logging Overhead**: Enhanced detailed logging for troubleshooting with comprehensive operation guard tracking -- **Thread Safety**: Operation guards add minimal overhead for thread-safe emergency mode operations -- **Deterministic Performance**: CHAIN_MAX_WITNESSES * 10 threshold prevents immediate emergency activation during sync -- **Replay Compatibility**: Same results on replay as original application with deterministic behavior - -**Section sources** -- [database.cpp:4863-5004](file://libraries/chain/database.cpp#L4863-L5004) -- [fork_database.cpp:81-88](file://libraries/chain/fork_database.cpp#L81-L88) -- [database.cpp:1556](file://libraries/chain/database.cpp#L1556) - -## Conclusion - -The Emergency Consensus System represents a sophisticated safety mechanism designed to maintain blockchain functionality during critical network failures. By implementing automatic activation, deterministic network behavior, and clear exit conditions, the system provides robust protection against network stalls while maintaining consensus integrity. - -**Updated** The enhanced system now features comprehensive emergency consensus constants and configuration options including CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC for timeout threshold control, CHAIN_EMERGENCY_WITNESS_ACCOUNT for emergency producer configuration, CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS for automatic exit conditions, CHAIN_IRREVERSIBLE_THRESHOLD for recovery validation, and CHAIN_MAX_WITNESSES * 10 threshold for deterministic synchronization detection. These constants establish the foundation for emergency consensus mode that activates when no blocks have been produced for the specified timeout period while preventing false activations during replay, reindex, and live sync scenarios. - -The system's three-state safety enforcement approach ensures that the network can recover from various failure scenarios without requiring manual intervention. Through careful integration with existing consensus mechanisms, network protocols, and comprehensive operation guard protection, the emergency system operates seamlessly with minimal disruption to normal network operations. - -Key enhancements include: -- **Automatic Schedule Recovery**: Comprehensive repair of broken validator schedules during node startup -- **Emergency Hybrid Schedule Override**: Dynamic adjustment of validator assignments based on real validator availability -- **Refined Exit Conditions**: Improved real validator recovery validation using 75% threshold -- **Redesigned LIB Computation**: Normal LIB advancement using all validators during emergency -- **Deterministic Sync Detection**: CHAIN_MAX_WITNESSES * 10 threshold prevents false activations during replay and catch-up scenarios -- **Automatic Recovery**: No manual intervention required for activation with comprehensive validation -- **Network Stability**: Prevents cascade failures during emergencies with enhanced tie-breaking -- **Consensus Integrity**: Maintains blockchain validity during recovery with improved error handling -- **Operational Continuity**: Ensures service availability during outages with comprehensive monitoring -- **Enhanced Reliability**: Improved detection algorithms, memory management, and operation guard protection -- **Better Troubleshooting**: Detailed logging and monitoring capabilities for easier diagnostics -- **Configurable Parameters**: Flexible timeout thresholds, exit conditions, and sync detection for different network conditions -- **Robust Emergency validator**: Dedicated emergency validator with proper key configuration, schedule override, and comprehensive penalty management -- **Thread-Safe Operations**: Comprehensive operation guard protection ensures concurrent access safety -- **Deterministic Behavior**: Same results on replay as original application with comprehensive sync detection -- **Advanced Concurrency Control**: Operation guards provide comprehensive thread safety for emergency mode operations - -The implementation demonstrates best practices in distributed systems design, providing a reliable foundation for blockchain resilience and operational continuity with significantly improved reliability, monitoring capabilities, and thread safety through comprehensive operation guard protection. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Core Libraries/NTP Synchronization System.md b/.qoder/repowiki/en/content/Core Libraries/NTP Synchronization System.md deleted file mode 100644 index d2c044bd59..0000000000 --- a/.qoder/repowiki/en/content/Core Libraries/NTP Synchronization System.md +++ /dev/null @@ -1,582 +0,0 @@ -# NTP Synchronization System - - -**Referenced Files in This Document** -- [ntp.hpp](file://thirdparty/fc/include/fc/network/ntp.hpp) -- [ntp.cpp](file://thirdparty/fc/src/network/ntp.cpp) -- [time.hpp](file://libraries/time/include/graphene/time/time.hpp) -- [time.cpp](file://libraries/time/time.cpp) -- [main.cpp](file://programs/vizd/main.cpp) -- [ntp_test.cpp](file://thirdparty/fc/tests/network/ntp_test.cpp) -- [validator.cpp](file://plugins/validator/validator.cpp) - - -## Update Summary -**Changes Made** -- Enhanced NTP server configuration validation with improved port parsing error handling -- Added comprehensive error validation for NTP server configuration strings -- Updated configuration parsing section to reflect new error handling mechanisms -- Revised troubleshooting guide to address new validation scenarios - -## Table of Contents -1. [Introduction](#introduction) -2. [System Architecture](#system-architecture) -3. [Core Components](#core-components) -4. [NTP Implementation Details](#ntp-implementation-details) -5. [Time Management Layer](#time-management-layer) -6. [Integration with Blockchain Operations](#integration-with-blockchain-operations) -7. [Configuration and Tuning](#configuration-and-tuning) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction - -The NTP (Network Time Protocol) Synchronization System in the VIZ blockchain node provides accurate time synchronization across distributed network participants. This system ensures that all nodes maintain consistent time references, which is critical for blockchain operations such as transaction validation, block production scheduling, and consensus mechanisms. - -The NTP system operates independently of the main blockchain processing but integrates seamlessly with the time management layer to provide synchronized time services to all components of the VIZ node. It implements robust error handling, statistical filtering, and automatic failover mechanisms to maintain reliable time synchronization even in challenging network conditions. - -**Updated** Enhanced with comprehensive server configuration validation and improved port parsing error handling for more reliable NTP server setup. - -## System Architecture - -The NTP synchronization system follows a layered architecture with clear separation of concerns: - -```mermaid -graph TB -subgraph "Application Layer" -VIZD[vizd Main Application] -Plugins[Blockchain Plugins] -end -subgraph "Time Management Layer" -GrapheneTime[Graphene Time Manager] -Config[Configuration Manager] -Validation[Server Validation] -end -subgraph "NTP Service Layer" -FCNTP[FC NTP Service] -Thread[NTP Worker Thread] -Socket[UDP Socket] -end -subgraph "Network Layer" -NTPServers[NTP Server Pool] -Network[Internet] -end -VIZD --> GrapheneTime -Plugins --> GrapheneTime -GrapheneTime --> Validation -GrapheneTime --> FCNTP -FCNTP --> Thread -Thread --> Socket -Socket --> NTPServers -Network --> NTPServers -Config -.-> GrapheneTime -Validation -.-> GrapheneTime -``` - -**Diagram sources** -- [main.cpp:108-142](file://programs/vizd/main.cpp#L108-L142) -- [time.cpp:53-79](file://libraries/time/time.cpp#L53-L79) -- [ntp.cpp:19-61](file://thirdparty/fc/src/network/ntp.cpp#L19-L61) - -The architecture consists of three main layers: - -1. **Application Layer**: The main VIZ node application and blockchain plugins that consume time services -2. **Time Management Layer**: Graphene's time management system that provides a unified interface for time operations and includes enhanced server validation -3. **NTP Service Layer**: The underlying FC (Fast Crypto) NTP implementation with dedicated worker threads - -## Core Components - -### FC NTP Service - -The FC NTP service provides the core time synchronization functionality through a sophisticated UDP-based implementation: - -```mermaid -classDiagram -class ntp { -+ntp() -+~ntp() -+add_server(hostname, port) -+set_servers(servers) -+set_request_interval(interval_sec) -+set_retry_interval(interval_sec) -+set_round_trip_threshold_ms(ms) -+set_delta_history_size(size) -+set_rejection_threshold_pct(pct) -+set_rejection_min_threshold_ms(ms) -+request_now() -+get_time() optional~time_point~ -} -class ntp_impl { --_ntp_thread thread --_ntp_hosts vector~pair~string,uint16_t~~ --_sock udp_socket --_request_interval_sec uint32_t --_retry_failed_request_interval_sec uint32_t --_last_valid_ntp_reply_received_time time_point --_last_ntp_delta_initialized atomic_bool --_last_ntp_delta_microseconds atomic_int64_t --_delta_history deque~int64_t~ --_round_trip_threshold_us uint32_t --_rejection_threshold_pct uint32_t --_rejection_min_threshold_us uint32_t -+start_read_loop() -+read_loop() -+request_time_task() -+request_now() -+ntp_timestamp_to_fc_time_point(ts) -+fc_time_point_to_ntp_timestamp(tp) -} -ntp --> ntp_impl : "owns" -ntp_impl --> udp_socket : "uses" -ntp_impl --> thread : "creates" -``` - -**Diagram sources** -- [ntp.hpp:17-52](file://thirdparty/fc/include/fc/network/ntp.hpp#L17-L52) -- [ntp.cpp:19-61](file://thirdparty/fc/src/network/ntp.cpp#L19-L61) - -### Graphene Time Manager - -The Graphene time manager provides a higher-level interface that integrates NTP services with the broader blockchain ecosystem: - -```mermaid -classDiagram -class time_manager { --_ntp_service atomic~ntp*~ --_ntp_config_mutex mutex --_ntp_service_initialization_mutex mutex --_pending_ntp_config ntp_config -+configure_ntp(config) -+ntp_time() optional~time_point~ -+now() time_point -+nonblocking_now() time_point -+update_ntp_time() -+ntp_error() microseconds -+shutdown_ntp_time() -+start_simulated_time(sim_time) -+advance_simulated_time_to(sim_time) -+advance_time(delta_seconds) -+time_discontinuity_signal signal -} -class ntp_config { -+servers vector~string~ -+request_interval_sec uint32_t -+retry_interval_sec uint32_t -+round_trip_threshold_ms uint32_t -+history_size uint32_t -+rejection_threshold_pct uint32_t -+rejection_min_threshold_ms uint32_t -} -time_manager --> ntp : "manages" -time_manager --> ntp_config : "applies" -``` - -**Diagram sources** -- [time.hpp:17-61](file://libraries/time/include/graphene/time/time.hpp#L17-L61) -- [time.cpp:19-51](file://libraries/time/time.cpp#L19-L51) - -**Section sources** -- [ntp.hpp:17-52](file://thirdparty/fc/include/fc/network/ntp.hpp#L17-L52) -- [time.hpp:17-61](file://libraries/time/include/graphene/time/time.hpp#L17-L61) - -## NTP Implementation Details - -### Time Synchronization Algorithm - -The NTP implementation uses the standard NTPv4 algorithm with several enhancements for blockchain-specific requirements: - -```mermaid -sequenceDiagram -participant Client as "Client Request" -participant NTP as "NTP Service" -participant Server as "NTP Server" -participant Filter as "Statistical Filter" -Client->>NTP : request_now() -NTP->>Server : Send NTP Packet -Server-->>NTP : Receive NTP Response -Note over NTP : Calculate RTT and Offset -NTP->>NTP : Calculate round-trip delay -NTP->>NTP : Calculate time offset -alt Valid Response -NTP->>Filter : Validate against thresholds -Filter-->>NTP : Accept/Reject Decision -alt Accepted -NTP->>NTP : Update delta history -NTP->>NTP : Calculate moving average -NTP->>NTP : Update last valid time -else Rejected -NTP->>NTP : Log rejection -NTP->>Server : Send new request -end -else Stale Response -NTP->>NTP : Request new time -end -Client-->>Client : Return synchronized time -``` - -**Diagram sources** -- [ntp.cpp:176-236](file://thirdparty/fc/src/network/ntp.cpp#L176-L236) - -### Statistical Filtering Mechanism - -The system implements a sophisticated statistical filtering mechanism to reject outliers and maintain accuracy: - -| Parameter | Default Value | Purpose | -|-----------|---------------|---------| -| `request_interval_sec` | 900 (15 minutes) | Time between regular updates | -| `retry_failed_request_interval_sec` | 300 (5 minutes) | Retry interval for failed requests | -| `round_trip_threshold_us` | 150,000 (150ms) | Maximum acceptable round-trip delay | -| `history_size` | 5 | Number of samples in moving average | -| `rejection_threshold_pct` | 50% | Percentage deviation threshold | -| `rejection_min_threshold_us` | 5,000 (5ms) | Minimum absolute deviation threshold | - -**Section sources** -- [ntp.cpp:43-57](file://thirdparty/fc/src/network/ntp.cpp#L43-L57) -- [time.cpp:27-50](file://libraries/time/time.cpp#L27-L50) - -## Time Management Layer - -### Enhanced Server Configuration Validation - -**Updated** The Graphene time manager now includes comprehensive validation for NTP server configuration strings with improved error handling: - -```mermaid -flowchart TD -Start([Server Configuration Input]) --> ParseString["Parse Server String"] -ParseString --> CheckColon{"Contains ':'?"} -CheckColon --> |Yes| ExtractHostPort["Extract Host and Port"] -CheckColon --> |No| UseDefaultPort["Use Default Port 123"] -ExtractHostPort --> ValidatePort["Validate Port Number"] -ValidatePort --> ParseSuccess{"Parse Success?"} -ParseSuccess --> |Yes| AddToParsed["Add to Parsed Servers"] -ParseSuccess --> |No| LogWarning["Log Warning: Invalid Port"] -LogWarning --> UseDefaultPort["Use Default Port 123"] -UseDefaultPort --> AddToParsed -AddToParsed --> NextServer{"More Servers?"} -NextServer --> |Yes| ParseString -NextServer --> |No| ApplyToService["Apply to NTP Service"] -ApplyToService --> ServiceReady([Service Ready]) -``` - -**Diagram sources** -- [time.cpp:29-57](file://libraries/time/time.cpp#L29-L57) - -The validation process includes: - -1. **String Parsing**: Each server string is parsed for host:port format -2. **Port Extraction**: Port numbers are extracted using `rfind(':')` method -3. **Error Handling**: Invalid port numbers are caught with try-catch blocks -4. **Fallback Logic**: Invalid ports fall back to default port 123 -5. **Logging**: Warning messages are logged for invalid configurations - -### Time Service Lifecycle - -The Graphene time manager implements a lazy initialization pattern to ensure efficient resource usage: - -```mermaid -flowchart TD -Start([Application Startup]) --> CheckConfig{"NTP Config Available?"} -CheckConfig --> |Yes| LoadConfig["Load Pending Configuration"] -CheckConfig --> |No| DefaultConfig["Use Defaults"] -LoadConfig --> InitService["Initialize NTP Service"] -DefaultConfig --> InitService -InitService --> CheckService{"Service Exists?"} -CheckService --> |No| CreateService["Create New NTP Service"] -CheckService --> |Yes| UseExisting["Use Existing Service"] -CreateService --> ApplyConfig["Apply Configuration with Validation"] -ApplyConfig --> ValidateServers["Validate Server Configurations"] -ValidateServers --> ApplyToService["Apply Validated Configuration"] -ApplyToService --> Ready([Service Ready]) -UseExisting --> Ready -Ready --> RequestTime["Handle Time Requests"] -RequestTime --> UpdateCheck{"Update Needed?"} -UpdateCheck --> |Yes| ForceUpdate["Force Immediate Update"] -UpdateCheck --> |No| ReturnTime["Return Cached Time"] -ForceUpdate --> ReturnTime -``` - -**Diagram sources** -- [time.cpp:53-79](file://libraries/time/time.cpp#L53-L79) - -### Time Discontinuity Handling - -The system provides mechanisms to handle time discontinuities gracefully: - -```mermaid -stateDiagram-v2 -[*] --> NormalOperation -NormalOperation --> TimeJumpDetected : "Large Time Change" -TimeJumpDetected --> GracefulRecovery : "Signal Handlers" -GracefulRecovery --> NormalOperation : "Stabilized" -state TimeJumpDetected { -[*] --> DetectJump -DetectJump --> LogEvent["Log Time Jump"] -LogEvent --> NotifyListeners["Notify Discontinuity Listeners"] -NotifyListeners --> AdjustOffset["Adjust Time Offset"] -AdjustOffset --> [*] -} -``` - -**Diagram sources** -- [time.cpp:134-137](file://libraries/time/time.cpp#L134-L137) - -**Section sources** -- [time.cpp:53-79](file://libraries/time/time.cpp#L53-L79) -- [time.cpp:134-137](file://libraries/time/time.cpp#L134-L137) - -## Integration with Blockchain Operations - -### Block Production Timing - -The NTP system integrates with the blockchain's block production mechanism to ensure proper timing: - -| Component | Integration Point | Purpose | -|-----------|-------------------|---------| -| Validator Plugin | Block slot calculation | Determines when validators can produce blocks | -| Chain Database | Block timestamp validation | Ensures block timestamps are reasonable | -| P2P Plugin | Peer synchronization | Maintains time consistency across network | -| Wallet | Transaction timestamping | Provides accurate timestamps for operations | - -### Transaction Validation Dependencies - -The time synchronization affects several critical validation processes: - -```mermaid -graph LR -subgraph "Transaction Validation" -T1[Expiration Check] -T2[Signature Validation] -T3[Authority Check] -end -subgraph "Time Dependencies" -NTPT[NTPT Time Source] -NOW[Current Time] -LATE[Late Block Detection] -end -NTPT --> T1 -NTPT --> T2 -NOW --> T1 -LATE --> T1 -style T1 fill:#ffcccc -style T2 fill:#ccffcc -style T3 fill:#ffffcc -``` - -**Diagram sources** -- [p2p_plugin.cpp:158-161](file://plugins/p2p/p2p_plugin.cpp#L158-L161) - -**Section sources** -- [p2p_plugin.cpp:158-161](file://plugins/p2p/p2p_plugin.cpp#L158-L161) - -## Configuration and Tuning - -### Runtime Configuration Options - -The NTP system provides extensive runtime configuration capabilities: - -| Configuration Option | Type | Default | Description | -|---------------------|------|---------|-------------| -| `servers` | `vector` | `["pool.ntp.org:123", "time.google.com:123", "time.cloudflare.com:123"]` | NTP server pool with host:port format | -| `request_interval_sec` | `uint32_t` | 900 | Interval between regular updates (seconds) | -| `retry_interval_sec` | `uint32_t` | 300 | Retry interval for failed requests (seconds) | -| `round_trip_threshold_ms` | `uint32_t` | 150 | Maximum acceptable round-trip delay (milliseconds) | -| `history_size` | `uint32_t` | 5 | Number of samples in moving average | -| `rejection_threshold_pct` | `uint32_t` | 50 | Percentage deviation threshold | -| `rejection_min_threshold_ms` | `uint32_t` | 5 | Minimum absolute deviation threshold (milliseconds) | - -### Enhanced Configuration Application Process - -**Updated** The configuration application process now includes comprehensive server validation: - -```mermaid -flowchart TD -Config[New Configuration] --> ParseServers["Parse Server Strings with Validation"] -ParseServers --> ValidateIntervals["Validate Intervals"] -ValidateIntervals --> ValidateThresholds["Validate Thresholds"] -ValidateThresholds --> ApplyToService["Apply to Active Service"] -ApplyToService --> UpdateExisting["Update Existing Service"] -ApplyToService --> CreateNew["Create New Service Instance"] -UpdateExisting --> RestartTasks["Restart Background Tasks"] -CreateNew --> InitializeTasks["Initialize New Tasks"] -RestartTasks --> ServiceReady([Service Updated]) -InitializeTasks --> ServiceReady -``` - -**Diagram sources** -- [time.cpp:29-57](file://libraries/time/time.cpp#L29-L57) - -### Server Configuration Validation - -**New** The system now validates NTP server configurations with comprehensive error handling: - -The server configuration validation process includes: - -1. **Format Validation**: Each server string is checked for proper host:port format -2. **Port Parsing**: Port numbers are extracted and validated using `std::stoul()` -3. **Error Recovery**: Invalid port numbers trigger fallback to default port 123 -4. **Logging**: Warning messages are generated for invalid configurations -5. **Graceful Degradation**: Invalid entries don't prevent service initialization - -**Section sources** -- [time.hpp:17-40](file://libraries/time/include/graphene/time/time.hpp#L17-L40) -- [time.cpp:29-57](file://libraries/time/time.cpp#L29-L57) - -## Performance Considerations - -### Memory Management - -The NTP system implements efficient memory management strategies: - -- **Thread-Safe Design**: Uses atomic operations for shared state -- **Circular Buffer**: Implements a deque-based history buffer with fixed capacity -- **Lazy Initialization**: Creates NTP service instances only when needed -- **Resource Cleanup**: Properly manages socket connections and thread resources - -### Network Efficiency - -The system optimizes network usage through: - -- **Connection Reuse**: Reuses UDP sockets across requests -- **Batch Processing**: Processes multiple NTP servers in sequence -- **Timeout Handling**: Implements appropriate timeouts for network operations -- **Error Recovery**: Automatically handles temporary network failures - -### Scalability Factors - -Key factors affecting NTP system scalability: - -| Factor | Impact | Optimization Strategy | -|--------|--------|----------------------| -| Server Pool Size | Directly affects reliability | Monitor response rates and adjust dynamically | -| History Window Size | Affects smoothing quality | Balance between responsiveness and stability | -| Update Frequency | Affects network traffic | Tune based on network conditions | -| Threshold Values | Affects accuracy vs. reliability | Calibrate based on deployment environment | - -## Troubleshooting Guide - -### Common Issues and Solutions - -#### NTP Service Not Starting - -**Symptoms**: Time functions return invalid results or throw exceptions - -**Causes**: -- Network connectivity issues -- Firewall blocking UDP port 123 -- DNS resolution failures -- Insufficient privileges - -**Solutions**: -1. Verify network connectivity to NTP servers -2. Check firewall settings for UDP port 123 -3. Test DNS resolution manually -4. Run with appropriate privileges - -#### Time Drift Problems - -**Symptoms**: Persistent time differences between nodes - -**Causes**: -- Incorrect system clock -- Network latency variations -- Server pool configuration issues - -**Solutions**: -1. Check system clock synchronization -2. Adjust round-trip delay thresholds -3. Modify server pool configuration -4. Review network topology - -#### Performance Degradation - -**Symptoms**: Slow response times or frequent timeouts - -**Causes**: -- Network congestion -- Excessive update frequency -- Insufficient history window - -**Solutions**: -1. Reduce update frequency -2. Increase retry intervals -3. Expand history window -4. Optimize network routing - -### Server Configuration Issues - -**Updated** New troubleshooting scenarios for enhanced server validation: - -#### Invalid Server Configuration Entries - -**Symptoms**: Warning messages about invalid port numbers in logs - -**Causes**: -- Malformed server strings (missing or invalid port numbers) -- Non-numeric port values -- Missing colons in server format - -**Solutions**: -1. Verify server strings use format `host:port` -2. Ensure port numbers are valid integers between 1-65535 -3. Check for typos in server hostnames -4. Remove entries with invalid configurations - -#### Port Parsing Errors - -**Symptoms**: Automatic fallback to default port 123 in logs - -**Causes**: -- Invalid port numbers in configuration -- Out-of-range port values -- Non-integer port specifications - -**Solutions**: -1. Use valid port numbers (1-65535) -2. Ensure ports are accessible and not blocked by firewalls -3. Test port connectivity using network tools -4. Consider using default port 123 for standard NTP servers - -### Diagnostic Commands - -The system provides several diagnostic capabilities: - -```bash -# Check current NTP status -curl -s http://localhost:8090/get_ntp_status - -# Monitor time synchronization -watch -n 1 'echo "Current time offset: $(./cli_wallet get_ntp_offset)"' - -# Verify NTP server connectivity -nslookup pool.ntp.org - -# Check for server configuration warnings -tail -f logs/vizd.log | grep "NTP: invalid port" -``` - -**Section sources** -- [ntp_test.cpp:9-28](file://thirdparty/fc/tests/network/ntp_test.cpp#L9-L28) - -## Conclusion - -The NTP Synchronization System in the VIZ blockchain node represents a robust, production-ready solution for maintaining accurate time across distributed network participants. The system's architecture balances reliability, performance, and ease of maintenance through several key design principles: - -**Key Strengths**: -- **Fault Tolerance**: Multiple NTP server support with automatic failover -- **Statistical Filtering**: Advanced outlier detection prevents time corruption -- **Thread Safety**: Concurrent access patterns with proper synchronization -- **Configurability**: Extensive runtime tuning options for various environments -- **Enhanced Validation**: Comprehensive server configuration validation with error recovery -- **Integration**: Seamless integration with blockchain operations and APIs - -**Operational Benefits**: -- **Consistent Timekeeping**: Ensures all blockchain operations use synchronized time -- **Network Coordination**: Enables proper block production and transaction validation -- **Reliability**: Maintains service availability even under adverse conditions -- **Performance**: Optimized for minimal resource usage while maximizing accuracy -- **Resilience**: Graceful handling of configuration errors and network issues - -**Updated** The recent enhancements to server configuration validation significantly improve the system's robustness by automatically handling malformed server entries and providing clear error feedback. This makes the NTP system more resilient to configuration mistakes while maintaining backward compatibility. - -The system's modular design allows for easy maintenance and extension, making it well-suited for the evolving needs of blockchain infrastructure. Its integration with the broader VIZ ecosystem demonstrates thoughtful engineering that prioritizes both technical excellence and operational practicality. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Cloud and Infrastructure.md b/.qoder/repowiki/en/content/Deployment and Operations/Cloud and Infrastructure.md deleted file mode 100644 index 6f7deab33e..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Cloud and Infrastructure.md +++ /dev/null @@ -1,360 +0,0 @@ -# Cloud and Infrastructure - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides cloud and infrastructure deployment guidance for the VIZ CPP Node. It focuses on containerized deployment using the official Docker images, highlights runtime configuration via environment variables and configuration files, and outlines operational practices for high availability, scaling, and observability. Where applicable, it references the repository’s Dockerfiles, configuration templates, and CI/CD workflows to ground recommendations in the existing build and packaging artifacts. - -## Project Structure -The repository includes: -- Container build definitions for production, low-memory, and testnet variants -- Runtime configuration templates for mainnet and testnet -- A container entrypoint script that initializes data directories, applies optional overrides, and starts the node -- GitHub Actions workflows that build and publish Docker images - -```mermaid -graph TB -subgraph "Container Images" -prod["Dockerfile-production"] -lowmem["Dockerfile-lowmem"] -testnet["Dockerfile-testnet"] -end -subgraph "Runtime" -cfg_main["config.ini"] -cfg_test["config_testnet.ini"] -entry["vizd.sh"] -end -gh_main[".github/workflows/docker-main.yml"] -gh_pr[".github/workflows/docker-pr-build.yml"] -prod --> cfg_main -lowmem --> cfg_main -testnet --> cfg_test -entry --> cfg_main -entry --> cfg_test -gh_main --> prod -gh_main --> testnet -gh_pr --> testnet -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -**Section sources** -- [README.md](file://README.md#L12-L29) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L88) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -## Core Components -- Container images - - Production image: optimized for mainnet operation - - Low-memory image: tuned for constrained environments - - Testnet image: preconfigured for testnet operation -- Runtime configuration - - Mainnet configuration template - - Testnet configuration template -- Entrypoint script - - Initializes data directory - - Applies environment-driven overrides - - Starts the node process - -Key runtime ports exposed by the images: -- RPC HTTP: 8090 -- RPC WS: 8091 -- P2P: 2001 - -Volumes: -- Data directory: persists blockchain data -- Config directory: allows mounting custom configuration - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L74-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L68-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L75-L88) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L16-L20) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L16-L20) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L74-L81) - -## Architecture Overview -The recommended deployment model is container-first: -- Run one or more VIZ node containers behind a load balancer -- Persist blockchain data via volumes -- Mount configuration files and seed node lists as needed -- Use environment variables to override endpoints and enable validator operation when required - -```mermaid -graph TB -LB["Load Balancer
HTTP 8090 / WS 8091"] -subgraph "Nodes" -N1["vizd Container 1"] -N2["vizd Container 2"] -Nn["... n replicas"] -end -VOL["Persistent Volume
/var/lib/vizd"] -CFG["Config Volume
/etc/vizd"] -LB --> N1 -LB --> N2 -LB --> Nn -N1 --> VOL -N2 --> VOL -Nn --> VOL -N1 --> CFG -N2 --> CFG -Nn --> CFG -``` - -[No sources needed since this diagram shows conceptual workflow, not actual code structure] - -## Detailed Component Analysis - -### Container Images and Build Artifacts -- Production image - - Multi-stage build with a base image and a final runtime stage - - Installs compiled binaries and sets up a dedicated user - - Exposes RPC and P2P ports - - Declares persistent volumes for data and config -- Low-memory image - - Similar structure to production but enables a low-memory mode during build -- Testnet image - - Includes testnet-specific configuration and snapshot - - Enables validator operation by default - -Operational notes: -- The images are published by the CI/CD workflows -- The workflows build and push images tagged as latest and testnet - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) - -### Runtime Configuration and Environment Overrides -The node reads configuration from a mounted config file and supports environment-driven overrides via the entrypoint script. Notable runtime parameters include: -- RPC endpoint binding -- P2P endpoint binding -- validator name and private key -- Seed nodes list -- Optional extra arguments - -Configuration templates: -- Mainnet template defines default endpoints, plugin list, and logging configuration -- Testnet template adds validator operation and adjusts participation thresholds - -```mermaid -flowchart TD -Start(["Container Start"]) --> LoadCfg["Load config.ini or config_testnet.ini"] -LoadCfg --> ApplyEnv["Apply environment overrides
RPC, P2P, Seed, validator, Private Key"] -ApplyEnv --> InitData["Initialize data directory
and blockchain cache if present"] -InitData --> RunNode["Start vizd with merged args"] -RunNode --> End(["Running"]) -``` - -**Diagram sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L39-L81) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L13-L39) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -### CI/CD and Image Publishing -- The main workflow builds and pushes: - - testnet image on push to master - - latest image on push to master -- The PR workflow builds a testnet image and tags it with the PR ref - -These workflows provide a baseline for automated image creation and can be extended to support cloud-native deployment pipelines. - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -## Dependency Analysis -The runtime depends on: -- Base OS packages installed in the image -- Compiled node binary and shared libraries -- Persistent volumes for data and configuration -- Network connectivity to peers and clients - -```mermaid -graph LR -IMG["Docker Image"] --> BIN["vizd Binary"] -IMG --> LIBS["Shared Libraries"] -IMG --> CFG["Config Files"] -BIN --> DATA["/var/lib/vizd"] -BIN --> LOGS["Logs"] -NET["Network Ports
8090/8091/2001"] --> PEERS["Peers"] -NET --> CLIENTS["Clients"] -``` - -[No sources needed since this diagram shows conceptual relationships, not specific code structure] - -## Performance Considerations -- Single write thread: the configuration encourages a single-threaded write path to reduce contention on the database lock -- Shared memory sizing: initial and incremental sizes are configurable to balance memory footprint and growth overhead -- Thread pool sizing: the HTTP server thread pool can be tuned to match CPU cores -- Lock wait limits: read/write lock wait microsecond and retry counts help manage concurrency under load - -Recommendations: -- Size shared memory according to expected chain state growth and available host memory -- Adjust thread pool size based on CPU cores and expected concurrent requests -- Monitor lock wait metrics and tune retries if contention is observed - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L36-L47) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) - -## Troubleshooting Guide -Common operational checks: -- Verify ports 8090 (HTTP), 8091 (WS), and 2001 (P2P) are reachable -- Confirm persistent volume is mounted and writable -- Review logs written to the configured log directories -- Validate seed nodes connectivity and adjust seed list if needed -- For validator nodes, confirm validator name and private key are set appropriately - -Environment overrides: -- Override RPC and P2P endpoints if necessary -- Provide custom seed nodes via environment variable -- Enable validator operation by setting validator name and private key - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L31-L37) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L111-L130) - -## Conclusion -The repository provides a solid foundation for deploying VIZ CPP Node in containers with production-ready images and configuration templates. By leveraging environment-driven overrides, persistent volumes, and a load-balanced deployment pattern, operators can achieve high availability and scalability. The CI/CD workflows automate image creation, which can be extended to support cloud-native deployment pipelines. - -## Appendices - -### Cloud Provider Deployment Strategies -- AWS - - Compute: choose general-purpose or compute-optimized instances depending on workload; consider EBS gp3 or io2 for IOPS-sensitive workloads - - Networking: place nodes in private subnets behind NAT for outbound access; expose RPC via Application Load Balancer with WSS support - - Storage: provision EBS volumes or EFS for shared data; enable snapshots for backups - - IAM: attach least-privilege roles for metadata and logging -- Google Cloud - - Compute: use sustained use discounts or committed use commitments; SSD persistent disks for performance - - Networking: Cloud Load Balancing with internal and external tiers; firewall rules for RPC and P2P ports - - Storage: Persistent Disk or Filestore; Cloud Backup for snapshots -- Azure - - Compute: burstable or DC-series VMs; Premium SSD disks for IOPS - - Networking: Load Balancer or Application Gateway; NSGs for port-based access - - Storage: Managed Disks or NetApp Files; Automation Account for scheduled snapshots - -[No sources needed since this section provides general guidance] - -### Infrastructure-as-Code Approaches -- Terraform - - Define VPC, subnets, security groups, autoscaling groups, and load balancers - - Provision managed volumes and attach to instances - - Use data sources to fetch latest container image digests for immutability -- CloudFormation (AWS) - - StackSets for multi-account/multi-region rollouts - - Parameterized templates for environment-specific tuning -- Other IaC tools - - Pulumi, Ansible playbooks, or Helm charts for Kubernetes-native deployments - -[No sources needed since this section provides general guidance] - -### Load Balancing, Auto Scaling, and High Availability -- Load balancing - - Distribute traffic across multiple node instances - - Enable health checks on RPC endpoints -- Auto scaling - - Scale on CPU, network I/O, or request latency - - Use predictive scaling for predictable traffic patterns -- High availability - - Deploy across multiple AZs - - Use standby nodes with failover mechanisms - -[No sources needed since this section provides general guidance] - -### CDN Integration, SSL/TLS, and Security Groups -- CDN - - Offload static assets and public APIs via CDN fronting -- SSL/TLS - - Terminate TLS at the load balancer; secure backend-to-backend communication -- Security groups/firewall - - Allow inbound RPC only from trusted networks - - Permit P2P egress to seed nodes and restrict ingress to necessary ports - -[No sources needed since this section provides general guidance] - -### Monitoring, Alerting, Log Aggregation, and Metrics -- Cloud-native tools - - Use platform-native observability suites for metrics and logs -- Log aggregation - - Stream node logs to centralized logging systems -- Metrics - - Track RPC latency, throughput, peer count, and disk usage - -[No sources needed since this section provides general guidance] - -### Cost Optimization, Reserved/Spot Instances -- Reserved instances - - Commit to steady-state capacity with savings plans or reserved instances -- Spot instances - - Use for fault-tolerant, stateless workers; avoid critical validator slots - -[No sources needed since this section provides general guidance] - -### Disaster Recovery, Backups, and Multi-Region -- Backups - - Snapshot persistent volumes regularly; retain offsite copies -- DR - - Replicate volumes across regions; automate failover procedures -- Multi-region - - Operate secondary region for read replicas or cold standby - -[No sources needed since this section provides general guidance] - -### Migration, Blue-Green, and Rolling Upgrades -- Blue-green - - Deploy new version alongside live; switch traffic on validation -- Rolling upgrades - - Upgrade nodes in batches with health checks between batches -- Migration - - Use snapshots to migrate between providers or regions - -[No sources needed since this section provides general guidance] \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Containerization and Docker.md b/.qoder/repowiki/en/content/Deployment and Operations/Containerization and Docker.md deleted file mode 100644 index c5d88b64b9..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Containerization and Docker.md +++ /dev/null @@ -1,440 +0,0 @@ -# Containerization and Docker - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini) -- [share/vizd/config/config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive guidance for containerizing and deploying the VIZ C++ Node using Docker. It covers image variants (production, testnet, low-memory, and MongoDB-enabled), Dockerfile construction and multi-stage builds, configuration and runtime customization, orchestration patterns, volume and network management, security and resource best practices, monitoring integration, deployment workflows, scaling, upgrades, and troubleshooting. - -## Project Structure -The repository includes Dockerfiles and configuration templates under share/vizd/docker and share/vizd/config, along with CI/CD workflows for automated image builds. - -```mermaid -graph TB -A["Dockerfiles"] --> A1["Dockerfile-production"] -A --> A2["Dockerfile-testnet"] -A --> A3["Dockerfile-lowmem"] -A --> A4["Dockerfile-mongo"] -B["Config Templates"] --> B1["config.ini"] -B --> B2["config_testnet.ini"] -B --> B3["config_mongo.ini"] -B --> B4["config_debug.ini"] -B --> B5["config_debug_mongo.ini"] -C["Entrypoint Script"] --> C1["vizd.sh"] -D["CI/CD Workflows"] --> D1[".github/workflows/docker-main.yml"] -D --> D2[".github/workflows/docker-pr-build.yml"] -E["MongoDB Plugin"] --> E1["mongo_db_plugin.hpp"] -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini#L1-L126) -- [share/vizd/config/config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) - -**Section sources** -- [README.md](file://README.md#L12-L52) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -## Core Components -- Docker image variants: - - Production: Built from master, intended for the main VIZ network. - - Testnet: Built from master, suitable for local/regression test networks. - - Low-memory: Optimized for constrained environments. - - MongoDB-enabled: Includes the mongo_db plugin for external database indexing/export. -- Entrypoint script: Initializes volumes, applies environment overrides, seeds peers, and starts the node. -- Configuration templates: Provide defaults for RPC endpoints, P2P, logging, plugins, and optional MongoDB connectivity. - -Key runtime environment variables supported by the entrypoint: -- VIZD_SEED_NODES: Override seed nodes. -- VIZD_WITNESS_NAME: Configure validator name for block production. -- VIZD_PRIVATE_KEY: Private key for signing blocks. -- VIZD_RPC_ENDPOINT: Override RPC HTTP endpoint. -- VIZD_P2P_ENDPOINT: Override P2P endpoint. -- VIZD_EXTRA_OPTS: Additional arguments appended to the node command. - -Exposed ports: -- 8090 TCP (HTTP RPC) -- 8091 TCP (WebSocket RPC) -- 2001 TCP (P2P) - -Volumes: -- /var/lib/vizd: Blockchain data directory (blocks, databases, caches). -- /etc/vizd: Configuration directory (config.ini, seednodes). - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L82) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L89-L111) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L13-L81) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L16-L20) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L16-L20) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L16-L20) - -## Architecture Overview -The container architecture consists of a multi-stage build that compiles the node in a builder stage and installs artifacts into a minimal runtime base. The runtime stage sets up a non-root user, exposes ports, mounts volumes, and starts the node via an entrypoint script. - -```mermaid -graph TB -subgraph "Builder Stage" -B1["Install build deps
cmake, gcc, boost, python3"] -B2["Clone repo and submodules"] -B3["Configure with CMake
Release build"] -B4["Compile and install"] -end -subgraph "Runtime Stage" -R1["Minimal base image"] -R2["Create non-root user 'vizd'"] -R3["Set up cache and config dirs"] -R4["Copy installed binaries and assets"] -R5["Expose ports 8090, 8091, 2001"] -R6["Mount volumes /var/lib/vizd, /etc/vizd"] -R7["Entrypoint script 'vizd.sh'"] -end -B4 --> R4 -R7 --> R8["Start vizd process"] -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L74-L81) - -## Detailed Component Analysis - -### Dockerfile-production -- Purpose: Production image for the main VIZ network. -- Build characteristics: - - Multi-stage build with a dedicated builder stage. - - Installs build dependencies and compiles Release binaries. - - Copies installed artifacts to the runtime stage. -- Runtime characteristics: - - Creates non-root user and cache directories. - - Copies default config and seednodes. - - Exposes RPC and P2P ports. - - Declares volumes for data and config. - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) - -### Dockerfile-testnet -- Purpose: Testnet image for local/regression testing. -- Differences from production: - - Enables BUILD_TESTNET during CMake configuration. - - Uses testnet-specific snapshot and config template. -- Runtime characteristics: - - Same runtime setup as production. - -**Section sources** -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) - -### Dockerfile-lowmem -- Purpose: Low-memory footprint variant for constrained environments. -- Differences: - - Configures LOW_MEMORY_NODE during CMake. - - Uses an older base image tag for smaller size. -- Runtime characteristics: - - Same runtime setup and exposed ports. - -**Section sources** -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) - -### Dockerfile-mongo -- Purpose: MongoDB-enabled image for external indexing/export. -- Differences: - - Installs MongoDB C/C++ drivers in the builder stage. - - Configures ENABLE_MONGO_PLUGIN during CMake. - - Copies mongo-specific config template. -- Runtime characteristics: - - Same runtime setup and volumes. - -**Section sources** -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) - -### Entrypoint Script (vizd.sh) -Responsibilities: -- Initialize ownership for data/config directories. -- Seed peers from /etc/vizd/seednodes if no explicit override is provided. -- Apply environment overrides for validator name/private key, RPC/P2P endpoints, and extra options. -- Optionally initialize blockchain from a cached snapshot if present. -- Start the node with chpst under the non-root user. - -```mermaid -flowchart TD -Start(["Entrypoint Start"]) --> InitDirs["Initialize ownership for /var/lib/vizd and /etc/vizd"] -InitDirs --> LoadSeed["Load default seednodes from /etc/vizd/seednodes"] -LoadSeed --> CheckEnvSeed{"VIZD_SEED_NODES set?"} -CheckEnvSeed --> |Yes| UseEnvSeed["Append user-provided seed nodes"] -CheckEnvSeed --> |No| UseDefaultSeed["Append default seed nodes"] -UseEnvSeed --> ApplyWitness["Apply VIZD_WITNESS_NAME and VIZD_PRIVATE_KEY if provided"] -UseDefaultSeed --> ApplyWitness -ApplyWitness --> CopyCfg["Copy /etc/vizd/config.ini to /var/lib/vizd/config.ini"] -CopyCfg --> CheckSnapshot{"Cached snapshot exists?"} -CheckSnapshot --> |Yes| Replay["Enable replay and extract snapshot to database"] -CheckSnapshot --> |No| PrepareArgs["Prepare default args"] -Replay --> PrepareArgs -PrepareArgs --> OverrideEndpoints["Apply VIZD_RPC_ENDPOINT and VIZD_P2P_ENDPOINT"] -OverrideEndpoints --> ExecNode["Exec vizd with args and VIZD_EXTRA_OPTS"] -ExecNode --> End(["Container Running"]) -``` - -**Diagram sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -### Configuration Templates -- config.ini: Default production configuration with RPC endpoints, plugin list, and logging. -- config_testnet.ini: Testnet-specific configuration with validator participation enabled and test keys. -- config_mongo.ini: MongoDB-enabled configuration with mongodb-uri and mongo_db plugin enabled. -- Debug configs: Smaller shared memory and debug logging for development/testing. - -Key configurable areas: -- RPC endpoints (HTTP and WS) -- P2P endpoint and seed nodes -- Plugin list and options -- Logging appenders and levels -- Optional MongoDB URI for mongo_db plugin - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini#L1-L126) -- [share/vizd/config/config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini#L1-L135) - -### MongoDB Plugin Integration -- The mongo_db plugin is declared in the mongo-enabled configuration and requires a MongoDB connection URI. -- The plugin header defines the plugin lifecycle hooks and dependencies. - -**Section sources** -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L72) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) - -## Dependency Analysis -- Dockerfiles depend on: - - Base image (phusion/baseimage) for runtime. - - Builder stage installing build tools and dependencies. - - CMake configuration flags controlling features (BUILD_TESTNET, LOW_MEMORY_NODE, ENABLE_MONGO_PLUGIN). -- Entrypoint depends on: - - Presence of config and seednodes in /etc/vizd. - - Optional cached snapshot in /var/cache/vizd. -- CI/CD workflows depend on Docker build-push action and Docker Hub credentials. - -```mermaid -graph LR -DFProd["Dockerfile-production"] --> |builds| IMGProd["Image: latest"] -DFTnet["Dockerfile-testnet"] --> |builds| IMGTnet["Image: testnet"] -DFLowmem["Dockerfile-lowmem"] --> |builds| IMGLowmem["Image: lowmem variant"] -DFMongo["Dockerfile-mongo"] --> |builds| IMGMongo["Image: mongo variant"] -IMGProd --> CFGProd["config.ini"] -IMGTnet --> CFGTnet["config_testnet.ini"] -IMGMongo --> CFGMongo["config_mongo.ini"] -Entryp["vizd.sh"] --> IMGProd -Entryp --> IMGTnet -Entryp --> IMGMongo -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) - -## Performance Considerations -- Multi-stage builds reduce final image size by discarding build tools and intermediate artifacts. -- Using Release builds and disabling shared library builds reduces binary size and improves runtime performance characteristics. -- Low-memory variant reduces shared memory footprint and related allocations for constrained environments. -- MongoDB driver installation adds overhead; enable only when required. -- Tune webserver-thread-pool-size and shared memory parameters per workload and CPU cores. -- Prefer pre-seeded snapshots to accelerate initial sync on first run. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Ports already in use: - - Ensure host ports 8090, 8091, and 2001 are available or map to different host ports. -- Permission denied on data directory: - - Verify /var/lib/vizd is writable by the non-root user. -- No connectivity to seed nodes: - - Override VIZD_SEED_NODES with reachable nodes or ensure default seednodes are present. -- MongoDB plugin failures: - - Confirm mongodb-uri is reachable from the container network and plugin is enabled in config. -- Slow initial sync: - - Provide a cached snapshot in /var/cache/vizd to enable automatic replay on first run. -- Logs not visible: - - Check default and p2p log appenders configured in the active config template. - -Operational tips: -- Use docker logs -f to stream logs. -- Adjust log levels in config for verbose diagnostics. -- Validate configuration syntax by mounting a custom config.ini to /etc/vizd and copying it into /var/lib/vizd during startup. - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L44-L53) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L112-L129) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L116-L134) - -## Conclusion -The VIZ C++ Node provides a robust set of Docker images tailored for production, testnet, low-memory, and MongoDB-enabled deployments. The multi-stage Dockerfiles, entrypoint-driven configuration, and modular config templates enable flexible, secure, and efficient containerized operations. By leveraging volumes, environment overrides, and CI/CD automation, operators can reliably deploy, scale, and maintain VIZ nodes in containerized environments. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Image Variants and Use Cases -- Production (latest): - - Use for main VIZ network. - - Built from master with standard production settings. -- Testnet (testnet): - - Use for local/regression testing. - - Includes testnet-specific snapshot and configuration. -- Low-memory: - - Use on constrained systems. - - Optimized shared memory and build settings. -- MongoDB-enabled: - - Use when external indexing/export to MongoDB is required. - - Includes MongoDB drivers and plugin configuration. - -**Section sources** -- [README.md](file://README.md#L16-L29) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L54) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L53) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L82) - -### Dockerfile Construction and Optimization -- Multi-stage builds: - - Builder stage installs build tools and compiles Release binaries. - - Runtime stage copies installed artifacts and sets up a minimal environment. -- Optimization techniques: - - Clean package manager caches after installs. - - Remove temporary build artifacts post-install. - - Use non-root user and restrict filesystem permissions. - - Keep base image versions pinned for reproducibility. - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L61-L64) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L62-L65) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L55-L58) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L32-L57) - -### Container Orchestration and Service Discovery -- Docker Compose: - - Define services with port mappings, volumes, environment variables, and restart policies. - - Use networks to connect vizd with MongoDB (for mongo-enabled variant). -- Kubernetes: - - Deploy as a StatefulSet with persistent volume claims for /var/lib/vizd. - - Use ConfigMaps for config.ini and seednodes. - - Expose services for RPC (TCP 8090/8091) and P2P (TCP 2001). - - Implement readiness/liveness probes against RPC endpoints. - -[No sources needed since this section provides general guidance] - -### Volume Management and Network Configuration -- Volumes: - - /var/lib/vizd: Persistent blockchain data and caches. - - /etc/vizd: Configuration and seednodes. -- Network: - - Publish 8090/8091 for RPC and 2001 for P2P. - - For mongo-enabled, ensure MongoDB is reachable via mongodb-uri. - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L87-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L87-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L81-L82) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L110-L111) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L71-L72) - -### Security Best Practices and Resource Limits -- Security: - - Run as non-root user. - - Limit container capabilities and mount only necessary volumes. - - Pin base image versions and rebuild periodically. -- Resource limits: - - Set CPU/memory limits appropriate for workload. - - Monitor shared memory growth and adjust shared-file-size accordingly. - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L70-L72) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L70-L72) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L63-L66) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) - -### Monitoring Integration -- Logs: - - Use console and file appenders defined in config templates. - - Stream container logs and ship to centralized logging systems. -- Health checks: - - Probe RPC endpoints for readiness. -- Metrics: - - Expose metrics via plugins if available; otherwise monitor logs and container stats. - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L112-L129) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L113-L131) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L116-L134) - -### Deployment Workflows, Scaling, and Upgrades -- Workflows: - - Automated builds for master branch (latest) and PRs (testnet). -- Scaling: - - Stateless RPC: Scale horizontally behind a load balancer. - - P2P: Coordinate seed nodes and network topology carefully. -- Upgrades: - - Pull new image tag, stop container, backup /var/lib/vizd, start with same volumes and env. - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) -- [README.md](file://README.md#L40-L52) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Deployment and Operations.md b/.qoder/repowiki/en/content/Deployment and Operations/Deployment and Operations.md deleted file mode 100644 index 069f172ab3..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Deployment and Operations.md +++ /dev/null @@ -1,425 +0,0 @@ -# Deployment and Operations - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [building.md](file://documentation/building.md) -- [testnet.md](file://documentation/testnet.md) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [docker-main.yml](file://.github/workflows/docker-main.yml) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_witness.ini](file://share/vizd/config/config_witness.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [vizd.sh](file://share/vizd/vizd.sh) -- [main.cpp](file://programs/vizd/main.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive deployment and operations guidance for the VIZ CPP Node. It covers production deployment strategies, hardware and security considerations, containerization with multiple image variants, orchestration options, cloud deployment, high availability, node types (full, validator, seed), monitoring and maintenance, security hardening, troubleshooting, and backup/disaster recovery. - -## Project Structure -The repository organizes deployment assets and operational artifacts primarily under share/vizd, with configuration templates, Dockerfiles, and scripts for containerized deployments. Documentation for building and testnet operations resides under documentation/. - -```mermaid -graph TB -A["Repository Root"] --> B["share/vizd"] -B --> B1["config/ (configs)"] -B --> B2["docker/ (Dockerfiles)"] -B --> B3["vizd.sh (entrypoint)"] -A --> C["documentation/ (build, testnet, debug_node)"] -A --> D["programs/vizd/main.cpp (node entry)"] -A --> E[".github/workflows/ (CI/CD)"] -``` - -**Diagram sources** -- [README.md](file://README.md#L1-L53) -- [building.md](file://documentation/building.md#L1-L212) -- [testnet.md](file://documentation/testnet.md#L1-L54) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [main.cpp](file://programs/vizd/main.cpp#L1-L291) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -**Section sources** -- [README.md](file://README.md#L1-L53) -- [building.md](file://documentation/building.md#L1-L212) -- [testnet.md](file://documentation/testnet.md#L1-L54) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [main.cpp](file://programs/vizd/main.cpp#L1-L291) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -## Core Components -- Container images - - Production image: Built from Dockerfile-production, intended for mainnet. - - Testnet image: Built from Dockerfile-testnet, intended for test networks. - - Low-memory image: Built from Dockerfile-lowmem, optimized for resource-constrained environments. -- Configuration templates - - config.ini: General-purpose configuration for mainnet. - - config_testnet.ini: Testnet-specific configuration with validator participation enabled. - - config_witness.ini: validator node configuration with RPC bound to localhost and virtual ops skipped. - - config_mongo.ini: Extended configuration including MongoDB plugin for analytics. -- Entrypoint script - - vizd.sh: Orchestrates seed node injection, RPC/P2P endpoints, replay initialization, and runtime arguments. -- Node binary - - programs/vizd/main.cpp: Registers plugins and initializes the node runtime. - -Key operational parameters and behaviors: -- P2P and RPC endpoints are configurable via environment variables and config files. -- Shared memory sizing and growth thresholds are tunable to manage memory pressure. -- Lock wait timeouts and retries are configurable to balance throughput and latency. -- Plugin selection determines node capabilities (e.g., validator, mongo, debug_node). - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [main.cpp](file://programs/vizd/main.cpp#L60-L91) - -## Architecture Overview -The VIZ node is a modular application with pluggable subsystems for chain processing, P2P networking, webserver APIs, and optional plugins (e.g., validator, mongo, debug_node). Containerization encapsulates dependencies and exposes standardized ports for RPC (HTTP/WebSocket) and P2P communication. - -```mermaid -graph TB -subgraph "Container Runtime" -C1["vizd container"] -end -subgraph "Node Process" -N1["vizd binary
plugins: chain, p2p, webserver, validator, mongo, etc."] -end -subgraph "Storage" -S1["/var/lib/vizd (data dir)"] -S2["/etc/vizd (config)"] -end -subgraph "Networking" -P1["P2P: 2001/tcp"] -P2["RPC HTTP: 8090/tcp"] -P3["RPC WS: 8091/tcp"] -end -C1 --> N1 -N1 --> S1 -N1 --> S2 -N1 --> P1 -N1 --> P2 -N1 --> P3 -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L74-L87) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L68-L79) -- [vizd.sh](file://share/vizd/vizd.sh#L74-L81) -- [config.ini](file://share/vizd/config/config.ini#L1-L20) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L20) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L20) - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L87) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L87) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L79) -- [vizd.sh](file://share/vizd/vizd.sh#L74-L81) -- [config.ini](file://share/vizd/config/config.ini#L1-L20) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L20) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L20) - -## Detailed Component Analysis - -### Container Images and Orchestration -- Production image - - Purpose: Run mainnet nodes. - - Build: Uses Dockerfile-production with Release build and standard plugins. - - Ports exposed: 8090 (HTTP RPC), 8091 (WS RPC), 2001 (P2P). - - Volumes: /var/lib/vizd (blockchain data), /etc/vizd (config). -- Testnet image - - Purpose: Local testnet and development. - - Build: Uses Dockerfile-testnet with BUILD_TESTNET enabled. - - Ports and volumes same as production. -- Low-memory image - - Purpose: Resource-constrained environments; consensus-only behavior. - - Build: Uses Dockerfile-lowmem with LOW_MEMORY_NODE enabled. -- CI/CD - - docker-main.yml builds and pushes latest and testnet tags. - - docker-pr-build.yml builds testnet images for pull requests. - -Operational notes: -- Environment overrides for endpoints and seed nodes are supported via vizd.sh. -- Snapshot caching enables fast startup when blocks are prepackaged. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -### Configuration Templates and Node Types -- Full node (mainnet) - - Use config.ini as baseline. - - Typical plugins include chain, p2p, webserver, database_api, account_history, operation_history, and others. -- Full node (testnet) - - Use config_testnet.ini; includes validator participation and a default validator identity. -- validator node - - Use config_witness.ini; RPC endpoints bound to localhost, skip virtual ops for reduced overhead. - - Configure validator name and private key for block production. -- Analytics node (MongoDB) - - Use config_mongo.ini; includes mongo_db plugin and market history settings. - -Key tuning knobs: -- Shared memory sizing and growth thresholds. -- Read/write lock wait and retries. -- Single write thread for improved database contention handling. -- Plugin notifications on push_transaction can be disabled to improve performance. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L1-L135) - -### Entrypoint Script Behavior -The entrypoint script sets ownership, injects seed nodes (from image or environment), optionally replays from cached snapshot, binds RPC/P2P endpoints, and launches the node with merged arguments. - -```mermaid -flowchart TD -Start(["Start"]) --> SetOwner["Set ownership on /var/lib/vizd"] -SetOwner --> LoadSeed["Load default seednodes or use VIZD_SEED_NODES"] -LoadSeed --> CopyConfig["Copy /etc/vizd/config.ini to $HOME/config.ini"] -CopyConfig --> InitSnapshot{"Snapshot exists?"} -InitSnapshot --> |Yes| Replay["Replay cached snapshot"] -InitSnapshot --> |No| SkipReplay["Skip replay"] -Replay --> BindEndpoints["Bind RPC/P2P endpoints"] -SkipReplay --> BindEndpoints -BindEndpoints --> ExecNode["Launch vizd with merged args"] -ExecNode --> End(["Run"]) -``` - -**Diagram sources** -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -### Node Binary and Plugin Registration -The node binary registers a comprehensive set of plugins, including chain, p2p, webserver, validator, database_api, social_network, account_history, private_message, tags, follow, and optional mongo_db plugin. This defines the node’s capabilities and API surface. - -```mermaid -classDiagram -class VizNode { -+registers plugins -+initializes logging -+starts network and RPC -} -class ChainPlugin -class P2PPlugin -class WebserverPlugin -class WitnessPlugin -class DatabaseApiPlugin -class MongoDbPlugin -VizNode --> ChainPlugin : "registers" -VizNode --> P2PPlugin : "registers" -VizNode --> WebserverPlugin : "registers" -VizNode --> WitnessPlugin : "registers" -VizNode --> DatabaseApiPlugin : "registers" -VizNode --> MongoDbPlugin : "registers (optional)" -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L60-L91) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L60-L91) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) - -### API Workflows and Health Checks -- RPC endpoints - - HTTP: 8090 - - WebSocket: 8091 -- Health checks - - Use HTTP RPC to query dynamic global properties or chain info. - - Monitor P2P connectivity and sync progress. -- validator operations - - Configure validator name and private key for block production. - - Bind RPC to localhost for validator nodes to minimize exposure. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L16-L20) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L16-L20) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L20) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L16-L20) - -## Dependency Analysis -The node depends on: -- Build-time: CMake, compiler toolchain, Boost, OpenSSL, Python3, and optional MongoDB plugin support. -- Runtime: Shared memory for chain state, persistent storage for blockchain data, and network connectivity for P2P. - -```mermaid -graph LR -A["CMake/Build Tools"] --> B["vizd binary"] -C["Boost"] --> B -D["OpenSSL"] --> B -E["Python3"] --> B -F["MongoDB Plugin (optional)"] --> B -G["Shared Memory"] --> B -H["Block Storage (/var/lib/vizd)"] --> B -I["Network (P2P/RPC)"] --> B -``` - -**Diagram sources** -- [building.md](file://documentation/building.md#L3-L16) -- [main.cpp](file://programs/vizd/main.cpp#L1-L31) - -**Section sources** -- [building.md](file://documentation/building.md#L3-L16) -- [main.cpp](file://programs/vizd/main.cpp#L1-L31) - -## Performance Considerations -- Shared memory sizing - - Adjust initial size and increment thresholds to reduce allocation pressure during replay or rapid growth. -- Lock tuning - - Increase single write thread to reduce database contention; tune read/write wait micros and retries to balance latency and throughput. -- Plugin overhead - - Disable plugin notifications on push_transaction to reduce CPU usage. - - Skip virtual operations for nodes not requiring them. -- Concurrency - - Tune webserver thread pool size according to CPU cores. -- Disk I/O - - Use SSD-backed storage for blockchain data and logs. -- Network - - Limit inbound P2P connections and prefer stable peers for consistent sync. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L49-L67) -- [config.ini](file://share/vizd/config/config.ini#L13-L14) -- [config.ini](file://share/vizd/config/config.ini#L42-L47) -- [config.ini](file://share/vizd/config/config.ini#L78-L79) - -## Troubleshooting Guide -Common operational issues and remedies: -- Startup hangs or slow sync - - Verify seed nodes and P2P connectivity; adjust p2p-max-connections. - - Check shared memory thresholds and free space triggers. -- RPC lock errors - - Increase write/read wait micros and retries; consider single-write-thread. -- Insufficient disk space - - Monitor free space thresholds and ensure adequate headroom for shared memory growth. -- validator node not producing blocks - - Confirm validator name and private key are set; ensure required participation threshold is appropriate. -- Debugging state changes - - Use debug_node plugin in isolated, localhost-bound RPC for controlled experiments. - -Security hardening tips: -- Bind RPC to localhost for validator nodes; expose externally via reverse proxy with authentication. -- Use firewalls to restrict P2P ingress to trusted peers. -- Rotate private keys and restrict filesystem permissions on /var/lib/vizd. - -Backup and recovery: -- Back up /var/lib/vizd regularly; maintain snapshots for quick recovery. -- For disaster scenarios, restore snapshot and replay minimal blocks to synchronize. - -**Section sources** -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L20) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L100-L103) -- [config.ini](file://share/vizd/config/config.ini#L22-L34) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) - -## Conclusion -This guide consolidates deployment and operations practices for VIZ CPP Node across containerized, cloud, and bare-metal environments. By leveraging the provided Dockerfiles, configuration templates, and entrypoint script, operators can deploy production-grade nodes, optimize performance, harden security, and maintain reliability with robust monitoring and recovery procedures. - -## Appendices - -### Appendix A: Node Types and Operational Procedures -- Full node - - Use config.ini; expose RPC publicly as needed; monitor P2P connectivity. -- validator node - - Use config_witness.ini; bind RPC to localhost; configure validator and private key. -- Seed node - - Use config.ini; focus on stable connectivity and minimal external exposure; consider low-memory image for constrained environments. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [building.md](file://documentation/building.md#L11-L15) - -### Appendix B: Monitoring and Maintenance -- Logs - - Console and file appenders configured; ensure log rotation and retention policies. -- Health checks - - Query dynamic global properties via HTTP RPC endpoint. -- Database maintenance - - Monitor shared memory growth and free space thresholds; adjust increments and thresholds as needed. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L111-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L113-L132) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L116-L135) - -### Appendix C: Security Hardening Checklist -- Network - - Restrict P2P ingress; whitelist seed nodes; use reverse proxy for RPC. -- Credentials - - Protect private keys; restrict filesystem permissions on data directory. -- Logging - - Avoid verbose logs in production; rotate and retain logs securely. -- Updates - - Pin container image tags; automate updates with CI/CD. - -**Section sources** -- [config_witness.ini](file://share/vizd/config/config_witness.ini#L17-L20) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L72) - -### Appendix D: Cloud Deployment and High Availability -- Orchestration - - Use Kubernetes or Docker Swarm to manage replicas and rolling updates. -- Load balancing - - Place a reverse proxy in front of RPC endpoints; distribute P2P traffic across nodes. -- High availability - - Run multiple full nodes behind a load balancer; maintain hot standby with snapshot-based recovery. - -[No sources needed since this section provides general guidance] - -### Appendix E: Backup and Disaster Recovery -- Snapshots - - Package blockchain snapshot into image for fast startup; store offsite backups. -- Recovery - - Restore snapshot and replay minimal blocks; validate chain integrity. - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L44-L53) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Monitoring and Maintenance.md b/.qoder/repowiki/en/content/Deployment and Operations/Monitoring and Maintenance.md deleted file mode 100644 index 56f6e3179d..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Monitoring and Maintenance.md +++ /dev/null @@ -1,499 +0,0 @@ -# Monitoring and Maintenance - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [config.ini](file://share/vizd/config/config.ini) -- [vizd.sh](file://share/vizd/vizd.sh) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [json_rpc_plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [database_api_plugin.cpp](file://plugins/database_api/plugin.cpp) -- [account_history_plugin.cpp](file://plugins/account_history/plugin.cpp) -- [operation_history_plugin.cpp](file://plugins/operation_history/plugin.cpp) -- [mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [network_broadcast_api_plugin.cpp](file://plugins/network_broadcast_api/network_broadcast_api.cpp) -- [witness_api_plugin.cpp](file://plugins/witness_api/plugin.cpp) -- [block_info_plugin.cpp](file://plugins/block_info/plugin.cpp) -- [raw_block_plugin.cpp](file://plugins/raw_block/plugin.cpp) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp) -- [chain_plugin.cpp](file://plugins/chain/plugin.cpp) -- [building.md](file://documentation/building.md) -- [testnet.md](file://documentation/testnet.md) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) - - -## Update Summary -**Changes Made** -- Added new section on P2P Peer Statistics System for real-time network observability -- Updated Health Checks and Readiness section to include peer statistics monitoring -- Enhanced Performance Monitoring section with P2P network metrics -- Added P2P statistics configuration and operational procedures -- Updated troubleshooting guide with peer statistics diagnostics - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive monitoring and maintenance guidance for VIZ CPP Node operations. It covers health checks, performance metrics collection, system monitoring integration with Prometheus, Grafana, and the ELK stack, log management and rotation, centralized logging, database maintenance (compaction, optimization, backup verification), performance monitoring (CPU, memory, disk I/O, network), proactive maintenance (updates, patches, configuration audits), incident response, capacity planning, and automation via scripts and dashboards. - -**Updated** Added new monitoring capabilities through P2P peer statistics system providing real-time peer health monitoring, latency measurements, and blocking status reporting for improved network observability. - -## Project Structure -The VIZ node is organized around a modular plugin architecture. The runtime is configured via a central configuration file and launched through a shell script wrapper. The webserver and JSON-RPC plugins expose HTTP and WebSocket endpoints for API access. Logging is configured via the configuration file's logging sections. Operational scripts support Docker-based deployments and seed node selection. - -```mermaid -graph TB -subgraph "Runtime" -CFG["Config File
share/vizd/config/config.ini"] -SH["Launcher Script
share/vizd/vizd.sh"] -BIN["vizd Binary"] -end -subgraph "Web/API Layer" -WS["Webserver Plugin
plugins/webserver"] -JR["JSON-RPC Plugin
plugins/json_rpc"] -APIs["Database/Chain APIs
plugins/*/api"] -end -subgraph "Network Monitoring" -P2P["P2P Plugin
plugins/p2p"] -STATS["Peer Statistics System
Real-time Metrics"] -end -subgraph "Storage" -DB["Object Database
chainbase/leveldb"] -LOGS["Logs
logs/"] -end -SH --> BIN -BIN --> CFG -BIN --> WS -WS --> JR -JR --> APIs -BIN --> P2P -P2P --> STATS -BIN --> DB -BIN --> LOGS -``` - -**Diagram sources** -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- [webserver_plugin.hpp:1-62](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L1-L62) -- [json_rpc_plugin.hpp:1-146](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L1-L146) -- [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) - -**Section sources** -- [README.md:1-53](file://README.md#L1-L53) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) - -## Core Components -- Webserver and JSON-RPC: Provide HTTP and WebSocket endpoints for API access and dispatch JSON-RPC requests to registered APIs. -- Logging: Configured via file appenders and loggers in the configuration file. -- P2P Peer Statistics System: Real-time monitoring of peer connections, latency, bandwidth usage, and blocking status for network observability. -- Plugins: Chain, Account History, Operation History, Mongo DB, P2P, Network Broadcast API, validator API, Block Info, Raw Block, Debug Node, and others. -- Runtime launcher: Sets endpoints, seed nodes, and replay options for Docker-based deployments. - -Key configuration and runtime elements: -- Endpoints: P2P, HTTP, WebSocket, and RPC endpoints are defined in the configuration file and can be overridden by environment variables in the launcher script. -- Plugins: Enabled via plugin directives; database API and chain are enabled by default. -- Logging: Console and file appenders with configurable log levels. -- P2P Statistics: Configurable interval-based peer monitoring with latency and bandwidth metrics. - -**Section sources** -- [webserver_plugin.hpp:19-31](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L19-L31) -- [json_rpc_plugin.hpp:13-36](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L13-L36) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [vizd.sh:62-81](file://share/vizd/vizd.sh#L62-L81) -- [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) - -## Architecture Overview -The VIZ node exposes an HTTP and WebSocket interface backed by JSON-RPC. Requests are dispatched to registered API methods. Logging is handled centrally via appenders and loggers. Storage relies on an object database with shared memory sizing controls. Plugins extend functionality and integrate with the chain and APIs. The P2P peer statistics system provides real-time network observability through periodic peer health monitoring. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "Webserver Plugin" -participant JR as "JSON-RPC Plugin" -participant API as "Registered API" -participant P2P as "P2P Plugin" -participant STATS as "Statistics System" -participant DB as "Object Database" -Client->>WS : "HTTP/WS Request" -WS->>JR : "Dispatch JSON-RPC" -JR->>API : "Invoke API Method" -API->>DB : "Read/Write Operations" -DB-->>API : "Result" -API-->>JR : "Response" -JR-->>WS : "JSON-RPC Response" -WS-->>Client : "HTTP/WS Response" -P2P->>STATS : "Collect Peer Metrics" -STATS-->>P2P : "Latency, Bandwidth, Status" -``` - -**Diagram sources** -- [webserver_plugin.hpp:19-31](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L19-L31) -- [json_rpc_plugin.hpp:103-113](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L103-L113) -- [database_api_plugin.cpp:1-200](file://plugins/database_api/plugin.cpp#L1-L200) -- [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) - -## Detailed Component Analysis - -### Health Checks and Readiness -- HTTP/WS endpoints: Configure readiness by ensuring the webserver plugin is active and reachable on the configured HTTP and WebSocket endpoints. -- JSON-RPC health: Use a lightweight method (e.g., a read-only chain property) to validate API responsiveness. -- P2P connectivity: Confirm peers are connected and block production is progressing (for validator nodes). -- **Updated** P2P peer statistics: Monitor peer health through the statistics system for real-time network observability. - -Operational references: -- Endpoints: [config.ini:16-20](file://share/vizd/config/config.ini#L16-L20) -- Webserver lifecycle: [webserver_plugin.hpp:48-52](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L48-L52) -- JSON-RPC lifecycle: [json_rpc_plugin.hpp:103-107](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L103-L107) -- **Updated** P2P statistics configuration: [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) - -**Section sources** -- [config.ini:16-20](file://share/vizd/config/config.ini#L16-L20) -- [webserver_plugin.hpp:48-52](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L48-L52) -- [json_rpc_plugin.hpp:103-107](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L103-L107) -- [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) - -### P2P Peer Statistics System -**New Section** The P2P peer statistics system provides comprehensive real-time monitoring of network peer health and performance metrics. - -#### Statistics Collection Features -- **Peer Connection Monitoring**: Tracks connected peers, IP addresses, and ports -- **Latency Measurements**: Real-time round-trip delay in milliseconds -- **Bandwidth Tracking**: Bytes received metrics with delta calculations -- **Blocking Status Reporting**: Identifies peers under soft-ban or inhibition -- **Reason Analysis**: Provides blocking reasons for network policy enforcement - -#### Configuration Options -- `p2p-stats-enabled`: Enable/disable periodic peer statistics logging (default: true) -- `p2p-stats-interval`: Interval between statistics dumps in seconds (default: 300) - -#### Statistics Fields -- **IP Address**: Peer's network address -- **Port**: Peer's listening port -- **Latency**: Round-trip delay in milliseconds -- **Bytes Received**: Delta bytes received since last measurement -- **Blocked Status**: Boolean indicating soft-ban/inhibition state -- **Blocked Reason**: Network policy reason for blocking - -#### Implementation Details -The statistics system runs as a scheduled task that: -1. Queries connected peers from the network node -2. Extracts peer information from variant objects -3. Calculates byte delta values for bandwidth monitoring -4. Logs formatted peer statistics with ANSI color coding -5. Schedules next execution based on configured interval - -**Section sources** -- [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) -- [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) -- [node.hpp:172-179](file://libraries/network/include/graphene/network/node.hpp#L172-L179) -- [peer_connection.hpp:320-342](file://libraries/network/include/graphene/network/peer_connection.hpp#L320-L342) - -### Performance Metrics Collection -- Built-in metrics: The node does not expose Prometheus metrics endpoints by default. Metrics must be collected externally or via custom instrumentation. -- External collection: Use system-level collectors (e.g., Node Exporter) for CPU, memory, disk I/O, and network utilization. -- API latency: Instrument JSON-RPC endpoints to capture request durations and error rates. -- **Updated** P2P network metrics: Utilize peer statistics for network performance monitoring and bandwidth analysis. - -Integration references: -- Endpoints: [config.ini:16-20](file://share/vizd/config/config.ini#L16-L20) -- Webserver threading model: [webserver_plugin.hpp:28-30](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L28-L30) -- **Updated** P2P statistics scheduling: [p2p_plugin.cpp:685-692](file://plugins/p2p/p2p_plugin.cpp#L685-L692) - -**Section sources** -- [config.ini:16-20](file://share/vizd/config/config.ini#L16-L20) -- [webserver_plugin.hpp:28-30](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L28-L30) -- [p2p_plugin.cpp:685-692](file://plugins/p2p/p2p_plugin.cpp#L685-L692) - -### System Monitoring Integration (Prometheus, Grafana, ELK) -- Prometheus: Scrape system metrics via Node Exporter; optionally scrape custom JSON-RPC metrics if instrumented. -- Grafana: Build dashboards for CPU, memory, disk I/O, network, and API latency. -- ELK: Ship logs to Logstash/Beats and visualize in Kibana for centralized log analysis. -- **Updated** Network monitoring: Integrate P2P statistics with monitoring systems for peer health visualization. - -Operational references: -- Logging configuration: [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- Launcher script for endpoint overrides: [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) -- **Updated** P2P statistics configuration: [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) - -**Section sources** -- [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) -- [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) - -### Log Management Strategies -- Console and file appenders: Configure loggers and appenders for different subsystems (e.g., default, p2p). -- Log levels: Adjust severity thresholds per logger. -- Log rotation: Use external log rotation tools (e.g., logrotate) to manage file sizes and retention. -- Centralized logging: Forward logs to a centralized collector (e.g., rsyslog, Fluent Bit) for aggregation. -- **Updated** P2P statistics logging: ANSI color-coded peer statistics with periodic dumps for network monitoring. - -References: -- Appenders and loggers: [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- Docker-based deployment and seed nodes: [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- **Updated** P2P statistics color coding: [p2p_plugin.cpp:15-17](file://plugins/p2p/p2p_plugin.cpp#L15-L17) - -**Section sources** -- [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- [p2p_plugin.cpp:15-17](file://plugins/p2p/p2p_plugin.cpp#L15-L17) - -### Database Maintenance Tasks -- Shared memory sizing: Tune shared file size, minimum free space, and increment steps to avoid allocation failures. -- Compaction and optimization: Rely on underlying storage engine defaults; monitor free space and adjust thresholds periodically. -- Backup verification: Periodically snapshot the blockchain database and verify replay integrity. - -References: -- Shared memory settings: [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- Replay option in launcher: [vizd.sh:44-53](file://share/vizd/vizd.sh#L44-L53) - -**Section sources** -- [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- [vizd.sh:44-53](file://share/vizd/vizd.sh#L44-L53) - -### Performance Monitoring (CPU, Memory, Disk I/O, Network) -- CPU: Track utilization and context switches; correlate with API throughput. -- Memory: Monitor RSS, shared memory usage, and free space thresholds. -- Disk I/O: Observe read/write latencies and queue depths; ensure adequate free space. -- Network: Measure P2P and RPC traffic; watch connection counts and bandwidth. -- **Updated** P2P network performance: Monitor peer latency, bandwidth usage, and connection health through statistics system. - -References: -- Endpoints and threading: [config.ini:13-47](file://share/vizd/config/config.ini#L13-L47) -- Webserver threading note: [webserver_plugin.hpp:28-30](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L28-L30) -- **Updated** Peer statistics metrics: [p2p_plugin.cpp:510-530](file://plugins/p2p/p2p_plugin.cpp#L510-L530) - -**Section sources** -- [config.ini:13-47](file://share/vizd/config/config.ini#L13-L47) -- [webserver_plugin.hpp:28-30](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L28-L30) -- [p2p_plugin.cpp:510-530](file://plugins/p2p/p2p_plugin.cpp#L510-L530) - -### Proactive Maintenance Procedures -- Regular updates: Rebuild from source or pull updated Docker images; validate against release notes. -- Security patches: Keep dependencies updated; scan for vulnerabilities. -- Configuration audits: Review enabled plugins, endpoints, and log levels; ensure least privilege exposure. -- **Updated** Network maintenance: Regularly review P2P statistics for peer health trends and network performance optimization. - -References: -- Build instructions: [building.md:1-212](file://documentation/building.md#L1-L212) -- Docker usage: [README.md:12-29](file://README.md#L12-L29) - -**Section sources** -- [building.md:1-212](file://documentation/building.md#L1-L212) -- [README.md:12-29](file://README.md#L12-L29) - -### Incident Response Procedures -- Initial assessment: Verify health endpoints, P2P connectivity, and log levels. -- Isolation: Temporarily disable non-essential plugins to reduce load. -- Recovery: Trigger replay if necessary; restore from verified snapshots; recheck shared memory thresholds. -- **Updated** Network incident response: Analyze P2P statistics for peer blocking patterns, latency spikes, and bandwidth anomalies to identify network issues. - -References: -- Replay option: [vizd.sh:47-48](file://share/vizd/vizd.sh#L47-L48) -- Shared memory tuning: [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- **Updated** P2P statistics monitoring: [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) - -**Section sources** -- [vizd.sh:47-48](file://share/vizd/vizd.sh#L47-L48) -- [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) - -### Capacity Planning and Resource Optimization -- Forecasting: Track block production rate, transaction volume, and plugin indexing overhead. -- Resource optimization: Adjust shared memory increments, thread pools, and plugin sets based on observed load. -- **Updated** Network capacity planning: Monitor P2P statistics for peer distribution, latency trends, and bandwidth utilization to optimize network topology and peer selection. - -References: -- Shared memory sizing: [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- Thread pool size: [config.ini:13-14](file://share/vizd/config/config.ini#L13-L14) - -**Section sources** -- [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- [config.ini:13-14](file://share/vizd/config/config.ini#L13-L14) - -### Automated Maintenance Scripts and Dashboards -- Maintenance scripts: Use the launcher script to initialize data directories, inject seed nodes, and set endpoints; extend for periodic checks and backups. -- Dashboards: Create Prometheus/Grafana dashboards for system metrics and API performance; integrate ELK for log analytics. -- **Updated** Network dashboards: Develop Grafana dashboards for P2P peer health, latency distributions, bandwidth utilization, and peer blocking status. - -References: -- Launcher script: [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- Testnet guidance: [testnet.md:21-37](file://documentation/testnet.md#L21-L37) - -**Section sources** -- [vizd.sh:1-82](file://share/vizd/vizd.sh#L1-L82) -- [testnet.md:21-37](file://documentation/testnet.md#L21-L37) - -## Dependency Analysis -The webserver plugin depends on the JSON-RPC plugin, which registers API methods exposed by various plugins. The chain plugin integrates with the database and provides core state access. Logging is configured centrally via the configuration file. The P2P plugin provides network connectivity and statistics collection for network observability. - -```mermaid -graph LR -WS["Webserver Plugin"] --> JR["JSON-RPC Plugin"] -JR --> APIs["Database/Chain APIs"] -APIs --> CHAIN["Chain Plugin"] -WS --> LOG["Logging Config"] -JR --> LOG -P2P["P2P Plugin"] --> NET["Network Layer"] -NET --> STATS["Statistics System"] -P2P --> LOG -``` - -**Diagram sources** -- [webserver_plugin.hpp:38-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L38-L38) -- [json_rpc_plugin.hpp:84-92](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L92) -- [database_api_plugin.cpp:1-200](file://plugins/database_api/plugin.cpp#L1-L200) -- [chain_plugin.cpp:1-200](file://plugins/chain/plugin.cpp#L1-L200) -- [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) - -**Section sources** -- [webserver_plugin.hpp:38-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L38-L38) -- [json_rpc_plugin.hpp:84-92](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L92) -- [database_api_plugin.cpp:1-200](file://plugins/database_api/plugin.cpp#L1-L200) -- [chain_plugin.cpp:1-200](file://plugins/chain/plugin.cpp#L1-L200) -- [config.ini:111-130](file://share/vizd/config/config.ini#L111-L130) -- [p2p_plugin.cpp:489-560](file://plugins/p2p/p2p_plugin.cpp#L489-L560) - -## Performance Considerations -- Single write thread: Writing operations are serialized to reduce contention; ensure adequate write lock retries and wait intervals. -- Read/write lock tuning: Adjust microsecond waits and retries to balance latency and throughput. -- Shared memory growth: Monitor free space thresholds and increment steps to prevent allocation failures. -- **Updated** P2P statistics overhead: Configure appropriate intervals to balance monitoring granularity with system performance impact. - -References: -- Single write thread: [config.ini:36-40](file://share/vizd/config/config.ini#L36-L40) -- Read/write waits: [config.ini:22-34](file://share/vizd/config/config.ini#L22-L34) -- Shared memory sizing: [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- **Updated** P2P statistics scheduling: [p2p_plugin.cpp:685-692](file://plugins/p2p/p2p_plugin.cpp#L685-L692) - -**Section sources** -- [config.ini:36-40](file://share/vizd/config/config.ini#L36-L40) -- [config.ini:22-34](file://share/vizd/config/config.ini#L22-L34) -- [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- [p2p_plugin.cpp:685-692](file://plugins/p2p/p2p_plugin.cpp#L685-L692) - -## Troubleshooting Guide -- No connectivity: Verify P2P endpoint and seed nodes; confirm firewall rules and DNS resolution. -- API unresponsive: Check JSON-RPC registration and method availability; review log levels for errors. -- Lock contention: Increase read/write wait retries or tune single write thread behavior. -- Out-of-memory: Inspect shared memory free space thresholds and increments; consider reducing plugin sets or replay options. -- **Updated** Network connectivity issues: Analyze P2P statistics for peer blocking patterns, excessive latency, or bandwidth saturation; review peer connection states and reasons for blocking. - -References: -- P2P and seed nodes: [config.ini:1-8](file://share/vizd/config/config.ini#L1-L8) -- Seed node injection: [vizd.sh:9-29](file://share/vizd/vizd.sh#L9-L29) -- Lock settings: [config.ini:22-40](file://share/vizd/config/config.ini#L22-L40) -- Shared memory: [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- **Updated** P2P statistics diagnostics: [p2p_plugin.cpp:508-552](file://plugins/p2p/p2p_plugin.cpp#L508-L552) - -**Section sources** -- [config.ini:1-8](file://share/vizd/config/config.ini#L1-L8) -- [vizd.sh:9-29](file://share/vizd/vizd.sh#L9-L29) -- [config.ini:22-40](file://share/vizd/config/config.ini#L22-L40) -- [config.ini:49-67](file://share/vizd/config/config.ini#L49-L67) -- [p2p_plugin.cpp:508-552](file://plugins/p2p/p2p_plugin.cpp#L508-L552) - -## Conclusion -This guide outlines how to operate, monitor, and maintain VIZ CPP Node instances effectively. By leveraging the configuration-driven logging and endpoints, integrating external monitoring stacks, utilizing the new P2P peer statistics system for network observability, and following the maintenance and troubleshooting procedures, operators can achieve reliable and scalable node operations with comprehensive network visibility. - -## Appendices - -### API Surface and Plugin Exposure -- Database API: Provides chain state queries and operations. -- Account History and Operation History: Index and expose historical data. -- P2P and Network Broadcast API: Manage peer connections and broadcast transactions. -- validator API: Expose validator-specific operations. -- Block Info and Raw Block: Provide block-level insights. -- Debug Node: Simulate chain state for testing and development. - -References: -- Database API plugin: [database_api_plugin.cpp:1-200](file://plugins/database_api/plugin.cpp#L1-L200) -- Account History plugin: [account_history_plugin.cpp:1-200](file://plugins/account_history/plugin.cpp#L1-L200) -- Operation History plugin: [operation_history_plugin.cpp:1-200](file://plugins/operation_history/plugin.cpp#L1-L200) -- P2P plugin: [p2p_plugin.cpp:1-200](file://plugins/p2p/p2p_plugin.cpp#L1-L200) -- Network Broadcast API plugin: [network_broadcast_api_plugin.cpp:1-200](file://plugins/network_broadcast_api/network_broadcast_api.cpp#L1-L200) -- validator API plugin: [witness_api_plugin.cpp:1-200](file://plugins/witness_api/plugin.cpp#L1-L200) -- Block Info plugin: [block_info_plugin.cpp:1-200](file://plugins/block_info/plugin.cpp#L1-L200) -- Raw Block plugin: [raw_block_plugin.cpp:1-200](file://plugins/raw_block/plugin.cpp#L1-L200) -- Debug Node plugin: [debug_node_plugin.cpp:1-200](file://plugins/debug_node/plugin.cpp#L1-L200) -- Chain plugin: [chain_plugin.cpp:1-200](file://plugins/chain/plugin.cpp#L1-L200) - -**Section sources** -- [database_api_plugin.cpp:1-200](file://plugins/database_api/plugin.cpp#L1-L200) -- [account_history_plugin.cpp:1-200](file://plugins/account_history/plugin.cpp#L1-L200) -- [operation_history_plugin.cpp:1-200](file://plugins/operation_history/plugin.cpp#L1-L200) -- [p2p_plugin.cpp:1-200](file://plugins/p2p/p2p_plugin.cpp#L1-L200) -- [network_broadcast_api_plugin.cpp:1-200](file://plugins/network_broadcast_api/network_broadcast_api.cpp#L1-L200) -- [witness_api_plugin.cpp:1-200](file://plugins/witness_api/plugin.cpp#L1-L200) -- [block_info_plugin.cpp:1-200](file://plugins/block_info/plugin.cpp#L1-L200) -- [raw_block_plugin.cpp:1-200](file://plugins/raw_block/plugin.cpp#L1-L200) -- [debug_node_plugin.cpp:1-200](file://plugins/debug_node/plugin.cpp#L1-L200) -- [chain_plugin.cpp:1-200](file://plugins/chain/plugin.cpp#L1-L200) - -### Testnet and Snapshot Usage -- Testnet launch: Use Docker images or build locally; inspect logs for initialization. -- Snapshots: Initialize data directories with prebuilt snapshots to accelerate bootstrapping. - -References: -- Testnet instructions: [testnet.md:21-37](file://documentation/testnet.md#L21-L37) -- Snapshot initialization: [vizd.sh:44-53](file://share/vizd/vizd.sh#L44-L53) - -**Section sources** -- [testnet.md:21-37](file://documentation/testnet.md#L21-L37) -- [vizd.sh:44-53](file://share/vizd/vizd.sh#L44-L53) - -### Debugging and Simulation -- Debug Node plugin: Simulate chain state changes and test features without affecting the live network. -- Example workflows: Push blocks, generate blocks, and update objects for experimentation. - -References: -- Debug Node documentation: [debug_node_plugin.md:50-134](file://documentation/debug_node_plugin.md#L50-L134) - -**Section sources** -- [debug_node_plugin.md:50-134](file://documentation/debug_node_plugin.md#L50-L134) - -### P2P Statistics Configuration Reference -**New Section** Complete reference for P2P statistics configuration and monitoring. - -#### Command Line Options -- `--p2p-stats-enabled`: Enable/disable peer statistics logging -- `--p2p-stats-interval`: Set statistics collection interval in seconds - -#### Statistics Output Format -``` -P2P peer | ip: 192.168.1.100 | port: 1776 | latency: 45ms | bytes_in: 12345 | blocked: false | reason: -P2P peer | ip: 10.0.0.5 | port: 1776 | latency: 120ms | bytes_in: 8765 | blocked: true | reason: soft_ban -``` - -#### Peer Status Indicators -- **Latency**: Round-trip delay in milliseconds -- **Bytes Received**: Delta bytes since last measurement -- **Blocked**: Soft-ban or inhibition status -- **Reason**: Network policy enforcement reason - -#### Monitoring Best Practices -- Set appropriate intervals based on network size and monitoring requirements -- Monitor for unusual latency spikes or bandwidth patterns -- Track peer blocking reasons for network policy analysis -- Correlate statistics with system performance metrics - -**Section sources** -- [p2p_plugin.cpp:570-589](file://plugins/p2p/p2p_plugin.cpp#L570-L589) -- [p2p_plugin.cpp:508-552](file://plugins/p2p/p2p_plugin.cpp#L508-L552) -- [node.hpp:172-179](file://libraries/network/include/graphene/network/node.hpp#L172-L179) -- [peer_connection.hpp:320-342](file://libraries/network/include/graphene/network/peer_connection.hpp#L320-L342) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Installation and Setup.md b/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Installation and Setup.md deleted file mode 100644 index 19071642e2..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Installation and Setup.md +++ /dev/null @@ -1,400 +0,0 @@ -# Installation and Setup - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [documentation/building.md](file://documentation/building.md) -- [CMakeLists.txt](file://CMakeLists.txt) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [.travis.yml](file://.travis.yml) -- [documentation/testing.md](file://documentation/testing.md) -- [documentation/testnet.md](file://documentation/testnet.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive installation and setup guidance for the VIZ CPP Node. It covers system requirements, platform-specific build instructions using CMake, dependency management, cross-compilation considerations, Docker containerization for production and development, verification procedures, and troubleshooting tips. The goal is to enable reliable installation and operation across Linux, macOS, and Windows environments, while also supporting containerized deployments. - -## Project Structure -At a high level, the repository is organized into: -- Top-level build configuration and CI scripts -- Libraries implementing blockchain logic, networking, and utilities -- Plugins extending functionality -- Programs (executables) such as the node and CLI wallet -- Documentation and Docker configurations for deployment - -```mermaid -graph TB -Root["Repository Root"] -CMake["Top-level CMakeLists.txt"] -Docs["Documentation"] -Libs["libraries/"] -Plugs["plugins/"] -Progs["programs/"] -Share["share/vizd/"] -Third["thirdparty/"] -Root --> CMake -Root --> Docs -Root --> Libs -Root --> Plugs -Root --> Progs -Root --> Share -Root --> Third -``` - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) -- [README.md](file://README.md#L1-L53) - -## Core Components -- Build system: CMake with configurable options for build type, memory profile, and plugin selection -- Executables: vizd (node), cli_wallet (command-line wallet) -- Plugins: modular extensions for APIs and services -- Docker images: prebuilt and multi-stage Dockerfiles for production, low-memory, testnet, and MongoDB variants -- Configuration templates: config.ini and config_testnet.ini for runtime behavior - -Key build options and flags are defined in the top-level CMake configuration and documented in the building guide. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L3-L104) -- [documentation/building.md](file://documentation/building.md#L3-L16) - -## Architecture Overview -The build and runtime architecture integrates CMake-driven compilation of libraries and plugins into node and wallet executables, with Docker encapsulating dependencies and runtime configuration. - -```mermaid -graph TB -subgraph "Build" -CMake["CMakeLists.txt"] -Libs["libraries/*"] -Plugs["plugins/*"] -Progs["programs/*"] -Third["thirdparty/*"] -end -subgraph "Executables" -VIZD["vizd (node)"] -CLI["cli_wallet"] -end -subgraph "Runtime Config" -CfgProd["config.ini"] -CfgTest["config_testnet.ini"] -Seed["seednodes"] -end -subgraph "Containerization" -DProd["Dockerfile-production"] -DLow["Dockerfile-lowmem"] -DTest["Dockerfile-testnet"] -Script["vizd.sh"] -end -CMake --> Libs -CMake --> Plugs -CMake --> Progs -CMake --> Third -Libs --> VIZD -Plugs --> VIZD -Progs --> VIZD -Progs --> CLI -CfgProd --> VIZD -CfgTest --> VIZD -Seed --> VIZD -DProd --> VIZD -DLow --> VIZD -DTest --> VIZD -Script --> VIZD -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -## Detailed Component Analysis - -### System Requirements and Dependencies -- Compiler requirements: - - GCC minimum version and Clang minimum version are enforced by the build system -- Operating systems: - - Official guidance supports Linux and macOS; Windows build instructions are not provided in the repository -- Core dependencies: - - Boost (minimum version requirement explicitly stated) - - OpenSSL - - CMake, compiler toolchain, and standard Unix utilities -- Optional dependencies: - - Tools for documentation and development experience - -Platform-specific package lists and notes are provided in the building guide. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L12-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [documentation/building.md](file://documentation/building.md#L76-L137) -- [documentation/building.md](file://documentation/building.md#L138-L201) - -### Platform-Specific Installation Procedures - -#### Linux (Ubuntu LTS) -- Install required and optional packages via the package manager -- Clone the repository and initialize submodules -- Configure with CMake and build node and wallet targets -- Optional: install to system prefix - -```mermaid -flowchart TD -Start(["Start"]) --> Pkgs["Install packages
apt dependencies"] -Pkgs --> Clone["Clone repo and init submodules"] -Clone --> Cfg["Configure with CMake
Release build"] -Cfg --> Build["Build vizd and cli_wallet"] -Build --> Install{"Install system-wide?"} -Install --> |Yes| DoInstall["make install"] -Install --> |No| SkipInstall["Skip install"] -DoInstall --> End(["Done"]) -SkipInstall --> End -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) - -#### macOS -- Install Xcode command line tools and accept license -- Use Homebrew to install dependencies including a compatible Boost version -- Export OpenSSL and Boost prefixes for CMake discovery -- Configure and build - -```mermaid -flowchart TD -Start(["Start"]) --> Xcode["Install Xcode command line tools"] -Xcode --> Brew["Install deps via Homebrew"] -Brew --> Prefixes["Set OPENSSL_ROOT_DIR and BOOST_ROOT"] -Prefixes --> Cfg["Configure with CMake"] -Cfg --> Build["Build targets"] -Build --> End(["Done"]) -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L138-L189) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L138-L189) - -#### Windows -- No official build instructions are provided in the repository -- The build system includes Windows-specific logic, indicating potential support with appropriate toolchains and environment setup - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L202-L212) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L157) - -### CMake Build Options and Flags -Key options exposed by the build system: -- Build type: Release or Debug -- Low memory node: consensus-only mode -- Testnet build: compile-time switch for testnet configuration -- Chainbase locking checks: optional debug/validation -- MongoDB plugin: optional inclusion -- Shared/static libraries: choice of linkage - -These options influence compilation flags and conditional compilation macros. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [CMakeLists.txt](file://CMakeLists.txt#L264-L274) - -### Cross-Compilation Considerations -- The repository does not include explicit cross-compilation instructions -- For cross-platform builds, ensure the target toolchain and SDKs are configured appropriately -- Respect compiler and dependency version constraints documented in the build system - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L12-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L157) - -### Docker Containerization - -#### Prebuilt Images -- Production image tag latest is available on Docker Hub -- Testnet image tag testnet is available on Docker Hub -- Example run commands and environment overrides are provided - -**Section sources** -- [README.md](file://README.md#L12-L29) - -#### Multi-Stage Dockerfiles -- Production: full-featured node with optimized runtime -- Low memory: consensus-only node for resource-constrained environments -- Testnet: preconfigured for local testnet with snapshot and default validator -- Each Dockerfile: - - Installs build dependencies - - Copies minimal source subsets - - Builds with CMake and installs - - Produces a runtime image with non-root user and volumes for persistent data - -```mermaid -graph LR -Builder["Builder Stage"] --> Runtime["Runtime Stage"] -Builder --> |Debian/Ubuntu base| Deps["Install build deps"] -Deps --> Source["Copy source subsets"] -Source --> CMake["CMake configure and build"] -CMake --> Install["make install"] -Install --> Runtime -Runtime --> Image["Final image with vizd"] -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) - -#### Container Entrypoint and Environment -- The container entrypoint script sets up seed nodes, optional validator configuration, copies configuration, initializes blockchain cache if present, and launches the node with environment-provided endpoints and arguments. - -```mermaid -sequenceDiagram -participant Docker as "Docker Engine" -participant Script as "vizd.sh" -participant Node as "vizd" -Docker->>Script : Start container -Script->>Script : Resolve seed nodes and env args -Script->>Script : Copy config.ini to data dir -Script->>Script : Optionally extract snapshot -Script->>Node : exec vizd with resolved args -Node-->>Docker : Logs and runtime -``` - -**Diagram sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -#### Manual Docker Builds -- CI matrix defines multiple Dockerfiles and tags for automated builds and pushes -- Local builds can mirror the CI stages - -**Section sources** -- [.travis.yml](file://.travis.yml#L12-L42) - -### Configuration Management -- Production configuration template defines P2P endpoints, RPC endpoints, threading, shared memory sizing, plugin list, and logging -- Testnet configuration template adds validator-related settings and enables stale production for local testing - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -## Dependency Analysis -The build system declares and locates required libraries and sets compiler/linker flags per platform. The Dockerfiles enumerate runtime dependencies. - -```mermaid -graph TB -CMake["CMakeLists.txt"] -Boost["Boost (>= 1.57)"] -SSL["OpenSSL"] -Readline["Readline (Linux/macOS)"] -Crypto["Crypto library (Linux)"] -CMake --> Boost -CMake --> SSL -CMake --> Readline -CMake --> Crypto -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [CMakeLists.txt](file://CMakeLists.txt#L160-L184) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [CMakeLists.txt](file://CMakeLists.txt#L160-L184) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L9-L30) - -## Performance Considerations -- Build type: Use Release for production to enable optimizations -- Low memory node: Recommended for consensus roles to reduce memory footprint -- Plugin selection: Disable unused plugins to minimize overhead -- Shared memory sizing: Adjust shared file size and increments according to expected load -- Threading: Tune RPC thread pool and consider single-write-thread for reduced contention - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L3-L16) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L13-L14) - -## Troubleshooting Guide -Common issues and remedies: -- Boost version mismatch: - - Ensure Boost meets the minimum version requirement; on older distributions, manual installation of a compatible version may be necessary -- OpenSSL discovery failures: - - On macOS, export the OpenSSL prefix for CMake to locate headers and libraries -- Missing readline on Linux/macOS: - - Install readline development packages if linking fails -- Windows build: - - No official instructions exist; consult Windows-specific CMake and toolchain setup if attempting unsupported builds -- Docker runtime: - - Verify exposed ports and mounted volumes - - Override seed nodes via environment variable - - Confirm configuration file is copied into the data directory - -Verification steps: -- Build artifacts: - - Confirm successful generation of vizd and cli_wallet -- Docker: - - Tail container logs to observe startup and synchronization progress - - Connect to RPC endpoints and query node status -- Testnet: - - Use provided testnet image and configuration for quick local validation - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L103-L112) -- [documentation/building.md](file://documentation/building.md#L176-L184) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [README.md](file://README.md#L21-L29) -- [documentation/testnet.md](file://documentation/testnet.md#L21-L37) - -## Conclusion -With the provided instructions, you can build and run the VIZ CPP Node on Linux and macOS, and optionally use Docker for streamlined deployment. Ensure dependencies meet the minimum requirements, select appropriate build options for your role (full node vs. low-memory), and leverage Docker for repeatable production and development setups. Use the verification procedures to confirm successful installation and basic functionality. - -## Appendices - -### A. Build Targets Reference -- vizd: primary node executable -- cli_wallet: command-line wallet -- chain_test: unit test suite - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L190-L200) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) - -### B. Testnet Quickstart -- Use the provided Dockerfile-testnet to build and run a local testnet node -- Predefined users and keys are available for immediate testing - -**Section sources** -- [documentation/testnet.md](file://documentation/testnet.md#L21-L54) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Node Deployment.md b/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Node Deployment.md deleted file mode 100644 index a5a1779740..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Node Deployment.md +++ /dev/null @@ -1,373 +0,0 @@ -# Node Deployment - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [documentation/building.md](file://documentation/building.md) -- [documentation/testnet.md](file://documentation/testnet.md) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [share/vizd/seednodes](file://share/vizd/seednodes) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Security Hardening](#security-hardening) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) -11. [Appendices](#appendices) - -## Introduction -This document provides comprehensive deployment guidance for VIZ CPP Node across production, testnet, and specialized configurations. It covers hardware and system prerequisites, installation procedures for multiple operating systems, Docker-based deployments, node types (full, validator, seed), configuration management, service integration, performance tuning, capacity planning, security hardening, and troubleshooting. - -## Project Structure -At a high level, the repository provides: -- A production-ready node binary (vizd) with extensive plugin support -- Multiple configuration templates for different node roles and networks -- Docker images for production, testnet, and low-memory deployments -- Scripts to bootstrap and run the node with seed nodes and optional snapshot replay - -```mermaid -graph TB -subgraph "Host" -OS["Operating System"] -Runtime["Container Runtime
Docker/Podman"] -end -subgraph "Docker Image" -IMG["Image Layers"] -BIN["vizd Binary"] -CFG["Config Template
config.ini / config_witness.ini / config_testnet.ini"] -SEED["Seed Nodes List"] -RUN["Startup Script
vizd.sh"] -end -subgraph "Node Process" -VIZD["vizd"] -PLUGINS["Plugins Loaded at Startup"] -LOG["Logging Config"] -end -OS --> Runtime -Runtime --> IMG -IMG --> BIN -IMG --> CFG -IMG --> SEED -IMG --> RUN -BIN --> VIZD -CFG --> VIZD -SEED --> VIZD -RUN --> VIZD -VIZD --> PLUGINS -VIZD --> LOG -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L82) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [README.md](file://README.md#L1-L53) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) - -## Core Components -- Node binary: vizd, which initializes plugins and starts the P2P and RPC services -- Configuration: INI-style configuration files for endpoints, plugins, logging, and runtime behavior -- Docker images: production, testnet, and low-memory variants with preconfigured volumes and ports -- Bootstrap script: sets up seed nodes, optional snapshot replay, and passes environment overrides to vizd - -Key behaviors: -- Plugin registration and initialization occur at startup -- Logging configuration is parsed from the config file sections -- Docker entrypoint supports environment-driven customization (RPC, P2P, validator identity, private key) - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L117-L140) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L167-L191) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L194-L289) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Architecture Overview -The node startup flow integrates CLI arguments, configuration files, and environment variables to initialize plugins and services. - -```mermaid -sequenceDiagram -participant Entrypoint as "Docker Entrypoint
vizd.sh" -participant Env as "Environment Variables" -participant Vizd as "vizd" -participant Cfg as "Config Loader" -participant Log as "Logging Config" -participant P2P as "P2P Plugin" -participant RPC as "Webserver Plugin" -Entrypoint->>Env : Read VIZD_* variables -Entrypoint->>Vizd : Pass --p2p-endpoint, --rpc-endpoint, --data-dir, seed nodes, validator, private-key -Vizd->>Cfg : Load config.ini and parse sections -Cfg-->>Log : Build logging config from [log.*] and [logger.*] -Vizd->>Log : Configure logging -Vizd->>P2P : Initialize P2P with endpoints and seeds -Vizd->>RPC : Initialize RPC with HTTP/WebSocket endpoints -Vizd-->>Entrypoint : Running -``` - -**Diagram sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L13-L81) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L117-L140) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L167-L191) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L194-L289) - -## Detailed Component Analysis - -### Node Types and Roles -- Full node: Participates in P2P gossip, serves RPC APIs, optionally tracks history and feeds -- validator node: Produces blocks; requires a configured validator name and private key -- Seed node: Minimal footprint, connects peers and advertises connectivity; recommended low-memory build - -Configuration templates: -- Production template: [config.ini](file://share/vizd/config/config.ini#L1-L130) -- validator template: [config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- Testnet template: [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -Operational differences: -- validator nodes enable block production and require private keys -- Seed nodes typically bind RPC to localhost and disable verbose plugins -- Testnet enables special participation rules and snapshot-based initialization - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [documentation/testnet.md](file://documentation/testnet.md#L1-L54) - -### Configuration Management -- Endpoints: P2P, HTTP RPC, WebSocket RPC -- Locking and threading: Read/write wait limits and single-write-thread behavior -- Shared memory sizing: Initial size, minimum free space, increment step, and periodic checks -- Plugins: Enabled via plugin directives; validator and API plugins commonly enabled -- Logging: Console and file appenders, logger levels, and appender routing - -Environment overrides: -- Docker entrypoint supports VIZD_RPC_ENDPOINT, VIZD_P2P_ENDPOINT, VIZD_SEED_NODES, VIZD_WITNESS_NAME, VIZD_PRIVATE_KEY, and VIZD_EXTRA_OPTS - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L13-L81) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L167-L191) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L194-L289) - -### Service Integration -- Docker volumes: - - /var/lib/vizd: persistent data directory (blockchain, logs, config) - - /etc/vizd: read-only config and seednodes -- Exposed ports: - - 2001: P2P - - 8090: HTTP RPC - - 8091: WebSocket RPC - -Dockerfiles: -- Production: [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- Testnet: [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L88) -- Low-memory: [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L82) - -**Section sources** -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L82) - -### Installation Procedures - -#### Docker (Recommended for Production) -- Pull or build the production image and run with mapped volumes and exposed ports -- Optionally override seed nodes via environment variable - -```mermaid -flowchart TD -Start(["Start"]) --> Pull["Pull or Build Image"] -Pull --> Run["Run Container with Volumes and Ports"] -Run --> Seed["Set VIZD_SEED_NODES (optional)"] -Seed --> validator["Set VIZD_WITNESS_NAME and VIZD_PRIVATE_KEY (optional)"] -validator --> Logs["Monitor Logs"] -Logs --> End(["Ready"]) -``` - -**Diagram sources** -- [README.md](file://README.md#L12-L29) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L17-L37) - -**Section sources** -- [README.md](file://README.md#L12-L29) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) - -#### Ubuntu (Native Build) -- Install dependencies, clone repository, initialize submodules, configure with CMake, build, and optionally install - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) - -#### macOS (Native Build) -- Install dependencies via Homebrew, export OpenSSL and Boost paths, configure with CMake, and build - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L138-L189) - -#### Low-Memory Builds -- Use the low-memory Dockerfile or build with LOW_MEMORY_NODE enabled to reduce memory footprint - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L11-L16) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) - -### Node Startup Process -- The entrypoint script prepares seed nodes, optionally replays a cached snapshot, and executes vizd with environment-derived arguments -- The binary registers plugins, loads logging configuration, and starts P2P and RPC services - -```mermaid -sequenceDiagram -participant Init as "vizd.sh" -participant FS as "Filesystem" -participant Bin as "vizd" -participant Net as "P2P" -participant API as "RPC" -Init->>FS : Read /etc/vizd/seednodes -Init->>Bin : exec vizd with --p2p-endpoint, --rpc-endpoint, --data-dir, --p2p-seed-node, --validator, --private-key -Bin->>Bin : Register plugins -Bin->>Bin : Load logging config -Bin->>Net : Start P2P -Bin->>API : Start RPC HTTP/WebSocket -Bin-->>Init : Running -``` - -**Diagram sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L11-L81) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) - -### Testnet Deployment -- Use the testnet Dockerfile or image to spin up a local test network -- Snapshot-based initialization is supported for quick bootstrapping - -**Section sources** -- [documentation/testnet.md](file://documentation/testnet.md#L21-L37) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L88) - -### Debugging and Simulation -- The debug_node plugin allows loading historical blocks and generating synthetic blocks for development and testing - -**Section sources** -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) - -## Dependency Analysis -- Build-time dependencies: CMake, compiler toolchain, Boost, OpenSSL, and related libraries -- Runtime dependencies: Shared libraries linked at build time; Docker images package all required runtime libraries -- Plugin ecosystem: Extensible via the appbase framework; plugins are registered at startup - -```mermaid -graph LR -CMake["CMake Build"] --> Bin["vizd Binary"] -Boost["Boost Libraries"] --> Bin -SSL["OpenSSL"] --> Bin -Libs["System Libraries"] --> Bin -Bin --> Plugins["Registered Plugins"] -Plugins --> P2P["P2P"] -Plugins --> RPC["Webserver"] -Plugins --> Chain["Chain"] -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L30-L63) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L30-L63) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) - -## Performance Considerations -- Thread pool sizing: Tune webserver-thread-pool-size to match CPU cores minus one for optimal throughput -- Locking behavior: Single-write-thread reduces contention; adjust read-wait-micro and max-read-wait-retries to balance latency and reliability -- Shared memory: Configure shared-file-size, min-free-shared-file-size, and inc-shared-file-size to avoid frequent resizing during replay or growth -- Plugin selection: Disable unused plugins to reduce memory and CPU overhead -- Network: Limit inbound connections via p2p-max-connections and leverage seednodes for faster bootstrapping - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L13-L67) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L13-L66) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L13-L67) - -## Security Hardening -- Bind RPC to localhost for validator nodes to prevent external exposure -- Use environment variables to inject secrets (private keys) and restrict filesystem access -- Restrict P2P exposure to trusted networks; consider firewall rules to allow only necessary ports -- Monitor logs and set appropriate logger levels to detect anomalies early - -**Section sources** -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L16-L20) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L167-L191) - -## Troubleshooting Guide -Common issues and resolutions: -- Startup failures due to missing configuration or invalid endpoints - - Verify config.ini presence and correctness; ensure endpoints are reachable -- Insufficient shared memory during replay - - Increase shared-file-size and min-free-shared-file-size -- Excessive lock wait timeouts - - Adjust read-wait-micro and max-read-wait-retries; consider single-write-thread -- No peers or slow bootstrapping - - Confirm p2p-seed-node entries; validate network accessibility on port 2001 -- validator node not producing blocks - - Ensure validator name and private key are set; verify required-participation and enable-stale-production as appropriate -- Testnet initialization problems - - Confirm snapshot availability and permissions; check testnet-specific configuration - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L76-L86) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L99-L111) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L44-L53) -- [share/vizd/seednodes](file://share/vizd/seednodes#L1-L6) - -## Conclusion -Deploying a VIZ CPP Node involves selecting the appropriate configuration template, preparing the environment (native or Docker), and integrating with monitoring and security controls. Use the provided Docker images for production, leverage validator and testnet configurations for specialized roles, and tune performance parameters according to workload profiles. - -## Appendices - -### Appendix A: Node Type Reference -- Full node: General-purpose node with comprehensive plugins -- validator node: Block producer with configured validator and private key -- Seed node: Minimal footprint, focused on peer connectivity - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L69-L73) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L68-L86) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L73) - -### Appendix B: Environment Variables (Docker) -- VIZD_RPC_ENDPOINT: Override RPC endpoint -- VIZD_P2P_ENDPOINT: Override P2P endpoint -- VIZD_SEED_NODES: Comma-separated seed nodes -- VIZD_WITNESS_NAME: validator name for block production -- VIZD_PRIVATE_KEY: Private key for validator signing -- VIZD_EXTRA_OPTS: Additional arguments to pass to vizd - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L17-L37) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L62-L81) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Node Types and Configurations.md b/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Node Types and Configurations.md deleted file mode 100644 index 157ede7a73..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Node Types and Configurations.md +++ /dev/null @@ -1,458 +0,0 @@ -# Node Types and Configurations - - -**Referenced Files in This Document** -- [main.cpp](file://programs/vizd/main.cpp) -- [config.ini](file://share/vizd/config/config.ini) -- [config_witness.ini](file://share/vizd/config/config_witness.ini) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini) -- [config_stock_exchange.ini](file://share/vizd/config/config_stock_exchange.ini) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [testnet.md](file://documentation/testnet.md) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp) -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp) - - -## Update Summary -**Changes Made** -- Updated P2P endpoint configuration section to reflect standardized port 2001 across all configuration files -- Removed references to external seed node file management system -- Updated seed node configuration to show direct integration into config.ini templates -- Added config_stock_exchange.ini to the configuration templates section -- Updated section sources to reflect current configuration files - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the different VIZ node types and their specific configurations. It covers full node setup with blockchain synchronization and API exposure, validator node configuration for block production and key management, seed node configuration for network bootstrap, and specialized configurations for testnet, debug, and MongoDB-integrated nodes. It also provides configuration file templates, parameter explanations, operational differences, performance tuning, resource allocation recommendations, monitoring requirements, and a comparison matrix across node configurations. - -## Project Structure -The repository organizes node configuration templates under share/vizd/config, Dockerfiles for different deployment modes under share/vizd/docker, and documentation under documentation. The main executable initializes plugins and loads configuration. - -```mermaid -graph TB -A["programs/vizd/main.cpp"] --> B["share/vizd/config/config.ini"] -A --> C["share/vizd/config/config_witness.ini"] -A --> D["share/vizd/config/config_debug.ini"] -A --> E["share/vizd/config/config_mongo.ini"] -A --> F["share/vizd/config/config_testnet.ini"] -A --> G["share/vizd/config/config_debug_mongo.ini"] -A --> H["share/vizd/config/config_stock_exchange.ini"] -I["share/vizd/docker/Dockerfile-production"] --> B -J["share/vizd/docker/Dockerfile-testnet"] --> F -K["share/vizd/docker/Dockerfile-mongo"] --> E -L["share/vizd/docker/Dockerfile-lowmem"] --> B -``` - -**Diagram sources** -- [main.cpp:106-158](file://programs/vizd/main.cpp#L106-L158) -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_debug_mongo.ini:1-135](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- [config_stock_exchange.ini:1-114](file://share/vizd/config/config_stock_exchange.ini#L1-L114) -- [Dockerfile-production:74-87](file://share/vizd/docker/Dockerfile-production#L74-L87) -- [Dockerfile-testnet:75-87](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [Dockerfile-mongo:97-110](file://share/vizd/docker/Dockerfile-mongo#L97-L110) -- [Dockerfile-lowmem:68-81](file://share/vizd/docker/Dockerfile-lowmem#L68-L81) - -**Section sources** -- [main.cpp:106-158](file://programs/vizd/main.cpp#L106-L158) -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [Dockerfile-production:74-87](file://share/vizd/docker/Dockerfile-production#L74-L87) -- [Dockerfile-testnet:75-87](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [Dockerfile-mongo:97-110](file://share/vizd/docker/Dockerfile-mongo#L97-L110) -- [Dockerfile-lowmem:68-81](file://share/vizd/docker/Dockerfile-lowmem#L68-L81) - -## Core Components -- Full node: default configuration with broad plugin set for general-purpose operation and API exposure. -- validator node: enables block production with validator and witness_api plugins, requires validator name and private key. -- Debug node: specialized configuration for simulation and experimentation with debug_node plugin. -- Testnet node: minimal configuration optimized for testnet operation with enabled stale production. -- MongoDB-integrated node: includes mongo_db plugin for external database indexing and analytics. -- Stock exchange node: specialized configuration for trading infrastructure with optimized performance settings. -- Seed node: bootstrap configuration with predefined seed peers for network discovery. - -Key configuration parameters: -- P2P endpoint (standardized to port 2001) and seed nodes for connectivity. -- Webserver endpoints for HTTP and WebSocket APIs. -- Plugin selection for functional capabilities. -- Shared memory sizing and growth thresholds for database performance. -- Lock wait timeouts and retries for RPC concurrency. -- validator participation and block production controls. -- Logging configuration via appenders and loggers. - -**Updated** Standardized P2P endpoint configuration to port 2001 across all configuration files, removing the previous inconsistency where some configurations used different ports. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_witness.ini:68-138](file://share/vizd/config/config_witness.ini#L68-L138) -- [config_debug.ini:69-126](file://share/vizd/config/config_debug.ini#L69-L126) -- [config_mongo.ini:69-135](file://share/vizd/config/config_mongo.ini#L69-L135) -- [config_testnet.ini:69-132](file://share/vizd/config/config_testnet.ini#L69-L132) -- [config_debug_mongo.ini:69-135](file://share/vizd/config/config_debug_mongo.ini#L69-L135) -- [config_stock_exchange.ini:69-114](file://share/vizd/config/config_stock_exchange.ini#L69-L114) - -## Architecture Overview -The VIZ node is a modular application built on appbase with a plugin architecture. The main program registers and initializes plugins, loads configuration, and starts the server loop. Different node types are achieved by selecting different plugins and configuration files. - -```mermaid -graph TB -subgraph "Executable" -MAIN["programs/vizd/main.cpp
Registers plugins and starts app"] -end -subgraph "Plugins" -CHAIN["chain"] -P2P["p2p"] -WEBSERVER["webserver"] -NB["network_broadcast_api"] -DBAPI["database_api"] -ACC_HIST["account_history"] -OP_HIST["operation_history"] -WIT["validator"] -WIT_API["witness_api"] -DEBUG["debug_node"] -MONGO["mongo_db"] -TESTAPI["test_api"] -TAGS["tags"] -FOLLOW["follow"] -SOCIAL["social_network"] -PRIVATE_MSG["private_message"] -COMMITTEE["committee_api"] -INVITE["invite_api"] -PAID_SUB["paid_subscription_api"] -CUSTOM["custom_protocol_api"] -STOCK_EX["stock_exchange"] -end -MAIN --> CHAIN -MAIN --> P2P -MAIN --> WEBSERVER -MAIN --> NB -MAIN --> DBAPI -MAIN --> ACC_HIST -MAIN --> OP_HIST -MAIN --> WIT -MAIN --> WIT_API -MAIN --> DEBUG -MAIN --> MONGO -MAIN --> TESTAPI -MAIN --> TAGS -MAIN --> FOLLOW -MAIN --> SOCIAL -MAIN --> PRIVATE_MSG -MAIN --> COMMITTEE -MAIN --> INVITE -MAIN --> PAID_SUB -MAIN --> CUSTOM -MAIN --> STOCK_EX -``` - -**Diagram sources** -- [main.cpp:62-91](file://programs/vizd/main.cpp#L62-L91) -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) - -**Section sources** -- [main.cpp:62-91](file://programs/vizd/main.cpp#L62-L91) -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) - -## Detailed Component Analysis - -### Full Node Configuration -- Purpose: General-purpose node with comprehensive API coverage and history tracking. -- Key parameters: - - P2P endpoint standardized to port 2001 with integrated seed nodes for connectivity. - - HTTP and WebSocket webserver endpoints for API access. - - Broad plugin set including chain, p2p, json_rpc, webserver, network_broadcast_api, database_api, account_history, operation_history, committee_api, invite_api, paid_subscription_api, custom_protocol_api, account_by_key, block_info, raw_block. - - Shared memory sizing and growth thresholds to manage database capacity. - - Lock wait and retry settings for RPC client concurrency. - - Optional virtual operations skipping and vote clearing to optimize performance. -- Operational differences: - - Exposes APIs publicly by default. - - Tracks extensive operation history. - - Suitable for production and public API services. - -**Updated** P2P endpoint now standardized to port 2001 with integrated seed node configuration directly in the template. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) - -### validator Node Configuration -- Purpose: Block production node with validator and witness_api plugins. -- Key parameters: - - P2P endpoint standardized to port 2001 with integrated seed nodes. - - Local webserver endpoints for internal access. - - Plugins: chain, p2p, json_rpc, webserver, network_broadcast_api, database_api, validator, witness_api. - - validator participation and stale production controls. - - Required validator name and private key for block signing. - - Optimized logging configuration. -- Operational differences: - - Requires valid validator credentials. - - Can operate with stricter network isolation (local webserver endpoints). - - Enables validator-specific APIs. - -```mermaid -sequenceDiagram -participant Operator as "Operator" -participant Node as "VIZ Node" -participant validator as "Validator Plugin" -participant Chain as "Chain Plugin" -Operator->>Node : Start with validator config -Node->>validator : Initialize with validator name and private key -validator->>Chain : Request scheduled slot -Chain-->>validator : Block production slot -validator->>Chain : Produce block with private key -Chain-->>Node : Accept block -Node-->>Operator : Report block production status -``` - -**Diagram sources** -- [config_witness.ini:82-86](file://share/vizd/config/config_witness.ini#L82-L86) -- [validator.hpp:20-32](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) - -**Section sources** -- [config_witness.ini:68-138](file://share/vizd/config/config_witness.ini#L68-L138) -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) - -### Debug Node Configuration -- Purpose: Simulation and experimentation with debug_node plugin for "what-if" scenarios. -- Key parameters: - - Minimal plugin set including chain, p2p, json_rpc, webserver, network_broadcast_api, database_api, debug_node, test_api, and others for deep inspection. - - Shared memory tuned for smaller footprint during development. - - Stale production enabled for accelerated simulation. - - Local webserver endpoints for secure access. -- Operational differences: - - Designed for local-only access. - - Provides APIs to push blocks, generate blocks, and update objects for simulation. - - Useful for hardfork testing and state experimentation. - -```mermaid -flowchart TD -Start(["Start Debug Node"]) --> LoadBlocks["Load blocks from directory"] -LoadBlocks --> Simulate["Simulate block production"] -Simulate --> UpdateState["Update chain state via debug APIs"] -UpdateState --> Verify["Verify operations and history"] -Verify --> End(["Stop and analyze"]) -``` - -**Diagram sources** -- [config_debug.ini:69-126](file://share/vizd/config/config_debug.ini#L69-L126) -- [plugin.hpp:62-90](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L62-L90) - -**Section sources** -- [config_debug.ini:69-126](file://share/vizd/config/config_debug.ini#L69-L126) -- [debug_node_plugin.md:50-134](file://documentation/debug_node_plugin.md#L50-L134) -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) - -### Testnet Node Configuration -- Purpose: Lightweight testnet node with stale production enabled for rapid iteration. -- Key parameters: - - P2P endpoint on port 4243 (different from mainnet) with integrated seed nodes. - - HTTP and WebSocket endpoints. - - Minimal plugin set focused on chain, p2p, json_rpc, webserver, network_broadcast_api, database_api, validator, witness_api. - - Stale production enabled and validator participation set to minimal. - - Predefined testnet validator and private key. -- Operational differences: - - Optimized for testnet with snapshot support. - - Simplified plugin set reduces overhead. - - Suitable for CI and automated testing. - -**Section sources** -- [config_testnet.ini:69-132](file://share/vizd/config/config_testnet.ini#L69-L132) -- [testnet.md:21-37](file://documentation/testnet.md#L21-L37) - -### MongoDB-Integrated Node Configuration -- Purpose: Node with mongo_db plugin for external analytics and off-chain indexing. -- Key parameters: - - P2P endpoint on port 4243 with integrated seed nodes. - - HTTP and WebSocket endpoints. - - Plugin set including mongo_db alongside standard plugins. - - MongoDB connection URI for external database. - - Market history bucket sizes and retention. -- Operational differences: - - Requires MongoDB instance and drivers. - - Adds significant I/O overhead; monitor storage and network. - - Enables advanced analytics and historical reporting. - -**Section sources** -- [config_mongo.ini:69-135](file://share/vizd/config/config_mongo.ini#L69-L135) - -### Stock Exchange Node Configuration -- Purpose: Specialized trading infrastructure node with optimized performance settings. -- Key parameters: - - P2P endpoint on port 4243 with integrated seed nodes. - - Local webserver endpoints for internal trading systems. - - Optimized plugin set focusing on chain, p2p, json_rpc, webserver, network_broadcast_api, validator, database_api, block_info, raw_block, operation_history, account_history, witness_api. - - Skip virtual operations and clear votes before block for improved performance. - - Reduced read wait retries for faster response times. -- Operational differences: - - Designed for high-frequency trading scenarios. - - Optimized for minimal latency and maximum throughput. - - Suitable for production trading infrastructure. - -**Section sources** -- [config_stock_exchange.ini:69-114](file://share/vizd/config/config_stock_exchange.ini#L69-L114) - -### Seed Node Configuration -- Purpose: Bootstrap and peer discovery for network formation. -- Key parameters: - - P2P endpoint bound to port 2001 for standard network compatibility. - - Integrated seed nodes configured directly in the template for immediate connectivity. - - Minimal plugin set to reduce resource usage. -- Operational differences: - - Does not synchronize the chain automatically. - - Serves as a discovery anchor for other nodes. - - Useful for private networks or isolated environments. - -**Updated** Removed references to external seed node file management system as seed nodes are now integrated directly into the config.ini templates. - -**Section sources** -- [config.ini:7-12](file://share/vizd/config/config.ini#L7-L12) - -## Dependency Analysis -The main executable registers and initializes plugins. validator and MongoDB plugins depend on the chain plugin. The debug node plugin depends on the chain plugin for state manipulation. Dockerfiles embed configuration templates and expose ports for RPC and P2P. - -```mermaid -graph LR -MAIN["programs/vizd/main.cpp"] --> REG["Register plugins"] -REG --> WIT["validator.hpp"] -REG --> MONGO["mongo_db_plugin.hpp"] -REG --> DEBUG["plugin.hpp"] -MAIN --> CFG["Config files"] -CFG --> DOCKER["Dockerfiles"] -``` - -**Diagram sources** -- [main.cpp:62-91](file://programs/vizd/main.cpp#L62-L91) -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) - -**Section sources** -- [main.cpp:62-91](file://programs/vizd/main.cpp#L62-L91) -- [validator.hpp:34-65](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) - -## Performance Considerations -- Shared memory sizing: - - Increase shared-file-size and adjust min-free-shared-file-size and inc-shared-file-size to accommodate larger histories and reduce resizing pressure. - - Monitor free space checks via block-num-check-free-size to balance safety and performance. -- Concurrency and locks: - - single-write-thread reduces contention on database writes. - - Tune read-wait-micro and max-read-wait-retries, write-wait-micro and max-write-wait-retries to match workload patterns. -- Plugin selection: - - Disable unused plugins to reduce memory and CPU overhead. - - Skip virtual operations and clear old votes to improve performance on full nodes. -- validator node specifics: - - Keep participation thresholds low for testnet; raise for production. - - Ensure private key availability and secure storage. -- Debug node specifics: - - Use local-only endpoints and smaller shared memory for development. - - Enable stale production for fast simulation. -- MongoDB node specifics: - - Provision adequate disk IOPS and network bandwidth for MongoDB. - - Monitor write amplification and index maintenance costs. -- Stock exchange node specifics: - - Optimize for high-frequency trading with reduced read wait retries. - - Enable skip-virtual-ops and clear-votes-before-block for maximum throughput. -- Resource allocation recommendations: - - Full node: moderate CPU, substantial RAM for shared memory, fast SSD for block log and database. - - validator node: dedicated CPU cores, reliable network, secure key management. - - Debug node: modest resources, local SSD, restricted network access. - - Testnet node: minimal resources, ephemeral data. - - MongoDB node: high IOPS storage, separate MongoDB cluster, network isolation. - - Stock exchange node: high-performance hardware, low-latency networking, optimized storage. - -## Troubleshooting Guide -- RPC lock errors: - - Adjust read-wait-micro/read-wait-retries and write-wait-micro/write-wait-retries. - - Consider enabling single-write-thread to reduce lock contention. -- Insufficient shared memory: - - Increase shared-file-size and tune min-free-shared-file-size and inc-shared-file-size. - - Monitor block-num-check-free-size frequency. -- validator production issues: - - Verify validator name and private key. - - Check participation thresholds and network synchronization. -- Debug node anomalies: - - Confirm local-only endpoints and secure access. - - Validate block pushing and generation commands. -- MongoDB integration: - - Verify mongodb-uri and connectivity. - - Monitor MongoDB performance and replica set health. -- P2P connectivity issues: - - Verify p2p-endpoint is set to port 2001 for standard network compatibility. - - Check p2p-seed-node configuration for proper peer discovery. -- Stock exchange performance: - - Monitor read-wait-micro settings for optimal latency. - - Ensure skip-virtual-ops and clear-votes-before-block are enabled. - -**Section sources** -- [config.ini:22-67](file://share/vizd/config/config.ini#L22-L67) -- [config_witness.ini:76-86](file://share/vizd/config/config_witness.ini#L76-L86) -- [config_debug.ini:95-105](file://share/vizd/config/config_debug.ini#L95-L105) -- [config_mongo.ini:71-72](file://share/vizd/config/config_mongo.ini#L71-L72) -- [config_stock_exchange.ini:22-34](file://share/vizd/config/config_stock_exchange.ini#L22-L34) - -## Conclusion -Different VIZ node types serve distinct operational needs. Full nodes provide broad API coverage, validator nodes enable consensus participation, debug nodes support experimentation, testnet nodes accelerate development, MongoDB-integrated nodes enable advanced analytics, and stock exchange nodes optimize for trading infrastructure. Proper configuration, performance tuning, and monitoring are essential for each type to achieve reliable operation. The standardized P2P endpoint configuration ensures consistent network compatibility across all node types. - -## Appendices - -### Configuration Templates and Parameters -- Full node template: [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- validator node template: [config_witness.ini:1-138](file://share/vizd/config/config_witness.ini#L1-L138) -- Debug node template: [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- MongoDB node template: [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- Testnet template: [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- Debug + MongoDB template: [config_debug_mongo.ini:1-135](file://share/vizd/config/config_debug_mongo.ini#L1-L135) -- Stock exchange template: [config_stock_exchange.ini:1-114](file://share/vizd/config/config_stock_exchange.ini#L1-L114) - -### Node Type Comparison Matrix - -| Feature | Full Node | validator Node | Debug Node | Testnet Node | MongoDB Node | Stock Exchange Node | -|---|---|---|---|---|---|---| -| P2P endpoint | 0.0.0.0:2001 | 0.0.0.0:2001 | Not set | 0.0.0.0:4243 | 0.0.0.0:4243 | 0.0.0.0:4243 | -| Seed nodes | Integrated | Integrated | Not set | Integrated | Integrated | Integrated | -| Webserver endpoints | Public | Local | Local | Public | Public | Local | -| Plugins | Broad | Essential + validator | Debug + test | Minimal | Mongo + essentials | Optimized trading | -| Shared memory | Large | Large | Small | Large | Large | Large | -| Stale production | Off | On (configurable) | On | On | Off | Off | -| validator participation | N/A | Configurable | N/A | Configurable | N/A | N/A | -| Private key | N/A | Required | N/A | N/A | N/A | N/A | -| MongoDB integration | No | No | No | No | Yes | No | -| Typical use | Production API | Consensus | Dev/Test | CI/Testing | Analytics | Trading Infrastructure | -| Port standardization | ✅ 2001 | ✅ 2001 | ❌ Not set | ❌ 4243 | ❌ 4243 | ❌ 4243 | - -**Updated** Added stock exchange node to the comparison matrix and updated P2P endpoint port information to reflect the standardized configuration. - -**Section sources** -- [config.ini:1-136](file://share/vizd/config/config.ini#L1-L136) -- [config_witness.ini:68-138](file://share/vizd/config/config_witness.ini#L68-L138) -- [config_debug.ini:69-126](file://share/vizd/config/config_debug.ini#L69-L126) -- [config_testnet.ini:69-132](file://share/vizd/config/config_testnet.ini#L69-L132) -- [config_mongo.ini:69-135](file://share/vizd/config/config_mongo.ini#L69-L135) -- [config_stock_exchange.ini:69-114](file://share/vizd/config/config_stock_exchange.ini#L69-L114) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Security Hardening.md b/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Security Hardening.md deleted file mode 100644 index 80ea17902e..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Security Hardening.md +++ /dev/null @@ -1,300 +0,0 @@ -# Security Hardening - - -**Referenced Files in This Document** -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [main.cpp](file://programs/vizd/main.cpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive security hardening guidance for deploying VIZ CPP Node across environments from development to production. It focuses on network security, API security, cryptography, system-level controls, monitoring, vulnerability management, updates, and incident response. The guidance is grounded in the repository’s configuration, plugin architecture, and network/cryptography components. - -## Project Structure -VIZ CPP Node is organized around: -- Application entry and plugin registration -- Network stack with P2P and secure transport -- Webserver and JSON-RPC API surface -- Configuration-driven runtime behavior -- Containerized production packaging - -```mermaid -graph TB -A["programs/vizd/main.cpp"] --> B["plugins/p2p/p2p_plugin.hpp"] -A --> C["plugins/webserver/webserver_plugin.hpp"] -A --> D["plugins/json_rpc/plugin.hpp"] -C --> D -B --> E["libraries/network/node.hpp"] -E --> F["libraries/network/config.hpp"] -E --> G["libraries/network/stcp_socket.hpp"] -H["share/vizd/config/config.ini"] --> A -I["share/vizd/docker/Dockerfile-production"] --> A -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) - -## Core Components -- P2P networking and peer management -- Secure transport via ECDH/AES -- HTTP/WebSocket API via webserver plugin backed by JSON-RPC -- Configuration-driven behavior and logging - -Key security-relevant elements: -- Network limits and timeouts -- Logging configuration -- Plugin exposure surface -- Transport encryption - -**Section sources** -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -## Architecture Overview -The node exposes: -- P2P endpoint for peer-to-peer synchronization -- HTTP and WebSocket endpoints for API access -- Optional validator production and related APIs - -```mermaid -graph TB -subgraph "Node Process" -N["Network Layer
node.hpp"] -S["Secure Transport
stcp_socket.hpp"] -W["Webserver Plugin
webserver_plugin.hpp"] -R["JSON-RPC Dispatcher
plugin.hpp"] -P["P2P Plugin
p2p_plugin.hpp"] -end -Internet["External Clients"] --> W -W --> R -N --> S -N --> P -P --> N -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) - -## Detailed Component Analysis - -### Network Security Configuration -- P2P endpoint binding and advertised ports -- Connection limits and timeouts -- Message size limits and bandwidth parameters -- Logging for P2P subsystem - -Recommendations: -- Restrict P2P listen address to internal or DMZ interfaces as appropriate -- Limit maximum connections and desired connections per operational profile -- Harden message size and rate parameters to mitigate resource exhaustion -- Enable and tune logging for anomaly detection - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L200-L304) - -### Port Management and Network Isolation -- Exposed ports in production container: - - HTTP API: 8090 - - WebSocket API: 8091 - - P2P: 2001 -- Default configuration binds P2P to 0.0.0.0:4243 - -Guidance: -- Apply firewall rules to restrict inbound access to only required ports -- Segment networks via VLANs or namespaces; prefer loopback/internal binding for P2P where feasible -- Use reverse proxies or gateways for external API access with TLS termination - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L79-L86) -- [config.ini](file://share/vizd/config/config.ini#L2-L20) - -### API Security: Authentication, Rate Limiting, Access Control -- Current configuration does not define explicit authentication or rate limiting for RPC endpoints -- JSON-RPC is the transport mechanism; API exposure depends on loaded plugins - -Recommendations: -- Deploy API behind an authenticating gateway or reverse proxy -- Enforce per-endpoint quotas and sliding window rate limits -- Restrict sensitive RPC methods to trusted IPs or VPN -- Rotate secrets and enforce mutual TLS where applicable - -Note: The repository does not include built-in authentication or rate limiting in the referenced files. - -**Section sources** -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [config.ini](file://share/vizd/config/config.ini#L13-L20) - -### Cryptographic Security Measures -- Secure TCP socket uses ECDH key exchange and AES encryption for P2P transport -- Shared secret derived per-connection - -Recommendations: -- Ensure private keys for signing are managed externally and rotated regularly -- Use hardware security modules or key management systems for key storage -- Validate certificates and enforce strict TLS policies at ingress - -**Section sources** -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L105-L111) - -### System-Level Security -- Production container creates a dedicated node user and separates volumes -- Logging configuration supports console and file appenders - -Recommendations: -- Run as non-root user with minimal privileges -- Bind mount persistent volumes with restrictive filesystem permissions -- Enable filesystem integrity monitoring and immutable logs where possible -- Disable unnecessary plugins to reduce attack surface - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L69-L88) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [config.ini](file://share/vizd/config/config.ini#L111-L130) - -### Security Monitoring and Threat Detection -- Logging configuration supports separate loggers and appenders -- P2P subsystem tracks propagation timing and peer status - -Recommendations: -- Centralize logs and correlate P2P and API events -- Monitor for unusual spikes in transactions or blocks -- Alert on repeated handshake failures, unexpected disconnects, or malformed messages - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L167-L289) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L173-L179) - -### Vulnerability Assessment and Patch Management -- Build-time toggles for optional components and memory safety checks -- Release builds recommended for production - -Recommendations: -- Perform static/dynamic analysis on release artifacts -- Establish a patch cadence aligned with upstream releases and security advisories -- Maintain SBOM and track third-party dependencies - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) - -### Incident Response Protocols -- Graceful shutdown hooks and structured logging -- Clear separation of concerns between plugins and network layers - -Recommendations: -- Define runbooks for high load, connectivity issues, and crypto key rotation -- Automate log collection and isolate affected nodes immediately -- Review and update configurations post-incident - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L220) - -## Dependency Analysis -The API stack depends on the webserver plugin and JSON-RPC dispatcher. The P2P stack depends on the node and secure transport components. - -```mermaid -graph LR -WS["webserver_plugin.hpp"] --> JR["plugin.hpp"] -P2P["p2p_plugin.hpp"] --> NET["node.hpp"] -NET --> CFG["config.hpp"] -NET --> STCP["stcp_socket.hpp"] -``` - -**Diagram sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -**Section sources** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp#L26-L106) -- [stcp_socket.hpp](file://libraries/network/include/graphene/network/stcp_socket.hpp#L37-L93) - -## Performance Considerations -- Lock wait and retry parameters influence resilience under load -- Single write thread reduces contention but may bottleneck high-throughput scenarios -- Adjust thread pool sizes and connection limits according to workload - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common areas to inspect: -- P2P connectivity and peer counts -- API latency and throughput -- Log levels and destinations -- Resource exhaustion symptoms (locks, memory, disk) - -Operational tips: -- Temporarily increase log verbosity for diagnosis -- Verify endpoint reachability and firewall rules -- Confirm plugin initialization order and dependencies - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L22-L47) -- [main.cpp](file://programs/vizd/main.cpp#L167-L289) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L248-L253) - -## Conclusion -This guide consolidates security hardening practices for VIZ CPP Node deployments using repository-provided configuration and components. By applying network isolation, robust logging, transport encryption, and operational controls, teams can significantly improve security posture across environments. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Compliance and Audit Considerations -- Maintain audit trails for sensitive operations -- Enforce least privilege and segregation of duties -- Document configuration baselines and deviations -- Align logging retention and immutability policies with compliance requirements - -[No sources needed since this section provides general guidance] \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Service Integration.md b/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Service Integration.md deleted file mode 100644 index fe26eefa46..0000000000 --- a/.qoder/repowiki/en/content/Deployment and Operations/Node Deployment/Service Integration.md +++ /dev/null @@ -1,339 +0,0 @@ -# Service Integration - - -**Referenced Files in This Document** -- [vizd.sh](file://share/vizd/vizd.sh) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [docker-main.yml](file://.github/workflows/docker-main.yml) -- [main.cpp](file://programs/vizd/main.cpp) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive service integration guidance for deploying and operating the VIZ CPP Node across Linux and Windows environments. It covers: -- Linux service configuration via containerized runtime with Phusion baseimage and runit-style supervision -- Windows service installation and management procedures -- Reverse proxy, load balancer, and API gateway integration -- Monitoring, health checks, metrics, and alerting -- Log rotation, centralized logging, and log aggregation -- Cloud platform, container orchestration, and CI/CD pipeline integration -- Backup and recovery, disaster recovery, and high availability -- Integration examples with monitoring/logging/infrastructure platforms - -## Project Structure -The repository organizes the VIZ node around: -- A main executable entry point -- A modular plugin architecture for P2P networking and HTTP/WebSocket APIs -- Container images and scripts for Linux deployment -- Configuration templates for mainnet and testnet - -```mermaid -graph TB -subgraph "Executable" -MAIN["programs/vizd/main.cpp"] -end -subgraph "Plugins" -WS["plugins/webserver/webserver_plugin.cpp"] -P2P["plugins/p2p/p2p_plugin.cpp"] -end -subgraph "Containerization" -DPROD["share/vizd/docker/Dockerfile-production"] -DTEST["share/vizd/docker/Dockerfile-testnet"] -DLOW["share/vizd/docker/Dockerfile-lowmem"] -RUN["share/vizd/vizd.sh"] -CFG["share/vizd/config/config.ini"] -CFGT["share/vizd/config/config_testnet.ini"] -end -MAIN --> WS -MAIN --> P2P -DPROD --> RUN -DPROD --> CFG -DTEST --> RUN -DTEST --> CFGT -DLOW --> RUN -DLOW --> CFG -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L248-L335) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L461-L603) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L87) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L87) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L81) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L248-L335) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L461-L603) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L87) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L87) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L81) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -## Core Components -- Executable entry point initializes plugins and starts the application lifecycle. -- Webserver plugin exposes HTTP and WebSocket endpoints for JSON-RPC. -- P2P plugin manages peer-to-peer connectivity, block synchronization, and transaction propagation. -- Container images define runtime user, exposed ports, and supervised startup script. -- Configuration files define endpoints, plugins, and logging behavior for mainnet and testnet. - -Key runtime and configuration touchpoints: -- HTTP and WebSocket endpoints are configurable via program options and configuration files. -- Logging configuration is loaded from the configuration file and supports console and file appenders. -- P2P endpoint and seed nodes are configurable via program options and environment variables in the container script. - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L254-L312) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L467-L529) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L81) - -## Architecture Overview -The VIZ node runs as a single binary with pluggable components. The containerized runtime supervises the process, injects configuration, and exposes network endpoints. The webserver plugin serves HTTP and WebSocket JSON-RPC traffic, while the P2P plugin handles blockchain synchronization and gossip. - -```mermaid -graph TB -subgraph "Host OS" -SVC["Supervision (Phusion baseimage)"] -NET["Network Interfaces"] -end -subgraph "Container Runtime" -IMG["Image: phusion/baseimage"] -RUN["/etc/service/vizd/run (vizd.sh)"] -BIN["/usr/local/bin/vizd"] -CFG["/etc/vizd/config.ini"] -DATA["/var/lib/vizd"] -end -subgraph "Node Process" -MAIN["main.cpp
registers plugins"] -WS["webserver_plugin.cpp
HTTP/WS JSON-RPC"] -P2P["p2p_plugin.cpp
P2P sync/gossip"] -end -SVC --> IMG -IMG --> RUN -RUN --> BIN -BIN --> WS -BIN --> P2P -BIN --> CFG -BIN --> DATA -NET --> WS -NET --> P2P -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L87) -- [vizd.sh](file://share/vizd/vizd.sh#L74-L81) -- [main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L248-L335) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L531-L566) - -## Detailed Component Analysis - -### Linux Service Configuration (Containerized) -The repository provides a production-ready container image that: -- Creates a dedicated node user -- Copies a supervision script to run the node under runit-style supervision -- Exposes RPC and P2P ports -- Mounts persistent volumes for configuration and data - -Operational guidance: -- Use the provided Dockerfile-production or testnet variant to build images. -- Run the container with volume mounts for configuration and data directories. -- Configure environment variables in the container to override endpoints and seed nodes as needed. -- The supervision script ensures the node restarts on failure and applies environment overrides. - -```mermaid -flowchart TD -Start(["Container Start"]) --> CopyCfg["Copy config.ini to /etc/vizd"] -CopyCfg --> SeedNodes["Load seednodes from /etc/vizd/seednodes"] -SeedNodes --> EnvOverride{"Environment overrides present?"} -EnvOverride --> |Yes| ApplyEnv["Apply VIZD_* env vars"] -EnvOverride --> |No| Defaults["Use defaults from config.ini"] -ApplyEnv --> Prepare["Prepare data-dir and cache"] -Defaults --> Prepare -Prepare --> Exec["Execute /usr/local/bin/vizd with args"] -Exec --> Supervise["Supervised by runit-style /etc/service/vizd/run"] -Supervise --> End(["Healthy Running"]) -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L69-L87) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L87) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L67-L87) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L81) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -### Windows Service Installation and Management -The repository does not include Windows-specific service files or scripts. To deploy on Windows: -- Package the VIZ node binary and required configuration files into a Windows-compatible artifact. -- Create a Windows service wrapper using a launcher such as NSSM or WinSW to manage the process lifecycle. -- Configure the service to run under a dedicated user account with appropriate permissions. -- Use the configuration files from the repository to define endpoints and plugins. -- Integrate with Windows Event Log for logging and monitoring. - -[No sources needed since this section provides general guidance] - -### Integration with Reverse Proxies, Load Balancers, and API Gateways -The node exposes: -- HTTP JSON-RPC endpoint -- WebSocket JSON-RPC endpoint -- P2P endpoint for peers - -Recommended integration patterns: -- Place an HTTP load balancer or reverse proxy in front of the HTTP and WebSocket endpoints. -- Configure health checks against the HTTP endpoint and/or a dedicated health path if available. -- Use sticky sessions only if required by client workflows; otherwise, distribute across multiple node instances. -- For API gateways, expose only the JSON-RPC endpoints and apply rate limiting and authentication policies at the gateway layer. - -[No sources needed since this section provides general guidance] - -### Monitoring Integration (Health Checks, Metrics, Alerting) -Monitoring capabilities: -- Health checks: Poll the HTTP endpoint to verify node responsiveness. -- Logs: The node supports console and file appenders configured via the configuration file. -- Metrics: No built-in metrics endpoint is present in the referenced code; integrate external metrics collection at the host/container level. - -Operational recommendations: -- Centralize logs from the node’s file appender to a SIEM or log aggregation platform. -- Use host/container metrics (CPU, memory, disk) and network throughput for alerting. -- Define alerts for restart storms, high latency, low free disk, and P2P connection counts. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L111-L130) -- [main.cpp](file://programs/vizd/main.cpp#L167-L191) - -### Log Rotation, Centralized Logging, and Log Aggregation -Logging configuration: -- Console and file appenders are supported and configurable. -- File appender rotation and flushing are programmatically enabled during configuration loading. - -Integration guidance: -- Route node logs to a centralized logging system (e.g., ELK, Loki, Splunk) using standard collectors. -- Ensure log paths are persisted via mounted volumes and managed by the container runtime. -- Rotate logs at the collector level to avoid blocking the node process. - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L211-L288) -- [config.ini](file://share/vizd/config/config.ini#L111-L130) - -### Cloud Platforms, Container Orchestration, and Automated Pipelines -Container images: -- Production and testnet images are built from the provided Dockerfiles. -- GitHub Actions workflow builds and publishes images on pushes to master. - -Orchestration and CI/CD: -- Deploy containers to Kubernetes, ECS, or similar orchestrators using the published images. -- Use the workflow as a template for automated image builds and publishing. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) - -### Backup and Recovery, Disaster Recovery, and High Availability -Backup and recovery: -- Back up the data directory (blockchain state) regularly. -- Snapshot the configuration directory to preserve runtime settings. -- Test restoration procedures in isolated environments before applying to production. - -High availability: -- Run multiple node instances behind a load balancer. -- Prefer validator nodes with redundant infrastructure and monitoring. -- Use rolling updates to minimize downtime during maintenance. - -[No sources needed since this section provides general guidance] - -### Integration Examples with Monitoring, Logging, and Infrastructure Platforms -- Monitoring: Prometheus node_exporter + custom JSON-RPC health exporter; Grafana dashboards for latency and peer counts. -- Logging: Fluent Bit or Filebeat shipping logs to Elasticsearch/OpenSearch; centralized dashboards. -- Infrastructure: Terraform/AWS/GCP/Azure for provisioning; Helm/Kustomize for Kubernetes deployments. - -[No sources needed since this section provides general guidance] - -## Dependency Analysis -The node executable registers and initializes plugins. The webserver plugin depends on the JSON-RPC plugin and chain plugin state. The P2P plugin depends on the chain plugin for database operations. - -```mermaid -graph LR -MAIN["main.cpp"] --> REG["register_plugins()"] -REG --> WSPLUG["webserver_plugin.cpp"] -REG --> P2PPLUG["p2p_plugin.cpp"] -WSPLUG --> JRPC["JSON-RPC plugin"] -WSPLUG --> CHAIN["chain plugin"] -P2PPLUG --> CHAIN -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L314-L326) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L531-L566) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L314-L326) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L531-L566) - -## Performance Considerations -- Thread pool sizing: Tune the webserver thread pool size according to CPU cores and expected concurrency. -- Lock contention: The configuration enables single-write-thread mode to reduce database lock contention. -- Shared memory sizing: Adjust shared file size and thresholds to prevent frequent resizing during operation. -- P2P connections: Limit maximum connections and carefully select seed nodes to balance connectivity and resource usage. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L13-L67) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L254-L263) - -## Troubleshooting Guide -Common operational issues and resolutions: -- Startup failures: Verify configuration file syntax and required directories exist inside the container. -- Network connectivity: Confirm P2P endpoint binding and firewall rules; ensure seed nodes are reachable. -- Log visibility: Check file appender paths and permissions; confirm log levels are set appropriately. -- Health checks failing: Validate HTTP/WS endpoints and ensure plugins are fully synced before exposing to clients. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L111-L130) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L531-L566) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp#L314-L326) - -## Conclusion -The VIZ CPP Node is designed for containerized deployment with robust supervision and flexible configuration. By leveraging the provided Docker images, configuration templates, and plugin architecture, operators can integrate the node into modern infrastructure with reverse proxies, load balancers, monitoring, logging, and CI/CD pipelines. For Windows environments, a Windows service wrapper should be created alongside the configuration artifacts. Disaster recovery and high availability are achieved through multiple node instances, regular backups, and careful orchestration. - -## Appendices -- Environment variable overrides in the container script: - - RPC and P2P endpoints - - validator name and private key - - Seed nodes - - Extra options - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L62-L81) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Scripts.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Scripts.md deleted file mode 100644 index 14a2884e4e..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Scripts.md +++ /dev/null @@ -1,416 +0,0 @@ -# Build Helper Scripts - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [programs/build_helpers/CMakeLists.txt](file://programs/build_helpers/CMakeLists.txt) -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [libraries/chain/hardfork.d/0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [libraries/chain/hardfork.d/1.hf](file://libraries/chain/hardfork.d/1.hf) -- [documentation/building.md](file://documentation/building.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document explains the build helper scripts and automation tools used to compile the VIZ blockchain node across multiple platforms. It covers the shell and batch scripts for Linux/macOS/Windows, the CMake-based build system, and specialized helper utilities that assist with hardfork file concatenation, reflection validation, and cross-platform configuration. - -## Project Structure -The build system is organized around: -- Platform-specific build scripts for quick configuration and compilation -- A centralized CMake configuration that defines compile-time options and platform flags -- Helper utilities under programs/build_helpers for specialized tasks like hardfork file assembly and reflection consistency checks -- Documentation that describes all supported build options and workflows - -```mermaid -graph TB -subgraph "Platform Build Scripts" -LNX["build-linux.sh"] -MAC["build-mac.sh"] -MGW["build-mingw.bat"] -MSC["build-msvc.bat"] -end -subgraph "CMake Configuration" -CML["CMakeLists.txt"] -HF0["libraries/chain/hardfork.d/0-preamble.hf"] -HF1["libraries/chain/hardfork.d/1.hf"] -end -subgraph "Build Helpers" -CATCPP["programs/build_helpers/cat-parts.cpp"] -CATPY["programs/build_helpers/cat_parts.py"] -REFCHK["programs/build_helpers/check_reflect.py"] -CFGPY["programs/build_helpers/configure_build.py"] -end -LNX --> CML -MAC --> CML -MGW --> CML -MSC --> CML -CML --> HF0 -CML --> HF1 -CML --> CATCPP -CML --> CATPY -CML --> REFCHK -CML --> CFGPY -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt) -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [programs/build_helpers/CMakeLists.txt](file://programs/build_helpers/CMakeLists.txt) -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [libraries/chain/hardfork.d/0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [libraries/chain/hardfork.d/1.hf](file://libraries/chain/hardfork.d/1.hf) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt) -- [documentation/building.md](file://documentation/building.md) - -## Core Components -- Platform build scripts: - - Linux: automated dependency installation, submodule initialization, CMake configuration, parallel build, and optional install - - macOS: Xcode/Homebrew checks, OpenSSL detection, CMake configuration, parallel build, and optional install - - Windows MSVC: CMake configuration with Visual Studio generator, build execution, and optional install - - Windows MinGW: CMake configuration with MinGW generator, build execution, and optional install -- CMake configuration: - - Centralized compile-time options (build type, memory mode, testnet, MongoDB plugin, shared/static libs) - - Platform-specific compiler flags and static linking policies - - Hardfork file inclusion and generation pipeline -- Build helper utilities: - - Concatenate hardfork .hf files into a single header - - Validate FC_REFLECT declarations match Doxygen-extracted class members - - Cross-platform CMake configuration helper for Windows builds - - Python-based file concatenation utility - -**Section sources** -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [CMakeLists.txt](file://CMakeLists.txt) -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) - -## Architecture Overview -The build architecture integrates platform scripts with CMake and helper utilities to provide a unified, repeatable build experience across platforms. - -```mermaid -graph TB -subgraph "User Workflow" -U1["Select platform script"] -U2["Configure options"] -U3["Run build"] -end -subgraph "Build System" -S1["Platform script"] -S2["CMake configuration"] -S3["Compiler and linker"] -S4["Helper utilities"] -end -subgraph "Outputs" -O1["Executable binaries"] -O2["Documentation artifacts"] -O3["Hardfork headers"] -end -U1 --> S1 -U2 --> S2 -U3 --> S2 -S4 --> S2 -S2 --> O1 -S2 --> O3 -S4 --> O2 -``` - -**Diagram sources** -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [CMakeLists.txt](file://CMakeLists.txt) -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) - -## Detailed Component Analysis - -### Linux Build Script -The Linux script automates dependency installation (based on detected package manager), submodule initialization, CMake configuration with configurable options, parallel build execution, and optional installation. - -```mermaid -flowchart TD -Start(["Script start"]) --> ParseArgs["Parse CLI arguments"] -ParseArgs --> DetectJobs["Detect parallel jobs"] -DetectJobs --> CheckDir["Verify source directory"] -CheckDir --> InstallDeps{"Skip deps?"} -InstallDeps --> |Yes| InitSubmodules["Initialize submodules"] -InstallDeps --> |No| InstallDepsFn["Install dependencies"] -InstallDepsFn --> InitSubmodules -InitSubmodules --> Configure["CMake configure with options"] -Configure --> Build["Parallel build"] -Build --> InstallOpt{"Install?"} -InstallOpt --> |Yes| DoInstall["Install to system"] -InstallOpt --> |No| SkipInstall["Skip install"] -DoInstall --> Done(["Complete"]) -SkipInstall --> Done -``` - -**Diagram sources** -- [build-linux.sh](file://build-linux.sh) - -**Section sources** -- [build-linux.sh](file://build-linux.sh) -- [documentation/building.md](file://documentation/building.md) - -### macOS Build Script -The macOS script verifies Xcode Command Line Tools and Homebrew, detects OpenSSL path, initializes submodules, configures CMake, builds in parallel, and optionally installs. - -```mermaid -flowchart TD -Start(["Script start"]) --> CheckXcode["Check Xcode Command Line Tools"] -CheckXcode --> CheckBrew["Check Homebrew"] -CheckBrew --> InstallDeps{"Skip deps?"} -InstallDeps --> |Yes| DetectOpenSSL["Detect OpenSSL path"] -InstallDeps --> |No| InstallDepsFn["Install dependencies via Homebrew"] -InstallDepsFn --> DetectOpenSSL -DetectOpenSSL --> InitSubmodules["Initialize submodules"] -InitSubmodules --> Configure["CMake configure with options"] -Configure --> Build["Parallel build"] -Build --> InstallOpt{"Install?"} -InstallOpt --> |Yes| DoInstall["Install to system"] -InstallOpt --> |No| SkipInstall["Skip install"] -DoInstall --> Done(["Complete"]) -SkipInstall --> Done -``` - -**Diagram sources** -- [build-mac.sh](file://build-mac.sh) - -**Section sources** -- [build-mac.sh](file://build-mac.sh) -- [documentation/building.md](file://documentation/building.md) - -### Windows Build Scripts -Two Windows scripts support different toolchains: -- MSVC: Uses Visual Studio generator and CMake configuration with optional extra flags -- MinGW: Uses MinGW Makefiles generator and CMake configuration with static/full-static options - -```mermaid -sequenceDiagram -participant User as "User" -participant Script as "Windows Build Script" -participant CMake as "CMake" -participant Generator as "Generator (MSVC/MinGW)" -participant Build as "Build System" -User->>Script : Invoke script with environment variables -Script->>CMake : Configure with generators and options -CMake->>Generator : Select generator and toolchain -Generator-->>CMake : Toolchain configured -CMake-->>Script : Configuration complete -Script->>Build : Build with selected generator -Build-->>Script : Build complete -Script-->>User : Report completion -``` - -**Diagram sources** -- [build-msvc.bat](file://build-msvc.bat) -- [build-mingw.bat](file://build-mingw.bat) - -**Section sources** -- [build-msvc.bat](file://build-msvc.bat) -- [build-mingw.bat](file://build-mingw.bat) -- [documentation/building.md](file://documentation/building.md) - -### CMake Configuration and Compile-Time Options -CMake centralizes build configuration, compile-time flags, and platform-specific settings. Key options include build type, memory mode, testnet, MongoDB plugin, shared/static libraries, and chainbase lock checking. - -```mermaid -flowchart TD -Start(["CMake configure"]) --> ReadOptions["Read compile-time options"] -ReadOptions --> PlatformFlags["Apply platform-specific flags"] -PlatformFlags --> FindDeps["Find Boost/OpenSSL"] -FindDeps --> GenerateTargets["Generate build targets"] -GenerateTargets --> WriteFiles["Write compile_commands.json"] -WriteFiles --> End(["Ready to build"]) -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt) -- [documentation/building.md](file://documentation/building.md) - -### Hardfork File Concatenation Utility (C++) -The C++ utility scans a directory for .hf files, sorts them, concatenates their contents, and writes a single output file if the content differs from existing output. - -```mermaid -flowchart TD -Start(["Start"]) --> Args["Validate arguments"] -Args --> Scan["Scan directory for .hf files"] -Scan --> Sort["Sort files lexicographically"] -Sort --> Read["Read concatenated content"] -Read --> Compare{"Compare with existing output?"} -Compare --> |Same| UpToDate["Mark as up-to-date"] -Compare --> |Different| Write["Write new output"] -UpToDate --> End(["Exit"]) -Write --> End -``` - -**Diagram sources** -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) - -**Section sources** -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [libraries/chain/hardfork.d/0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [libraries/chain/hardfork.d/1.hf](file://libraries/chain/hardfork.d/1.hf) - -### Hardfork File Concatenation Utility (Python) -The Python utility provides equivalent functionality to the C++ version with robust error handling and directory creation. - -```mermaid -flowchart TD -Start(["Start"]) --> Args["Validate arguments"] -Args --> Exists{"Output exists?"} -Exists --> |Yes| ReadOld["Read existing output"] -ReadOld --> Compute["Compute new concatenated content"] -Compute --> Same{"Content same?"} -Same --> |Yes| Exit0["Exit 0 (up-to-date)"] -Same --> |No| Write["Write new content"] -Exists --> |No| MkDir["Ensure parent directory exists"] -MkDir --> Compute -Write --> Exit0 -``` - -**Diagram sources** -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) - -**Section sources** -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) - -### Reflection Consistency Checker -The Python script validates that FC_REFLECT declarations match Doxygen-extracted class members, reporting mismatches and duplicates. - -```mermaid -flowchart TD -Start(["Start"]) --> ParseDoxygen["Parse Doxygen XML index"] -ParseDoxygen --> ExtractMembers["Extract class members"] -ExtractMembers --> WalkSources["Walk source tree for FC_REFLECT"] -WalkSources --> Compare["Compare member sets"] -Compare --> Report["Report OK/Not Evaluated/Error items"] -Report --> Exit(["Exit with status"]) -``` - -**Diagram sources** -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) - -**Section sources** -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) - -### Cross-Platform CMake Configuration Helper (Windows) -The Python helper constructs CMake commands with platform-specific flags, environment variable support, and optional additional options. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Helper as "configure_build.py" -participant CMake as "CMake" -participant FS as "Filesystem" -Dev->>Helper : Provide options (paths, flags) -Helper->>FS : Resolve paths and validate -Helper->>Helper : Detect Boost version -Helper->>CMake : Build command with flags -CMake-->>Helper : Execute configuration -Helper-->>Dev : Print command and exit code -``` - -**Diagram sources** -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) - -**Section sources** -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) - -## Dependency Analysis -The build system exhibits clear separation of concerns: -- Platform scripts depend on CMake and system tools -- CMake depends on Boost and OpenSSL availability -- Helper utilities are standalone and can be invoked independently -- Hardfork concatenation utilities depend on filesystem access and input ordering - -```mermaid -graph TB -LNX["build-linux.sh"] --> CML["CMakeLists.txt"] -MAC["build-mac.sh"] --> CML -MGW["build-mingw.bat"] --> CML -MSC["build-msvc.bat"] --> CML -CATCPP["cat-parts.cpp"] --> HFDIR["hardfork.d/*.hf"] -CATPY["cat_parts.py"] --> HFDIR -REFCHK["check_reflect.py"] --> SRC["Source tree"] -CFGPY["configure_build.py"] --> CML -CML --> BOOST["Boost libraries"] -CML --> SSL["OpenSSL libraries"] -``` - -**Diagram sources** -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [CMakeLists.txt](file://CMakeLists.txt) -- [programs/build_helpers/cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [libraries/chain/hardfork.d/0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [libraries/chain/hardfork.d/1.hf](file://libraries/chain/hardfork.d/1.hf) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt) -- [programs/build_helpers/CMakeLists.txt](file://programs/build_helpers/CMakeLists.txt) - -## Performance Considerations -- Parallel builds: All platform scripts support parallel job counts to speed up compilation -- Dependency caching: CMake and ccache integration reduce rebuild times -- Static linking: Windows scripts offer full static builds to simplify deployment -- Hardfork concatenation: Efficient sorting and streaming minimize I/O overhead - -## Troubleshooting Guide -- Linux/macOS dependency issues: Use the provided scripts to install required packages; verify submodules are initialized -- Windows environment variables: Ensure BOOST_ROOT and OPENSSL_ROOT_DIR are set and point to valid directories -- Hardfork header mismatch: Re-run the concatenation utility to regenerate the combined header -- Reflection validation failures: Fix FC_REFLECT declarations to match Doxygen-extracted members -- CMake configuration errors: Confirm compiler versions meet minimum requirements and required libraries are discoverable - -**Section sources** -- [documentation/building.md](file://documentation/building.md) -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) - -## Conclusion -The build helper scripts and CMake configuration provide a robust, cross-platform build system for the VIZ node. They automate dependency management, platform-specific configurations, and quality checks, enabling contributors and operators to build reliably across Linux, macOS, and Windows environments. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Build Helper Tools.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Build Helper Tools.md deleted file mode 100644 index b323302dd9..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Build Helper Tools.md +++ /dev/null @@ -1,686 +0,0 @@ -# Build Helper Tools - - -**Referenced Files in This Document** -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [newplugin.py](file://programs/util/newplugin.py) -- [pretty_schema.py](file://programs/util/pretty_schema.py) -- [schema_test.cpp](file://programs/util/schema_test.cpp) -- [configure_build.py](file://programs/build_helpers/configure_build.py) -- [install-deps-linux.sh](file://install-deps-linux.sh) -- [build-linux.sh](file://build-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [CMakeLists.txt (build_helpers)](file://programs/build_helpers/CMakeLists.txt) -- [CMakeLists.txt (util)](file://programs/util/CMakeLists.txt) -- [building.md](file://documentation/building.md) -- [README.md](file://README.md) - - -## Update Summary -**Changes Made** -- Added comprehensive documentation for the new install-deps-linux.sh dependency installer script -- Updated build system architecture documentation to reflect the separation of dependency management from the main build process -- Revised build-linux.sh documentation to reflect the use of --clean instead of --skip-deps option -- Enhanced security practices documentation highlighting the improved build system architecture -- Updated practical examples to demonstrate the new two-script build workflow - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Compilation Correctness and Best Practices](#compilation-correctness-and-best-practices) -9. [Security and Build System Architecture](#security-and-build-system-architecture) -10. [Troubleshooting Guide](#troubleshooting-guide) -11. [Conclusion](#conclusion) -12. [Appendices](#appendices) - -## Introduction -This document describes the VIZ C++ Node build helper tools that streamline development tasks such as assembling source fragments, validating reflection metadata, scaffolding custom plugins, generating formatted schema representations, and validating database schemas. It explains command-line usage, input/output formats, and integration with the main build process. The build system has been enhanced with a new dependency installer script that separates system dependency management from the main build process, improving security and maintainability. - -**Updated** Enhanced with new dependency management capabilities and improved build system architecture. - -## Project Structure -The build helper tools live under programs/build_helpers and programs/util. They integrate with CMake via dedicated CMakeLists.txt files and complement the broader build system described in documentation/building.md. The system now includes a new dependency installer script that manages system-level dependencies separately from the main build process. - -```mermaid -graph TB -subgraph "Build System Scripts" -IDL["install-deps-linux.sh
✓ New Dependency Installer"] -BL["build-linux.sh
✓ Updated with --clean Option"] -BM["build-mac.sh
✓ Maintains --skip-deps Option"] -end -subgraph "Build Helpers" -CP["cat-parts.cpp
✓ Algorithm Header Included"] -CPY["cat_parts.py"] -CR["check_reflect.py"] -CFG["configure_build.py"] -end -subgraph "Utilities" -NP["newplugin.py"] -PS["pretty_schema.py"] -ST["schema_test.cpp"] -end -subgraph "CMake Integration" -CL1["programs/build_helpers/CMakeLists.txt"] -CL2["programs/util/CMakeLists.txt"] -end -IDL --> BL -BL --> CL1 -BM --> CL1 -CP --> CL1 -CPY --> CL1 -CR --> CL1 -CFG --> CL1 -NP --> CL2 -PS --> CL2 -ST -. "commented in CMake" .-> CL2 -``` - -**Diagram sources** -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) -- [build-mac.sh:1-242](file://build-mac.sh#L1-L242) -- [CMakeLists.txt (build_helpers):1-8](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (util):1-69](file://programs/util/CMakeLists.txt#L1-L69) - -**Section sources** -- [CMakeLists.txt (build_helpers):1-8](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (util):1-69](file://programs/util/CMakeLists.txt#L1-L69) -- [building.md:183-220](file://documentation/building.md#L183-L220) - -## Core Components -- **install-deps-linux.sh**: New dependency installer script that installs all required system dependencies for VIZ C++ Node builds. Requires root privileges and supports both Ubuntu/Debian (apt-get) and Fedora/RHEL (dnf) package managers. -- **build-linux.sh**: Enhanced Linux build script that uses --clean option instead of --skip-deps, improving build system architecture and security practices by separating dependency management from the main build process. -- **build-mac.sh**: macOS build script that maintains the --skip-deps option for Homebrew dependency management. -- **cat-parts**: Concatenates hardfork fragment files (.hf) from a directory into a single output file, with up-to-date checks and minimal rebuild behavior. **Enhanced with proper algorithm header inclusion for compilation correctness**. -- **cat_parts.py**: Python counterpart to cat-parts with similar behavior and robustness for directory creation and file existence checks. -- **check_reflect.py**: Validates FC_REFLECT and FC_REFLECT_DERIVED declarations against Doxygen XML class member lists to ensure reflection parity. -- **newplugin.py**: Generates a complete plugin skeleton with standardized file structure and boilerplate code for a given provider and plugin name. -- **pretty_schema.py**: Queries a local debug node JSON-RPC endpoint to fetch and pretty-print the schema representation. -- **schema_test.cpp**: Demonstrates retrieving and printing schema information for specific chain objects using the schema API. -- **configure_build.py**: A helper to invoke cmake with sensible defaults and optional cross-compilation and external library flags. - -**Section sources** -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) -- [build-linux.sh:17-29](file://build-linux.sh#L17-L29) -- [build-mac.sh:15-26](file://build-mac.sh#L15-L26) -- [cat-parts.cpp:1-68](file://programs/build_helpers/cat-parts.cpp#L1-L68) -- [cat_parts.py:1-74](file://programs/build_helpers/cat_parts.py#L1-L74) -- [check_reflect.py:1-160](file://programs/build_helpers/check_reflect.py#L1-L160) -- [newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) -- [pretty_schema.py:1-28](file://programs/util/pretty_schema.py#L1-L28) -- [schema_test.cpp:1-57](file://programs/util/schema_test.cpp#L1-L57) -- [configure_build.py:1-202](file://programs/build_helpers/configure_build.py#L1-L202) - -## Architecture Overview -The tools are designed to be invoked from the command line and integrated into higher-level build scripts or CI. They rely on: -- Standard filesystem operations for reading/writing files -- Regular expressions for parsing reflection declarations -- XML parsing for Doxygen-generated class member lists -- JSON-RPC calls for schema retrieval -- CMake targets for compilation and installation -- **New**: Separated dependency management system for improved security and maintainability - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Dep as "System Dependencies" -participant CMake as "CMake Targets" -participant Tool as "Build Helper Tool" -participant FS as "Filesystem" -Dev->>Dep : "Install system dependencies (root)" -Dep-->>Dev : "Dependencies ready" -Dev->>CMake : "Configure and build (regular user)" -CMake->>Tool : "Execute helper (e.g., cat_parts.py)" -Tool->>FS : "Read input directory and write output" -FS-->>Tool : "Success/Failure" -Tool-->>CMake : "Exit code" -CMake-->>Dev : "Build artifacts ready" -``` - -**Updated** Enhanced with new dependency management workflow that separates system-level operations from build operations. - -[No sources needed since this diagram shows conceptual workflow, not actual code structure] - -## Detailed Component Analysis - -### install-deps-linux.sh (New) -A comprehensive dependency installer script that manages all system-level dependencies required for VIZ C++ Node builds. This script separates dependency management from the main build process, improving security and maintainability. - -Key behaviors: -- Requires root privileges (sudo) for system-level package installation -- Supports Ubuntu/Debian systems using apt-get package manager -- Supports Fedora/RHEL systems using dnf package manager -- Installs essential build tools: cmake, git, ccache, build-essential -- Installs Boost libraries with comprehensive development headers -- Installs compression libraries: bzip2, lzma, zstd, zlib -- Installs SSL/TLS support and development tools -- Provides color-coded status messages and error handling -- Automatically detects package manager and installs appropriate dependencies - -```mermaid -flowchart TD -Start(["Start"]) --> RootCheck["Check for root privileges"] -RootCheck --> |No| Error["Print error and exit"] -RootCheck --> |Yes| Detect["Detect package manager"] -Detect --> |apt-get| Ubuntu["Install Ubuntu/Debian deps"] -Detect --> |dnf| Fedora["Install Fedora/RHEL deps"] -Detect --> |Other| Warn["Warn unsupported package manager"] -Ubuntu --> Success["Print success message"] -Fedora --> Success -Warn --> Exit["Exit with error"] -Error --> Exit -Success --> Next["Next step: ./build-linux.sh"] -``` - -**Diagram sources** -- [install-deps-linux.sh:28-106](file://install-deps-linux.sh#L28-L106) - -**Section sources** -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) - -### build-linux.sh (Enhanced) -Enhanced Linux build script that now uses --clean option instead of --skip-deps, reflecting the improved separation between dependency management and build processes. This change improves security by ensuring clean builds and better maintainability. - -Key behaviors: -- **Updated**: Uses --clean option for clean build directory management -- **Enhanced**: Improved argument parsing with comprehensive build options -- **Maintained**: Preserves all existing build configurations -- **Improved**: Better error handling and user feedback -- **Security**: Refuses to run as root (build must run as regular user) - -```mermaid -flowchart TD -Start(["Start"]) --> Parse["Parse CLI arguments"] -Parse --> Clean{"--clean option?"} -Clean --> |Yes| Remove["Remove existing build directory"] -Clean --> |No| Continue["Continue with existing build dir"] -Remove --> Continue -Continue --> Config["Configure with CMake"] -Config --> Build["Build with make"] -Build --> Install{"--install option?"} -Install --> |Yes| DoInstall["Run make install"] -Install --> |No| Skip["Skip installation"] -DoInstall --> Done(["Complete"]) -Skip --> Done -``` - -**Diagram sources** -- [build-linux.sh:63-98](file://build-linux.sh#L63-L98) -- [build-linux.sh:120-128](file://build-linux.sh#L120-L128) - -**Section sources** -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) - -### build-mac.sh (Maintained) -macOS build script that maintains the --skip-deps option for Homebrew dependency management. This preserves the existing workflow for macOS development environments. - -Key behaviors: -- **Maintained**: Uses --skip-deps option for Homebrew dependency management -- **Enhanced**: Improved Xcode Command Line Tools detection -- **Improved**: Better OpenSSL path detection and configuration -- **Preserved**: All existing build configurations and options - -**Section sources** -- [build-mac.sh:1-242](file://build-mac.sh#L1-L242) - -### cat-parts (C++) -Concatenates .hf files from a directory into a single output file, skipping non-.hf entries and sorting filenames numerically. It compares the generated content with existing output to avoid unnecessary writes. - -**Updated** Enhanced with proper algorithm header inclusion for improved compilation reliability across different environments. - -Key behaviors: -- Command-line arguments: input directory and output file path -- Filters files by extension and sorts them using std::sort -- Compares new content with existing output to skip redundant writes -- Uses Boost filesystem and streams - -```mermaid -flowchart TD -Start(["Start"]) --> Args["Validate CLI args"] -Args --> DirOK{"Input dir exists?"} -DirOK --> |No| ErrArgs["Print usage and exit"] -DirOK --> |Yes| Scan["Scan directory for .hf files"] -Scan --> Sort["Sort files by name
std::sort(v.begin(), v.end())"] -Sort --> Read["Read all files in order"] -Read --> Compare{"Output exists?"} -Compare --> |Yes| Exists["Compare content with new data"] -Exists --> |Same| UpToDate["Mark as up-to-date and exit"] -Exists --> |Different| Write["Write concatenated data"] -Compare --> |No| Write -Write --> Done(["Done"]) -UpToDate --> Done -ErrArgs --> Done -``` - -**Diagram sources** -- [cat-parts.cpp:7-68](file://programs/build_helpers/cat-parts.cpp#L7-L68) - -**Section sources** -- [cat-parts.cpp:1-68](file://programs/build_helpers/cat-parts.cpp#L1-L68) - -### cat_parts.py (Python) -A Python reimplementation of cat-parts with explicit checks for directory creation and file existence. It supports filtering by file suffix and writes the concatenated content to the output file. - -Key behaviors: -- Command-line arguments: input directory and output file path -- Creates parent directories if missing -- Reads and concatenates files in sorted order -- Skips writing if content is identical to existing output - -```mermaid -flowchart TD -Start(["Start"]) --> Parse["Parse CLI args"] -Parse --> Valid{"Valid input dir?"} -Valid --> |No| Usage["Print usage and exit"] -Valid --> |Yes| List["List files with suffix filter"] -List --> Sorted["Sort files"] -Sorted --> Read["Read file contents"] -Read --> Exists{"Output exists?"} -Exists --> |Yes| Same{"Content same?"} -Same --> |Yes| Skip["Skip write and exit"] -Same --> |No| Write["Write concatenated content"] -Exists --> |No| MkDir["Ensure parent dir exists"] -MkDir --> Write -Write --> Done(["Done"]) -Skip --> Done -Usage --> Done -``` - -**Diagram sources** -- [cat_parts.py:28-74](file://programs/build_helpers/cat_parts.py#L28-L74) - -**Section sources** -- [cat_parts.py:1-74](file://programs/build_helpers/cat_parts.py#L1-L74) - -### check_reflect.py (Python) -Validates that FC_REFLECT and FC_REFLECT_DERIVED declarations match Doxygen XML class member lists. It scans source files for reflection macros, parses Doxygen XML, and reports mismatches, duplicates, and missing items. - -Key behaviors: -- Parses Doxygen XML index.xml to extract class member lists -- Scans libraries/, programs/, tests/ for .cpp/.hpp files -- Extracts reflection declarations using regular expressions -- Compares member sets and prints categorized results -- Exits with success if no errors are found - -```mermaid -flowchart TD -Start(["Start"]) --> LoadXML["Load Doxygen XML index.xml"] -LoadXML --> ExtractDoxy["Extract class member lists"] -ExtractDoxy --> ScanSrc["Walk source directories for .cpp/.hpp"] -ScanSrc --> ParseReflect["Find FC_REFLECT(_DERIVED) declarations"] -ParseReflect --> Compare["Compare member sets"] -Compare --> Report["Report OK, Not Evaluated, Errors"] -Report --> ExitCode{"Any errors?"} -ExitCode --> |No| Success["Exit 0"] -ExitCode --> |Yes| Failure["Exit 1"] -``` - -**Diagram sources** -- [check_reflect.py:44-160](file://programs/build_helpers/check_reflect.py#L44-L160) - -**Section sources** -- [check_reflect.py:1-160](file://programs/build_helpers/check_reflect.py#L1-L160) - -### newplugin.py (Python) -Generates a complete plugin skeleton under libraries/plugins/ with standardized files and boilerplate. It supports templating for CMakeLists.txt, plugin headers, plugin implementation, API headers, and API implementation. - -Key behaviors: -- Command-line arguments: provider and plugin name -- Renders templates with placeholders -- Creates directories and writes files atomically -- Outputs generated file paths to console - -```mermaid -flowchart TD -Start(["Start"]) --> Args["Parse provider and plugin name"] -Args --> Render["Render templates with context"] -Render --> Mkdir["Ensure output directory exists"] -Mkdir --> Write["Write files to disk"] -Write --> Done(["Done"]) -``` - -**Diagram sources** -- [newplugin.py:225-247](file://programs/util/newplugin.py#L225-L247) - -**Section sources** -- [newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) - -### pretty_schema.py (Python) -Connects to a local debug node JSON-RPC endpoint to retrieve the schema, parses it, pretty-prints it, and handles embedded JSON strings. - -Key behaviors: -- Sends a JSON-RPC POST request to the debug node API -- Converts the returned string schema to JSON -- Pretty-prints the schema with indentation and sorted keys - -```mermaid -sequenceDiagram -participant Script as "pretty_schema.py" -participant RPC as "Local Debug Node" -Script->>RPC : "POST JSON-RPC call" -RPC-->>Script : "Schema JSON string" -Script->>Script : "Parse and pretty-print" -Script-->>Script : "Print formatted schema" -``` - -**Diagram sources** -- [pretty_schema.py:9-27](file://programs/util/pretty_schema.py#L9-L27) - -**Section sources** -- [pretty_schema.py:1-28](file://programs/util/pretty_schema.py#L1-L28) - -### schema_test.cpp (C++) -Demonstrates retrieving and printing schema information for specific chain objects. It uses the schema API to gather dependent schemas and prints names, dependencies, and serialized schema strings. - -Key behaviors: -- Includes schema headers and chain objects -- Defines a test struct with FC_REFLECT -- Retrieves schemas for chain objects and dependent schemas -- Prints schema metadata to stdout - -```mermaid -flowchart TD -Start(["Start"]) --> Include["Include schema headers"] -Include --> Define["Define test struct with FC_REFLECT"] -Define --> GetSchemas["Get schemas for chain objects"] -GetSchemas --> Deps["Add dependent schemas"] -Deps --> Print["Iterate and print schema info"] -Print --> End(["End"]) -``` - -**Diagram sources** -- [schema_test.cpp:15-56](file://programs/util/schema_test.cpp#L15-L56) - -**Section sources** -- [schema_test.cpp:1-57](file://programs/util/schema_test.cpp#L1-L57) - -### configure_build.py (Python) -A helper to invoke cmake with sensible defaults and optional flags for cross-compilation and external libraries. It supports environment variables for locating Boost and OpenSSL, and passes through additional cmake options. - -Key behaviors: -- Parses command-line arguments with mutually exclusive groups -- Resolves environment variables for SYS_ROOT, BOOST_ROOT, OPENSSL_ROOT_DIR -- Detects Boost version and adds appropriate flags -- Supports Windows cross-compilation with MinGW -- Builds and executes cmake with collected options - -```mermaid -flowchart TD -Start(["Start"]) --> Parse["Parse CLI args and env vars"] -Parse --> Boost{"Boost dir provided?"} -Boost --> |Yes| Ver["Detect Boost version"] -Ver --> Flags["Add Boost/OpenSSL flags"] -Boost --> |No| Flags -Flags --> Win{"Windows cross-compile?"} -Win --> |Yes| Mingw["Add MinGW flags"] -Win --> |No| Cfg["Set LOW_MEMORY_NODE and BUILD_TYPE"] -Mingw --> Cfg -Cfg --> RootPath["Assemble root search paths"] -RootPath --> Exec["Execute cmake with options"] -Exec --> Done(["Done"]) -``` - -**Diagram sources** -- [configure_build.py:143-196](file://programs/build_helpers/configure_build.py#L143-L196) - -**Section sources** -- [configure_build.py:1-202](file://programs/build_helpers/configure_build.py#L1-L202) - -## Dependency Analysis -The build helper tools depend on standard libraries and external systems: -- **install-deps-linux.sh**: Depends on system package managers (apt-get/dnf) and root privileges -- **build-linux.sh**: Depends on CMake, make, and system-level build tools -- **build-mac.sh**: Depends on Homebrew, Xcode Command Line Tools, and macOS-specific tools -- cat-parts and cat_parts.py depend on filesystem semantics and sorting -- check_reflect.py depends on Doxygen XML and regular expressions -- newplugin.py depends on Python's string templating and filesystem operations -- pretty_schema.py depends on a running debug node and JSON-RPC -- schema_test.cpp depends on schema headers and chain objects -- configure_build.py depends on cmake, environment variables, and optional toolchains - -```mermaid -graph LR -IDL["install-deps-linux.sh
✓ New"] --> SYS["System Package Managers"] -BL["build-linux.sh
✓ Enhanced"] --> CMAKE["CMake"] -BM["build-mac.sh
✓ Maintained"] --> HB["Homebrew"] -CP["cat-parts.cpp
✓ Algorithm Header"] --> FS["Boost Filesystem"] -CPY["cat_parts.py"] --> PY["Python Pathlib"] -CR["check_reflect.py"] --> RX["Regex"] -CR --> XML["xml.etree.ElementTree"] -NP["newplugin.py"] --> PY -PS["pretty_schema.py"] --> REQ["requests"] -ST["schema_test.cpp"] --> SCH["graphene/db/schema"] -CFG["configure_build.py"] --> CMK["cmake"] -CFG --> ENV["Environment Variables"] -``` - -**Diagram sources** -- [install-deps-linux.sh:34-106](file://install-deps-linux.sh#L34-L106) -- [build-linux.sh:155-165](file://build-linux.sh#L155-L165) -- [build-mac.sh:126-171](file://build-mac.sh#L126-L171) -- [cat-parts.cpp:1-6](file://programs/build_helpers/cat-parts.cpp#L1-L6) -- [cat_parts.py:3-4](file://programs/build_helpers/cat_parts.py#L3-L4) -- [check_reflect.py:3-6](file://programs/build_helpers/check_reflect.py#L3-L6) -- [newplugin.py:1-2](file://programs/util/newplugin.py#L1-L2) -- [pretty_schema.py:3-5](file://programs/util/pretty_schema.py#L3-L5) -- [schema_test.cpp:1-3](file://programs/util/schema_test.cpp#L1-L3) -- [configure_build.py:3-6](file://programs/build_helpers/configure_build.py#L3-L6) - -**Section sources** -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) -- [build-mac.sh:1-242](file://build-mac.sh#L1-L242) -- [cat-parts.cpp:1-6](file://programs/build_helpers/cat-parts.cpp#L1-L6) -- [cat_parts.py:3-4](file://programs/build_helpers/cat_parts.py#L3-L4) -- [check_reflect.py:3-6](file://programs/build_helpers/check_reflect.py#L3-L6) -- [newplugin.py:1-2](file://programs/util/newplugin.py#L1-L2) -- [pretty_schema.py:3-5](file://programs/util/pretty_schema.py#L3-L5) -- [schema_test.cpp:1-3](file://programs/util/schema_test.cpp#L1-L3) -- [configure_build.py:3-6](file://programs/build_helpers/configure_build.py#L3-L6) - -## Performance Considerations -- **install-deps-linux.sh**: Package installation performance varies by distribution and network connectivity; consider caching and offline installation for CI environments -- **build-linux.sh**: Enhanced with --clean option for guaranteed clean builds, which may take longer but ensures reproducibility -- **build-mac.sh**: Maintains --skip-deps option for efficient Homebrew dependency management -- cat-parts and cat_parts.py: Sorting and reading many small .hf files is efficient; the up-to-date check avoids unnecessary writes -- check_reflect.py: Walking source trees and parsing XML can be slow on large projects; restrict scanning to necessary directories if needed -- pretty_schema.py: Network latency to the debug node can dominate; cache results locally if regenerating frequently -- schema_test.cpp: Schema traversal is lightweight for a small set of types; adding many types increases runtime linearly -- configure_build.py: cmake invocation overhead is minimal compared to the build itself; passing additional flags can increase configuration time slightly - -[No sources needed since this section provides general guidance] - -## Compilation Correctness and Best Practices - -### Header Management Standards -The cat-parts utility demonstrates proper C++ header management practices that enhance compilation reliability: - -**Algorithm Header Inclusion** -- The `` header is properly included to support std::sort operations -- This prevents compilation issues across different compiler environments -- Ensures consistent behavior regardless of platform-specific header implementations - -**Best Practices for Header Management** -- Always include headers for functions you use (std::sort requires ) -- Place standard library headers before third-party headers -- Keep header includes minimal and specific -- Consider header ordering for compilation speed and predictability - -**Cross-Platform Compatibility** -- Proper header inclusion prevents environment-specific compilation failures -- Reduces reliance on implicit declarations that vary between compilers -- Enhances portability across different development environments - -**Section sources** -- [cat-parts.cpp:4-33](file://programs/build_helpers/cat-parts.cpp#L4-L33) - -## Security and Build System Architecture - -### Enhanced Build System Security -The new dependency installer script and improved build architecture significantly enhance security practices: - -**Separation of Privileges** -- System-level dependency installation requires root privileges (sudo) -- Main build process runs as regular user for security isolation -- Prevents privilege escalation during build operations - -**Clean Build Architecture** -- The --clean option ensures fresh build directories are created -- Eliminates potential contamination from previous builds -- Improves reproducibility and debugging capabilities - -**Improved Error Handling** -- Comprehensive error checking and user feedback -- Clear separation between dependency management and build processes -- Better logging and diagnostic information - -**Section sources** -- [install-deps-linux.sh:28-31](file://install-deps-linux.sh#L28-L31) -- [build-linux.sh:57-61](file://build-linux.sh#L57-L61) -- [build-linux.sh:83-84](file://build-linux.sh#L83-L84) - -## Troubleshooting Guide -Common issues and resolutions: -- **install-deps-linux.sh** - - Symptom: Permission denied error - - Resolution: Run with sudo privileges as required by the script - - Symptom: Unsupported package manager detected - - Resolution: Install dependencies manually or use supported distributions -- **build-linux.sh** - - Symptom: Cannot run as root - - Resolution: Install dependencies first with sudo ./install-deps-linux.sh, then run build as regular user - - Symptom: Clean build takes too long - - Resolution: Use --clean option for guaranteed clean builds; consider incremental builds for development - - Symptom: Build fails due to missing dependencies - - Resolution: Re-run dependency installer or install missing packages manually -- **build-mac.sh** - - Symptom: Xcode Command Line Tools not found - - Resolution: Install Xcode Command Line Tools using xcode-select --install - - Symptom: Homebrew not detected - - Resolution: Install Homebrew from https://brew.sh/ and ensure it's in PATH - - Symptom: --skip-deps option not working - - Resolution: This option is maintained for macOS compatibility; use --skip-deps to skip Homebrew installation -- **cat-parts/cat_parts.py** - - Symptom: Incorrect number of arguments or invalid directory. - - Resolution: Ensure two arguments are provided: input directory and output file. Verify the directory exists and is readable. - - Symptom: Output not written despite changes. - - Resolution: Confirm that the concatenated content differs from the existing output; otherwise, the tool considers it up-to-date. -- **check_reflect.py** - - Symptom: No Doxygen XML found. - - Resolution: Run doxygen to generate XML before invoking the tool. - - Symptom: Reflection mismatch reported. - - Resolution: Align FC_REFLECT declarations with actual class members; remove duplicates and ensure completeness. -- **newplugin.py** - - Symptom: Permission denied when writing files. - - Resolution: Ensure the destination directory is writable; run with appropriate privileges. - - Symptom: Generated files not linked into the build. - - Resolution: Add the plugin's CMakeLists.txt to the parent CMakeLists.txt and ensure target_link_libraries includes required libraries. -- **pretty_schema.py** - - Symptom: Connection refused or timeout. - - Resolution: Start the debug node and ensure the JSON-RPC endpoint is reachable on the configured address. -- **schema_test.cpp** - - Symptom: Link errors for schema headers. - - Resolution: Ensure the schema headers are available and the target links against graphene_chain and fc. -- **configure_build.py** - - Symptom: cmake cannot find Boost or OpenSSL. - - Resolution: Set BOOST_ROOT and OPENSSL_ROOT_DIR environment variables or pass --boost-dir and --openssl-dir. - - Symptom: Cross-compilation fails. - - Resolution: Ensure MinGW toolchain is installed and CMAKE_FIND_ROOT_PATH_MODE settings are correct. - -**Section sources** -- [install-deps-linux.sh:28-31](file://install-deps-linux.sh#L28-L31) -- [build-linux.sh:57-61](file://build-linux.sh#L57-L61) -- [build-linux.sh:83-84](file://build-linux.sh#L83-L84) -- [build-mac.sh:108-114](file://build-mac.sh#L108-L114) -- [build-mac.sh:118-122](file://build-mac.sh#L118-L122) -- [build-mac.sh:71-72](file://build-mac.sh#L71-L72) -- [cat-parts.cpp:8-11](file://programs/build_helpers/cat-parts.cpp#L8-L11) -- [cat_parts.py:29-36](file://programs/build_helpers/cat_parts.py#L29-L36) -- [check_reflect.py:44-49](file://programs/build_helpers/check_reflect.py#L44-L49) -- [newplugin.py:236-244](file://programs/util/newplugin.py#L236-L244) -- [pretty_schema.py:9-12](file://programs/util/pretty_schema.py#L9-L12) -- [schema_test.cpp:1-3](file://programs/util/schema_test.cpp#L1-L3) -- [configure_build.py:114-118](file://programs/build_helpers/configure_build.py#L114-L118) - -## Conclusion -These build helper tools automate repetitive tasks, enforce consistency, and integrate cleanly with the CMake-based build system. The recent enhancements, particularly the addition of the install-deps-linux.sh dependency installer script and the improved build architecture with --clean option, demonstrate best practices for C++ development that enhance security, maintainability, and reproducibility. The separation of dependency management from the main build process provides better security isolation and cleaner build environments. By following the documented usage patterns and best practices, developers can maintain organized codebases, validate reflection integrity, scaffold plugins efficiently, and inspect schemas reliably while benefiting from improved security and build system architecture. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Examples - -- **Install system dependencies (Linux)** - - Use install-deps-linux.sh to install all required system dependencies with root privileges - - Example command: sudo ./install-deps-linux.sh - -- **Build VIZ node (Linux)** - - Use build-linux.sh to configure and build the VIZ node after dependencies are installed - - Example command: ./build-linux.sh --clean --install - - Note: Must run as regular user (not root) - -- **Build VIZ node (macOS)** - - Use build-mac.sh to configure and build the VIZ node with Homebrew dependencies - - Example command: ./build-mac.sh --skip-deps --install - -- Assemble hardfork fragments - - Use cat_parts.py to concatenate .hf files from a directory into a single output file. The tool ensures the output is only rewritten when content changes. - - Example command: python3 programs/build_helpers/cat_parts.py libraries/chain/hardfork.d build/hardforks.inc - -- Validate reflection declarations - - Run check_reflect.py after generating Doxygen XML to compare FC_REFLECT declarations with class members. Fix reported mismatches and duplicates. - - Example command: python3 programs/build_helpers/check_reflect.py - -- Scaffold a new plugin - - Use newplugin.py to generate a plugin skeleton under libraries/plugins/. Customize the generated files and integrate with CMake. - - Example command: python3 programs/util/newplugin.py viz myplugin - -- Pretty-print schema - - Use pretty_schema.py to fetch and format the schema from a running debug node. Pipe to a file for inspection. - - Example command: python3 programs/util/pretty_schema.py > schema.json - -- Test schema retrieval - - Build and run schema_test.cpp to print schema information for specific chain objects. Useful for debugging schema-related issues. - - Example command: make schema_test && ./bin/schema_test - -- Configure and build - - Use configure_build.py to invoke cmake with sensible defaults and optional flags for cross-compilation and external libraries. - - Example command: python3 programs/build_helpers/configure_build.py --boost-dir /opt/boost --openssl-dir /opt/openssl - -**Section sources** -- [install-deps-linux.sh:8-9](file://install-deps-linux.sh#L8-L9) -- [build-linux.sh:14-15](file://build-linux.sh#L14-L15) -- [build-linux.sh:204](file://build-linux.sh#L204) -- [build-mac.sh:12-13](file://build-mac.sh#L12-L13) -- [build-mac.sh:349](file://build-mac.sh#L349) -- [cat_parts.py:28-69](file://programs/build_helpers/cat_parts.py#L28-L69) -- [check_reflect.py:153-160](file://programs/build_helpers/check_reflect.py#L153-L160) -- [newplugin.py:225-247](file://programs/util/newplugin.py#L225-L247) -- [pretty_schema.py:9-27](file://programs/util/pretty_schema.py#L9-L27) -- [schema_test.cpp:44-56](file://programs/util/schema_test.cpp#L44-L56) -- [configure_build.py:143-196](file://programs/build_helpers/configure_build.py#L143-L196) - -### Integration with Main Build Process -- **CMake targets** - - cat-parts is built as an executable and linked against fc and platform libs. Use it in custom targets or prebuild steps. - - Utilities like pretty_schema.py and schema_test.cpp are standalone scripts/executables; integrate them into CI or developer workflows as needed. -- **Build options** - - Refer to documentation/building.md for CMAKE_BUILD_TYPE and LOW_MEMORY_NODE options. configure_build.py sets these defaults and forwards additional cmake options. -- **New dependency management** - - The install-deps-linux.sh script is designed to be run separately from the main build process - - Dependencies are installed once (per system) and cached for subsequent builds - - Build scripts automatically detect and use installed dependencies - -**Section sources** -- [CMakeLists.txt (build_helpers):1-8](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (util):46-56](file://programs/util/CMakeLists.txt#L46-L56) -- [building.md:189-220](file://documentation/building.md#L189-L220) -- [README.md:7-10](file://README.md#L7-L10) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Code Assembly Tools.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Code Assembly Tools.md deleted file mode 100644 index 9e8df50c4c..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Code Assembly Tools.md +++ /dev/null @@ -1,390 +0,0 @@ -# Code Assembly Tools - - -**Referenced Files in This Document** -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp) -- [cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt) -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf) -- [1.hf](file://libraries/chain/hardfork.d/1.hf) -- [2.hf](file://libraries/chain/hardfork.d/2.hf) -- [3.hf](file://libraries/chain/hardfork.d/3.hf) -- [configure_build.py](file://programs/build_helpers/configure_build.py) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the code assembly tools used by the VIZ C++ Node build pipeline. It focuses on: -- The C++ utility cat-parts.cpp for assembling hardfork header files from a directory of fragments. -- The Python counterpart cat_parts.py for similar automation, including change detection and output directory creation. -- The integration of these tools into the CMake build system via custom targets. -- Practical usage scenarios for schema assembly and automated builds. -- Command-line options, input validation, error handling, and troubleshooting guidance. - -## Project Structure -The code assembly tools live under programs/build_helpers and are consumed by the libraries/chain module, which organizes hardfork-related fragments in a dedicated directory. - -```mermaid -graph TB -subgraph "Build Helpers" -CP["programs/build_helpers/cat-parts.cpp"] -PY["programs/build_helpers/cat_parts.py"] -CH["programs/build_helpers/CMakeLists.txt"] -end -subgraph "Chain Module" -HFDIR["libraries/chain/hardfork.d/"] -HFBASE["libraries/chain/include/graphene/chain/hardfork.hpp"] -LCCMAKE["libraries/chain/CMakeLists.txt"] -end -CP --> LCCMAKE -PY --> LCCMAKE -HFDIR --> CP -HFDIR --> PY -LCCMAKE --> HFBASE -``` - -**Diagram sources** -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp#L1-L68) -- [cat_parts.py](file://programs/build_helpers/cat_parts.py#L1-L74) - -**Section sources** -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) - -## Core Components -- cat-parts.cpp: A C++ program that scans a directory for files ending with .hf, sorts them, concatenates their contents, and writes the result to an output file. It compares the new content with the existing output file and exits early if unchanged. -- cat_parts.py: A Python script that performs equivalent logic with explicit input validation, output directory creation, and change detection. -- CMake integration: Custom targets generate the hardfork.hpp file from hardfork.d fragments during the build. - -Key behaviors: -- Directory scanning: Filters entries by extension and constructs a sorted list of fragment files. -- Concatenation: Reads each file and appends its content to a buffer. -- Change detection: Compares the computed content with the existing output file; if equal, skips writing. -- Output: Writes the assembled content to the target file. - -**Section sources** -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp#L7-L68) -- [cat_parts.py](file://programs/build_helpers/cat_parts.py#L11-L69) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) - -## Architecture Overview -The build system orchestrates code assembly through CMake custom targets. On Unix-like systems, the Python script is invoked; on Windows, the native C++ utility is used. Both produce the same output file from the same input directory of fragments. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant CMake as "CMake" -participant Py as "cat_parts.py" -participant Cpp as "cat-parts.cpp" -participant HFDir as "hardfork.d/" -participant Out as "hardfork.hpp" -Dev->>CMake : Configure and generate -CMake->>CMake : add_custom_target(build_hardfork_hpp) -alt UNIX (non-Windows) -CMake->>Py : Run with (HFDir, Out) -Py->>HFDir : List and read .hf files -Py->>Py : Sort filenames -Py->>Out : Write assembled content (if changed) -else MSVC (Windows) -CMake->>Cpp : Run with (HFDir, Out) -Cpp->>HFDir : Scan .hf files -Cpp->>Cpp : Sort paths -Cpp->>Out : Write assembled content (if changed) -end -CMake-->>Dev : Build target ready -``` - -**Diagram sources** -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L9) -- [cat_parts.py](file://programs/build_helpers/cat_parts.py#L28-L69) -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp#L15-L66) - -## Detailed Component Analysis - -### cat-parts.cpp -Purpose: -- Assemble hardfork fragments into a single header file. -- Provide change detection to avoid unnecessary rebuilds. - -Command-line syntax: -- Syntax: cat-parts DIR OUTFILE -- Exits with non-zero status on invalid arguments or filesystem errors. - -Directory scanning logic: -- Iterates over directory entries. -- Filters entries whose names end with ".hf". -- Collects matching paths into a vector. - -Sorting algorithm: -- Sorts collected paths lexicographically. - -Concatenation process: -- Opens each file and appends its content to a string buffer. -- Writes the buffer to the output file if it differs from the existing content. - -Change detection: -- Reads the existing output file into memory. -- Compares the computed content with the existing content. -- Prints a message and exits early if identical. - -Error handling: -- Catches filesystem errors and prints diagnostic messages. -- Returns non-zero status on failure. - -```mermaid -flowchart TD -Start(["Start main(argc, argv)"]) -CheckArgs["Validate argument count"] -ArgsOK{"argc == 3?"} -MakePath["Create path from argv[1]"] -Iterate["Iterate directory entries"] -Filter["Filter entries ending with '.hf'"] -Collect["Collect matching paths"] -Sort["Sort paths"] -ReadAll["Read all files and concatenate"] -ComputeNew["Compute new content"] -Exists{"Output exists?"} -ReadOld["Read existing output"] -Compare{"Content equals?"} -UpToDate["Print 'up-to-date' and exit"] -Write["Write new content to output"] -Done(["Exit 0"]) -Start --> CheckArgs --> ArgsOK -ArgsOK --> |No| ExitErr["Exit 1"] -ArgsOK --> |Yes| MakePath --> Iterate --> Filter --> Collect --> Sort --> ReadAll --> ComputeNew --> Exists -Exists --> |No| Write --> Done -Exists --> |Yes| ReadOld --> Compare -Compare --> |Yes| UpToDate --> Done -Compare --> |No| Write --> Done -``` - -**Diagram sources** -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp#L7-L68) - -**Section sources** -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp#L7-L68) - -### cat_parts.py -Purpose: -- Provide a portable, Python-based alternative to cat-parts.cpp. -- Offer robust input validation and automatic output directory creation. - -Command-line syntax: -- Syntax: cat_parts.py DIR OUTFILE -- Prints usage and exits with non-zero status if arguments are missing or invalid. - -Input validation: -- Validates that the input directory exists and is a directory. -- Ensures the output path is a file (not a directory) when it exists. - -Change detection: -- Reads the existing output file and compares it with newly computed content. -- Skips writing if contents match. - -Output directory creation: -- Creates parent directories recursively if they do not exist. -- Handles common exceptions during directory creation. - -```mermaid -flowchart TD -Start(["Start main(program_name, args)"]) -ArgsLen["Check number of arguments"] -Usage["Print usage and exit"] -ParseDir["Parse input directory"] -DirValid{"Is directory?"} -ParseOut["Parse output file path"] -OutExists{"Does output exist?"} -ReadExisting["Read existing content"] -Compare{"Content equals?"} -UpToDate["Print 'up-to-date' and exit"] -EnsureDir["Ensure parent directory exists"] -Compute["Compute concatenated content"] -Write["Write content to output"] -Exit(["Exit 0"]) -Start --> ArgsLen --> ArgsLenOK{">= 2?"} -ArgsLenOK --> |No| Usage -ArgsLenOK --> |Yes| ParseDir --> DirValid -DirValid --> |No| Usage -DirValid --> |Yes| ParseOut --> OutExists -OutExists --> |Yes| ReadExisting --> Compare -Compare --> |Yes| UpToDate -Compare --> |No| EnsureDir --> Compute --> Write --> Exit -OutExists --> |No| EnsureDir --> Compute --> Write --> Exit -``` - -**Diagram sources** -- [cat_parts.py](file://programs/build_helpers/cat_parts.py#L28-L69) - -**Section sources** -- [cat_parts.py](file://programs/build_helpers/cat_parts.py#L1-L74) - -### CMake Integration -- Adds a custom target that generates hardfork.hpp from hardfork.d. -- Uses cat-parts.cpp on Windows and cat_parts.py on UNIX-like systems. -- Marks the generated file as GENERATED for proper build semantics. -- Adds a dependency so the chain library links against the generated header. - -```mermaid -graph LR -CMake["libraries/chain/CMakeLists.txt"] -Target["add_custom_target(build_hardfork_hpp)"] -Py["cat_parts.py"] -Cpp["cat-parts.cpp"] -HFDir["hardfork.d/"] -Out["hardfork.hpp"] -CMake --> Target -Target --> |Windows| Cpp -Target --> |UNIX| Py -Cpp --> HFDir -Py --> HFDir -HFDir --> Out -Target --> Out -``` - -**Diagram sources** -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt#L1-L8) - -**Section sources** -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt#L1-L8) - -### Hardfork Directory Processing Mechanism -- Fragment files are named with .hf extension and placed under hardfork.d. -- The assembly tools sort these files lexicographically to define the order of inclusion. -- Example fragments include preamble definitions and version/time constants for specific hardforks. - -```mermaid -graph TB -HFDir["hardfork.d/"] -F0["0-preamble.hf"] -F1["1.hf"] -F2["2.hf"] -F3["3.hf"] -Fragments["Fragments (sorted)"] -Out["hardfork.hpp"] -HFDir --> F0 -HFDir --> F1 -HFDir --> F2 -HFDir --> F3 -F0 --> Fragments -F1 --> Fragments -F2 --> Fragments -F3 --> Fragments -Fragments --> Out -``` - -**Diagram sources** -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf#L1-L56) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) -- [2.hf](file://libraries/chain/hardfork.d/2.hf#L1-L7) -- [3.hf](file://libraries/chain/hardfork.d/3.hf#L1-L7) - -**Section sources** -- [0-preamble.hf](file://libraries/chain/hardfork.d/0-preamble.hf#L1-L56) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) -- [2.hf](file://libraries/chain/hardfork.d/2.hf#L1-L7) -- [3.hf](file://libraries/chain/hardfork.d/3.hf#L1-L7) - -## Dependency Analysis -- cat-parts.cpp depends on Boost.Filesystem for directory traversal and file I/O. -- cat_parts.py uses pathlib for path manipulation and file I/O. -- The CMake build integrates both tools into the chain library build process. -- Additional build helpers exist for reflection checks and build configuration, complementing the assembly workflow. - -```mermaid -graph TB -CP["cat-parts.cpp"] -PY["cat_parts.py"] -CH["CMakeLists.txt (build helpers)"] -LC["CMakeLists.txt (libraries/chain)"] -CR["check_reflect.py"] -CB["configure_build.py"] -CP --> CH -PY --> LC -LC --> CP -LC --> PY -CR --> LC -CB --> LC -``` - -**Diagram sources** -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L1-L160) -- [configure_build.py](file://programs/build_helpers/configure_build.py#L1-L202) - -**Section sources** -- [CMakeLists.txt (build helpers)](file://programs/build_helpers/CMakeLists.txt#L1-L8) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L12) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L1-L160) -- [configure_build.py](file://programs/build_helpers/configure_build.py#L1-L202) - -## Performance Considerations -- Sorting cost: Lexicographic sorting of fragment paths is O(n log n); acceptable for small to moderate numbers of fragments typical in hardfork.d. -- I/O cost: Reading all fragments and writing the output is linear in total content size; efficient for typical hardfork fragment sizes. -- Change detection avoids unnecessary writes and rebuilds, reducing downstream compilation work. -- Using the native C++ tool on Windows and Python tool on UNIX leverages platform-specific strengths. - -## Troubleshooting Guide -Common issues and resolutions: -- Incorrect command syntax: - - Ensure the utility receives exactly two arguments: the input directory and the output file path. -- Input directory does not exist or is not a directory: - - Verify the path to hardfork.d and permissions to read it. -- Output path is not a file: - - Ensure the output path points to a regular file; the Python tool validates this and exits with an error if not. -- Output directory creation fails: - - The Python tool attempts to create parent directories; check write permissions for the parent directory. -- Filesystem errors: - - The C++ tool catches filesystem errors and prints diagnostics; review stderr output for details. -- Permission problems: - - Ensure read access to all .hf files and write access to the output file’s directory. -- Fragment ordering concerns: - - Confirm lexicographic sorting matches intended order; rename files if necessary to achieve desired sequence. - -Integration tips: -- On Windows, the CMake target invokes the C++ utility; on UNIX-like systems, the Python script is used. -- The generated header is marked as GENERATED and included in the chain library build. - -**Section sources** -- [cat-parts.cpp](file://programs/build_helpers/cat-parts.cpp#L8-L11) -- [cat_parts.py](file://programs/build_helpers/cat_parts.py#L28-L43) -- [CMakeLists.txt (libraries/chain)](file://libraries/chain/CMakeLists.txt#L1-L9) - -## Conclusion -The VIZ C++ Node code assembly tools provide a reliable, cross-platform mechanism to generate hardfork.hpp from a collection of .hf fragments. The C++ and Python utilities implement consistent logic with change detection and robust error handling, while CMake integrates them seamlessly into the build pipeline. These tools enable maintainable schema assembly and automated build processes for the chain module. - -## Appendices - -### Practical Examples -- Generating hardfork.hpp from hardfork.d: - - Windows: cat-parts hardfork.d hardfork.hpp - - UNIX-like: python3 cat_parts.py hardfork.d hardfork.hpp -- Automated build integration: - - CMake adds a custom target that runs the appropriate tool and produces hardfork.hpp. -- Schema assembly: - - Place preprocessor definitions and version/time constants in .hf files within hardfork.d; the tools will assemble them in lexicographic order. - -### Related Build Helpers -- Reflection validation: check_reflect.py compares Doxygen-derived member lists with FC_REFLECT declarations to detect mismatches. -- Build configuration: configure_build.py helps set up CMake with optional flags and environment-driven paths. - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L1-L160) -- [configure_build.py](file://programs/build_helpers/configure_build.py#L1-L202) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Plugin Development Tools.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Plugin Development Tools.md deleted file mode 100644 index f586942b0f..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Plugin Development Tools.md +++ /dev/null @@ -1,340 +0,0 @@ -# Plugin Development Tools - - -**Referenced Files in This Document** -- [newplugin.py](file://programs/util/newplugin.py) -- [plugin.md](file://documentation/plugin.md) -- [building.md](file://documentation/building.md) -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt) -- [CMakeLists.txt (main)](file://CMakeLists.txt) -- [account_by_key_plugin.cpp](file://plugins/account_by_key/plugin.cpp) -- [account_by_key_plugin.hpp](file://plugins/account_by_key/include/graphene/plugins/account_by_key/account_by_key_plugin.hpp) -- [account_by_key_objects.hpp](file://plugins/account_by_key/include/graphene/plugins/account_by_key/account_by_key_objects.hpp) -- [chain_plugin.cpp](file://plugins/chain/plugin.cpp) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the VIZ CPP Node plugin development tool and the template system used to generate custom plugin boilerplate code. It covers the directory structure generation, header and implementation skeleton templates, plugin naming conventions, file organization patterns, and integration requirements into the main application. It also provides step-by-step examples for creating new plugins, modifying generated code, and integrating plugins into the application, along with command-line options, customization parameters, best practices, common pitfalls, testing strategies, and deployment considerations. - -## Project Structure -The plugin development workflow centers around a Python script that generates a standardized plugin skeleton under the libraries/plugins directory. The generated plugin integrates with the application via CMake discovery and registration mechanisms. - -```mermaid -graph TB -subgraph "Tooling" -NP["programs/util/newplugin.py"] -end -subgraph "Generated Plugin Skeleton" -OUTDIR["libraries/plugins//"] -INC["include/graphene/plugins//"] -SRC["_plugin.cpp"] -API["_api.cpp"] -HP["_plugin.hpp"] -AP["_api.hpp"] -CL["CMakeLists.txt"] -end -subgraph "Application Build" -PCML["plugins/CMakeLists.txt"] -MCML["CMakeLists.txt"] -end -NP --> OUTDIR -OUTDIR --> INC -OUTDIR --> SRC -OUTDIR --> API -OUTDIR --> HP -OUTDIR --> AP -OUTDIR --> CL -PCML --> MCML -``` - -**Diagram sources** -- [newplugin.py](file://programs/util/newplugin.py#L236-L246) -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) -- [CMakeLists.txt (main)](file://CMakeLists.txt#L210-L213) - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [plugin.md](file://documentation/plugin.md#L1-L28) -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) -- [CMakeLists.txt (main)](file://CMakeLists.txt#L210-L213) - -## Core Components -- Template engine: A dictionary of file templates keyed by destination paths. Each template contains placeholders for provider and plugin name. -- CLI interface: Accepts provider and plugin name arguments to drive template instantiation. -- Directory generation: Creates include directories and writes all generated files into a new plugin folder under libraries/plugins/. -- Integration hooks: Generated CMakeLists.txt links against core libraries and registers the plugin via a macro. - -Key behaviors: -- Provider naming convention: The provider is a namespace identifier (e.g., viz) used to prefix namespaces and plugin factories. -- Plugin naming convention: The plugin name becomes part of the namespace and filenames. -- Generated files: Header and implementation files for plugin and API classes, plus a CMakeLists.txt tailored for linking and inclusion. - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L3-L218) -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) - -## Architecture Overview -The plugin generation tool produces a complete, buildable skeleton that integrates with the application’s CMake-based plugin discovery and registration system. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Tool as "newplugin.py" -participant FS as "Filesystem" -participant CMake as "CMake Discovery" -participant App as "Application" -Dev->>Tool : Run with provider and plugin_name -Tool->>FS : Create include/graphene/plugins// -Tool->>FS : Write _plugin.cpp, _api.cpp,
_plugin.hpp, _api.hpp, CMakeLists.txt -FS-->>Tool : Files written -Tool-->>Dev : Skeleton ready in libraries/plugins/ -Dev->>CMake : Build application -CMake->>CMake : Discover plugins via plugins/CMakeLists.txt -CMake->>App : Register plugin factory and link libraries -App-->>Dev : Plugin available at runtime -``` - -**Diagram sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) -- [CMakeLists.txt (main)](file://CMakeLists.txt#L210-L213) - -## Detailed Component Analysis - -### Template System and File Generation -The template system defines: -- CMakeLists.txt: Adds the library, links against core libraries, and sets include directories. -- Plugin header: Declares the plugin class inheriting from the framework’s plugin base. -- Plugin implementation: Implements lifecycle methods and connects to the chain database. -- API header: Declares the API class and FC_API method list. -- API implementation: Provides API factory registration and helper accessors. - -```mermaid -flowchart TD -Start(["Run newplugin.py"]) --> ParseArgs["Parse provider and plugin_name"] -ParseArgs --> BuildCtx["Build context dict"] -BuildCtx --> OutDir["Compute output directory
libraries/plugins/"] -OutDir --> IterateTemplates["Iterate templates"] -IterateTemplates --> FormatContent["Format template with context"] -FormatContent --> MakeDirs["Ensure include directory exists"] -MakeDirs --> WriteFiles["Write files to disk"] -WriteFiles --> Done(["Skeleton ready"]) -``` - -**Diagram sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L3-L218) -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) - -### Plugin Naming Conventions and File Organization -- Provider: Used as a namespace prefix for plugin and API classes. -- Plugin Name: Used as the base for class names, filenames, and CMake target names. -- Include Path Pattern: include/graphene/plugins//_*.hpp -- Implementation Files: _plugin.cpp and _api.cpp -- CMake Target: {provider}_{plugin_name} - -Best practices: -- Choose a concise, lowercase provider name. -- Use a descriptive, lowercase plugin name. -- Keep include paths aligned with the pattern to ensure CMake discovery works correctly. - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L3-L218) - -### Integration Requirements -- CMake Discovery: The plugins root CMakeLists.txt scans subdirectories and adds discovered plugin directories to a list used by the application. -- Link Libraries: Generated CMakeLists.txt links against core libraries (application, chain, protocol). -- Registration Macro: The plugin implementation uses a macro to register the plugin factory with the application. - -```mermaid -graph LR -PRoot["plugins/CMakeLists.txt"] --> PSub["/CMakeLists.txt"] -PSub --> Lib["{provider}_{plugin_name} library"] -Lib --> App["Application loads plugin"] -``` - -**Diagram sources** -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) -- [newplugin.py](file://programs/util/newplugin.py#L4-L16) - -**Section sources** -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) -- [newplugin.py](file://programs/util/newplugin.py#L4-L16) - -### Step-by-Step Example: Creating a New Plugin -- Prepare: Ensure the environment supports building the project. -- Generate: Run the tool with provider and plugin name to scaffold the plugin. -- Review: Confirm include paths, class names, and CMake target names match expectations. -- Customize: Implement plugin initialization, API methods, and database connections. -- Build: Configure and build the application; the plugin is discovered and linked automatically. -- Enable: Configure the application to enable the plugin and any required dependencies. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Tool as "newplugin.py" -participant Cfg as "Application Config" -participant Build as "CMake/Build" -participant App as "Application" -Dev->>Tool : Execute with provider and plugin_name -Tool-->>Dev : Generated files in libraries/plugins/ -Dev->>Cfg : Add enable-plugin and public-api settings -Dev->>Build : Configure and build -Build-->>App : Discover and link plugin -App-->>Dev : Plugin active at runtime -``` - -**Diagram sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) -- [plugin.md](file://documentation/plugin.md#L11-L28) - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) -- [building.md](file://documentation/building.md#L1-L212) - -### Command-Line Options and Customization Parameters -- Provider: Namespace identifier for plugin and API classes. -- Plugin Name: Base name for all generated files and targets. -- CMake Parameters: Adjust include directories and link libraries as needed in the generated CMakeLists.txt. - -Customization tips: -- Modify the generated CMakeLists.txt to add extra include directories or link additional libraries. -- Update plugin and API headers to declare new methods and reflection lists. -- Implement plugin lifecycle methods to register signal handlers and API factories. - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) -- [newplugin.py](file://programs/util/newplugin.py#L4-L16) - -### Best Practices for Plugin Development -- Keep plugin logic decoupled from UI; expose functionality via APIs. -- Use the plugin’s startup/shutdown methods to register/unregister signal handlers. -- Reflect API methods in the FC_API declaration to expose them to clients. -- Avoid heavy computations in hot paths; defer to worker threads when necessary. -- Use database snapshots and weak read locks for safe reads in APIs. - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L21-L28) - -### Common Plugin Development Pitfalls -- Incorrect include paths preventing CMake discovery. -- Missing API method declarations in FC_API leading to unavailable RPC endpoints. -- Forgetting to register the API factory during plugin startup. -- Not handling chain events properly, causing missed updates or crashes. -- Enabling/disabling plugins that maintain persistent state without replaying the chain. - -Mitigation: -- Verify include directories match the expected pattern. -- Ensure all declared API methods are implemented and reflected. -- Connect to chain signals in plugin startup and disconnect in shutdown. -- Rebuild and replay when toggling stateful plugins. - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) - -### Testing Strategies -- Unit tests: Test isolated logic in plugin internals and API helpers. -- Integration tests: Exercise plugin APIs against a live or mocked chain database. -- Replay tests: Validate behavior changes when enabling/disabling stateful plugins by replaying blocks. -- API tests: Use JSON-RPC calls to verify exposed endpoints. - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) - -### Deployment Considerations -- Build variants: Release vs. Debug configurations affect performance and debugging capabilities. -- Low-memory nodes: Specialized builds reduce memory footprint for resource-constrained environments. -- Docker: Containerized builds simplify environment setup and reproducibility. -- External plugins: Third-party plugins can be dropped into external directories and built alongside internal plugins. - -**Section sources** -- [building.md](file://documentation/building.md#L1-L212) - -## Dependency Analysis -The plugin system relies on a few key relationships: -- The generator depends on Python’s standard library and argparse. -- Generated plugins depend on core libraries (application, chain, protocol). -- Application discovery depends on CMake scanning plugin directories. - -```mermaid -graph TB -Gen["programs/util/newplugin.py"] --> Tpl["Template Definitions"] -Gen --> FS["Filesystem"] -FS --> Lib["libraries/plugins/"] -Lib --> CMake["CMakeLists.txt"] -CMake --> App["Application"] -App --> Chain["Chain Database"] -``` - -**Diagram sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L225-L246) -- [CMakeLists.txt (plugins root)](file://plugins/CMakeLists.txt#L1-L12) - -## Performance Considerations -- Minimize work in event callbacks; offload to worker threads when appropriate. -- Use weak read locks for database reads in APIs to avoid contention. -- Avoid unnecessary allocations and copies in hot paths. -- Tune shared memory and flush intervals for chain performance. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Plugin not discovered: - - Verify the plugin directory exists under libraries/plugins/. - - Ensure the directory contains a CMakeLists.txt and that it is processed by the plugins root CMakeLists.txt. -- API not available: - - Confirm API methods are declared in the header and reflected in FC_API. - - Ensure the API factory is registered during plugin startup. -- Chain state inconsistencies: - - If a plugin maintains persistent state, rebuild and replay the chain when toggling its enablement. - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) - -## Conclusion -The VIZ CPP Node plugin development tool streamlines the creation of new plugins by generating a complete, buildable skeleton that adheres to established naming and organizational conventions. By following the template system, CMake integration, and best practices outlined here, developers can quickly implement robust plugins that integrate seamlessly with the application and support scalable testing and deployment strategies. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Appendix A: Generated File Reference -- CMakeLists.txt: Adds the library, sets include directories, and links core libraries. -- _plugin.hpp: Declares the plugin class and its lifecycle methods. -- _plugin.cpp: Implements plugin lifecycle, signal connections, and API registration. -- _api.hpp: Declares the API class and FC_API method list. -- _api.cpp: Implements API factory registration and helper accessors. - -**Section sources** -- [newplugin.py](file://programs/util/newplugin.py#L3-L218) - -### Appendix B: Example Plugin Patterns -- Signal-driven plugins: Connect to chain database signals in plugin startup and disconnect in shutdown. -- API-only plugins: Expose read-only or write APIs via the API class and FC_API declaration. -- Stateful plugins: Maintain indices or caches and handle replay when toggled on/off. - -**Section sources** -- [account_by_key_plugin.cpp](file://plugins/account_by_key/plugin.cpp#L197-L222) -- [account_by_key_plugin.hpp](file://plugins/account_by_key/include/graphene/plugins/account_by_key/account_by_key_plugin.hpp#L19-L44) -- [account_by_key_objects.hpp](file://plugins/account_by_key/include/graphene/plugins/account_by_key/account_by_key_objects.hpp#L18-L67) -- [chain_plugin.cpp](file://plugins/chain/plugin.cpp#L254-L396) -- [debug_node_plugin.cpp](file://plugins/debug_node/plugin.cpp#L117-L156) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Reflection Validation Tools.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Reflection Validation Tools.md deleted file mode 100644 index 6f3ed73e5c..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Reflection Validation Tools.md +++ /dev/null @@ -1,339 +0,0 @@ -# Reflection Validation Tools - - -**Referenced Files in This Document** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [Doxyfile](file://Doxyfile) -- [CMakeLists.txt](file://CMakeLists.txt) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp) -- [reflect_util.hpp](file://libraries/wallet/include/graphene/wallet/reflect_util.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [node.cpp](file://libraries/network/node.cpp) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp) -- [docker-main.yml](file://.github/workflows/docker-main.yml) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the VIZ CPP Node reflection validation tool, check_reflect.py, which ensures that reflection metadata for blockchain objects and operations remains consistent between: -- Doxygen-generated documentation of class members -- Actual FC_REFLECT and FC_REFLECT_DERIVED declarations in the codebase - -The tool cross-validates reflected member lists to guarantee proper serialization/deserialization behavior across the system, preventing subtle bugs in binary protocols, APIs, and persistence layers. - -## Project Structure -The reflection validation pipeline spans three stages: -- Code scanning: Extracts FC_REFLECT and FC_REFLECT_DERIVED declarations from C++ sources -- Documentation extraction: Parses Doxygen XML to obtain documented member lists -- Validation: Compares both sets and reports mismatches - -```mermaid -graph TB -A["C++ Sources
libraries/, programs/, tests/"] --> B["Regex Scanner
check_reflect.py"] -B --> C["name2members_re
Parsed Reflection Members"] -D["Doxygen XML
doxygen/xml/index.xml"] --> E["XML Parser
check_reflect.py"] -E --> F["name2members_doxygen
Documented Members"] -C --> G["Validator
validate_members()"] -F --> G -G --> H["Results
ok/error/not evaluated"] -``` - -**Diagram sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L84-L105) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L44-L50) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L1-L160) -- [Doxyfile](file://Doxyfile#L783-L786) - -## Core Components -- Regex-based scanner for FC_REFLECT and FC_REFLECT_DERIVED -- Doxygen XML parser for documented members -- Validation engine comparing reflected vs documented members -- Exit-code driven integration for CI - -Key behaviors: -- Scans libraries/, programs/, tests/ recursively for .cpp/.hpp files -- Builds a map of class names to reflected member lists -- Filters out static members (space_id, type_id) from validation -- Reports duplicates, missing members, and symmetric differences - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L61-L77) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L84-L105) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L51-L54) - -## Architecture Overview -The validation tool orchestrates three major phases: discovery, documentation ingestion, and comparison. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Tool as "check_reflect.py" -participant FS as "Filesystem" -participant DX as "Doxygen XML" -participant Val as "Validator" -Dev->>Tool : Run validation -Tool->>FS : Walk libraries/, programs/, tests/ -FS-->>Tool : Match .cpp/.hpp files -Tool->>Tool : Scan FC_REFLECT/DERIVED -Tool->>DX : Parse doxygen/xml/index.xml -DX-->>Tool : Struct/class member lists -Tool->>Val : validate_members(reflected, documented) -Val-->>Tool : {ok, error, not evaluated} -Tool-->>Dev : Exit code + summary -``` - -**Diagram sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L86-L105) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L44-L50) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L153-L160) - -## Detailed Component Analysis - -### Reflection Scanner (FC_REFLECT/FC_REFLECT_DERIVED) -- Purpose: Extract reflected member lists from C++ sources -- Scope: Recursively scans libraries/, programs/, tests/ for .cpp/.hpp -- Patterns: - - FC_REFLECT(class, (member1)(member2)...) - - FC_REFLECT_DERIVED(class, (base), (member1)(member2)...) - -Validation rules: -- Member names are captured via a helper that splits parenthesized lists -- Duplicate members are detected and reported -- Case-sensitive ordering is normalized for comparison - -```mermaid -flowchart TD -Start(["Scan Files"]) --> Walk["Walk directories
libraries/, programs/, tests/"] -Walk --> Filter["Filter .cpp/.hpp"] -Filter --> Read["Read file content"] -Read --> MatchReflect{"Match FC_REFLECT?"} -MatchReflect --> |Yes| CaptureReflect["Capture class + members"] -MatchReflect --> |No| MatchDerived{"Match FC_REFLECT_DERIVED?"} -MatchDerived --> |Yes| CaptureDerived["Capture class + derived members"] -MatchDerived --> |No| Next["Next file"] -CaptureReflect --> Next -CaptureDerived --> Next -Next --> End(["name2members_re populated"]) -``` - -**Diagram sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L86-L105) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L61-L77) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L80-L82) - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L61-L77) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L80-L82) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L84-L105) - -### Doxygen XML Ingestion -- Purpose: Obtain documented member lists for structs/classes -- Mechanism: Parses doxygen/xml/index.xml and collects member names per class -- Post-processing: Removes static members (space_id, type_id) from validation - -```mermaid -flowchart TD -A["Parse doxygen/xml/index.xml"] --> B["Iterate compounds (class/struct)"] -B --> C["Collect and "] -C --> D["Build name2members_doxygen"] -D --> E["Remove static members:
space_id, type_id"] -``` - -**Diagram sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L44-L50) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L51-L54) - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L44-L50) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L51-L54) - -### Validation Engine -- Purpose: Compare reflected vs documented members -- Checks: - - Presence of class in both sets - - Duplicate members in documented set - - Symmetric difference between reflected and documented members -- Outputs: - - Lists of ok, not evaluated, and error classes - - Detailed diffs for mismatches - -```mermaid -flowchart TD -Start(["validate_members"]) --> ForEach["For each reflected class"] -ForEach --> Present{"Present in documented?"} -Present --> |No| MarkNE["Add to 'not evaluated'"] -Present --> |Yes| Dupes{"Duplicates in documented?"} -Dupes --> |Yes| ReportDupe["Report duplicates"] -Dupes --> |No| Compare["Compare sorted member lists"] -Compare --> Same{"Equal?"} -Same --> |Yes| MarkOK["Add to 'ok'"] -Same --> |No| ReportDiff["Report diff"] -MarkNE --> Next["Next class"] -ReportDupe --> Next -ReportDiff --> Next -MarkOK --> Next -Next --> End(["Return results"]) -``` - -**Diagram sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) - -### Practical Usage Examples -- Running the validator: - - Ensure Doxygen XML exists (generate docs with Doxygen) - - Execute the script from the repository root - - Interpret exit code and printed summaries - -- Interpreting results: - - ok: Classes with consistent reflected and documented members - - not evaluated: Classes reflected but not documented - - error: Classes with duplicates or differing members - -- Fixing reflection issues: - - Align FC_REFLECT/FC_REFLECT_DERIVED member lists with actual class members - - Remove duplicates in reflected declarations - - Add missing members to reflected declarations if they should be serialized - -[No sources needed since this subsection provides general guidance] - -### Integration with Build System and CI -- Doxygen configuration: - - INPUT includes libraries/chain, libraries/wallet, libraries/plugins, and libraries/app - - Output directory configured for documentation/doxygen - -- CMake export: - - CMAKE_EXPORT_COMPILE_COMMANDS enabled to aid tooling - -- CI integration: - - Docker build workflows exist for production/testnet images - - Validation can be added as a pre-submit step to run check_reflect.py and Doxygen - -**Section sources** -- [Doxyfile](file://Doxyfile#L783-L786) -- [Doxyfile](file://Doxyfile#L61-L61) -- [CMakeLists.txt](file://CMakeLists.txt#L24-L24) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) - -## Dependency Analysis -The validator depends on: -- Filesystem traversal and regex parsing for reflection declarations -- Doxygen XML output for documented members -- Consistent class naming conventions across FC_REFLECT and Doxygen - -Potential coupling and risks: -- If Doxygen is not regenerated, documented members may lag behind code changes -- If FC_REFLECT declarations are inconsistent, validation will fail -- Static members filtering must remain synchronized with actual class definitions - -```mermaid -graph LR -SRC["C++ Sources"] --> REGEX["Regex Scanner"] -DOX["Doxygen XML"] --> PARSER["XML Parser"] -REGEX --> MAPR["name2members_re"] -PARSER --> MAPD["name2members_doxygen"] -MAPR --> VAL["Validator"] -MAPD --> VAL -VAL --> OUT["Validation Report"] -``` - -**Diagram sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L86-L105) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L44-L50) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L44-L50) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L86-L105) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) - -## Performance Considerations -- File scanning is linear in total source lines; typical repositories process quickly -- Regex compilation occurs once; repeated scans are efficient -- XML parsing overhead is bounded by documented class count -- Recommendations: - - Limit scan scope to modified directories during development - - Regenerate Doxygen incrementally when only specific modules change - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- No documented members found: - - Ensure Doxygen is run and doxygen/xml/index.xml exists - - Verify Doxyfile INPUT paths include relevant modules - -- Unexpected "error" items: - - Check for typos in FC_REFLECT/FC_REFLECT_DERIVED member names - - Confirm member ordering does not matter (validation normalizes order) - - Resolve duplicates flagged by the validator - -- "not evaluated" items: - - Add FC_REFLECT/FC_REFLECT_DERIVED declarations for newly added classes - - Ensure Doxygen documents the class and members - -- Static member filtering: - - space_id and type_id are intentionally excluded from validation - - Do not add these to reflected lists if they are not real data fields - -Debugging tips: -- Temporarily print intermediate maps (name2members_doxygen, name2members_re) to inspect discrepancies -- Run Doxygen with verbose output to confirm XML generation -- Use smaller file subsets to localize issues - -**Section sources** -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L51-L54) -- [check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L151) - -## Conclusion -The check_reflect.py tool provides a robust mechanism to maintain reflection consistency across the VIZ CPP Node codebase. By validating FC_REFLECT declarations against Doxygen documentation, it helps prevent serialization inconsistencies and improves reliability of blockchain operations, APIs, and persistence layers. Integrating this tool into CI and adopting the best practices outlined here will keep reflection metadata accurate and maintainable. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Example Reflection Declarations in the Codebase -- Chain objects: - - withdraw_vesting_route_object - - escrow_object - - award_shares_expire_object - - block_post_validation_object - -- Network and plugins: - - node_configuration - - json_rpc_error - - json_rpc_response - -These declarations demonstrate the use of FC_REFLECT for serialization and are validated by the tool. - -**Section sources** -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L208-L225) -- [node.cpp](file://libraries/network/node.cpp#L244-L244) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L427-L428) - -### Wallet Reflection Utilities -- Utility functions for static_variant mapping and variant conversion -- Support for operation name-to-ID mapping and variant parsing - -These utilities complement reflection by enabling runtime handling of polymorphic types. - -**Section sources** -- [reflect_util.hpp](file://libraries/wallet/include/graphene/wallet/reflect_util.hpp#L1-L91) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Schema Generation Tools.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Schema Generation Tools.md deleted file mode 100644 index 34ef36e1fc..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build Helper Tools/Schema Generation Tools.md +++ /dev/null @@ -1,281 +0,0 @@ -# Schema Generation Tools - - -**Referenced Files in This Document** -- [pretty_schema.py](file://programs/util/pretty_schema.py) -- [schema_test.cpp](file://programs/util/schema_test.cpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the schema generation and validation tools used in the VIZ CPP Node project. It focuses on: -- The pretty_schema.py utility for fetching, parsing, and formatting JSON schema representations from the running node’s debug interface. -- The schema_test.cpp utility for validating and inspecting object schemas and their dependencies at compile-time and runtime. -- How these tools integrate into development workflows, support schema evolution, and help maintain backward compatibility. - -These tools enable: -- Generating human-readable schema documentation from blockchain object definitions. -- Validating schema correctness and dependency relationships during development and CI. -- Supporting schema evolution by ensuring consistent type naming, dependency resolution, and unique schema IDs. - -## Project Structure -The schema-related capabilities are implemented across: -- A Python script that queries the node’s debug API and prints a formatted JSON schema. -- A C++ program that introspects object schemas and their dependencies via the database schema API. -- Core node logic that builds and stores the canonical JSON schema representation. - -```mermaid -graph TB -subgraph "Utilities" -PS["pretty_schema.py"] -ST["schema_test.cpp"] -end -subgraph "Node Runtime" -DN["Debug Node API
JSON Schema Endpoint"] -end -subgraph "Core Libraries" -DB["database.cpp
Schema Assembly"] -CO["custom_operation_interpreter.hpp
abstract_schema"] -end -PS --> DN -ST --> DB -DB --> CO -``` - -**Diagram sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L1-L28) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L20) - -**Section sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L1-L28) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L20) - -## Core Components -- pretty_schema.py - - Purpose: Connects to the local debug node API endpoint to retrieve the current JSON schema, parses and normalizes types, and prints a sorted, indented JSON schema for documentation. - - Key steps: HTTP POST to the debug API, JSON parsing, safe conversion of string fields to JSON where applicable, and pretty-printing. - - Output customization: Indentation and sorting are controlled by the printing routine. -- schema_test.cpp - - Purpose: Validates and inspects schemas for selected blockchain objects, collects dependent schemas, and prints schema metadata and dependency lists. - - Key steps: Obtain schemas for target types, add dependent schemas, iterate and print schema names, dependencies, and serialized schema strings. - - Output customization: Console output formatting is handled by the program itself. - -Practical usage examples: -- Documentation generation: Run pretty_schema.py against a running node to produce a stable, sorted schema for inclusion in docs or CI artifacts. -- Validation: Build and run schema_test.cpp to verify schema correctness and dependency resolution for core objects. - -**Section sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L1-L28) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) - -## Architecture Overview -The schema generation pipeline integrates a Python client with the node’s debug API and a C++ introspection utility with the schema subsystem. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant PS as "pretty_schema.py" -participant Node as "VIZ Node Debug API" -participant ST as "schema_test.cpp" -participant DB as "database.cpp" -Dev->>PS : Execute to fetch JSON schema -PS->>Node : HTTP POST debug_get_json_schema -Node-->>PS : JSON schema string -PS->>PS : Parse and normalize types -PS-->>Dev : Pretty-printed JSON schema -Dev->>ST : Build and run schema validator -ST->>DB : Request schemas for types -DB-->>ST : Schema list with dependencies -ST-->>Dev : Printed schema info and dependencies -``` - -**Diagram sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L9-L13) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L44-L56) -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) - -## Detailed Component Analysis - -### pretty_schema.py -- Fetching the schema: - - Sends a JSON-RPC 2.0 request to the debug node API endpoint. - - Reads the response and extracts the schema string. -- Normalizing types: - - Attempts to parse each type value as JSON; if it fails, keeps the original string. -- Formatting and output: - - Prints the entire schema with indentation and sorted keys for readability. - -```mermaid -flowchart TD -Start(["Start"]) --> Post["POST to debug_get_json_schema"] -Post --> Receive{"Response OK?"} -Receive --> |No| Error["Report error and exit"] -Receive --> |Yes| Parse["Parse result and extract schema string"] -Parse --> Normalize["Normalize types to JSON where possible"] -Normalize --> Print["Pretty-print JSON with indentation and sorted keys"] -Print --> End(["Done"]) -Error --> End -``` - -**Diagram sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L9-L27) - -**Section sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L1-L28) - -### schema_test.cpp -- Schema collection: - - Requests schemas for specific blockchain object types. - - Adds dependent schemas to ensure complete dependency coverage. -- Inspection: - - Iterates over collected schemas, extracting names, dependencies, and serialized schema strings. -- Output: - - Prints schema metadata to console for inspection and validation. - -```mermaid -flowchart TD -Init(["Initialize"]) --> Collect["Collect schemas for target types"] -Collect --> AddDeps["Add dependent schemas"] -AddDeps --> Iterate{"More schemas?"} -Iterate --> |Yes| Inspect["Get name, deps, and serialized schema"] -Inspect --> Print["Print schema info"] -Print --> Iterate -Iterate --> |No| Exit(["Exit"]) -``` - -**Diagram sources** -- [schema_test.cpp](file://programs/util/schema_test.cpp#L44-L56) - -**Section sources** -- [schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) - -### Core Schema Assembly in the Node -- The node compiles a comprehensive list of object schemas, operation schemas, and custom operation schemas. -- It ensures uniqueness and ordering by schema ID, then serializes the final schema map for use by APIs and tools. - -```mermaid -sequenceDiagram -participant DB as "database.cpp" -participant Types as "Object/Operation Types" -participant Deps as "add_dependent_schemas" -participant Out as "Final Schema Map" -DB->>Types : Gather object schemas -DB->>Types : Get operation schema -DB->>Types : Collect custom operation schemas -DB->>Deps : Add dependent schemas -Deps-->>DB : Expanded schema list -DB->>DB : Sort and deduplicate by schema ID -DB->>Out : Serialize to JSON map -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) - -## Dependency Analysis -- pretty_schema.py depends on: - - A running node exposing the debug API endpoint. - - The node’s JSON schema serialization mechanism. -- schema_test.cpp depends on: - - The schema subsystem in the node’s chain library. - - The presence of specific object types whose schemas are requested. -- Core schema assembly depends on: - - The schema registry and the abstract schema interface used by custom operation interpreters. - -```mermaid -graph LR -PS["pretty_schema.py"] --> API["Debug Node API"] -ST["schema_test.cpp"] --> DB["database.cpp"] -DB --> AS["abstract_schema (custom_operation_interpreter.hpp)"] -``` - -**Diagram sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L9-L13) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L10-L13) -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L20) - -**Section sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L1-L28) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) -- [database.cpp](file://libraries/chain/database.cpp#L2997-L3050) -- [custom_operation_interpreter.hpp](file://libraries/chain/include/graphene/chain/custom_operation_interpreter.hpp#L1-L20) - -## Performance Considerations -- pretty_schema.py: - - Network latency to the local debug API is minimal but still present; consider caching outputs when iterating on documentation. - - JSON parsing and normalization are lightweight; avoid repeated runs in tight loops. -- schema_test.cpp: - - Schema collection and dependency addition are O(n log n) due to sorting; keep the number of target types reasonable for quick iteration. - - Printing to console is I/O bound; avoid excessive logging in production builds. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- pretty_schema.py cannot connect to the debug API: - - Ensure the node is running and the debug API is enabled. - - Verify the endpoint URL and port match the node configuration. -- Unexpected schema output: - - Confirm the node is fully initialized so that all schemas are populated. - - Re-run after applying recent schema changes to ensure the latest schema is retrieved. -- schema_test.cpp missing types: - - Ensure the requested types are compiled into the binary and exposed via the schema subsystem. - - Confirm that dependent schemas are included by invoking the dependency collector before printing. -- Dependency mismatches: - - Use the printed dependency lists to verify that all referenced types are present and uniquely named. - - Resolve naming conflicts by adjusting type names or aliases to ensure uniqueness. - -Interpreting results: -- Schema names and IDs must be unique; duplicates indicate conflicting definitions. -- Dependencies should form a directed acyclic graph; cycles suggest incorrect schema composition. -- Serialized schema strings should be valid JSON; malformed entries require fixing the underlying type definitions. - -**Section sources** -- [pretty_schema.py](file://programs/util/pretty_schema.py#L9-L13) -- [schema_test.cpp](file://programs/util/schema_test.cpp#L25-L42) -- [database.cpp](file://libraries/chain/database.cpp#L3040-L3047) - -## Conclusion -The VIZ CPP Node provides robust schema generation and validation tools: -- pretty_schema.py offers a quick way to export a formatted JSON schema for documentation and review. -- schema_test.cpp enables developers to validate schema correctness and dependencies during development. -Together with the node’s schema assembly logic, these tools support reliable schema evolution and backward compatibility checks. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Workflows -- Documentation generation: - - Start the node with the debug API enabled. - - Run pretty_schema.py to capture the current schema. - - Save the output to a documentation artifact or CI cache. -- Development validation: - - Build schema_test.cpp and run it against the current binary. - - Review printed schema names, dependencies, and serialized forms. - - Fix any naming conflicts or missing dependencies before merging changes. -- Automated schema testing: - - Integrate schema_test.cpp into CI to validate schema integrity on pull requests. - - Optionally wrap pretty_schema.py in CI to compare diffs in schema outputs across commits. - -[No sources needed since this section provides general guidance] \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Build System.md b/.qoder/repowiki/en/content/Development Tools/Build System/Build System.md deleted file mode 100644 index 289f783151..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Build System.md +++ /dev/null @@ -1,960 +0,0 @@ -# Build System - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [programs/CMakeLists.txt](file://programs/CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt) -- [thirdparty/CMakeLists.txt](file://thirdparty/CMakeLists.txt) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [programs/build_helpers/cat_parts.py](file://programs/build_helpers/cat_parts.py) -- [programs/util/newplugin.py](file://programs/util/newplugin.py) -- [documentation/building.md](file://documentation/building.md) -- [build-linux.sh](file://build-linux.sh) -- [install-deps-linux.sh](file://install-deps-linux.sh) -- [build-mac.sh](file://build-mac.sh) -- [build-mingw.bat](file://build-mingw.bat) -- [build-msvc.bat](file://build-msvc.bat) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh) - - -## Update Summary -**Changes Made** -- Enhanced build system architecture with dedicated dependency management using install-deps-linux.sh + build-linux.sh two-script process -- Improved error handling and security practices with root privilege separation and user permission enforcement -- Updated platform-specific build scripts with enhanced validation and security measures -- Revised Docker configurations to use Boost 1.71 packages (libboost-coroutine-dev, libboost-context-dev) -- Added comprehensive troubleshooting guidance for the new two-script build process - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Enhanced Two-Script Build Process](#enhanced-two-script-build-process) -7. [Platform-Specific Build Scripts](#platform-specific-build-scripts) -8. [Dependency Analysis](#dependency-analysis) -9. [Performance Considerations](#performance-considerations) -10. [Security Best Practices](#security-best-practices) -11. [Troubleshooting Guide](#troubleshooting-guide) -12. [Conclusion](#conclusion) -13. [Appendices](#appendices) - -## Introduction -This document explains the build system for VIZ CPP Node, focusing on the enhanced CMake-based configuration with dedicated dependency management, cross-platform compilation support, and improved security practices. The build system now features a two-script architecture (install-deps-linux.sh + build-linux.sh) that separates dependency installation (requiring root) from the build process (running as a regular user), providing better security and reliability. The system supports Ubuntu 24.04+, macOS with Homebrew, and Windows with both MSVC and MinGW toolchains, all requiring Boost 1.71+ with mandatory coroutine component. - -## Project Structure -The build system is organized around a top-level CMake project with enhanced dependency management and platform-specific build automation: -- Top-level CMake project defines compiler checks, options, platform-specific flags, and includes subdirectories for thirdparty, libraries, plugins, and programs -- Dedicated dependency management scripts provide secure, validated dependency installation -- Platform-specific build scripts offer automated configuration and building with enhanced error handling -- Subprojects: - - libraries: API, chain, protocol, network, time, utilities, wallet - - plugins: dynamically discovered via a scanning mechanism - - thirdparty: appbase, fc, chainbase - - programs: build_helpers, cli_wallet, vizd, js_operation_serializer, size_checker, util - -```mermaid -graph TB -Root["Top-level CMakeLists.txt"] -TP["thirdparty/CMakeLists.txt"] -LIB["libraries/CMakeLists.txt"] -PLG["plugins/CMakeLists.txt"] -PRG["programs/CMakeLists.txt"] -LinuxDeps["install-deps-linux.sh"] -LinuxBuild["build-linux.sh"] -Mac["build-mac.sh"] -MinGW["build-mingw.bat"] -MSVC["build-msvc.bat"] -Root --> TP -Root --> LIB -Root --> PLG -Root --> PRG -LinuxDeps --> LinuxBuild -LinuxBuild --> Root -Mac --> Root -MinGW --> Root -MSVC --> Root -``` - -**Diagram sources** -- [CMakeLists.txt:206-209](file://CMakeLists.txt#L206-L209) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) -- [build-mac.sh:1-242](file://build-mac.sh#L1-L242) -- [build-mingw.bat:1-125](file://build-mingw.bat#L1-L125) -- [build-msvc.bat:1-116](file://build-msvc.bat#L1-L116) - -**Section sources** -- [CMakeLists.txt:1-271](file://CMakeLists.txt#L1-L271) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) - -## Core Components -- Top-level CMake project: - - Enforces minimum CMake version 3.16 and compiler versions for GCC and Clang - - Configures Boost usage with version 1.71 and coroutine component requirement, optional static/shared libraries, and PCH support - - Provides compile-time options: BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, ENABLE_MONGO_PLUGIN - - Sets platform-specific flags for Windows (MSVC/Mingw), macOS, and Linux - - Enables ccache globally when available - - Supports optional CPack installer generation -- Enhanced dependency management: - - **install-deps-linux.sh**: Secure dependency installation script requiring root privileges with comprehensive package management for Ubuntu/Debian (apt-get) and Fedora/RHEL (dnf) - - **build-linux.sh**: User-friendly build script with enhanced error handling, argument validation, and security enforcement -- Platform-specific build scripts: - - **build-mac.sh**: Automated Xcode/Homebrew dependency installation, OpenSSL detection, and building for macOS with comprehensive validation - - **build-mingw.bat**: Windows MinGW build with environment variable configuration and static linking - - **build-msvc.bat**: Windows MSVC build with Visual Studio generator configuration -- Helper scripts: - - configure_build.py: wraps cmake with sensible defaults and cross-compilation support - - cat_parts.py: concatenates files from a directory tree into a single output file - - newplugin.py: scaffolds a new plugin directory and files - -**Section sources** -- [CMakeLists.txt:1-271](file://CMakeLists.txt#L1-L271) -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) -- [build-mac.sh:1-242](file://build-mac.sh#L1-L242) -- [build-mingw.bat:1-125](file://build-mingw.bat#L1-L125) -- [build-msvc.bat:1-116](file://build-msvc.bat#L1-L116) -- [programs/build_helpers/configure_build.py:1-202](file://programs/build_helpers/configure_build.py#L1-L202) -- [programs/build_helpers/cat_parts.py:1-74](file://programs/build_helpers/cat_parts.py#L1-L74) -- [programs/util/newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) - -## Architecture Overview -The enhanced build pipeline integrates CMake configuration, secure dependency management, platform detection, and helper tools to produce binaries with improved security and reliability. The new two-script architecture separates dependency installation (requiring root) from the build process (running as a regular user), providing better security boundaries while maintaining consistency with the core CMake configuration. - -```mermaid -graph TB -subgraph "Secure Host Environment" -DepsScript["install-deps-linux.sh (root)"] -BuildScript["build-linux.sh (user)"] -MacScript["build-mac.sh"] -MinGWScript["build-mingw.bat"] -MSVCScript["build-msvc.bat"] -CFG["configure_build.py"] -CC["Compiler (GCC/Clang/MSVC/Mingw)"] -CMake["CMake (Top-level)"] -Helpers["cat_parts.py / newplugin.py"] -end -subgraph "Source Tree" -Root["CMakeLists.txt"] -Libs["libraries/*"] -Plugins["plugins/*"] -Third["thirdparty/*"] -Progs["programs/*"] -end -subgraph "Artifacts" -Bin["Binaries (vizd, cli_wallet, ...)"] -Inst["Installed Layout (/usr/local or CPack)"] -end -DepsScript --> BuildScript -BuildScript --> CMake -MacScript --> CMake -MinGWScript --> CMake -MSVCScript --> CMake -CFG --> CMake -CMake --> Root -Root --> Libs -Root --> Plugins -Root --> Third -Root --> Progs -CC --> CMake -CMake --> Bin -Helpers --> Root -CMake --> Inst -``` - -**Diagram sources** -- [CMakeLists.txt:206-209](file://CMakeLists.txt#L206-L209) -- [install-deps-linux.sh:28-31](file://install-deps-linux.sh#L28-L31) -- [build-linux.sh:57-61](file://build-linux.sh#L57-L61) -- [build-linux.sh:214-229](file://build-linux.sh#L214-L229) -- [build-mac.sh:210-224](file://build-mac.sh#L210-L224) -- [build-mingw.bat:90-111](file://build-mingw.bat#L90-L111) -- [build-msvc.bat:82-102](file://build-msvc.bat#L82-L102) -- [programs/build_helpers/configure_build.py:143-195](file://programs/build_helpers/configure_build.py#L143-L195) -- [programs/build_helpers/cat_parts.py:11-69](file://programs/build_helpers/cat_parts.py#L11-L69) -- [programs/util/newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) - -## Detailed Component Analysis - -### CMake Configuration and Options -Key behaviors: -- CMake minimum version: Requires CMake 3.16 or higher for modern C++ features and improved dependency management -- Compiler enforcement: Fails early if GCC < 4.8 or Clang < 3.3 -- Boost configuration: Uses Boost 1.71 with coroutine component requirement; supports static usage; removes deprecated 1.53 compatibility logic -- Platform flags: - - Windows (MSVC): Adds warning suppressions, disables safe-seh, ensures debug info, locates TCL - - Windows (MinGW): Enables C++11, permissive mode, SSE4.2, big obj, sets Release/Debug optimization flags, supports full static build - - macOS: Uses libc++, C++14, sets common warnings - - Linux: Uses C++14, enables rt and pthread, optional crypto library, supports full static build -- Coverage/testing: Optional --coverage flag when enabled -- Options: - - BUILD_TESTNET: Adds preprocessor defines and prints configuration status - - LOW_MEMORY_NODE: Adds preprocessor defines and prints configuration status - - CHAINBASE_CHECK_LOCKING: Adds preprocessor defines and prints configuration status - - ENABLE_MONGO_PLUGIN: Adds MongoDB plugin linkage and preprocessor defines - - BUILD_SHARED_LIBRARIES: Defaults to off - - USE_PCH: Optional cotire precompiled headers support - - ENABLE_INSTALLER: Optional CPack packaging - -Build targets: -- The top-level project includes subprojects for thirdparty, libraries, plugins, and programs. Programs include build_helpers, cli_wallet, vizd, js_operation_serializer, size_checker, and util. - -**Section sources** -- [CMakeLists.txt:2-3](file://CMakeLists.txt#L2-L3) -- [CMakeLists.txt:11-20](file://CMakeLists.txt#L11-L20) -- [CMakeLists.txt:38-49](file://CMakeLists.txt#L38-L49) -- [CMakeLists.txt:51-53](file://CMakeLists.txt#L51-L53) -- [CMakeLists.txt:55-80](file://CMakeLists.txt#L55-L80) -- [CMakeLists.txt:82-88](file://CMakeLists.txt#L82-L88) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:108-152](file://CMakeLists.txt#L108-L152) -- [CMakeLists.txt:154-198](file://CMakeLists.txt#L154-L198) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) - -### Cross-Platform Compilation Flags and Toolchains -- Windows: - - MSVC: Warning suppressions, safe-seh disable, ensures debug info, finds TCL - - MinGW: C++11, permissive mode, SSE4.2, big obj, Release/Debug optimization, optional full static build -- macOS: - - C++14, libc++, common warnings, disables certain conversion warnings -- Linux: - - C++14, rt and pthread libraries, optional crypto library, optional full static build -- Ninja + Clang diagnostics: colorized diagnostics when generator is Ninja and compiler is Clang -- Debug build: defines DEBUG automatically - -**Section sources** -- [CMakeLists.txt:108-152](file://CMakeLists.txt#L108-L152) -- [CMakeLists.txt:154-198](file://CMakeLists.txt#L154-L198) - -### Dependency Management -- Boost: Required version 1.71 with components including thread, date_time, system, filesystem, program_options, serialization, chrono, unit_test_framework, context, locale, and coroutine. Static usage is preferred; coroutine is now a mandatory component for Boost >= 1.71 -- OpenSSL: Optional via OPENSSL_ROOT_DIR; used when present -- Readline: Found on non-Windows platforms; included if available -- Crypto library: Defaults to crypto on Linux; configurable -- ccache: Detected and used globally for compile/link steps when available - -**Updated** Enhanced Boost dependency requirements with version 1.71 and mandatory coroutine component - -**Section sources** -- [CMakeLists.txt:38-49](file://CMakeLists.txt#L38-L49) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:102-106](file://CMakeLists.txt#L102-L106) -- [CMakeLists.txt:156-160](file://CMakeLists.txt#L156-L160) -- [CMakeLists.txt:172-176](file://CMakeLists.txt#L172-L176) - -### Build Targets -- Programs: - - vizd: main node binary - - cli_wallet: command-line wallet - - js_operation_serializer: utility for JS operation serialization - - size_checker: utility for size analysis - - build_helpers: helper utilities - - util: various utilities -- Libraries: - - api, chain, protocol, network, time, utilities, wallet -- Plugins: - - Discovered dynamically via scanning for subdirectories with CMakeLists.txt - -**Section sources** -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) - -### Build Helper Tools - -#### configure_build.py -- Purpose: Simplifies invoking cmake with sensible defaults and cross-compilation support -- Features: - - Accepts --sys-root, --boost-dir, --openssl-dir to guide find modules - - Supports LOW_MEMORY_NODE and CMAKE_BUILD_TYPE toggles - - Supports Windows cross-compilation via MinGW with static linking flags and root path modes - - Passes additional cmake options after a separator -- Typical usage: - - Configure for Release with LOW_MEMORY_NODE=OFF - - Configure for Debug with LOW_MEMORY_NODE=ON - - Cross-compile for Windows using MinGW with static linking - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Script as "configure_build.py" -participant CMake as "CMake" -participant FS as "Filesystem" -Dev->>Script : Invoke with options (--boost-dir, --openssl-dir, --sys-root, -w/--windows, -f/-w, -r/-d, -- [CMAKEOPTS]) -Script->>Script : Parse arguments and validate paths -Script->>Script : Detect Boost version and construct flags -Script->>Script : Optionally enable Windows cross-compilation flags -Script->>Script : Set LOW_MEMORY_NODE and CMAKE_BUILD_TYPE -Script->>CMake : Run cmake with constructed arguments -CMake->>FS : Discover Boost/OpenSSL/dependencies -CMake-->>Dev : Configure result and build instructions -``` - -**Diagram sources** -- [programs/build_helpers/configure_build.py:35-119](file://programs/build_helpers/configure_build.py#L35-L119) -- [programs/build_helpers/configure_build.py:143-195](file://programs/build_helpers/configure_build.py#L143-L195) - -**Section sources** -- [programs/build_helpers/configure_build.py:1-202](file://programs/build_helpers/configure_build.py#L1-L202) - -#### cat_parts.py -- Purpose: Concatenates files from a directory tree into a single output file, preserving order and skipping non-files -- Behavior: - - Validates input directory and output file path - - Skips non-file entries and filters by suffix if requested - - Compares generated content with existing output to avoid unnecessary writes - - Creates parent directories if missing - -```mermaid -flowchart TD -Start(["Start"]) --> Args["Parse input_dir and out_file"] -Args --> Validate{"input_dir is dir
out_file path valid?"} -Validate --> |No| Error["Print usage and exit"] -Validate --> |Yes| Scan["Scan files in input_dir"] -Scan --> Filter["Filter by suffix (optional)"] -Filter --> Sort["Sort files lexicographically"] -Sort --> Read["Read file contents in order"] -Read --> Compare{"Output exists and matches?"} -Compare --> |Yes| UpToDate["Print 'up-to-date' and exit"] -Compare --> |No| Write["Write concatenated content to out_file"] -Write --> Done(["Done"]) -Error --> Done -UpToDate --> Done -``` - -**Diagram sources** -- [programs/build_helpers/cat_parts.py:11-69](file://programs/build_helpers/cat_parts.py#L11-L69) - -**Section sources** -- [programs/build_helpers/cat_parts.py:1-74](file://programs/build_helpers/cat_parts.py#L1-L74) - -#### newplugin.py -- Purpose: Generates boilerplate files for a new plugin under libraries/plugins/ -- Templates: - - CMakeLists.txt for the plugin target - - Plugin header and implementation - - API header and implementation -- Behavior: - - Accepts provider and plugin name - - Writes files into a dedicated directory under libraries/plugins/ - -```mermaid -flowchart TD -Start(["Start"]) --> Parse["Parse provider and name"] -Parse --> MakeDir["Ensure output directory exists"] -MakeDir --> WriteFiles["Write template files:
CMakeLists.txt,
plugin header/impl,
api header/impl"] -WriteFiles --> Done(["Done"]) -``` - -**Diagram sources** -- [programs/util/newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) - -**Section sources** -- [programs/util/newplugin.py:1-251](file://programs/util/newplugin.py#L1-L251) - -### Docker-Based Builds -The repository ships Dockerfiles for multiple environments: -- Production: Full node build with Release, shared libraries disabled, lock checking disabled, MongoDB plugin disabled -- Low-memory: Same as production but with LOW_MEMORY_NODE enabled -- Mongo: Installs MongoDB C/C++ drivers and enables ENABLE_MONGO_PLUGIN -- Testnet: Builds with BUILD_TESTNET enabled - -Each Dockerfile: -- Uses a two-stage build to minimize image size -- Copies only necessary source files to reduce rebuilds -- Runs cmake with explicit options and compiles with parallel jobs -- Installs artifacts and prepares runtime configuration files and volumes - -**Updated** Docker configurations now use Boost 1.71 packages (libboost-coroutine-dev, libboost-context-dev) instead of older versions - -```mermaid -graph TB -subgraph "Builder Stage" -Dkfile["Dockerfile-*"] -Deps["Install build deps"] -CopySrc["Copy minimal sources"] -GitSub["Init submodules"] -CMakeCfg["Run cmake with options"] -Make["Compile with make -j$(nproc)"] -Install["make install"] -end -subgraph "Runtime Stage" -Runtime["Base image"] -User["Create vizd user"] -Vars["Set volumes and ports"] -Cfg["Copy configs and snapshots"] -end -Dkfile --> Deps --> CopySrc --> GitSub --> CMakeCfg --> Make --> Install --> Runtime -Runtime --> User --> Vars --> Cfg -``` - -**Diagram sources** -- [share/vizd/docker/Dockerfile-production:1-102](file://share/vizd/docker/Dockerfile-production#L1-L102) -- [share/vizd/docker/Dockerfile-lowmem:1-85](file://share/vizd/docker/Dockerfile-lowmem#L1-L85) -- [share/vizd/docker/Dockerfile-mongo:1-114](file://share/vizd/docker/Dockerfile-mongo#L1-L114) -- [share/vizd/docker/Dockerfile-testnet:1-102](file://share/vizd/docker/Dockerfile-testnet#L1-L102) - -**Section sources** -- [share/vizd/docker/Dockerfile-production:56-62](file://share/vizd/docker/Dockerfile-production#L56-L62) -- [share/vizd/docker/Dockerfile-lowmem:43-49](file://share/vizd/docker/Dockerfile-lowmem#L43-L49) -- [share/vizd/docker/Dockerfile-mongo:72-78](file://share/vizd/docker/Dockerfile-mongo#L72-L78) -- [share/vizd/docker/Dockerfile-testnet:56-62](file://share/vizd/docker/Dockerfile-testnet#L56-L62) - -## Enhanced Two-Script Build Process - -### install-deps-linux.sh - Secure Dependency Management -The `install-deps-linux.sh` script provides secure, automated dependency installation for Linux systems with comprehensive error handling and validation: - -**Security Features:** -- Requires root privileges with explicit EUID validation -- Comprehensive package manager detection (apt-get for Ubuntu/Debian, dnf for Fedora/RHEL) -- Color-coded logging with error/warn/info categorization -- Graceful fallback for unsupported package managers - -**Ubuntu/Debian Dependencies Installed:** -- Core build tools: cmake, gcc/g++, git, make, pkg-config, ccache -- Boost components: chrono, context, coroutine, date_time, filesystem, iostreams, locale, program-options, serialization, system, test, thread -- Compression libraries: bzip2, lzma, zstd, zlib -- Security: OpenSSL development headers -- Optional: readline, ncurses - -**Fedora/RHEL Dependencies Installed:** -- Core build tools: cmake, gcc-c++, git, ccache -- Boost development: boost-devel -- Compression libraries: bzip2-devel, lzma-devel, zstd-devel, zlib-devel -- Security: openssl-devel -- Optional: readline-devel, ncurses-devel, libtool - -**Error Handling:** -- Immediate termination on dependency installation failures -- Clear error messages with remediation suggestions -- Support for retry logic in Docker environments - -**Section sources** -- [install-deps-linux.sh:1-113](file://install-deps-linux.sh#L1-L113) - -### build-linux.sh - Enhanced Build Script -The `build-linux.sh` script provides a comprehensive automated build solution with enhanced security and validation: - -**Security Enhancements:** -- Refuses to run as root (enforced with EUID check) -- Validates source directory structure before proceeding -- Comprehensive argument parsing with validation -- Secure temporary directory handling - -**Enhanced Features:** -- Automatic dependency detection and installation for Ubuntu and Fedora -- Support for both Debian-style (apt) and RPM-style (dnf) package managers -- Comprehensive Boost 1.71+ dependency management with all required components -- Flexible configuration options for different build types and requirements -- Parallel job control and optional installation step -- Enhanced error handling and user feedback - -**Key Dependencies Installed:** -- Core build tools: cmake, gcc/g++, git, make, pkg-config -- Boost components: chrono, context, coroutine, date_time, filesystem, iostreams, locale, program-options, serialization, system, test, thread -- Compression libraries: bzip2, lzma, zstd, zlib -- Security: OpenSSL development headers -- Optional: readline, ccache, ncurses - -**Usage Examples:** -```bash -# Basic build with enhanced security -./build-linux.sh - -# Low memory node for validators -./build-linux.sh -l - -# Testnet build -./build-linux.sh -n - -# Debug build with 4 parallel jobs -./build-linux.sh -t Debug -j 4 - -# Skip dependency installation (already installed) -./build-linux.sh --skip-deps - -# Install to system after build -./build-linux.sh --install - -# Custom Boost and OpenSSL paths -./build-linux.sh --boost-root /opt/boost_1_74_0 --openssl-root /opt/openssl -``` - -**Section sources** -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) - -## Platform-Specific Build Scripts - -### Linux Build Script (build-linux.sh) -The `build-linux.sh` script provides a comprehensive automated build solution for Ubuntu 24.04+ and Fedora systems with enhanced security: - -**Security Features:** -- Refuses to run as root (EUID check at line 59-61) -- Validates CMakeLists.txt presence in source directory -- Comprehensive argument validation and error handling - -**Features:** -- Automatic dependency detection and installation for Ubuntu and Fedora -- Support for both Debian-style (apt) and RPM-style (dnf) package managers -- Comprehensive Boost 1.71+ dependency management with all required components -- Flexible configuration options for different build types and requirements -- Parallel job control and optional installation step - -**Key Dependencies Installed:** -- Core build tools: cmake, gcc/g++, git, make, pkg-config -- Boost components: chrono, context, coroutine, date_time, filesystem, iostreams, locale, program-options, serialization, system, test, thread -- Compression libraries: bzip2, lzma, zstd, zlib -- Security: OpenSSL development headers -- Optional: readline, ccache, ncurses - -**Usage Examples:** -```bash -# Basic build -./build-linux.sh - -# Low memory node for validators -./build-linux.sh -l - -# Testnet build -./build-linux.sh -n - -# Debug build with 4 parallel jobs -./build-linux.sh -t Debug -j 4 - -# Skip dependency installation (already installed) -./build-linux.sh --skip-deps - -# Install to system after build -./build-linux.sh --install - -# Custom Boost and OpenSSL paths -./build-linux.sh --boost-root /opt/boost_1_74_0 --openssl-root /opt/openssl -``` - -**Section sources** -- [build-linux.sh:1-191](file://build-linux.sh#L1-L191) - -### macOS Build Script (build-mac.sh) -The `build-mac.sh` script streamlines macOS development with Homebrew integration: - -**Features:** -- Xcode Command Line Tools verification and automatic installation -- Homebrew dependency management with modern Boost 1.71+ -- Automatic OpenSSL path detection from Homebrew -- Comprehensive error handling and validation -- Flexible configuration options mirroring Linux script capabilities - -**Key Dependencies Installed via Homebrew:** -- Core: autoconf, automake, boost, cmake, git, libtool, python3, readline -- Security: openssl -- Optional: google-perftools (for LevelDB TCMalloc) - -**macOS-Specific Features:** -- Xcode Command Line Tools detection and installation -- Homebrew prefix detection and validation -- OpenSSL path resolution from Homebrew installation -- Automatic environment setup for successful CMake configuration - -**Usage Examples:** -```bash -# Basic macOS build -./build-mac.sh - -# Low memory node for validator operations -./build-mac.sh -l - -# Testnet configuration -./build-mac.sh -n - -# Debug build with custom Boost path -./build-mac.sh --boost-root /opt/boost_1_74_0 - -# Skip dependency installation -./build-mac.sh --skip-deps -``` - -**Section sources** -- [build-mac.sh:1-242](file://build-mac.sh#L1-L242) - -### Windows MinGW Build Script (build-mingw.bat) -The `build-mingw.bat` script provides Windows development support with MinGW-w64: - -**Environment Requirements:** -- MinGW-w64 with g++ supporting C++11 and SSE4.2 -- CMake 3.16+ installed -- Git for Windows -- Boost 1.71+ built with specific configuration: `link=static threading=multi runtime-link=shared` -- OpenSSL for Windows (Win32OpenSSL) - -**Required Environment Variables:** -- `BOOST_ROOT`: Path to Boost installation (e.g., `C:\Boost`) -- `OPENSSL_ROOT_DIR`: Path to OpenSSL installation (e.g., `C:\OpenSSL-Win64`) - -**Optional Environment Variables:** -- `VIZ_BUILD_TYPE`: Release or Debug (default: Release) -- `VIZ_LOW_MEMORY`: ON or OFF (default: OFF) -- `VIZ_BUILD_TESTNET`: ON or OFF (default: OFF) -- `VIZ_FULL_STATIC`: ON or OFF (default: OFF, produces static exe) -- `VIZ_CMAKE_EXTRA`: Additional CMake options - -**Usage Examples:** -```cmd -REM Set environment variables -set BOOST_ROOT=C:\Boost -set OPENSSL_ROOT_DIR=C:\OpenSSL-Win64 - -REM Basic MinGW build -build-mingw.bat - -REM Full static build for distribution -set VIZ_FULL_STATIC=ON -build-mingw.bat - -REM Testnet build -set VIZ_BUILD_TESTNET=ON -build-mingw.bat -``` - -**Section sources** -- [build-mingw.bat:1-125](file://build-mingw.bat#L1-L125) - -### Windows MSVC Build Script (build-msvc.bat) -The `build-msvc.bat` script provides Visual Studio integration for Windows development: - -**Requirements:** -- Visual Studio 2019+ with "Desktop development with C++" workload -- CMake 3.16+ (installed or via VS CMake workload) -- Git for Windows -- Boost 1.71+ built with static linking configuration -- OpenSSL for Windows (Win32OpenSSL) - -**Environment Requirements:** -- Same as MinGW script: `BOOST_ROOT` and `OPENSSL_ROOT_DIR` environment variables -- Visual Studio generators available for CMake - -**Optional Environment Variables:** -- `VIZ_VS_VERSION`: Visual Studio generator version (default: "Visual Studio 17 2022") -- `VIZ_BUILD_TYPE`: Release or Debug (default: Release) -- `VIZ_LOW_MEMORY`: ON or OFF (default: OFF) -- `VIZ_BUILD_TESTNET`: ON or OFF (default: OFF) -- `VIZ_CMAKE_EXTRA`: Additional CMake options - -**Usage Examples:** -```cmd -REM Set environment variables -set BOOST_ROOT=C:\Boost -set OPENSSL_ROOT_DIR=C:\OpenSSL-Win64 - -REM Basic MSVC build -build-msvc.bat - -REM Specify Visual Studio version -set VIZ_VS_VERSION=Visual Studio 16 2019 -build-msvc.bat -``` - -**Section sources** -- [build-msvc.bat:1-116](file://build-msvc.bat#L1-L116) - -## Dependency Analysis -- Coupling: - - Top-level CMake depends on subproject CMakeLists.txt files to register targets - - configure_build.py depends on filesystem layout and optional environment variables for Boost/OpenSSL - - cat_parts.py depends on directory structure and file suffix filtering - - newplugin.py depends on the libraries/plugins directory layout - - Platform-specific build scripts depend on CMake configuration and system package managers -- External dependencies: - - Boost 1.71 (required), OpenSSL (optional), Readline (optional), ccache (optional), MongoDB drivers (optional) - - Platform-specific: Ubuntu/Fedora package managers, Homebrew, Visual Studio/MinGW toolchains -- Indirect dependencies: - - Plugins are discovered dynamically; their presence affects the build graph - - Platform scripts handle dependency installation automatically - -```mermaid -graph LR -CMakeTop["CMakeLists.txt"] -Libs["libraries/CMakeLists.txt"] -Plugins["plugins/CMakeLists.txt"] -Third["thirdparty/CMakeLists.txt"] -Progs["programs/CMakeLists.txt"] -CFGPy["configure_build.py"] -CATPy["cat_parts.py"] -NEWPy["newplugin.py"] -LinuxDeps["install-deps-linux.sh"] -LinuxBuild["build-linux.sh"] -MacScript["build-mac.sh"] -MinGWScript["build-mingw.bat"] -MSVCScript["build-msvc.bat"] -CMakeTop --> Libs -CMakeTop --> Plugins -CMakeTop --> Third -CMakeTop --> Progs -CFGPy --> CMakeTop -CATPy --> CMakeTop -NEWPy --> Libs -LinuxDeps --> LinuxBuild -LinuxBuild --> CMakeTop -MacScript --> CMakeTop -MinGWScript --> CMakeTop -MSVCScript --> CMakeTop -``` - -**Diagram sources** -- [CMakeLists.txt:206-209](file://CMakeLists.txt#L206-L209) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [programs/CMakeLists.txt:1-8](file://programs/CMakeLists.txt#L1-L8) -- [programs/build_helpers/configure_build.py:143-195](file://programs/build_helpers/configure_build.py#L143-L195) -- [programs/build_helpers/cat_parts.py:11-69](file://programs/build_helpers/cat_parts.py#L11-L69) -- [programs/util/newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) -- [install-deps-linux.sh:98-106](file://install-deps-linux.sh#L98-L106) -- [build-linux.sh:107-178](file://build-linux.sh#L107-L178) -- [build-mac.sh:125-171](file://build-mac.sh#L125-L171) -- [build-mingw.bat:32-56](file://build-mingw.bat#L32-L56) -- [build-msvc.bat:32-56](file://build-msvc.bat#L32-L56) - -**Section sources** -- [CMakeLists.txt:206-209](file://CMakeLists.txt#L206-L209) -- [programs/build_helpers/configure_build.py:143-195](file://programs/build_helpers/configure_build.py#L143-L195) -- [programs/build_helpers/cat_parts.py:11-69](file://programs/build_helpers/cat_parts.py#L11-L69) -- [programs/util/newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) -- [install-deps-linux.sh:98-106](file://install-deps-linux.sh#L98-L106) -- [build-linux.sh:107-178](file://build-linux.sh#L107-L178) -- [build-mac.sh:125-171](file://build-mac.sh#L125-L171) -- [build-mingw.bat:32-56](file://build-mingw.bat#L32-L56) -- [build-msvc.bat:32-56](file://build-msvc.bat#L32-L56) - -## Performance Considerations -- Compiler flags: - - Release builds use aggressive optimization for MinGW and Linux - - Debug builds define DEBUG and can be paired with coverage instrumentation when enabled -- Precompiled headers: - - USE_PCH enables cotire for faster incremental builds -- Static vs shared libraries: - - BUILD_SHARED_LIBRARIES defaults to off, reducing runtime dependencies and potentially improving startup performance -- Memory profile: - - LOW_MEMORY_NODE reduces storage overhead by excluding non-consensus data, beneficial for resource-constrained nodes -- Lock checking: - - CHAINBASE_CHECK_LOCKING can be disabled for production builds to reduce overhead -- ccache: - - Global launch wrappers accelerate rebuilds when available -- Platform-specific optimizations: - - Linux builds leverage system Boost 1.71+ packages for optimal performance - - macOS builds utilize Homebrew's optimized dependencies - - Windows builds support both static and dynamic linking configurations - -Practical implications: -- Choose Release for production builds to maximize runtime performance -- Disable CHAINBASE_CHECK_LOCKING and LOW_MEMORY_NODE unless required for specific roles -- Enable USE_PCH for faster local development cycles -- Use platform-specific build scripts for optimal dependency management and performance - -**Section sources** -- [CMakeLists.txt:144-152](file://CMakeLists.txt#L144-L152) -- [CMakeLists.txt:176-183](file://CMakeLists.txt#L176-L183) -- [CMakeLists.txt:51-53](file://CMakeLists.txt#L51-L53) -- [CMakeLists.txt:65-73](file://CMakeLists.txt#L65-L73) -- [CMakeLists.txt:75-80](file://CMakeLists.txt#L75-L80) -- [CMakeLists.txt:102-106](file://CMakeLists.txt#L102-L106) - -## Security Best Practices - -### Root Privilege Separation -The enhanced build system implements strict security boundaries: -- **install-deps-linux.sh**: Must run as root with explicit EUID validation -- **build-linux.sh**: Must run as regular user (refuses to run as root) -- **Automatic validation**: Both scripts verify they're in the correct directory structure - -### Enhanced Error Handling -- **Comprehensive validation**: All scripts validate inputs, dependencies, and environment variables -- **Color-coded logging**: Clear distinction between info, warning, and error messages -- **Graceful degradation**: Scripts handle missing optional dependencies gracefully -- **Clear error messages**: Specific guidance for resolving dependency issues - -### Secure Environment Setup -- **macOS**: Verifies Xcode Command Line Tools and Homebrew installation -- **Windows**: Validates environment variables and required tool availability -- **Linux**: Detects and installs appropriate package manager dependencies - -### Dependency Management Security -- **Package manager detection**: Automatically detects and uses appropriate package managers -- **Repository validation**: Ensures dependencies are installed from trusted repositories -- **Version validation**: Confirms minimum required versions for all dependencies - -**Section sources** -- [install-deps-linux.sh:28-31](file://install-deps-linux.sh#L28-L31) -- [build-linux.sh:57-61](file://build-linux.sh#L57-L61) -- [build-linux.sh:109-112](file://build-linux.sh#L109-L112) -- [build-mac.sh:102-115](file://build-mac.sh#L102-L115) - -## Troubleshooting Guide -Common issues and resolutions: - -### Enhanced Two-Script Build Process Issues -- **Root privilege errors**: `Do not run this script as root` - Use `sudo ./install-deps-linux.sh` for dependencies, then run `./build-linux.sh` as regular user -- **Dependency installation failures**: Check package manager logs and retry with `--no-cache` option in Docker environments -- **Permission errors**: Ensure proper file permissions and run scripts from repository root directory - -### Boost Version Issues -- **Problem**: Boost version below 1.71 -- **Solution**: Ensure Boost 1.71+ is installed via package manager or built from source -- **Platform-specific**: Ubuntu 24.04 provides Boost 1.74, macOS Homebrew provides recent versions, Windows requires manual installation - -### Dependency Installation Problems -- **Linux**: Package manager failures (apt/dnf) - verify internet connectivity and retry -- **macOS**: Homebrew installation issues - run `brew doctor` and fix any reported problems -- **Windows**: Missing Visual Studio/MinGW components - install required workloads and tools - -### Environment Variable Issues -- **Windows MinGW**: `BOOST_ROOT` and `OPENSSL_ROOT_DIR` must be set and point to valid installations -- **Windows MSVC**: Same environment variables required for Visual Studio builds -- **macOS**: OpenSSL path detection may fail - set `OPENSSL_ROOT_DIR` manually - -### Platform-Specific Issues -- **Linux**: Ensure system Boost 1.71+ packages are installed (coroutine, context components) -- **macOS**: Xcode Command Line Tools must be installed and accepted -- **Windows**: MinGW requires C++11 support and SSE4.2 capability - -### Build Script Issues -- **Permission errors**: Make scripts executable with `chmod +x install-deps-linux.sh build-linux.sh` or `build-*.bat` -- **Path issues**: Run scripts from repository root directory -- **Parallel job issues**: Adjust `-j` parameter based on available CPU cores - -**Updated** Enhanced Boost dependency requirements and platform-specific build script troubleshooting with new two-script architecture - -**Section sources** -- [install-deps-linux.sh:28-31](file://install-deps-linux.sh#L28-L31) -- [build-linux.sh:57-61](file://build-linux.sh#L57-L61) -- [build-linux.sh:109-112](file://build-linux.sh#L109-L112) -- [build-linux.sh:189-191](file://build-linux.sh#L189-L191) -- [build-mac.sh:102-115](file://build-mac.sh#L102-L115) -- [build-mac.sh:166-171](file://build-mac.sh#L166-L171) -- [build-mingw.bat:32-56](file://build-mingw.bat#L32-L56) -- [build-msvc.bat:32-56](file://build-msvc.bat#L32-L56) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) - -## Conclusion -The VIZ CPP Node build system has evolved significantly with the introduction of a secure, two-script architecture that enhances both security and reliability. The new `install-deps-linux.sh` script handles dependency installation with proper root privilege separation, while `build-linux.sh` provides a user-friendly build experience with comprehensive error handling and validation. The system continues to support Ubuntu 24.04+, macOS with Homebrew, and Windows with both MSVC and MinGW toolchains, all requiring Boost 1.71+ with mandatory coroutine component. Recent updates include upgrading to CMake 3.16, Boost 1.71, and adding coroutine component requirements. The platform-specific scripts handle dependency management, configuration, and building automatically, while the traditional CMake approach remains available for advanced users. Dockerfiles continue to streamline both development and production workflows with updated dependency specifications. By leveraging the appropriate build script for your platform and following the enhanced security practices, developers can quickly set up consistent builds with optimal performance characteristics and improved security boundaries. - -## Appendices - -### Practical Build Scenarios - -#### Enhanced Linux Development Build -```bash -# Clone repository -git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node -cd viz-cpp-node - -# Install dependencies (requires root) -chmod +x install-deps-linux.sh -sudo ./install-deps-linux.sh - -# Make build script executable -chmod +x build-linux.sh - -# Basic development build (runs as user) -./build-linux.sh - -# Low memory node for validator operations -./build-linux.sh -l - -# Testnet build -./build-linux.sh -n - -# Debug build with custom jobs -./build-linux.sh -t Debug -j 4 -``` - -#### macOS Development Build -```bash -# Clone repository -git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node -cd viz-cpp-node - -# Make script executable -chmod +x build-mac.sh - -# Basic macOS build -./build-mac.sh - -# Custom Boost path -./build-mac.sh --boost-root /opt/boost_1_74_0 - -# Skip dependencies if already installed -./build-mac.sh --skip-deps -``` - -#### Windows MinGW Build -```cmd -REM Set environment variables -set BOOST_ROOT=C:\Boost -set OPENSSL_ROOT_DIR=C:\OpenSSL-Win64 - -REM Basic MinGW build -build-mingw.bat - -REM Full static build for distribution -set VIZ_FULL_STATIC=ON -build-mingw.bat - -REM Testnet configuration -set VIZ_BUILD_TESTNET=ON -build-mingw.bat -``` - -#### Windows MSVC Build -```cmd -REM Set environment variables -set BOOST_ROOT=C:\Boost -set OPENSSL_ROOT_DIR=C:\OpenSSL-Win64 - -REM Basic MSVC build -build-msvc.bat - -REM Specify Visual Studio version -set VIZ_VS_VERSION=Visual Studio 16 2019 -build-msvc.bat -``` - -#### Advanced CMake Configuration -```bash -# Traditional CMake approach with configure_build.py -python3 programs/build_helpers/configure_build.py --release --src ../.. - -# Cross-compilation for Windows using MinGW -python3 programs/build_helpers/configure_build.py --win --release - -# Custom Boost and OpenSSL paths -python3 programs/build_helpers/configure_build.py --boost-dir /opt/boost_1_74_0 --openssl-dir /opt/openssl --release -``` - -**Updated** Enhanced platform-specific build script usage examples and advanced configuration options with new two-script architecture - -**Section sources** -- [install-deps-linux.sh:108-113](file://install-deps-linux.sh#L108-L113) -- [build-linux.sh:189-191](file://build-linux.sh#L189-L191) -- [build-mac.sh:330-351](file://build-mac.sh#L330-L351) -- [build-mingw.bat:12-22](file://build-mingw.bat#L12-L22) -- [build-msvc.bat:12-22](file://build-msvc.bat#L12-L22) -- [programs/build_helpers/configure_build.py:168-184](file://programs/build_helpers/configure_build.py#L168-L184) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Build Targets.md b/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Build Targets.md deleted file mode 100644 index dfa4ce8d6d..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Build Targets.md +++ /dev/null @@ -1,526 +0,0 @@ -# Build Targets - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [programs/CMakeLists.txt](file://programs/CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt) -- [programs/js_operation_serializer/CMakeLists.txt](file://programs/js_operation_serializer/CMakeLists.txt) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt) -- [plugins/chain/CMakeLists.txt](file://plugins/chain/CMakeLists.txt) -- [plugins/webserver/CMakeLists.txt](file://plugins/webserver/CMakeLists.txt) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt) -- [programs/build_helpers/CMakeLists.txt](file://programs/build_helpers/CMakeLists.txt) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document describes the build targets for the VIZ CPP Node CMake configuration. It focuses on the main executables (vizd, cli_wallet, js_operation_serializer), library targets (libraries/*), plugin targets (plugins/*), and supporting utilities. It also covers static vs shared library compilation, test-related options, installation targets, and platform-specific considerations. The goal is to help developers customize the build scope for efficient development workflows. - -## Project Structure -The top-level CMake configuration orchestrates subdirectories for third-party dependencies, libraries, plugins, and programs. Library and plugin targets are built conditionally based on shared/static selection and optional features. - -```mermaid -graph TB -Root["Root CMakeLists.txt"] -ThirdParty["thirdparty (external deps)"] -Libs["libraries/ (CMakeLists.txt)"] -Plugins["plugins/ (CMakeLists.txt)"] -Programs["programs/ (CMakeLists.txt)"] -Root --> ThirdParty -Root --> Libs -Root --> Plugins -Root --> Programs -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt](file://programs/CMakeLists.txt#L1-L8) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [programs/CMakeLists.txt](file://programs/CMakeLists.txt#L1-L8) - -## Core Components -This section summarizes the primary build targets and their roles. - -- Executables - - vizd: Full node executable with numerous internal plugins linked statically by default. - - cli_wallet: Command-line wallet application linking against chain, protocol, utilities, and wallet libraries plus selected plugins. - - js_operation_serializer: Operation serialization tool for JavaScript consumers. -- Libraries - - libraries/chain, api, protocol, network, utilities, wallet: Core libraries compiled either as static or shared depending on BUILD_SHARED_LIBRARIES. -- Plugins - - plugins/*: Dynamically loaded modules compiled as static or shared per BUILD_SHARED_LIBRARIES; discovery via globbing. -- Utilities - - programs/util/*: Developer and testing utilities (signing, block log tests, etc.). - -Key configuration toggles: -- BUILD_SHARED_LIBRARIES: Controls whether libraries are built as static or shared. -- ENABLE_MONGO_PLUGIN: Enables MongoDB plugin linkage and defines a preprocessor macro. -- BUILD_TESTNET: Adds a preprocessor definition for testnet builds. -- LOW_MEMORY_NODE: Adds a preprocessor definition for low-memory builds. -- CHAINBASE_CHECK_LOCKING: Adds a preprocessor definition enabling chainbase locking checks. -- USE_PCH: Optional precompiled header support via cotire. -- FULL_STATIC_BUILD: Platform-specific static linking flags for MinGW/MSVC. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L52-L89) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L16-L124) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt#L28-L49) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt#L40-L57) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L24-L44) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt#L22-L31) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L38-L70) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) - -## Architecture Overview -The build architecture links applications to libraries and plugins. vizd links to many internal plugins and core libraries. cli_wallet links to core libraries and selected plugins. js_operation_serializer links to minimal core libraries. - -```mermaid -graph TB -subgraph "Applications" -VIZD["vizd"] -CLI["cli_wallet"] -SER["js_operation_serializer"] -end -subgraph "Core Libraries" -Chain["graphene_chain"] -Protocol["graphene_protocol"] -Network["graphene_network"] -Utilities["graphene_utilities"] -Wallet["graphene_wallet"] -Api["graphene_api"] -end -subgraph "Plugins" -PChain["graphene::chain_plugin"] -PWeb["graphene::webserver_plugin"] -PJson["graphene::json_rpc"] -PWitness["graphene::validator"] -PTags["graphene::tags"] -PFollow["graphene::follow"] -PCommittee["graphene::committee_api"] -PMongo["graphene::mongo_db (optional)"] -end -VIZD --> PChain -VIZD --> PWeb -VIZD --> PWitness -VIZD --> PTags -VIZD --> PFollow -VIZD --> PCommittee -VIZD --> PMongo -VIZD --> Chain -VIZD --> Protocol -VIZD --> Utilities -VIZD --> Network -VIZD --> Api -CLI --> Chain -CLI --> Protocol -CLI --> Utilities -CLI --> Wallet -CLI --> Api -SER --> Chain -SER --> Protocol -SER --> Utilities -``` - -**Diagram sources** -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L16-L49) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L21-L41) -- [programs/js_operation_serializer/CMakeLists.txt](file://programs/js_operation_serializer/CMakeLists.txt#L6-L7) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L126-L128) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt#L43-L49) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt#L55-L57) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L39) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt#L31) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L50-L70) -- [plugins/chain/CMakeLists.txt](file://plugins/chain/CMakeLists.txt#L26-L33) -- [plugins/webserver/CMakeLists.txt](file://plugins/webserver/CMakeLists.txt#L26-L32) - -## Detailed Component Analysis - -### vizd (Full Node Executable) -- Purpose: The primary node executable with a broad set of enabled plugins. -- Linkage: Links to appbase, webserver plugin, p2p, chain plugin, network broadcast API, validator, validator API, database API, test API, social network, tags, operation history, account by key, account history, private message, auth utility, debug node, raw block, block info, JSON RPC, follow, committee API, invite API, paid subscription API, custom protocol API, and optionally MongoDB plugin. Also links to protocol, fc, and platform-specific libraries. -- Installation: Installable under bin with standard DESTINATION entries. -- Platform specifics: - - UNIX (non-Apple): Links to rt if found. - - Apple: Links to readline if found. - - Gperftools detection: If found, links to tcmalloc. -- Static vs shared: Libraries are controlled by BUILD_SHARED_LIBRARIES; vizd itself is an executable. - -```mermaid -sequenceDiagram -participant CMake as "CMake" -participant VIZD as "vizd target" -participant Libs as "Core Libraries" -participant Plugs as "Internal Plugins" -CMake->>VIZD : "add_executable(vizd)" -VIZD->>Plugs : "link graphene : : webserver_plugin" -VIZD->>Plugs : "link graphene : : p2p" -VIZD->>Plugs : "link graphene : : chain_plugin" -VIZD->>Plugs : "link graphene : : network_broadcast_api" -VIZD->>Plugs : "link graphene : : validator" -VIZD->>Plugs : "link graphene : : witness_api" -VIZD->>Plugs : "link graphene : : database_api" -VIZD->>Plugs : "link graphene : : test_api_plugin" -VIZD->>Plugs : "link graphene : : social_network" -VIZD->>Plugs : "link graphene : : tags" -VIZD->>Plugs : "link graphene : : operation_history" -VIZD->>Plugs : "link graphene : : account_by_key" -VIZD->>Plugs : "link graphene : : account_history" -VIZD->>Plugs : "link graphene : : private_message" -VIZD->>Plugs : "link graphene : : auth_util" -VIZD->>Plugs : "link graphene : : debug_node" -VIZD->>Plugs : "link graphene : : raw_block" -VIZD->>Plugs : "link graphene : : block_info" -VIZD->>Plugs : "link graphene : : json_rpc" -VIZD->>Plugs : "link graphene : : follow" -VIZD->>Plugs : "link graphene : : committee_api" -VIZD->>Plugs : "link graphene : : invite_api" -VIZD->>Plugs : "link graphene : : paid_subscription_api" -VIZD->>Plugs : "link graphene : : custom_protocol_api" -VIZD->>Libs : "link graphene_protocol, fc" -VIZD-->>CMake : "install(TARGETS vizd ...)" -``` - -**Diagram sources** -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L1-L58) - -**Section sources** -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L1-L58) - -### cli_wallet (Command-Line Wallet) -- Purpose: A command-line wallet for interacting with the node. -- Linkage: Links to graphene_network, graphene_chain, graphene_protocol, graphene_utilities, graphene_wallet, and several API plugins (database_api, account_history, social_network, private_message, follow, network_broadcast_api, witness_api). Also links to fc, readline (on Apple), dl libs, platform-specific libs, and Boost regex. -- Installation: Installable under bin with standard DESTINATION entries. -- Platform specifics: - - UNIX (non-Apple): Links to rt if found. - - Apple: Links to readline if found. - - Gperftools detection: If found, links to tcmalloc. - - MSVC: Applies /bigobj to main.cpp. - -```mermaid -sequenceDiagram -participant CMake as "CMake" -participant CLI as "cli_wallet target" -participant Libs as "Core Libraries" -participant Plugs as "Selected Plugins" -CMake->>CLI : "add_executable(cli_wallet)" -CLI->>Libs : "link graphene_network" -CLI->>Libs : "link graphene_chain" -CLI->>Libs : "link graphene_protocol" -CLI->>Libs : "link graphene_utilities" -CLI->>Libs : "link graphene_wallet" -CLI->>Plugs : "link graphene : : database_api" -CLI->>Plugs : "link graphene : : account_history" -CLI->>Plugs : "link graphene : : social_network" -CLI->>Plugs : "link graphene : : private_message" -CLI->>Plugs : "link graphene : : follow" -CLI->>Plugs : "link graphene : : network_broadcast_api" -CLI->>Plugs : "link graphene : : witness_api" -CLI->>Libs : "link fc" -CLI-->>CMake : "install(TARGETS cli_wallet ...)" -``` - -**Diagram sources** -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L1-L54) - -**Section sources** -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L1-L54) - -### js_operation_serializer (Operation Serialization Tool) -- Purpose: Generates serialized operation schemas for JavaScript. -- Linkage: Minimal linkage to graphene_chain, graphene_protocol, graphene_utilities, and fc with dl libs and platform-specific libs. -- Installation: Installable under bin with standard DESTINATION entries. - -```mermaid -sequenceDiagram -participant CMake as "CMake" -participant SER as "js_operation_serializer target" -participant Libs as "Core Libraries" -CMake->>SER : "add_executable(js_operation_serializer)" -SER->>Libs : "link graphene_chain" -SER->>Libs : "link graphene_protocol" -SER->>Libs : "link graphene_utilities" -SER->>Libs : "link fc" -SER-->>CMake : "install(TARGETS js_operation_serializer ...)" -``` - -**Diagram sources** -- [programs/js_operation_serializer/CMakeLists.txt](file://programs/js_operation_serializer/CMakeLists.txt#L1-L16) - -**Section sources** -- [programs/js_operation_serializer/CMakeLists.txt](file://programs/js_operation_serializer/CMakeLists.txt#L1-L16) - -### Library Targets (libraries/*) -- graphene_chain - - Static or shared based on BUILD_SHARED_LIBRARIES. - - Depends on graphene_protocol, graphene_utilities, fc, chainbase, appbase. - - Includes generated hardfork.hpp via a custom target. - - MSVC: Applies /bigobj to database.cpp. -- graphene_api - - Static or shared based on BUILD_SHARED_LIBRARIES. - - Depends on graphene_chain, graphene_protocol, graphene_utilities, fc. -- graphene_protocol - - Static or shared based on BUILD_SHARED_LIBRARIES. - - Depends on fc; includes version headers. -- graphene_network - - Static or shared based on BUILD_SHARED_LIBRARIES. - - Depends on fc and graphene_protocol; supports PCH via cotire if enabled. - - MSVC: Applies /bigobj to node.cpp. -- graphene_utilities - - Static or shared based on BUILD_SHARED_LIBRARIES. - - Depends on fc; generates git_revision.cpp via configure_file. - - Supports PCH via cotire if enabled. -- graphene_wallet - - Static or shared based on BUILD_SHARED_LIBRARIES. - - Depends on multiple API plugins and core libraries; supports PCH via cotire if enabled. - - MSVC: Applies /bigobj to wallet.cpp. - -```mermaid -classDiagram -class Chain["graphene_chain"] -class Api["graphene_api"] -class Protocol["graphene_protocol"] -class Network["graphene_network"] -class Utilities["graphene_utilities"] -class Wallet["graphene_wallet"] -Chain --> Protocol : "depends on" -Chain --> Utilities : "depends on" -Chain --> Network : "generated header" -Api --> Chain : "depends on" -Api --> Protocol : "depends on" -Api --> Utilities : "depends on" -Protocol --> Utilities : "depends on" -Network --> Protocol : "depends on" -Network --> Utilities : "depends on" -Wallet --> Network : "depends on" -Wallet --> Chain : "depends on" -Wallet --> Protocol : "depends on" -Wallet --> Utilities : "depends on" -``` - -**Diagram sources** -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L126-L128) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt#L43-L49) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt#L55-L57) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L39) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt#L31) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L50-L70) - -**Section sources** -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L16-L142) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt#L28-L60) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt#L40-L70) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L24-L64) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt#L22-L47) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L38-L85) - -### Plugin Targets (plugins/*) -Plugin discovery is automated via globbing over plugin directories. Each plugin target is built as static or shared according to BUILD_SHARED_LIBRARIES and exposes an alias graphene::. - -- Example: chain plugin - - Provides graphene::chain_plugin alias. - - Depends on graphene_chain, graphene_protocol, fc, appbase, and json_rpc. -- Example: webserver plugin - - Provides graphene::webserver_plugin alias. - - Depends on graphene::json_rpc, graphene_chain, graphene::chain_plugin, appbase, fc. - -```mermaid -flowchart TD -Scan["Scan plugins/*"] --> Found{"Has CMakeLists.txt?"} -Found --> |Yes| Add["add_subdirectory(plugin)"] -Found --> |No| Skip["Skip"] -Add --> Target["graphene:: (STATIC|SHARED)"] -Target --> Alias["ALIAS graphene::"] -Alias --> Link["Link dependencies"] -``` - -**Diagram sources** -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [plugins/chain/CMakeLists.txt](file://plugins/chain/CMakeLists.txt#L10-L33) -- [plugins/webserver/CMakeLists.txt](file://plugins/webserver/CMakeLists.txt#L11-L32) - -**Section sources** -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt#L1-L12) -- [plugins/chain/CMakeLists.txt](file://plugins/chain/CMakeLists.txt#L1-L44) -- [plugins/webserver/CMakeLists.txt](file://plugins/webserver/CMakeLists.txt#L1-L43) - -### Utility Programs (programs/util/*) -- get_dev_key, test_shared_mem, sign_digest, sign_transaction, test_block_log: Each links to core libraries and installs under bin. - -```mermaid -graph LR -Util["programs/util/CMakeLists.txt"] -GD["get_dev_key"] -TSM["test_shared_mem"] -SD["sign_digest"] -ST["sign_transaction"] -TBL["test_block_log"] -Util --> GD -Util --> TSM -Util --> SD -Util --> ST -Util --> TBL -``` - -**Diagram sources** -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) - -**Section sources** -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) - -### Build Helpers (programs/build_helpers) -- cat-parts: A small helper tool linking to fc and platform libs. - -**Section sources** -- [programs/build_helpers/CMakeLists.txt](file://programs/build_helpers/CMakeLists.txt#L1-L8) - -## Dependency Analysis -This section maps how targets depend on each other and external libraries. - -```mermaid -graph TB -Root["Root CMakeLists.txt"] -Libs["libraries/*"] -Plugins["plugins/*"] -Progs["programs/*"] -Root --> Libs -Root --> Plugins -Root --> Progs -subgraph "Programs" -VIZD["vizd"] -CLI["cli_wallet"] -SER["js_operation_serializer"] -UTIL["programs/util/*"] -end -subgraph "Libraries" -Chain["graphene_chain"] -Protocol["graphene_protocol"] -Network["graphene_network"] -Utilities["graphene_utilities"] -Wallet["graphene_wallet"] -Api["graphene_api"] -end -subgraph "Plugins" -PChain["graphene::chain_plugin"] -PWeb["graphene::webserver_plugin"] -PJson["graphene::json_rpc"] -end -VIZD --> Chain -VIZD --> Protocol -VIZD --> Network -VIZD --> Utilities -VIZD --> Api -VIZD --> PChain -VIZD --> PWeb -VIZD --> PJson -CLI --> Chain -CLI --> Protocol -CLI --> Utilities -CLI --> Wallet -CLI --> Api -CLI --> PJson -SER --> Chain -SER --> Protocol -SER --> Utilities -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L16-L49) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L21-L41) -- [programs/js_operation_serializer/CMakeLists.txt](file://programs/js_operation_serializer/CMakeLists.txt#L6-L7) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L126-L128) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt#L43-L49) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt#L55-L57) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L39) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt#L31) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L50-L70) -- [plugins/chain/CMakeLists.txt](file://plugins/chain/CMakeLists.txt#L26-L33) -- [plugins/webserver/CMakeLists.txt](file://plugins/webserver/CMakeLists.txt#L26-L32) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L16-L49) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L21-L41) -- [programs/js_operation_serializer/CMakeLists.txt](file://programs/js_operation_serializer/CMakeLists.txt#L6-L7) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L126-L128) -- [libraries/api/CMakeLists.txt](file://libraries/api/CMakeLists.txt#L43-L49) -- [libraries/protocol/CMakeLists.txt](file://libraries/protocol/CMakeLists.txt#L55-L57) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L39) -- [libraries/utilities/CMakeLists.txt](file://libraries/utilities/CMakeLists.txt#L31) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt#L50-L70) -- [plugins/chain/CMakeLists.txt](file://plugins/chain/CMakeLists.txt#L26-L33) -- [plugins/webserver/CMakeLists.txt](file://plugins/webserver/CMakeLists.txt#L26-L32) - -## Performance Considerations -- Precompiled Headers (PCH): Optional cotire support exists for some libraries (e.g., network, utilities) to speed up compilation. Enable via USE_PCH. -- Compiler flags: - - Windows (MSVC): Applies /wd4503, /wd4267, /wd4244 warnings suppressions and /SAFESEH:NO linker flags; Debug adds /DEBUG. - - MinGW: Uses -std=c++11, -fpermissive, -msse4.2, -Wa,-mbig-obj; Debug optimized to -O2; Release to -O3; optional static linking flags via FULL_STATIC_BUILD. - - Clang on macOS: -stdlib=libc++; Ninja generator adds -fcolor-diagnostics; enables -DDEBUG in Debug. - - GCC on Linux: Adds -fno-builtin-memcmp; Ninja adds -fcolor-diagnostics; enables -DDEBUG in Debug; optional static linking flags via FULL_STATIC_BUILD. -- Memory profiling: Gperftools detection enables tcmalloc linkage for vizd and cli_wallet when available. -- Coverage: ENABLE_COVERAGE_TESTING toggles --coverage in CXX flags. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L123-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L166-L202) -- [CMakeLists.txt](file://CMakeLists.txt#L206-L208) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L10-L14) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L10-L14) - -## Troubleshooting Guide -- Boost static/shared linkage: - - Boost_USE_STATIC_LIBS defaults to TRUE globally. - - On Windows, BOOST_ALL_DYN_LINK is OFF to force static linking; ensure compatible Boost libraries. -- MongoDB plugin: - - ENABLE_MONGO_PLUGIN adds graphene::mongo_db to vizd and defines DMONGODB_PLUGIN_BUILT macros. -- Testnet and Low-Memory: - - BUILD_TESTNET and LOW_MEMORY_NODE inject -DBUILD_TESTNET and -DIS_LOW_MEM respectively. -- Chainbase locking checks: - - CHAINBASE_CHECK_LOCKING adds -DCHAINBASE_CHECK_LOCKING when enabled. -- Hardfork header generation: - - chain library depends on a generated hardfork.hpp via a custom target; ensure cat-parts is available on Windows or Python-based script on Unix-like systems. -- Platform-specific libraries: - - UNIX (non-Apple): rt library linkage. - - Apple: readline linkage. - - dl libs automatically linked via ${CMAKE_DL_LIBS}. -- Static vs shared: - - BUILD_SHARED_LIBRARIES controls library type for libraries/* and plugins/*. - - FULL_STATIC_BUILD toggles static linking flags for MinGW/MSVC. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L52-L89) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L158-L202) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L1-L9) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L4-L8) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt#L2-L8) - -## Conclusion -The VIZ CPP Node build system organizes targets into libraries, plugins, and applications. Libraries support both static and shared modes controlled by BUILD_SHARED_LIBRARIES. Applications link to core libraries and selected plugins, with platform-specific optimizations and optional features like MongoDB plugin, testnet/low-memory configurations, and PCH support. Developers can tailor builds by toggling options and selectively enabling/disabling components. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/CMake Configuration.md b/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/CMake Configuration.md deleted file mode 100644 index fd9799f367..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/CMake Configuration.md +++ /dev/null @@ -1,381 +0,0 @@ -# CMake Configuration - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt) -- [documentation/building.md](file://documentation/building.md) -- [documentation/testing.md](file://documentation/testing.md) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the CMake configuration for the VIZ CPP Node, focusing on the top-level CMake project setup, compiler requirements, platform-specific configurations, build options, Boost library configuration, compiler flags, and practical CMake invocation examples. It also covers how options affect compilation flags and feature availability, and provides troubleshooting guidance for common build issues. - -## Project Structure -The build system is organized around a top-level CMake project that orchestrates subprojects: -- Top-level project defines compiler requirements, options, and platform flags. -- Subprojects include thirdparty libraries, internal libraries, plugins, and programs. -- Platform-specific logic configures flags for Windows, macOS, and Linux. -- Optional MongoDB plugin integrates via dedicated CMake logic gated by an option. - -```mermaid -graph TB -Root["Top-level CMakeLists.txt
Defines project, options, platform flags"] --> ThirdParty["thirdparty/<...>
Subproject"] -Root --> Libraries["libraries/<...>
Subproject"] -Root --> Plugins["plugins/<...>
Subproject"] -Root --> Programs["programs/<...>
Subproject"] -subgraph "Plugins" -Mongo["plugins/mongo_db/CMakeLists.txt
Conditional MongoDB plugin build"] -end -subgraph "Programs" -Vizd["programs/vizd/CMakeLists.txt
Links to enabled plugins and libs"] -end -Plugins --> Mongo -Programs --> Vizd -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L1-L58) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) - -## Core Components -This section documents the primary CMake configuration elements defined in the top-level CMakeLists.txt. - -- Project setup and minimum CMake version - - The project is named and requires a minimum CMake version. - - Compiler requirement enforcement for GCC and Clang is performed early in configuration. - -- Build options and their effects - - BUILD_TESTNET: Adds preprocessor definitions enabling testnet-specific code paths. - - LOW_MEMORY_NODE: Adds preprocessor definitions enabling low-memory node behavior. - - CHAINBASE_CHECK_LOCKING: Adds preprocessor definitions enabling chainbase locking checks. - - ENABLE_MONGO_PLUGIN: Enables MongoDB plugin build and adds a preprocessor definition; links the plugin target when found. - -- Boost library configuration - - Declares required Boost components. - - Forces static Boost usage by default. - - Adjusts Windows-specific Boost settings and handles coroutine component detection. - - Requires a minimum Boost version and locates Boost with COMPONENTS. - -- Platform-specific configurations - - Windows (MSVC/MINGW): Applies Windows-specific compiler and linker flags, TLS/CCache integration, and TCL library discovery. - - macOS: Uses C++ standard and libc++, sets warning flags, and applies platform-specific behavior. - - Linux: Uses C++ standard, sets warning flags, locates readline, and applies platform-specific libraries. - -- Compiler flags and optimization - - Sets C++ standard per platform. - - Configures debug and release flags, including debug macro definitions. - - Adds coverage flags when enabled. - - Uses ccache when detected. - -- Subproject inclusion - - Includes thirdparty, libraries, plugins, and programs subdirectories. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) - -## Architecture Overview -The CMake configuration enforces compiler requirements, exposes build options, configures platform flags, locates dependencies, and wires subprojects together. The MongoDB plugin is conditionally included and linked into the node executable when enabled. - -```mermaid -graph TB -A["CMakeLists.txt
Compiler checks, options, platform flags"] --> B["Boost
Components and linkage"] -A --> C["Compiler Flags
Per-platform"] -A --> D["Options
BUILD_TESTNET, LOW_MEMORY_NODE,
CHAINBASE_CHECK_LOCKING, ENABLE_MONGO_PLUGIN"] -D --> E["plugins/mongo_db/CMakeLists.txt
Conditional plugin build"] -A --> F["Subprojects
thirdparty, libraries, plugins, programs"] -F --> G["programs/vizd/CMakeLists.txt
Executable and link targets"] -E --> G -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L12-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L38-L104) -- [CMakeLists.txt](file://CMakeLists.txt#L112-L202) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L16-L49) - -## Detailed Component Analysis - -### Compiler Requirements and Toolchain -- GCC minimum version enforced during configuration. -- Clang minimum version enforced during configuration. -- Early failure if requirements are not met prevents downstream build issues. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L12-L20) - -### Build Options and Feature Flags -- BUILD_TESTNET - - Adds preprocessor definitions for both C and C++. - - Prints configuration status and final mode selection. -- LOW_MEMORY_NODE - - Adds preprocessor definitions for both C and C++. - - Prints configuration status and final mode selection. -- CHAINBASE_CHECK_LOCKING - - Adds preprocessor definitions for both C and C++. -- ENABLE_MONGO_PLUGIN - - Enables MongoDB plugin build and adds a preprocessor definition. - - Links the plugin target into dependent executables. - -```mermaid -flowchart TD -Start(["Configure"]) --> CheckOptions["Evaluate Options:
BUILD_TESTNET, LOW_MEMORY_NODE,
CHAINBASE_CHECK_LOCKING, ENABLE_MONGO_PLUGIN"] -CheckOptions --> Testnet{"BUILD_TESTNET?"} -Testnet --> |Yes| AddDef1["Add -DBUILD_TESTNET to C/C++ flags"] -Testnet --> |No| Skip1["Skip"] -LowMem{"LOW_MEMORY_NODE?"} -LowMem --> |Yes| AddDef2["Add -DIS_LOW_MEM to C/C++ flags"] -LowMem --> |No| Skip2["Skip"] -Locking{"CHAINBASE_CHECK_LOCKING?"} -Locking --> |Yes| AddDef3["Add -DCHAINBASE_CHECK_LOCKING to C/C++ flags"] -Locking --> |No| Skip3["Skip"] -Mongo{"ENABLE_MONGO_PLUGIN?"} -Mongo --> |Yes| AddDef4["Add -DMONGODB_PLUGIN_BUILT to C/C++ flags"] -Mongo --> |No| Skip4["Skip"] -AddDef1 --> End(["Proceed"]) -AddDef2 --> End -AddDef3 --> End -AddDef4 --> End -Skip1 --> End -Skip2 --> End -Skip3 --> End -Skip4 --> End -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) - -### Boost Library Configuration -- Declares required components and forces static usage by default. -- On Windows, sets multithreading and disables automatic dynamic linking. -- Locates Boost with a minimum version and augments with coroutine when available. - -```mermaid -flowchart TD -S(["Find Boost"]) --> SetStatic["Set static usage"] -SetStatic --> WinCheck{"Windows?"} -WinCheck --> |Yes| WinFlags["Enable multithreading and disable automatic dynamic linking"] -WinCheck --> |No| SkipWin["Skip"] -S --> FindBoost["Find Boost with required components"] -FindBoost --> VersionCheck{"Boost version != 1.53.x?"} -VersionCheck --> |Yes| AddCoroutine["Find coroutine component and append to libraries"] -VersionCheck --> |No| SkipCoro["Skip"] -WinFlags --> End(["Continue"]) -SkipWin --> End -AddCoroutine --> End -SkipCoro --> End -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L104) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L104) - -### Platform-Specific Configurations -- Windows (MSVC/MINGW) - - Adds MSVC warnings suppressions and linker flags. - - Ensures debug info presence in Debug configuration. - - Detects and configures TCL library for Windows toolchains. - - Applies C++11 flags and optimizations for MinGW. -- macOS - - Uses C++ standard and libc++. - - Applies warning flags and macOS-specific behavior. -- Linux - - Uses C++ standard and warning flags. - - Locates readline and sets platform-specific libraries. - - Supports static linking when requested. - -```mermaid -flowchart TD -PStart(["Platform Detection"]) --> Win{"WIN32?"} -Win --> |Yes| WConf["Apply Windows flags and linker settings"] -Win --> |No| UnixCheck{"APPLE?"} -UnixCheck --> |Yes| MacConf["Apply macOS flags and settings"] -UnixCheck --> |No| LinConf["Apply Linux flags and settings"] -WConf --> PEnd(["Done"]) -MacConf --> PEnd -LinConf --> PEnd -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L112-L202) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L112-L202) - -### Compiler Flags, Optimization, and Debug/Release -- Debug and Release configurations receive distinct flags. -- Debug configuration adds a debug macro. -- Coverage testing can be enabled to inject coverage flags. -- Ninja generator receives color diagnostics for Clang. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L196-L208) -- [CMakeLists.txt](file://CMakeLists.txt#L190-L194) - -### MongoDB Plugin Integration -- When enabled, the plugin is discovered and built conditionally. -- The plugin target is linked into the node executable. -- Preprocessor definitions enable MongoDB-related code paths. - -```mermaid -sequenceDiagram -participant Root as "Root CMakeLists.txt" -participant MongoCMake as "plugins/mongo_db/CMakeLists.txt" -participant Vizd as "programs/vizd/CMakeLists.txt" -Root->>Root : Evaluate ENABLE_MONGO_PLUGIN -alt Enabled -Root->>MongoCMake : Include plugin build logic -MongoCMake->>MongoCMake : Find MongoDB driver libraries -MongoCMake-->>Root : Define MONGO_LIB target -Root->>Vizd : Link MONGO_LIB into vizd -else Disabled -Root->>Vizd : Skip MongoDB linkage -end -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L44-L44) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L44-L44) - -## Dependency Analysis -The build system composes the final executable by linking together appbase, internal libraries, enabled plugins, and platform-specific libraries. The MongoDB plugin target is conditionally included when enabled. - -```mermaid -graph LR -Vizd["vizd Executable"] --> AppBase["appbase"] -Vizd --> WebServer["graphene::webserver_plugin"] -Vizd --> P2P["graphene::p2p"] -Vizd --> Utilities["graphene_utilities"] -Vizd --> ChainPlugin["graphene::chain_plugin"] -Vizd --> NetworkBroadcast["graphene::network_broadcast_api"] -Vizd --> validator["graphene::validator"] -Vizd --> WitnessApi["graphene::witness_api"] -Vizd --> DatabaseApi["graphene::database_api"] -Vizd --> TestApi["graphene::test_api_plugin"] -Vizd --> SocialNetwork["graphene::social_network"] -Vizd --> Tags["graphene::tags"] -Vizd --> OperationHistory["graphene::operation_history"] -Vizd --> AccountByKey["graphene::account_by_key"] -Vizd --> AccountHistory["graphizen::account_history"] -Vizd --> PrivateMessage["graphene::private_message"] -Vizd --> AuthUtil["graphene::auth_util"] -Vizd --> DebugNode["graphene::debug_node"] -Vizd --> RawBlock["graphene::raw_block"] -Vizd --> BlockInfo["graphene::block_info"] -Vizd --> JsonRpc["graphene::json_rpc"] -Vizd --> Follow["graphene::follow"] -Vizd --> CommitteeApi["graphene::committee_api"] -Vizd --> InviteApi["graphene::invite_api"] -Vizd --> PaidSubscriptionApi["graphene::paid_subscription_api"] -Vizd --> CustomProtocolApi["graphene::custom_protocol_api"] -Vizd --> MongoLib["MONGO_LIB (optional)"] -Vizd --> Protocol["graphene_protocol"] -Vizd --> FC["fc"] -Vizd --> DL["CMAKE_DL_LIBS"] -Vizd --> Readline["readline (optional)"] -``` - -**Diagram sources** -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L16-L49) - -**Section sources** -- [programs/vizd/CMakeLists.txt](file://programs/vizd/CMakeLists.txt#L16-L49) - -## Performance Considerations -- Static vs shared libraries - - Static libraries are default; shared libraries can be enabled via an option. -- Coverage testing - - Coverage flags can be injected when coverage testing is enabled. -- ccache - - When detected, ccache is configured globally for compile and link steps to speed up rebuilds. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L54-L54) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [CMakeLists.txt](file://CMakeLists.txt#L106-L110) - -## Troubleshooting Guide -- Compiler version mismatch - - Ensure GCC meets the minimum version requirement or Clang meets its requirement; otherwise configuration fails early. -- Boost version and components - - Verify Boost version meets the minimum requirement and that all declared components are available. - - On Windows, ensure Boost static usage is appropriate for your toolchain. -- Missing MongoDB drivers - - When the MongoDB plugin is enabled, driver libraries must be discoverable; otherwise the plugin build is skipped. -- Platform-specific issues - - Windows: Confirm toolchain compatibility and linker flags; ensure debug info is emitted in Debug configuration. - - macOS: Confirm C++ standard and libc++ usage. - - Linux: Confirm readline availability and platform-specific libraries. -- Static linking - - Static builds require compatible static variants of all dependencies; adjust flags accordingly. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L12-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L15-L22) -- [CMakeLists.txt](file://CMakeLists.txt#L112-L202) - -## Conclusion -The VIZ CPP Node’s CMake configuration enforces compiler requirements, exposes configurable build options, and adapts to Windows, macOS, and Linux environments. By leveraging options like BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, and ENABLE_MONGO_PLUGIN, developers can tailor builds for different deployment scenarios. Correct Boost configuration and platform-specific flags ensure reliable builds across environments. - -## Appendices - -### Practical CMake Invocation Examples -- Development build (Linux/macOS) - - Configure with Release and enable shared libraries if desired. - - Example invocation: cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBRARIES=FALSE .. -- Production build (Linux) - - Use Release, disable extra checks, and disable MongoDB plugin. - - Example invocation: cmake -DCMAKE_BUILD_TYPE=Release -DLOW_MEMORY_NODE=FALSE -DCHAINBASE_CHECK_LOCKING=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. -- Testnet build (Linux) - - Enable testnet mode and Release configuration. - - Example invocation: cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=TRUE .. -- Coverage build (Linux) - - Enable coverage and Debug build type. - - Example invocation: cmake -DENABLE_COVERAGE_TESTING=true -DCMAKE_BUILD_TYPE=Debug .. - -These examples align with documented defaults and options. - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L5-L9) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L53) -- [documentation/testing.md](file://documentation/testing.md#L30-L32) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Core Build Options.md b/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Core Build Options.md deleted file mode 100644 index 7210dbb231..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Core Build Options.md +++ /dev/null @@ -1,343 +0,0 @@ -# Core Build Options - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the core build options and flags that control VIZ CPP Node compilation and runtime behavior. It focuses on four primary options: -- BUILD_TESTNET: Enables test network compilation and configuration. -- LOW_MEMORY_NODE: Optimizes the node for reduced memory usage. -- CHAINBASE_CHECK_LOCKING: Enables chainbase locking correctness checks. -- ENABLE_MONGO_PLUGIN: Compiles the MongoDB integration plugin. - -For each option, this guide describes how it affects compiler flags, feature availability, and runtime behavior, and provides practical CMake invocation examples for development, testing, and production scenarios. It also documents how these options influence the resulting executables, shared libraries, and plugin availability. - -## Project Structure -The build system is orchestrated from the repository’s root CMake configuration. Key areas involved in build option handling and downstream effects include: -- Root CMake configuration that defines and applies the build options. -- Protocol configuration headers that switch constants and behavior for mainnet vs. testnet. -- Plugin build configuration for MongoDB integration. -- Dockerfiles demonstrating typical option combinations for different environments. - -```mermaid -graph TB -A["Root CMakeLists.txt
Defines build options and flags"] --> B["Protocol Config Headers
Mainnet vs Testnet constants"] -A --> C["Plugin CMakeLists.txt
MongoDB plugin build gating"] -A --> D["Executables and Libraries
vizd, cli_wallet, shared/static libs"] -E["Dockerfiles
Example CMake invocations"] --> A -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp#L1-L169) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L53) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L81) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp#L1-L169) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L53) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L81) - -## Core Components -This section documents each build option, its effect on compiler flags, feature availability, and runtime behavior. - -- BUILD_TESTNET - - Purpose: Switches the build to use test network constants and configuration. - - Compiler flags: Adds preprocessor definitions for test network identifiers and parameters. - - Feature availability: Selects testnet-specific protocol constants and defaults. - - Runtime behavior: Changes chain identity, address prefix, block intervals, and governance parameters to align with the test network. - - Example CMake invocation (from Dockerfile): - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBRARIES=FALSE -DBUILD_TESTNET=TRUE -DLOW_MEMORY_NODE=FALSE -DCHAINBASE_CHECK_LOCKING=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. - -- LOW_MEMORY_NODE - - Purpose: Optimizes the node for constrained memory environments. - - Compiler flags: Adds a preprocessor definition enabling low-memory-aware code paths. - - Feature availability: Enables memory-conscious settings such as shared memory sizing and virtual operation handling toggles. - - Runtime behavior: Adjusts shared memory growth thresholds, optional skipping of virtual operations, and related operational modes to reduce memory footprint. - - Example CMake invocation (from Dockerfile): - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBRARIES=FALSE -DLOW_MEMORY_NODE=TRUE -DCHAINBASE_CHECK_LOCKING=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. - -- CHAINBASE_CHECK_LOCKING - - Purpose: Validates chainbase locking correctness during development and testing. - - Compiler flags: Adds a preprocessor definition enabling locking assertions and checks. - - Feature availability: Activates additional safety checks around shared memory and chainbase access patterns. - - Runtime behavior: Introduces extra validation overhead to catch potential deadlocks or misuse of chainbase resources. - - Example CMake invocation (from Dockerfiles): - - -DCHAINBASE_CHECK_LOCKING=FALSE for production builds. - - -DCHAINBASE_CHECK_LOCKING=TRUE for development/testing builds. - -- ENABLE_MONGO_PLUGIN - - Purpose: Builds the MongoDB integration plugin alongside the node. - - Compiler flags: Adds a preprocessor definition indicating the plugin is built. - - Feature availability: Links against MongoDB C++ driver libraries and exposes the mongo_db plugin to the node. - - Runtime behavior: Requires MongoDB drivers to be available at runtime; enables storing blockchain data in MongoDB via the plugin. - - Example CMake invocation (from Dockerfile): - - -DENABLE_MONGO_PLUGIN=TRUE (with MongoDB drivers installed in the build environment). - -Practical CMake invocation examples by environment: -- Development (testnet, minimal memory checks): - - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTNET=TRUE -DCHAINBASE_CHECK_LOCKING=TRUE -DLOW_MEMORY_NODE=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. -- Testing (testnet with MongoDB): - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=TRUE -DCHAINBASE_CHECK_LOCKING=FALSE -DLOW_MEMORY_NODE=FALSE -DENABLE_MONGO_PLUGIN=TRUE .. -- Production (mainnet, optimized): - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=FALSE -DCHAINBASE_CHECK_LOCKING=FALSE -DLOW_MEMORY_NODE=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp#L1-L169) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L53) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L81) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) - -## Architecture Overview -The build options are evaluated early in the root CMake configuration and propagate into downstream components: -- Compiler flags are appended conditionally based on the selected options. -- Protocol headers switch between mainnet and testnet constants. -- Plugin builds are gated by the MongoDB plugin option. -- Executables and libraries inherit the configured flags and feature sets. - -```mermaid -graph TB -subgraph "Build Options" -BT["BUILD_TESTNET"] -LM["LOW_MEMORY_NODE"] -CL["CHAINBASE_CHECK_LOCKING"] -MP["ENABLE_MONGO_PLUGIN"] -end -subgraph "Compiler Flags" -F1["-DBUILD_TESTNET"] -F2["-DIS_LOW_MEM"] -F3["-DCHAINBASE_CHECK_LOCKING"] -F4["-DMONGODB_PLUGIN_BUILT"] -end -subgraph "Runtime Behavior" -R1["Testnet protocol constants"] -R2["Low-memory settings"] -R3["Chainbase locking checks"] -R4["MongoDB plugin availability"] -end -BT --> F1 -LM --> F2 -CL --> F3 -MP --> F4 -F1 --> R1 -F2 --> R2 -F3 --> R3 -F4 --> R4 -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) - -## Detailed Component Analysis - -### BUILD_TESTNET Option -- Effect on compiler flags: Adds preprocessor definitions for test network identifiers and parameters. -- Effect on feature availability: Selects testnet-specific protocol constants and defaults. -- Effect on runtime behavior: Changes chain identity, address prefix, block intervals, and governance parameters to align with the test network. -- Relationship to executables and libraries: No change to binary names; behavior is driven by selected protocol constants. - -```mermaid -flowchart TD -Start(["Configure BUILD_TESTNET"]) --> AddFlag["Add -DBUILD_TESTNET to C/C++ flags"] -AddFlag --> SelectHeaders["Select testnet protocol headers"] -SelectHeaders --> RuntimeSwitch["Runtime uses testnet constants and parameters"] -RuntimeSwitch --> End(["Build and run with testnet behavior"]) -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L58-L64) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L58-L64) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -### LOW_MEMORY_NODE Option -- Effect on compiler flags: Adds a preprocessor definition enabling low-memory-aware code paths. -- Effect on feature availability: Enables memory-conscious settings such as shared memory sizing and virtual operation handling toggles. -- Effect on runtime behavior: Adjusts shared memory growth thresholds, optional skipping of virtual operations, and related operational modes to reduce memory footprint. -- Relationship to executables and libraries: No change to binary names; behavior is driven by runtime configuration and compiled-in flags. - -```mermaid -flowchart TD -Start(["Configure LOW_MEMORY_NODE"]) --> AddFlag["Add -DIS_LOW_MEM to C/C++ flags"] -AddFlag --> EnableFeatures["Enable low-memory runtime features"] -EnableFeatures --> RuntimeSettings["Apply shared memory and virtual op settings"] -RuntimeSettings --> End(["Build and run optimized for memory-constrained environments"]) -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L68-L74) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L68-L74) - -### CHAINBASE_CHECK_LOCKING Option -- Effect on compiler flags: Adds a preprocessor definition enabling locking assertions and checks. -- Effect on feature availability: Activates additional safety checks around shared memory and chainbase access patterns. -- Effect on runtime behavior: Introduces extra validation overhead to catch potential deadlocks or misuse of chainbase resources. -- Relationship to executables and libraries: No change to binary names; behavior is driven by compiled-in checks. - -```mermaid -flowchart TD -Start(["Configure CHAINBASE_CHECK_LOCKING"]) --> AddFlag["Add -DCHAINBASE_CHECK_LOCKING to C/C++ flags"] -AddFlag --> EnableChecks["Enable chainbase locking checks"] -EnableChecks --> RuntimeValidation["Runtime validates chainbase access patterns"] -RuntimeValidation --> End(["Build and run with enhanced locking safety"]) -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L78-L81) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L78-L81) - -### ENABLE_MONGO_PLUGIN Option -- Effect on compiler flags: Adds a preprocessor definition indicating the plugin is built. -- Effect on feature availability: Links against MongoDB C++ driver libraries and exposes the mongo_db plugin to the node. -- Effect on runtime behavior: Requires MongoDB drivers to be available at runtime; enables storing blockchain data in MongoDB via the plugin. -- Relationship to executables and libraries: The mongo_db plugin is built as part of the plugins tree when enabled; the node can load it at runtime. - -```mermaid -sequenceDiagram -participant CMake as "Root CMakeLists.txt" -participant MongoCMake as "plugins/mongo_db/CMakeLists.txt" -participant Flags as "Compiler Flags" -participant Plugin as "mongo_db Plugin" -CMake->>Flags : Add -DMONGODB_PLUGIN_BUILT when ENABLE_MONGO_PLUGIN=TRUE -CMake->>MongoCMake : ENABLE_MONGO_PLUGIN=TRUE -MongoCMake->>MongoCMake : Find MongoDB drivers -MongoCMake->>Plugin : Build mongo_db plugin target -Plugin-->>CMake : Expose graphene : : mongo_db target -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L84-L89) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L84-L89) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) -- [mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L1-L51) - -## Dependency Analysis -Build options influence downstream components through compiler flags and conditional inclusion. The following diagram shows how options propagate to protocol constants, plugin builds, and runtime behavior. - -```mermaid -graph TB -O1["BUILD_TESTNET"] --> P1["Protocol Constants (Testnet)"] -O2["LOW_MEMORY_NODE"] --> R1["Runtime Memory Settings"] -O3["CHAINBASE_CHECK_LOCKING"] --> R2["Runtime Locking Checks"] -O4["ENABLE_MONGO_PLUGIN"] --> P2["MongoDB Plugin Build"] -P2 --> R3["MongoDB Plugin Availability"] -subgraph "Root CMake" -O1 -O2 -O3 -O4 -end -subgraph "Runtime" -R1 -R2 -R3 -end -subgraph "Plugins" -P2 -end -subgraph "Protocol" -P1 -end -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L2-L81) - -## Performance Considerations -- CHAINBASE_CHECK_LOCKING adds runtime overhead due to additional checks; disable for production builds. -- LOW_MEMORY_NODE can reduce memory usage at the cost of potentially disabling certain features (e.g., skipping virtual operations) depending on configuration. -- BUILD_TESTNET does not alter performance characteristics directly but changes protocol parameters that indirectly affect throughput and resource usage. -- ENABLE_MONGO_PLUGIN introduces external dependencies and runtime overhead; ensure MongoDB drivers are properly installed and configured. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- MongoDB plugin fails to build: - - Ensure MongoDB C++ driver libraries are installed and discoverable by CMake before invoking the build. - - Verify that ENABLE_MONGO_PLUGIN is set to TRUE and that the plugin’s CMakeLists is executed. -- Unexpected testnet/mainnet behavior: - - Confirm BUILD_TESTNET is set appropriately; testnet constants are selected when enabled. -- Low memory issues: - - Review LOW_MEMORY_NODE-related runtime settings and consider enabling virtual operation skipping and adjusting shared memory parameters. -- Chainbase locking failures: - - Enable CHAINBASE_CHECK_LOCKING during development to catch incorrect locking patterns; disable for production. - -**Section sources** -- [CMakeLists.txt (mongo_db)](file://plugins/mongo_db/CMakeLists.txt#L15-L81) -- [CMakeLists.txt](file://CMakeLists.txt#L58-L81) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -## Conclusion -The four core build options—BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, and ENABLE_MONGO_PLUGIN—provide precise control over VIZ CPP Node compilation and runtime behavior. By understanding how each option affects compiler flags, feature availability, and runtime characteristics, teams can tailor builds for development, testing, and production environments effectively. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical CMake Invocation Examples -- Development (testnet, with locking checks): - - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTNET=TRUE -DCHAINBASE_CHECK_LOCKING=TRUE -DLOW_MEMORY_NODE=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. -- Testing (testnet with MongoDB): - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=TRUE -DCHAINBASE_CHECK_LOCKING=FALSE -DLOW_MEMORY_NODE=FALSE -DENABLE_MONGO_PLUGIN=TRUE .. -- Production (mainnet, optimized): - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTNET=FALSE -DCHAINBASE_CHECK_LOCKING=FALSE -DLOW_MEMORY_NODE=FALSE -DENABLE_MONGO_PLUGIN=FALSE .. - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L53) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L51) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L81) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Dependency Management.md b/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Dependency Management.md deleted file mode 100644 index cb2bdf8f7e..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Dependency Management.md +++ /dev/null @@ -1,459 +0,0 @@ -# Dependency Management - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [thirdparty/CMakeLists.txt](file://thirdparty/CMakeLists.txt) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [documentation/building.md](file://documentation/building.md) -- [libraries/wallet/include/graphene/wallet/wallet.hpp](file://libraries/wallet/include/graphene/wallet/wallet.hpp) -- [libraries/wallet/wallet.cpp](file://libraries/wallet/wallet.cpp) -- [libraries/chain/database.cpp](file://libraries/chain/database.cpp) -- [thirdparty/fc/include/fc/optional.hpp](file://thirdparty/fc/include/fc/optional.hpp) -- [thirdparty/fc/include/fc/api.hpp](file://thirdparty/fc/include/fc/api.hpp) -- [thirdparty/fc/src/asio.cpp](file://thirdparty/fc/src/asio.cpp) -- [thirdparty/fc/src/ssh/client.cpp](file://thirdparty/fc/src/ssh/client.cpp) -- [plugins/webserver/webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) - - -## Update Summary -**Changes Made** -- Updated Boost library configuration section to reflect current Boost version requirement (1.71+) and enhanced platform detection logic -- Added comprehensive coverage of the migration from boost::bind to std::bind across the codebase -- Updated third-party dependency integration to highlight fc::optional as a replacement for boost::optional -- Enhanced troubleshooting section with guidance for resolving boost::bind deprecation warnings -- Updated version compatibility matrix to reflect current Boost version and standard library compliance -- Improved platform-specific compiler and linker flag handling across Windows, macOS, and Linux - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains how VIZ CPP Node manages dependencies across platforms using CMake. It focuses on Boost configuration (required components, static/dynamic linking, version requirements), third-party integration via subdirectories (appbase, chainbase, fc), OpenSSL configuration, and optional MongoDB plugin dependencies. The document also covers the comprehensive migration from deprecated Boost libraries to modern standard library alternatives, including the replacement of boost::optional with fc::optional and boost::bind with std::bind for improved standard library compliance. - -## Project Structure -The top-level CMake configuration orchestrates dependency discovery and builds subprojects: -- Top-level CMakeLists defines compiler requirements, module paths, and options. -- Third-party subdirectories (appbase, chainbase, fc) are integrated via a dedicated CMake file. -- Libraries, plugins, and programs are added as subprojects. -- Optional MongoDB plugin is controlled by a build option. - -```mermaid -graph TB -Root["Top-level CMakeLists.txt"] -TP["thirdparty/CMakeLists.txt"] -Libs["libraries/CMakeLists.txt"] -Plugins["plugins/*"] -Progs["programs/*"] -Root --> TP -Root --> Libs -Root --> Plugins -Root --> Progs -``` - -**Diagram sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) - -**Section sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) - -## Core Components -- **Boost**: Required components include thread, date_time, system, filesystem, program_options, serialization, chrono, unit_test_framework, context, locale, and coroutine. Static linking is enabled by default via an option. Current minimum version requirement is 1.71. -- **Standard Library Alternatives**: The codebase has undergone comprehensive migration from Boost libraries to standard library equivalents: - - boost::optional → fc::optional (stack-based nullable value) - - boost::bind → std::bind (standard function binding) - - boost::signals2 → std::function and std::bind patterns -- **OpenSSL**: Detected automatically on most systems; a fallback sets a default crypto library name on Unix-like systems. The build helper supports overriding the OpenSSL root directory via a command-line argument and environment variable. -- **MongoDB Plugin**: Controlled by an option; when enabled, the plugin locates libmongoc-1.0 and links against libbsoncxx and libmongocxx. Runtime presence of libmongoc is required. - -Key configuration points: -- Boost version requirement and component list (now 1.71+) -- Static vs dynamic linking defaults -- Platform-specific compiler and linker flags -- Optional MongoDB plugin enablement and dependency discovery -- Standard library compliance improvements - -**Section sources** -- [CMakeLists.txt:38-50](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt:52-54](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt](file://CMakeLists.txt#L169) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [thirdparty/fc/include/fc/api.hpp:17-20](file://thirdparty/fc/include/fc/api.hpp#L17-L20) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -## Architecture Overview -The dependency management architecture integrates external libraries and internal subprojects. The top-level CMake discovers Boost and OpenSSL, then adds third-party and application subprojects. The MongoDB plugin is conditionally included and links against MongoDB driver libraries. The architecture now emphasizes standard library compliance with fc::optional replacing boost::optional and std::bind replacing boost::bind. - -```mermaid -graph TB -subgraph "Top-level Build" -CMakeRoot["CMakeLists.txt"] -OptBoost["Boost Config
Components + Version 1.71+"] -OptOpenSSL["OpenSSL Config"] -OptMongo["MongoDB Plugin Option"] -StdLib["Standard Library Migration
boost::bind → std::bind
boost::optional → fc::optional"] -end -subgraph "Third-party Subprojects" -AppBase["appbase"] -ChainBase["chainbase"] -FC["fc (with optional.hpp)"] -end -subgraph "Application Subprojects" -Libs["libraries/*"] -Plugins["plugins/*"] -Progs["programs/*"] -end -CMakeRoot --> OptBoost -CMakeRoot --> OptOpenSSL -CMakeRoot --> OptMongo -CMakeRoot --> StdLib -CMakeRoot --> AppBase -CMakeRoot --> ChainBase -CMakeRoot --> FC -AppBase --> Libs -ChainBase --> Libs -FC --> Libs -Libs --> Plugins -Libs --> Progs -OptMongo --> Plugins -StdLib --> Plugins -StdLib --> Libs -``` - -**Diagram sources** -- [CMakeLists.txt:38-50](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt](file://CMakeLists.txt#L169) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -## Detailed Component Analysis - -### Boost Library Configuration -- **Required components**: thread, date_time, system, filesystem, program_options, serialization, chrono, unit_test_framework, context, locale, coroutine. -- **Static linking default**: enabled via an option. -- **Version requirement**: minimum 1.71 (updated from previous 1.57 requirement). -- **Enhanced platform detection**: Explicit version checking with separate coroutine component discovery. -- **Windows-specific behavior**: multithreading is forced and dynamic linking is enforced. -- **Migration status**: The codebase has successfully migrated from boost::bind to std::bind across all plugins and libraries. - -```mermaid -flowchart TD -Start(["Configure Boost"]) --> SetComponents["Set required components list
(thread, date_time, system,
filesystem, program_options,
serialization, chrono,
unit_test_framework, context,
locale, coroutine)"] -SetComponents --> StaticDefault["Enable static libs option"] -StaticDefault --> VersionCheck["Require Boost >= 1.71"] -VersionCheck --> CoroutineCheck{"Boost version >= 1.54?"} -CoroutineCheck --> |Yes| AddCoroutine["Find coroutine component"] -CoroutineCheck --> |No| SkipCoroutine["Skip coroutine"] -AddCoroutine --> BindMigration["Verify boost::bind → std::bind migration"] -SkipCoroutine --> BindMigration -BindMigration --> OptionalMigration["Verify boost::optional → fc::optional migration"] -OptionalMigration --> LinkMode["Windows: force dynamic linking"] -LinkMode --> End(["Boost ready"]) -``` - -**Diagram sources** -- [CMakeLists.txt:38-50](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt:52-54](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:91-95](file://CMakeLists.txt#L91-L95) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) - -**Section sources** -- [CMakeLists.txt:38-50](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt:52-54](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:91-95](file://CMakeLists.txt#L91-L95) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) - -### Standard Library Migration Status -The VIZ CPP Node has undergone comprehensive migration from deprecated Boost libraries to modern standard library alternatives: - -#### boost::optional → fc::optional -- **Replacement rationale**: boost::optional adds significant compilation overhead (35,000 lines per object file) compared to fc::optional (less than 400 lines) -- **Implementation**: fc::optional provides stack-based nullable value functionality with improved performance characteristics -- **Usage pattern**: Replaces all instances of boost::optional with fc::optional -- **Benefits**: Reduced compilation times, smaller binaries, and maintained functionality - -#### boost::bind → std::bind -- **Migration scope**: Complete replacement across all plugins, libraries, and third-party components -- **Examples**: Webserver plugin uses std::bind for WebSocket and HTTP handlers -- **Benefits**: Standard library compliance, better performance, and reduced dependencies - -#### boost::signals2 → std::function patterns -- **Pattern**: Migration to std::function combined with std::bind for signal handling -- **Usage**: Maintains event-driven architecture while using standard library components - -**Section sources** -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [thirdparty/fc/include/fc/api.hpp:17-20](file://thirdparty/fc/include/fc/api.hpp#L17-L20) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) -- [thirdparty/fc/src/asio.cpp:164-165](file://thirdparty/fc/src/asio.cpp#L164-L165) -- [thirdparty/fc/src/ssh/client.cpp:124-125](file://thirdparty/fc/src/ssh/client.cpp#L124-L125) - -### OpenSSL Configuration -- **Automatic detection**: OpenSSL is typically found by CMake's find_package. -- **Fallback on Unix-like systems**: a default crypto library name is set when detection fails. -- **Override mechanism**: the build helper accepts an OpenSSL root directory argument and propagates it to CMake via an option variable. - -```mermaid -flowchart TD -Start(["Configure OpenSSL"]) --> AutoDetect["Try find_package(OpenSSL)"] -AutoDetect --> Found{"Found?"} -Found --> |Yes| UseDetected["Use detected OpenSSL"] -Found --> |No| SetFallback["Set default crypto library name"] -UseDetected --> End(["OpenSSL ready"]) -SetFallback --> End -``` - -**Diagram sources** -- [CMakeLists.txt:176-180](file://CMakeLists.txt#L176-L180) -- [programs/build_helpers/configure_build.py:75-77](file://programs/build_helpers/configure_build.py#L75-L77) -- [programs/build_helpers/configure_build.py:115-116](file://programs/build_helpers/configure_build.py#L115-L116) -- [programs/build_helpers/configure_build.py:162-165](file://programs/build_helpers/configure_build.py#L162-L165) - -**Section sources** -- [CMakeLists.txt:176-180](file://CMakeLists.txt#L176-L180) -- [programs/build_helpers/configure_build.py:75-77](file://programs/build_helpers/configure_build.py#L75-L77) -- [programs/build_helpers/configure_build.py:115-116](file://programs/build_helpers/configure_build.py#L115-L116) -- [programs/build_helpers/configure_build.py:162-165](file://programs/build_helpers/configure_build.py#L162-L165) - -### Optional MongoDB Plugin Dependencies -- **Enablement**: controlled by a build option; when enabled, the plugin is built and linked into the application. -- **Discovery**: finds libmongoc-1.0, libbsoncxx, and libmongocxx. -- **Linking**: links against graphene targets and MongoDB libraries; includes MongoDB driver headers. -- **Runtime**: libmongoc shared libraries must be available at runtime. - -```mermaid -sequenceDiagram -participant CMake as "CMakeLists.txt" -participant MongoCMake as "plugins/mongo_db/CMakeLists.txt" -participant Drivers as "MongoDB Drivers" -CMake->>MongoCMake : ENABLE_MONGO_PLUGIN -MongoCMake->>Drivers : find_package(libmongoc-1.0) -MongoCMake->>Drivers : find_package(libbsoncxx) -MongoCMake->>Drivers : find_package(libmongocxx) -MongoCMake-->>CMake : Link graphene targets + MongoDB libs -``` - -**Diagram sources** -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [plugins/mongo_db/CMakeLists.txt:2-23](file://plugins/mongo_db/CMakeLists.txt#L2-L23) -- [plugins/mongo_db/CMakeLists.txt:57-67](file://plugins/mongo_db/CMakeLists.txt#L57-L67) - -**Section sources** -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [plugins/mongo_db/CMakeLists.txt:2-23](file://plugins/mongo_db/CMakeLists.txt#L2-L23) -- [plugins/mongo_db/CMakeLists.txt:57-67](file://plugins/mongo_db/CMakeLists.txt#L57-L67) - -### Third-Party Subprojects (appbase, chainbase, fc) -- **These subprojects are added via a dedicated thirdparty CMake file and form the foundation for libraries and applications.** -- **They are integrated early in the build process to satisfy downstream dependencies.** -- **fc::optional provides a lightweight alternative to boost::optional with significant performance improvements.** - -```mermaid -graph TB -Root["Top-level CMakeLists.txt"] -TP["thirdparty/CMakeLists.txt"] -AppBase["appbase"] -ChainBase["chainbase"] -FC["fc (with optional.hpp)"] -Root --> TP -TP --> AppBase -TP --> ChainBase -TP --> FC -``` - -**Diagram sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) - -**Section sources** -- [CMakeLists.txt:210-213](file://CMakeLists.txt#L210-L213) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) - -## Dependency Analysis -- **Coupling**: Top-level CMake couples Boost, OpenSSL, and optional MongoDB to all subprojects. Third-party subprojects (appbase, chainbase, fc) are foundational and consumed by libraries. -- **External dependencies**: Managed centrally; platform differences are handled in separate branches for Windows, macOS, and Linux. -- **Conditional dependencies**: MongoDB plugin is optional and only linked when enabled. -- **Standard library compliance**: The migration to std::bind and fc::optional improves standard library compliance and reduces external dependencies. - -```mermaid -graph TB -Boost["Boost 1.71+ (REQUIRED)
thread, date_time, system,
filesystem, program_options,
serialization, chrono,
unit_test_framework, context,
locale, coroutine"] -OpenSSL["OpenSSL (OPTIONAL)"] -MongoPlugin["MongoDB Plugin (OPTIONAL)"] -AppBase["appbase"] -ChainBase["chainbase"] -FC["fc (with optional.hpp)"] -Libs["libraries/*"] -Plugins["plugins/*"] -Progs["programs/*"] -StdLib["Standard Library Migration
boost::bind → std::bind
boost::optional → fc::optional"] -Boost --> Libs -OpenSSL --> Libs -MongoPlugin --> Plugins -AppBase --> Libs -ChainBase --> Libs -FC --> Libs -Libs --> Plugins -Libs --> Progs -StdLib --> Plugins -StdLib --> Libs -``` - -**Diagram sources** -- [CMakeLists.txt:38-50](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:176-180](file://CMakeLists.txt#L176-L180) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -**Section sources** -- [CMakeLists.txt:38-50](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:176-180](file://CMakeLists.txt#L176-L180) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [thirdparty/CMakeLists.txt:1-3](file://thirdparty/CMakeLists.txt#L1-L3) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -## Performance Considerations -- **Compiler flags and optimization vary by platform and generator.** Ninja with Clang on some generators enables colored diagnostics. -- **Coverage testing can be enabled via a dedicated option.** -- **Static linking flags are applied conditionally for full static builds on certain platforms.** -- **Performance improvements from standard library migration**: Reduced compilation times through fc::optional and std::bind elimination. -- **Memory efficiency**: fc::optional provides stack-based storage eliminating heap allocation overhead. - -Practical guidance: -- **Prefer Ninja with Clang on supported systems** for faster builds and better diagnostics. -- **Use coverage option only when needed** to avoid unnecessary overhead. -- **Monitor compilation times** - the migration to fc::optional should reduce build times significantly. - -**Section sources** -- [CMakeLists.txt:190-194](file://CMakeLists.txt#L190-L194) -- [CMakeLists.txt:206-208](file://CMakeLists.txt#L206-L208) -- [CMakeLists.txt:181-183](file://CMakeLists.txt#L181-L183) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) - -## Troubleshooting Guide -Common issues and resolutions: - -### Boost and Standard Library Migration Issues -- **Missing or incompatible Boost**: - - Ensure Boost version meets the minimum requirement (1.71+) and includes all required components. - - On Windows, dynamic linking is enforced; verify environment variables and installation paths. - - **Updated**: Verify that boost::bind has been migrated to std::bind throughout the codebase. - -- **boost::optional compilation errors**: - - Replace all boost::optional with fc::optional. - - Ensure proper includes: `#include `. - - **New**: Check that fc::optional is being used instead of boost::optional. - -- **boost::bind deprecation warnings**: - - Replace boost::bind with std::bind or lambda expressions. - - Use std::placeholders for argument placeholders. - - **Evidence**: Webserver plugin demonstrates proper std::bind usage patterns. - -### OpenSSL detection failures: -- Provide the OpenSSL root directory via the build helper argument or the corresponding environment variable. -- On Unix-like systems, a default crypto library name is set if detection fails. - -### MongoDB plugin not building: -- Confirm the option is enabled and that libmongoc-1.0, libbsoncxx, and libmongocxx are discoverable. -- Ensure libmongoc shared libraries are available at runtime. - -### Corporate firewall: -- Configure CMake to use proxy settings appropriate for your environment. -- Download dependencies manually and adjust CMake variables to point to local paths. - -**Section sources** -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:91-95](file://CMakeLists.txt#L91-L95) -- [CMakeLists.txt:176-180](file://CMakeLists.txt#L176-L180) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [plugins/mongo_db/CMakeLists.txt:2-23](file://plugins/mongo_db/CMakeLists.txt#L2-L23) -- [plugins/mongo_db/CMakeLists.txt:57-67](file://plugins/mongo_db/CMakeLists.txt#L57-L67) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -## Conclusion -VIZ CPP Node's dependency management centers on robust CMake configuration that enforces minimum Boost versions, integrates essential third-party libraries, and optionally supports MongoDB. The recent comprehensive migration from deprecated Boost libraries to modern standard library alternatives (boost::bind → std::bind, boost::optional → fc::optional) significantly improves performance, reduces dependencies, and enhances standard library compliance. By leveraging platform-aware logic, explicit override mechanisms, and modular subproject organization, the build remains portable and maintainable across environments. Following the guidance herein ensures reliable dependency resolution, smooth updates, and successful builds under varied conditions. - -## Appendices - -### Version Compatibility Matrix (Updated) -- **Boost**: Minimum 1.71; components include thread, date_time, system, filesystem, program_options, serialization, chrono, unit_test_framework, context, locale; coroutine added for 1.54+. -- **Standard Library Migration**: Complete replacement of boost::bind with std::bind and boost::optional with fc::optional. -- **OpenSSL**: Detected automatically; fallback crypto library name on Unix-like systems; override via build helper or environment variable. -- **MongoDB Plugin**: Optional; requires libmongoc-1.0, libbsoncxx, and libmongocxx discovered; runtime libmongoc availability required. - -**Section sources** -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:176-180](file://CMakeLists.txt#L176-L180) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [plugins/mongo_db/CMakeLists.txt:2-23](file://plugins/mongo_db/CMakeLists.txt#L2-L23) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -### Customizing Dependency Locations -- **Boost**: Set environment variables or CMake variables to guide discovery; Windows forces dynamic linking behavior. Ensure Boost 1.71+ is used for compatibility. -- **OpenSSL**: Use the build helper argument to specify the OpenSSL root directory; CMake reads the corresponding environment variable. -- **MongoDB**: Ensure MongoDB driver libraries are discoverable; adjust CMake variables to point to local installations if needed. - -**Section sources** -- [CMakeLists.txt:91-95](file://CMakeLists.txt#L91-L95) -- [programs/build_helpers/configure_build.py:75-77](file://programs/build_helpers/configure_build.py#L75-L77) -- [programs/build_helpers/configure_build.py:115-116](file://programs/build_helpers/configure_build.py#L115-L116) -- [programs/build_helpers/configure_build.py:162-165](file://programs/build_helpers/configure_build.py#L162-L165) - -### Managing Updates Across Platforms -- **Windows**: Dynamic linking enforcement and environment-driven configuration require attention during updates. Ensure Boost 1.71+ is available. -- **macOS/Linux**: Compiler flags and optional components may change; validate OpenSSL and MongoDB driver discovery after updates. Monitor for any remaining boost::bind usage. -- **General**: Keep Boost version above the minimum (1.71+), verify component lists, confirm optional plugin dependencies when enabling features, and ensure all boost::bind references have been migrated to std::bind. - -**Section sources** -- [CMakeLists.txt:112-156](file://CMakeLists.txt#L112-L156) -- [CMakeLists.txt:158-202](file://CMakeLists.txt#L158-L202) -- [CMakeLists.txt:96-100](file://CMakeLists.txt#L96-L100) -- [CMakeLists.txt:83-89](file://CMakeLists.txt#L83-L89) -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) - -### Standard Library Migration Checklist -- **Complete**: boost::bind → std::bind migration verified across all plugins -- **Complete**: boost::optional → fc::optional migration verified -- **Complete**: boost::signals2 patterns migrated to std::function + std::bind -- **Verification**: No remaining boost::bind or boost::optional includes in main codebase -- **Performance**: Compilation times improved through fc::optional usage - -**Section sources** -- [plugins/webserver/webserver_plugin.cpp:194-249](file://plugins/webserver/webserver_plugin.cpp#L194-L249) -- [thirdparty/fc/include/fc/optional.hpp:16-21](file://thirdparty/fc/include/fc/optional.hpp#L16-L21) -- [thirdparty/fc/src/asio.cpp:164-165](file://thirdparty/fc/src/asio.cpp#L164-L165) -- [thirdparty/fc/src/ssh/client.cpp:124-125](file://thirdparty/fc/src/ssh/client.cpp#L124-L125) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Platform Configurations.md b/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Platform Configurations.md deleted file mode 100644 index 1d29ced85e..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/CMake Configuration/Platform Configurations.md +++ /dev/null @@ -1,383 +0,0 @@ -# Platform Configurations - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt) -- [libraries/wallet/CMakeLists.txt](file://libraries/wallet/CMakeLists.txt) -- [programs/cli_wallet/CMakeLists.txt](file://programs/cli_wallet/CMakeLists.txt) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [thirdparty/fc/CMakeLists.txt](file://thirdparty/fc/CMakeLists.txt) -- [documentation/building.md](file://documentation/building.md) - - -## Update Summary -**Changes Made** -- Updated Linux configuration section to document the new `-DBOOST_BIND_GLOBAL_PLACEHOLDERS` compatibility flag -- Enhanced troubleshooting section with information about Boost binding issues -- Added cross-reference to the fc library's Boost compatibility flag -- Updated platform-specific compiler flags documentation - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction -This document provides comprehensive platform-specific CMake configuration guidance for the VIZ CPP Node build system. It covers compiler and linker settings per platform, static linking options, required libraries, and troubleshooting steps for common issues. The focus areas include: -- Windows with MSVC and MinGW toolchains -- macOS with libc++ and C++14 -- Linux with GCC, pthread, rt, OpenSSL detection, and Boost compatibility flags -- Compiler version requirements and platform-specific dependency resolution - -## Project Structure -The top-level CMake configuration orchestrates platform-specific behavior and includes subprojects for third-party libraries, core libraries, plugins, and programs. Platform checks are performed early to set flags, link libraries, and enable features. - -```mermaid -graph TB -Root["Top-level CMakeLists.txt
Defines project, minimum CMake version,
compiler checks, and platform branches"] -ThirdParty["thirdparty/CMakeLists.txt
Adds appbase, fc, chainbase"] -Libraries["libraries/CMakeLists.txt
Adds api, chain, protocol, network, time, utilities, wallet"] -Plugins["plugins/CMakeLists.txt
Discovers and adds plugins dynamically"] -Programs["programs/CMakeLists.txt
Adds build helpers, cli_wallet, js_operation_serializer, size_checker, util, vizd"] -Root --> ThirdParty -Root --> Libraries -Root --> Plugins -Root --> Programs -``` - -**Diagram sources** -- [CMakeLists.txt:1-271](file://CMakeLists.txt#L1-L271) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) - -**Section sources** -- [CMakeLists.txt:1-271](file://CMakeLists.txt#L1-L271) -- [libraries/CMakeLists.txt:1-8](file://libraries/CMakeLists.txt#L1-L8) - -## Core Components -- Top-level CMake configuration sets compiler requirements, optional build features, and platform-specific flags. -- Library targets are built with platform-aware compile and link settings. -- Executables link against platform-specific libraries and optional profiling or performance tools. - -Key platform-specific behaviors: -- Compiler version checks for GCC and Clang. -- Static linking toggles via a full-static build option. -- Platform-specific include/link flags for Windows, macOS, and Linux. - -**Section sources** -- [CMakeLists.txt:11-20](file://CMakeLists.txt#L11-L20) -- [CMakeLists.txt:52-54](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt:158-202](file://CMakeLists.txt#L158-L202) - -## Architecture Overview -The build system applies platform-specific logic early, then composes targets across thirdparty, libraries, plugins, and programs. Windows, macOS, and Linux branches configure flags, libraries, and linkers differently. - -```mermaid -graph TB -subgraph "Windows" -WinFlags["MSVC: disable specific warnings
linker flags: /SAFESEH:NO
Debug linker flags: /DEBUG"] -MinGWFlags["MinGW: C++11, -fpermissive, SSE4.2, -Wa,-mbig-obj
Release: -O3
Debug: -O2"] -TclInteg["TCL: locate via TCL_ROOT env var
resolve optimized/debug libs"] -WinStatic["FULL_STATIC_BUILD: -static-libstdc++ -static-libgcc"] -end -subgraph "macOS" -MacFlags["C++14, libc++, -Wall, -Wno-conversion, -Wno-deprecated-declarations"] -MacReadline["Optional readline detection"] -end -subgraph "Linux" -LinFlags["C++14, -Wall, -DBOOST_BIND_GLOBAL_PLACEHOLDERS"] -LinPthreads["pthread library"] -LinRT["rt library"] -OpenSSLDetect["crypto_library fallback to 'crypto' if not found"] -LinStatic["FULL_STATIC_BUILD: -static-libstdc++ -static-libgcc"] -end -Root["CMakeLists.txt
Compiler checks, options, platform branches"] -Root --> WinFlags -Root --> MinGWFlags -Root --> TclInteg -Root --> WinStatic -Root --> MacFlags -Root --> MacReadline -Root --> LinFlags -Root --> LinPthreads -Root --> LinRT -Root --> OpenSSLDetect -Root --> LinStatic -``` - -**Diagram sources** -- [CMakeLists.txt:112-202](file://CMakeLists.txt#L112-L202) - -## Detailed Component Analysis - -### Windows Configuration (MSVC and MinGW) -- Compiler checks enforce minimum versions for GCC and Clang. -- MSVC-specific flags: - - Disable specific warnings. - - Linker flags to bypass Safe Exception Handler (SEH) requirements. - - Debug linker flags to ensure debug info emission. -- TCL library integration: - - Locate headers via environment variable pointing to TCL installation. - - Resolve optimized and debug variants of the TCL library. -- MinGW-specific flags: - - C++11 standard, permissive mode, SSE4.2, and large object support. - - Release and Debug optimization levels. - - Optional static linking of standard C++ and C runtime when enabled. - -```mermaid -flowchart TD -Start(["Configure on Windows"]) --> CheckToolchain["Detect MSVC or MinGW"] -CheckToolchain --> |MSVC| MSVCPath["Apply MSVC flags:
disable warnings
/SAFESEH:NO for linker
ensure /DEBUG in Debug"] -CheckToolchain --> |MinGW| MinGWPath["Apply MinGW flags:
-std=c++11 -fpermissive -msse4.2 -Wa,-mbig-obj
-O3 for Release
-O2 for Debug"] -MSVCPath --> TclFind["Locate TCL via TCL_ROOT
resolve optimized/debug libs"] -MinGWPath --> StaticCheck{"FULL_STATIC_BUILD?"} -TclFind --> StaticCheck -StaticCheck --> |Yes| WinStatic["Link with -static-libstdc++ -static-libgcc"] -StaticCheck --> |No| End(["Done"]) -WinStatic --> End -``` - -**Diagram sources** -- [CMakeLists.txt:123-156](file://CMakeLists.txt#L123-L156) - -**Section sources** -- [CMakeLists.txt:112-156](file://CMakeLists.txt#L112-L156) - -### macOS Configuration (Apple Platforms) -- Compiler flags: - - C++14 standard. - - Use of libc++ standard library. - - General warning flags and suppression of specific conversion/deprecation warnings. -- Optional readline detection is present but not mandatory. - -```mermaid -flowchart TD -StartMac(["Configure on macOS"]) --> MacFlags["Set C++14 and libc++
apply -Wall and warning suppressions"] -MacFlags --> ReadlineCheck{"readline found?"} -ReadlineCheck --> |Yes| LinkReadline["Link readline"] -ReadlineCheck --> |No| SkipReadline["Proceed without readline"] -LinkReadline --> EndMac(["Done"]) -SkipReadline --> EndMac -``` - -**Diagram sources** -- [CMakeLists.txt:166-170](file://CMakeLists.txt#L166-L170) - -**Section sources** -- [CMakeLists.txt:166-170](file://CMakeLists.txt#L166-L170) - -### Linux Configuration (GNU Toolchain) -- Compiler flags: - - C++14 standard and general warnings. - - **Updated**: Added `-DBOOST_BIND_GLOBAL_PLACEHOLDERS` flag to address Boost library binding issues in modern C++ environments. -- Required libraries: - - pthread library linkage. - - rt library linkage. - - OpenSSL detection fallback to the crypto library if not found by find_package. -- Optional static linking of standard C++ and C runtime when enabled. - -**Updated** The Linux configuration now includes a compatibility flag specifically designed to address Boost library binding issues that arise with newer C++ standards. This flag ensures proper compilation with modern Boost versions and C++ standards. - -```mermaid -flowchart TD -StartLin(["Configure on Linux"]) --> LinFlags["Set C++14 and -Wall
-DBOOST_BIND_GLOBAL_PLACEHOLDERS"] -LinFlags --> Pthread["Ensure pthread library"] -Pthread --> RTlib["Ensure rt library"] -RTlib --> OpenSSL["If OpenSSL not detected, default to 'crypto'"] -OpenSSL --> StaticCheck{"FULL_STATIC_BUILD?"} -StaticCheck --> |Yes| LinStatic["Link with -static-libstdc++ -static-libgcc"] -StaticCheck --> |No| EndLin(["Done"]) -LinStatic --> EndLin -``` - -**Diagram sources** -- [CMakeLists.txt:168-184](file://CMakeLists.txt#L168-L184) - -**Section sources** -- [CMakeLists.txt:168-184](file://CMakeLists.txt#L168-L184) - -### Static Linking Option -- A dedicated build mode enables full static linking of standard C++ and C runtime libraries on both Windows and Linux platforms. -- The option is surfaced in the build helper script and consumed by the top-level configuration. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant Helper as "configure_build.py" -participant Root as "CMakeLists.txt" -Dev->>Helper : Invoke build helper -Helper->>Root : Pass -DFULL_STATIC_BUILD=ON -Root->>Root : Apply platform-specific static flags -Root-->>Dev : Configure with static linking -``` - -**Diagram sources** -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L170) -- [CMakeLists.txt:153-155](file://CMakeLists.txt#L153-L155) -- [CMakeLists.txt:181-183](file://CMakeLists.txt#L181-L183) - -**Section sources** -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L170) -- [CMakeLists.txt:153-155](file://CMakeLists.txt#L153-L155) -- [CMakeLists.txt:181-183](file://CMakeLists.txt#L181-L183) - -### Library Targets and Platform-Aware Settings -- Library targets are built with platform-specific compile flags and link dependencies. -- Examples: - - Chain library applies MSVC-specific bigobj handling and links to protocol, utilities, fc, chainbase, appbase, and optional patch merge library. - - Network library applies MSVC bigobj handling and links to fc and protocol. - - Wallet library links to numerous internal plugins and platform-specific libraries. - -```mermaid -graph TB -Chain["graphene_chain
Compile flags: /bigobj on MSVC
Links: graphene_protocol, graphene_utilities, fc, chainbase, appbase"] -Network["graphene_network
Compile flags: /bigobj on MSVC
Links: fc, graphene_protocol"] -Wallet["graphene_wallet
Links: graphene_network, graphene_chain,
multiple internal plugins, fc, platform libs"] -RootLibs["libraries/CMakeLists.txt
Add subdirectories for api, chain, protocol, network, time, utilities, wallet"] -RootLibs --> Chain -RootLibs --> Network -RootLibs --> Wallet -``` - -**Diagram sources** -- [libraries/chain/CMakeLists.txt:130-132](file://libraries/chain/CMakeLists.txt#L130-L132) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L127) -- [libraries/network/CMakeLists.txt:46-48](file://libraries/network/CMakeLists.txt#L46-L48) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L39) -- [libraries/wallet/CMakeLists.txt:73-75](file://libraries/wallet/CMakeLists.txt#L73-L75) -- [libraries/wallet/CMakeLists.txt:50-70](file://libraries/wallet/CMakeLists.txt#L50-L70) - -**Section sources** -- [libraries/chain/CMakeLists.txt:130-132](file://libraries/chain/CMakeLists.txt#L130-L132) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L127) -- [libraries/network/CMakeLists.txt:46-48](file://libraries/network/CMakeLists.txt#L46-L48) -- [libraries/network/CMakeLists.txt](file://libraries/network/CMakeLists.txt#L39) -- [libraries/wallet/CMakeLists.txt:73-75](file://libraries/wallet/CMakeLists.txt#L73-L75) -- [libraries/wallet/CMakeLists.txt:50-70](file://libraries/wallet/CMakeLists.txt#L50-L70) - -### Executable Targets and Platform-Specific Libraries -- CLI wallet links to network, chain, protocol, utilities, wallet, multiple internal plugins, fc, and platform-specific libraries. -- On Unix (non-macOS), the rt library is added conditionally. -- On macOS, readline is linked if found. -- Optional performance tooling via gperftools detection. - -```mermaid -sequenceDiagram -participant CMake as "CMakeLists.txt" -participant CLI as "cli_wallet target" -participant Libs as "Linked Libraries" -CMake->>CLI : Define target and sources -CMake->>Libs : Add graphene_network, graphene_chain, graphene_protocol, graphene_utilities, graphene_wallet -CMake->>Libs : Add internal plugins and fc -CMake->>Libs : Add ${readline_libraries}, ${CMAKE_DL_LIBS}, ${PLATFORM_SPECIFIC_LIBS} -CMake->>Libs : Add ${Boost_LIBRARIES} -CLI-->>CMake : Target ready for build -``` - -**Diagram sources** -- [programs/cli_wallet/CMakeLists.txt:21-41](file://programs/cli_wallet/CMakeLists.txt#L21-L41) -- [programs/cli_wallet/CMakeLists.txt:2-8](file://programs/cli_wallet/CMakeLists.txt#L2-L8) - -**Section sources** -- [programs/cli_wallet/CMakeLists.txt:21-41](file://programs/cli_wallet/CMakeLists.txt#L21-L41) -- [programs/cli_wallet/CMakeLists.txt:2-8](file://programs/cli_wallet/CMakeLists.txt#L2-L8) - -## Dependency Analysis -- Compiler and toolchain: - - Minimum versions enforced for GCC and Clang. - - Ninja generator receives additional diagnostics flags for Clang. -- Boost: - - Static usage is enabled by default. - - Coroutine component is conditionally added for newer Boost versions. - - **Updated**: Both top-level CMake configuration and fc library include `-DBOOST_BIND_GLOBAL_PLACEHOLDERS` to address Boost binding compatibility issues in modern C++ environments. -- Platform libraries: - - Windows: TCL integration via environment-driven discovery. - - macOS: optional readline linkage. - - Linux: pthread and rt linkage; OpenSSL fallback to crypto. - -**Updated** The Boost compatibility fix is implemented at two levels: the main CMake configuration for Linux builds and the fc library's CMake configuration, ensuring comprehensive coverage across the entire build system. - -```mermaid -graph TB -Compilers["Compiler Checks
GCC >= 4.8
Clang >= 3.3"] -BoostCfg["Boost Static Usage Enabled"] -CoroutineFix["Conditional coroutine component for Boost >= 1.54"] -BoostCompat["Boost Compatibility Flags
-DBOOST_BIND_GLOBAL_PLACEHOLDERS
in both main and fc libraries"] -PlatformLibs["Platform Libraries
Windows: TCL
macOS: readline
Linux: pthread, rt, crypto"] -Root["CMakeLists.txt"] -Root --> Compilers -Root --> BoostCfg -Root --> CoroutineFix -Root --> BoostCompat -Root --> PlatformLibs -``` - -**Diagram sources** -- [CMakeLists.txt:12-20](file://CMakeLists.txt#L12-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L52) -- [CMakeLists.txt:99-104](file://CMakeLists.txt#L99-L104) -- [CMakeLists.txt:168-170](file://CMakeLists.txt#L168-L170) -- [thirdparty/fc/CMakeLists.txt:340-341](file://thirdparty/fc/CMakeLists.txt#L340-L341) - -**Section sources** -- [CMakeLists.txt:12-20](file://CMakeLists.txt#L12-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L52) -- [CMakeLists.txt:99-104](file://CMakeLists.txt#L99-L104) -- [CMakeLists.txt:168-170](file://CMakeLists.txt#L168-L170) -- [thirdparty/fc/CMakeLists.txt:340-341](file://thirdparty/fc/CMakeLists.txt#L340-L341) - -## Performance Considerations -- Optimization flags: - - MinGW Debug builds use a moderate optimization level to avoid assembler errors. - - General Debug builds add a debug macro definition. -- Static linking: - - Enables full static linking of standard C++ and C runtime libraries when requested. -- Build acceleration: - - Optional ccache usage if available. - -## Troubleshooting Guide -Common platform-specific issues and resolutions: -- Windows - - Missing TCL headers or libraries: - - Ensure TCL_ROOT environment variable points to a valid TCL installation so headers and libraries can be discovered. - - SEH-related linker warnings or failures: - - The configuration disables Safe Exception Handler emission; ensure downstream packaging does not require SEH. - - Large object compilation: - - MSVC targets apply bigobj handling; verify that sources triggering large object counts still compile. -- macOS - - Missing readline: - - readline is optional; if not found, the build proceeds without it. Install readline if interactive editing is desired. - - Standard library mismatch: - - Ensure libc++ is used consistently across dependencies. -- Linux - - pthread or rt missing: - - The build expects pthread and rt to be available; ensure the system provides these libraries. - - OpenSSL detection failure: - - If find_package cannot locate OpenSSL, the configuration defaults to linking against the crypto library; adjust package configuration or pass explicit hints if necessary. - - Static linking failures: - - When enabling full static linking, ensure all transitive dependencies are also statically available. - - **Updated** Boost binding issues: - - If experiencing Boost library binding errors with modern C++ standards, the `-DBOOST_BIND_GLOBAL_PLACEHOLDERS` flag is automatically included in Linux builds to resolve compatibility issues. This flag is also present in the fc library configuration for comprehensive coverage. - -**Updated** Added troubleshooting guidance for Boost binding issues that the new compatibility flag addresses. - -**Section sources** -- [CMakeLists.txt:132-145](file://CMakeLists.txt#L132-L145) -- [CMakeLists.txt:130-132](file://CMakeLists.txt#L130-L132) -- [CMakeLists.txt:160-164](file://CMakeLists.txt#L160-L164) -- [CMakeLists.txt:174-180](file://CMakeLists.txt#L174-L180) -- [CMakeLists.txt:181-183](file://CMakeLists.txt#L181-L183) -- [thirdparty/fc/CMakeLists.txt:340-341](file://thirdparty/fc/CMakeLists.txt#L340-L341) - -## Conclusion -The VIZ CPP Node build system applies robust, platform-aware configuration to ensure reliable builds across Windows, macOS, and Linux. By enforcing compiler versions, integrating platform-specific libraries, and supporting a static-linking mode, the system accommodates diverse deployment scenarios. - -**Updated** The recent addition of the `-DBOOST_BIND_GLOBAL_PLACEHOLDERS` compatibility flag enhances Linux build reliability by addressing Boost library binding issues that commonly occur with modern C++ standards. This dual-layer implementation (both in the main CMake configuration and the fc library) ensures comprehensive compatibility across the entire build system. Use the platform-specific guidance and troubleshooting tips herein to resolve typical build issues and tailor configurations to your environment. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Cross-Platform Compilation.md b/.qoder/repowiki/en/content/Development Tools/Build System/Cross-Platform Compilation.md deleted file mode 100644 index 7c396d67a0..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Cross-Platform Compilation.md +++ /dev/null @@ -1,384 +0,0 @@ -# Cross-Platform Compilation - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [README.md](file://README.md) -- [documentation/building.md](file://documentation/building.md) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [.travis.yml](file://.travis.yml) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive cross-platform build instructions and configuration guidance for VIZ CPP Node. It covers platform-specific compiler requirements, dependency installation, build options, static/dynamic linking behavior, optimization flags, and platform-specific artifacts. It also documents CI/CD automation via GitHub Actions and Travis CI, along with Docker-based reproducible builds. Guidance is derived from the repository’s CMake configuration, platform-specific documentation, and CI workflow definitions. - -## Project Structure -The build system is driven by CMake with platform-specific logic and CI workflows that build Docker images. The repository includes: -- Root CMake configuration with compiler checks, options, and platform branches -- Platform-specific build documentation -- CI workflows for Docker image builds -- Dockerfiles for production and testnet variants - -```mermaid -graph TB -A["Root CMakeLists.txt"] --> B["libraries/CMakeLists.txt"] -A --> C["plugins/CMakeLists.txt"] -A --> D["programs/CMakeLists.txt"] -A --> E["thirdparty/CMakeLists.txt"] -F["documentation/building.md"] --> A -G[".github/workflows/docker-main.yml"] --> H["Dockerfile-production"] -G --> I["Dockerfile-testnet"] -J[".github/workflows/docker-pr-build.yml"] --> I -K[".travis.yml"] --> H -K --> I -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [.travis.yml](file://.travis.yml#L1-L46) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [.travis.yml](file://.travis.yml#L1-L46) - -## Core Components -- Compiler requirements and flags: - - GCC minimum version check and Clang minimum version check are enforced at the top level. - - Platform-specific flags: - - Windows (MSVC): warning suppressions and linker flags; TCL detection and linkage. - - Windows (MinGW): C++11 dialect, permissive mode, SSE4.2, big object, separate Release/Debug optimization flags, optional full static linking. - - macOS: C++14, libc++, warnings adjustments. - - Linux: C++14, common warnings, pthread/rt/crypto libraries, optional full static linking. - - Coverage and PCH support toggles. -- Linking and libraries: - - Static Boost linkage by default on Windows. - - Optional MongoDB plugin build flag. - - Optional static executable linking via FULL_STATIC_BUILD. -- Build options: - - BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, ENABLE_MONGO_PLUGIN, BUILD_SHARED_LIBRARIES, ENABLE_INSTALLER, ENABLE_COVERAGE_TESTING. - -Key build targets are defined under programs (e.g., vizd, cli_wallet) and assembled by the root CMake configuration. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L11-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L202) -- [CMakeLists.txt](file://CMakeLists.txt#L52-L89) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) - -## Architecture Overview -The build pipeline integrates local CMake builds with CI-driven Docker builds. The CI orchestrates image builds for production and testnet variants, while local documentation describes manual builds on Ubuntu and macOS. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant CMake as "CMake (Local)" -participant Make as "make/ninja" -participant CI as "CI (GitHub Actions/Travis)" -participant Docker as "Docker Build" -Dev->>CMake : Configure with options -CMake->>Make : Generate build system -Make-->>Dev : Produce executables/binaries -Dev->>CI : Push/PR triggers workflow -CI->>Docker : Build Docker image with Dockerfile -Docker-->>CI : Publish image (optional) -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) -- [.travis.yml](file://.travis.yml#L22-L42) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L40-L55) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L40-L55) - -## Detailed Component Analysis - -### Windows (MSVC and MinGW) -- Compiler requirements: - - Enforced minimum versions for GCC and Clang apply globally; MSVC is supported via CMake generator selection. -- Dependencies: - - Boost components are required; Windows forces static Boost usage. - - TCL is detected and linked on Windows; include path is derived from environment. -- Flags and options: - - MSVC: warning disables, SafeSEH flags, debug info linkage. - - MinGW: C++11 dialect, permissive mode, SSE4.2, big object, separate Release/Debug optimization flags, optional full static linking. -- Linking: - - Static Boost linkage on Windows; optional full static executable linking controlled by FULL_STATIC_BUILD. -- Artifacts: - - Executables produced under the configured install prefix; Windows packaging controlled by CPack when enabled. - -```mermaid -flowchart TD -Start(["Configure on Windows"]) --> DetectCompiler["Detect MSVC or MinGW"] -DetectCompiler --> |MSVC| MSVCFlags["Apply MSVC flags
warning disables, SafeSEH, debug info"] -DetectCompiler --> |MinGW| MinGWFlags["Apply MinGW flags
C++11, permissive, SSE4.2, big obj"] -MSVCFlags --> BoostWin["Find Boost (static libs)"] -MinGWFlags --> BoostWin -BoostWin --> LinkStep["Link executables"] -LinkStep --> End(["Artifacts ready"]) -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L91-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt](file://CMakeLists.txt#L123-L156) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L91-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L52-L54) - -### macOS (Xcode) -- Compiler requirements: - - Xcode command line tools required; CMake detects compilers automatically. -- Dependencies: - - Boost via Homebrew; OpenSSL prefix exported for discovery. -- Flags and options: - - C++14, libc++, common warnings adjustments. -- Artifacts: - - Executables built with standard CMake generators; install targets available. - -```mermaid -flowchart TD -StartMac(["macOS Build"]) --> XcodeTools["Install Xcode CLI tools"] -XcodeTools --> BrewDeps["Install Boost/OpenSSL via Homebrew"] -BrewDeps --> ExportEnv["Export OPENSSL_ROOT_DIR and BOOST_ROOT"] -ExportEnv --> CMakeConf["Run CMake configure"] -CMakeConf --> Build["Build targets (vizd, cli_wallet)"] -Build --> EndMac(["Artifacts ready"]) -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L138-L201) -- [CMakeLists.txt](file://CMakeLists.txt#L166-L170) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L138-L201) -- [CMakeLists.txt](file://CMakeLists.txt#L166-L170) - -### Linux (Ubuntu LTS) -- Compiler requirements: - - Supported with GCC and Clang; C++14 standard is used. -- Dependencies: - - System packages include CMake, GCC, Git, OpenSSL dev, Boost dev packages, and optional tools (Doxygen, readline, ncurses). -- Flags and options: - - C++14, common warnings; pthread/rt/crypto libraries; optional full static linking. -- Artifacts: - - Executables built via make; install target produces system-wide binaries. - -```mermaid -flowchart TD -StartLinux(["Linux Build"]) --> AptPkgs["Install system packages"] -AptPkgs --> Submodule["Initialize submodules"] -Submodule --> CMakeConf["Configure with CMake"] -CMakeConf --> BuildLinux["Build targets"] -BuildLinux --> Install["Optionally install"] -Install --> EndLinux(["Artifacts ready"]) -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [CMakeLists.txt](file://CMakeLists.txt#L171-L184) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [CMakeLists.txt](file://CMakeLists.txt#L171-L184) - -### Static vs Dynamic Linking -- Library linkage: - - Windows: Boost linkage forced static. - - Linux/macOS: default is dynamic; static linkage can be enabled via FULL_STATIC_BUILD. -- Executable linkage: - - FULL_STATIC_BUILD toggles static linking for executables on Linux/macOS; Windows applies static Boost and optional full static executable linkage. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L52-L54) -- [CMakeLists.txt](file://CMakeLists.txt#L153-L155) -- [CMakeLists.txt](file://CMakeLists.txt#L181-L183) - -### Platform-Specific Optimization Flags -- Windows: - - MSVC: SafeSEH flags and debug info linkage. - - MinGW: SSE4.2, big object, separate Release/Debug optimization flags. -- macOS/Linux: - - C++14 standard; Linux adds pthread/rt/crypto libraries; Ninja with Clang enables colored diagnostics. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L123-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L166-L184) -- [CMakeLists.txt](file://CMakeLists.txt#L190-L194) - -### Build Targets and Artifacts -- Executables: - - vizd, cli_wallet, and additional utilities are built from programs. -- Packaging: - - CPack is conditionally enabled for installer generation; Windows uses ZIP/NSIS; macOS uses DragNDrop; Linux uses TGZ. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) -- [CMakeLists.txt](file://CMakeLists.txt#L215-L262) - -## Dependency Analysis -- External dependencies: - - Boost components are required; Windows forces static Boost linkage. - - OpenSSL and readline are discovered via CMake; Linux sets pthread/rt/crypto defaults. -- Optional components: - - MongoDB plugin can be enabled via a build flag. -- CI-driven builds: - - Dockerfiles define reproducible environments and build options. - -```mermaid -graph LR -CMake["CMakeLists.txt"] --> Boost["Boost (required)"] -CMake --> OpenSSL["OpenSSL (discovered)"] -CMake --> Readline["Readline (discovered)"] -CMake --> Plugins["Optional Plugins"] -Plugins --> Mongo["MongoDB Plugin"] -CI["CI Workflows"] --> DockerProd["Dockerfile-production"] -CI --> DockerTest["Dockerfile-testnet"] -CI --> DockerLowMem["Dockerfile-lowmem"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [CMakeLists.txt](file://CMakeLists.txt#L160-L180) -- [CMakeLists.txt](file://CMakeLists.txt#L83-L89) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L7-L30) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L7-L30) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L7-L30) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [CMakeLists.txt](file://CMakeLists.txt#L160-L180) -- [CMakeLists.txt](file://CMakeLists.txt#L83-L89) - -## Performance Considerations -- Compiler and flags: - - Use Release builds for production; Linux/macOS enable C++14; MinGW enables SSE4.2. - - Full static linking reduces runtime dependencies but may increase binary size and startup time. -- Build system: - - ccache is enabled when available to speed up rebuilds. - - Ninja generator with Clang enables colored diagnostics for readability. -- CI and reproducibility: - - Dockerfiles pin system packages and build options for deterministic builds. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L106-L110) -- [CMakeLists.txt](file://CMakeLists.txt#L186-L194) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L54) - -## Troubleshooting Guide -- Compiler version errors: - - GCC and Clang minimum versions are enforced; ensure toolchains meet requirements. -- Boost linkage issues on Windows: - - Static Boost linkage is forced; ensure environment variables and paths are correct. -- MinGW “File too big” errors: - - Debug builds use reduced optimization; enabling big object support is handled in flags. -- macOS OpenSSL discovery: - - Set OPENSSL_ROOT_DIR to Homebrew OpenSSL prefix. -- Linux readline/ncurses: - - Some distributions require readline development packages; CMake attempts to locate them. -- CI failures: - - Verify Dockerfile context copying and submodule initialization steps. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L11-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L91-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L160-L180) -- [documentation/building.md](file://documentation/building.md#L138-L201) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L32-L43) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L32-L43) - -## Conclusion -The repository provides robust, cross-platform build support with explicit platform branches, strong compiler requirements, and CI-driven Docker builds. Developers can build locally on Windows, macOS, and Linux using documented steps, while CI ensures reproducible Docker images for production and testnet deployments. - -## Appendices - -### Step-by-Step Compilation Instructions - -- Ubuntu 16.04 LTS - - Install system packages and Boost development packages. - - Initialize submodules, configure with CMake, build targets, and optionally install. - - Reference: [documentation/building.md](file://documentation/building.md#L25-L75) - -- Ubuntu 14.04 LTS - - Install required packages; build and install Boost 1.57 manually if needed. - - Configure with CMake and build targets. - - Reference: [documentation/building.md](file://documentation/building.md#L76-L137) - -- macOS X - - Install Xcode CLI tools and Homebrew; install Boost and OpenSSL via Homebrew. - - Export OpenSSL and Boost prefixes, initialize submodules, configure with CMake, and build. - - Reference: [documentation/building.md](file://documentation/building.md#L138-L201) - -- Windows (MSVC/MinGW) - - Configure with CMake generator for desired toolchain; MSVC flags and SafeSEH settings are applied automatically; MinGW enables C++11, permissive mode, SSE4.2, and big object support. - - Reference: [CMakeLists.txt](file://CMakeLists.txt#L91-L156) - -### Continuous Integration and Automated Builds -- GitHub Actions - - Builds Docker images for testnet and production on push to master and PRs. - - References: - - [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41) - - [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) - -- Travis CI - - Matrix builds multiple Docker variants; pushes tagged images when applicable. - - Reference: [.travis.yml](file://.travis.yml#L19-L42) - -### Docker-Based Builds -- Production - - Sets Release build, disables shared libraries, disables extra features, installs dependencies, builds, installs, and prepares runtime image. - - Reference: [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L7-L60) - -- Testnet - - Same as production with BUILD_TESTNET enabled. - - Reference: [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L7-L60) - -- Low-memory node - - Same as production with LOW_MEMORY_NODE enabled. - - Reference: [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L7-L60) - -### Relationship Between Platform Choices and Runtime Performance -- Windows: - - MSVC SafeSEH and debug info flags impact link-time behavior; static Boost reduces runtime dependencies. -- macOS: - - C++14 and libc++ align with modern ABI; minimal extra flags optimize for standard compliance. -- Linux: - - C++14 and pthread/rt/crypto libraries ensure efficient threading and timing; static linking reduces external dependencies at the cost of larger binaries. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L123-L156) -- [CMakeLists.txt](file://CMakeLists.txt#L166-L184) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L54) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L46-L54) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Docker Integration.md b/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Docker Integration.md deleted file mode 100644 index 86b1ffb327..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Docker Integration.md +++ /dev/null @@ -1,492 +0,0 @@ -# Docker Integration - - -**Referenced Files in This Document** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [docker-main.yml](file://.github/workflows/docker-main.yml) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [vizd.sh](file://share/vizd/vizd.sh) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [snapshot.json](file://share/vizd/snapshot.json) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json) -- [seednodes](file://share/vizd/seednodes) -- [CMakeLists.txt](file://CMakeLists.txt) - - -## Update Summary -**Changes Made** -- Updated GitHub Actions workflow configurations to reflect major version upgrades (actions/checkout v2→v4, docker/build-push-action v1→v6) -- Added docker/login-action@v3 for enhanced authentication in CI/CD pipelines -- Improved error handling and workflow configurations for better build reliability and security -- Enhanced CI/CD pipeline documentation with current best practices - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive Docker integration guidance for the VIZ CPP Node across development and production environments. It covers multi-stage Dockerfiles for production, testnet, low-memory, and MongoDB-enabled builds, the GitHub Actions CI/CD pipeline for automated Docker builds with enhanced security and reliability, container orchestration patterns, volume mounting for persistence, network configuration, environment variable usage, and the relationship between Docker configurations and CMake build options. Practical examples are included for running development containers, connecting to test networks, and deploying production nodes. Security considerations and troubleshooting tips are also provided. - -## Project Structure -The Docker integration is centered around four primary Dockerfiles under share/vizd/docker, each tailored to a specific deployment profile. Supporting assets include configuration templates, scripts, snapshots, and seednode lists. The CI/CD pipeline is defined via GitHub Actions workflows with enhanced security and reliability features. - -```mermaid -graph TB -subgraph "Docker Build Configurations" -DProd["Dockerfile-production"] -DTest["Dockerfile-testnet"] -DLow["Dockerfile-lowmem"] -DMongo["Dockerfile-mongo"] -end -subgraph "Runtime Assets" -Script["vizd.sh"] -Seed["seednodes"] -Snap["snapshot.json"] -SnapTest["snapshot-testnet.json"] -CfgProd["config.ini"] -CfgTest["config_testnet.ini"] -CfgMongo["config_mongo.ini"] -CfgDebug["config_debug.ini"] -end -subgraph "Enhanced CI/CD" -GHMain[".github/workflows/docker-main.yml
v4 checkout + v6 build-push + v3 login"] -GHPR[".github/workflows/docker-pr-build.yml
v4 checkout + v6 build-push + v3 login"] -end -DProd --> Script -DProd --> Seed -DProd --> Snap -DProd --> CfgProd -DTest --> Script -DTest --> Seed -DTest --> SnapTest -DTest --> CfgTest -DLow --> Script -DLow --> Seed -DLow --> Snap -DLow --> CfgProd -DMongo --> Script -DMongo --> Seed -DMongo --> Snap -DMongo --> CfgMongo -GHMain --> DProd -GHMain --> DTest -GHPR --> DTest -``` - -**Diagram sources** -- [Dockerfile-production:1-98](file://share/vizd/docker/Dockerfile-production#L1-L98) -- [Dockerfile-testnet:1-98](file://share/vizd/docker/Dockerfile-testnet#L1-L98) -- [Dockerfile-lowmem:1-80](file://share/vizd/docker/Dockerfile-lowmem#L1-L80) -- [Dockerfile-mongo:1-109](file://share/vizd/docker/Dockerfile-mongo#L1-L109) -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) -- [seednodes:1-6](file://share/vizd/seednodes#L1-L6) -- [snapshot.json:1-174](file://share/vizd/snapshot.json#L1-L174) -- [snapshot-testnet.json:1-35](file://share/vizd/snapshot-testnet.json#L1-L35) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) -- [docker-main.yml:1-53](file://.github/workflows/docker-main.yml#L1-L53) -- [docker-pr-build.yml:1-30](file://.github/workflows/docker-pr-build.yml#L1-L30) - -**Section sources** -- [Dockerfile-production:1-98](file://share/vizd/docker/Dockerfile-production#L1-L98) -- [Dockerfile-testnet:1-98](file://share/vizd/docker/Dockerfile-testnet#L1-L98) -- [Dockerfile-lowmem:1-80](file://share/vizd/docker/Dockerfile-lowmem#L1-L80) -- [Dockerfile-mongo:1-109](file://share/vizd/docker/Dockerfile-mongo#L1-L109) -- [docker-main.yml:1-53](file://.github/workflows/docker-main.yml#L1-L53) -- [docker-pr-build.yml:1-30](file://.github/workflows/docker-pr-build.yml#L1-L30) - -## Core Components -- Multi-stage Dockerfiles: - - Production: Builds with standard node settings, exposes RPC and P2P ports, mounts data directories for persistence. - - Testnet: Similar to production but enables testnet-specific configuration and snapshot. - - Low-memory: Optimized for constrained environments using a low-memory build option. - - MongoDB-enabled: Installs MongoDB C/C++ drivers and enables the mongo_db plugin. -- Enhanced CI/CD: - - Automated Docker builds for master branch (production and testnet) with improved security using docker/login-action@v3. - - PR builds for production images with ref tagging using latest GitHub Actions versions. - - Robust error handling and authentication mechanisms for reliable builds. -- Runtime: - - A service wrapper script initializes configuration, applies optional seed nodes, and starts the node with environment-driven endpoints and optional validator settings. - - Configuration templates define RPC endpoints, P2P endpoints, plugin sets, logging, and optional MongoDB connection. - - Snapshot assets enable fast initialization of blockchain data. - -Key runtime environment variables supported by the container entrypoint: -- VIZD_SEED_NODES: Comma-separated list of seed nodes to connect to. -- VIZD_WITNESS_NAME: Optional validator name for block production. -- VIZD_PRIVATE_KEY: Private key for validator signing. -- VIZD_RPC_ENDPOINT: Override RPC endpoint binding. -- VIZD_P2P_ENDPOINT: Override P2P endpoint binding. -- VIZD_EXTRA_OPTS: Additional arguments appended to the node command. - -Exposed ports: -- 8090: HTTP RPC -- 8091: WebSocket RPC -- 2001: P2P - -Volumes: -- /var/lib/vizd: Blockchain data directory -- /etc/vizd: Configuration directory - -**Section sources** -- [Dockerfile-production:76-98](file://share/vizd/docker/Dockerfile-production#L76-L98) -- [Dockerfile-testnet:77-98](file://share/vizd/docker/Dockerfile-testnet#L77-L98) -- [Dockerfile-lowmem:58-80](file://share/vizd/docker/Dockerfile-lowmem#L58-L80) -- [Dockerfile-mongo:87-109](file://share/vizd/docker/Dockerfile-mongo#L87-L109) -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) -- [docker-main.yml:1-53](file://.github/workflows/docker-main.yml#L1-L53) -- [docker-pr-build.yml:1-30](file://.github/workflows/docker-pr-build.yml#L1-L30) - -## Architecture Overview -The Docker-based deployment architecture separates build-time and runtime concerns with enhanced CI/CD security: -- Build-time: Multi-stage Dockerfiles compile the node with CMake options selected per variant. -- Runtime: A minimal base image runs the node under a service supervisor, with volumes for persistence and configuration overlays. -- CI/CD: Enhanced GitHub Actions workflows with improved authentication, error handling, and build reliability. - -```mermaid -graph TB -subgraph "Enhanced Build Stages" -B1["Stage 1: Builder Base
Install deps, clone repo, cmake configure
actions/checkout@v4 + docker/login-action@v3"] -B2["Stage 2: Install Artifacts
make install, cleanup"] -end -subgraph "Enhanced Runtime Image" -R1["Minimal Base"] -R2["Copy /usr/local from builder"] -R3["Add user, cache dirs, service wrapper"] -R4["Mount volumes, expose ports"] -end -B1 --> B2 --> R2 --> R3 --> R4 -``` - -**Diagram sources** -- [Dockerfile-production:1-98](file://share/vizd/docker/Dockerfile-production#L1-L98) -- [Dockerfile-testnet:1-98](file://share/vizd/docker/Dockerfile-testnet#L1-L98) -- [Dockerfile-lowmem:1-80](file://share/vizd/docker/Dockerfile-lowmem#L1-L80) -- [Dockerfile-mongo:1-109](file://share/vizd/docker/Dockerfile-mongo#L1-L109) -- [docker-main.yml:17-21](file://.github/workflows/docker-main.yml#L17-L21) -- [docker-pr-build.yml:15-19](file://.github/workflows/docker-pr-build.yml#L15-L19) - -## Detailed Component Analysis - -### Dockerfile Variants and CMake Options -Each Dockerfile selects CMake options to tailor the build: -- Production: Standard release build with full plugin set and no special flags. -- Testnet: Enables BUILD_TESTNET and uses testnet configuration and snapshot. -- Low-memory: Enables LOW_MEMORY_NODE to reduce memory footprint. -- MongoDB-enabled: Installs MongoDB C drivers and enables ENABLE_MONGO_PLUGIN. - -```mermaid -flowchart TD -Start(["Start Build"]) --> Stage1["Stage 1: Install Build Tools and Dependencies"] -Stage1 --> CopySrc["Copy Source and Configure CMake"] -CopySrc --> CMakeCfg{"Select CMake Options"} -CMakeCfg --> |Production| OptProd["Release, Shared=OFF,
Testnet=OFF, LowMem=OFF, Mongo=OFF"] -CMakeCfg --> |Testnet| OptTest["Release, Shared=OFF,
Testnet=ON, LowMem=OFF, Mongo=OFF"] -CMakeCfg --> |Low-Memory| OptLM["Release, Shared=OFF,
Testnet=OFF, LowMem=ON, Mongo=OFF"] -CMakeCfg --> |MongoDB| OptMongo["Release, Shared=OFF,
Testnet=OFF, LowMem=OFF, Mongo=ON"] -OptProd --> Build["Compile and Install"] -OptTest --> Build -OptLM --> Build -OptMongo --> MongoDeps["Install MongoDB C/C++ Drivers"] --> Build -Build --> Stage2["Stage 2: Runtime Image Setup"] -Stage2 --> End(["Image Ready"]) -``` - -**Diagram sources** -- [Dockerfile-production:51-69](file://share/vizd/docker/Dockerfile-production#L51-L69) -- [Dockerfile-testnet:51-65](file://share/vizd/docker/Dockerfile-testnet#L51-L65) -- [Dockerfile-lowmem:37-51](file://share/vizd/docker/Dockerfile-lowmem#L37-L51) -- [Dockerfile-mongo:66-80](file://share/vizd/docker/Dockerfile-mongo#L66-L80) -- [CMakeLists.txt:56-89](file://CMakeLists.txt#L56-L89) - -**Section sources** -- [Dockerfile-production:51-69](file://share/vizd/docker/Dockerfile-production#L51-L69) -- [Dockerfile-testnet:51-65](file://share/vizd/docker/Dockerfile-testnet#L51-L65) -- [Dockerfile-lowmem:37-51](file://share/vizd/docker/Dockerfile-lowmem#L37-L51) -- [Dockerfile-mongo:66-80](file://share/vizd/docker/Dockerfile-mongo#L66-L80) -- [CMakeLists.txt:56-89](file://CMakeLists.txt#L56-L89) - -### Enhanced CI/CD Pipeline (GitHub Actions) -Automated Docker builds are configured with enhanced security and reliability: -- Master branch: Builds production and testnet images with improved authentication using docker/login-action@v3 and pushes to the registry with appropriate tags. -- Pull requests: Builds a production image and tags it with the PR ref for review using latest GitHub Actions versions. - -**Updated** Enhanced with major version upgrades and improved security measures - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant GH as "GitHub" -participant Act as "Actions Runner" -participant Auth as "docker/login-action@v3" -participant Build as "docker/build-push-action@v6" -participant Reg as "Container Registry" -Dev->>GH : Push to master -GH->>Act : Trigger docker-main.yml -Act->>Auth : Authenticate with Docker Hub -Auth->>Reg : Login with credentials -Act->>Build : Build and push production image (latest) -Build->>Reg : Push with docker/build-push-action@v6 -Act->>Build : Build and push testnet image (testnet) -Build->>Reg : Push with docker/build-push-action@v6 -Dev->>GH : Open PR -GH->>Act : Trigger docker-pr-build.yml -Act->>Auth : Authenticate with Docker Hub -Auth->>Reg : Login with credentials -Act->>Build : Build and push production image (ref-tagged) -Build->>Reg : Push with docker/build-push-action@v6 -``` - -**Diagram sources** -- [docker-main.yml:17-31](file://.github/workflows/docker-main.yml#L17-L31) -- [docker-pr-build.yml:15-29](file://.github/workflows/docker-pr-build.yml#L15-L29) - -**Section sources** -- [docker-main.yml:1-53](file://.github/workflows/docker-main.yml#L1-L53) -- [docker-pr-build.yml:1-30](file://.github/workflows/docker-pr-build.yml#L1-L30) - -### Container Runtime and Environment Variables -The container entrypoint script orchestrates node startup: -- Applies default seed nodes from the seednodes file if none are provided via environment. -- Copies the packaged configuration into the data directory and adjusts ownership. -- Optionally replays from a cached snapshot if present. -- Starts the node with configurable RPC and P2P endpoints and optional validator parameters. - -```mermaid -sequenceDiagram -participant Entrypoint as "vizd.sh" -participant FS as "Mounted Volumes" -participant Node as "vizd" -Entrypoint->>Entrypoint : Parse env vars (seed, validator, keys, endpoints) -Entrypoint->>FS : Copy /etc/vizd/config.ini -> /var/lib/vizd/config.ini -Entrypoint->>FS : Optionally extract cached snapshot to blockchain dir -Entrypoint->>Node : exec vizd with data-dir, endpoints, plugins, extra opts -Node-->>Entrypoint : stdout/stderr -``` - -**Diagram sources** -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) - -**Section sources** -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) - -### Configuration Templates and Plugin Sets -Configuration files define RPC endpoints, plugin sets, logging, and optional MongoDB URI. The testnet configuration enables validator production and includes a default validator and private key suitable for automated testing. - -- Production: Full plugin set excluding MongoDB. -- Testnet: Includes Validator Plugin and default validator credentials. -- MongoDB: Adds mongo_db plugin and a MongoDB URI for external connectivity. - -**Section sources** -- [config.ini:1-130](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini:1-132](file://share/vizd/config/config_testnet.ini#L1-L132) -- [config_mongo.ini:1-135](file://share/vizd/config/config_mongo.ini#L1-L135) - -### Volume Mounting and Persistence -- /var/lib/vizd: Contains blockchain data, logs, and configuration overrides. -- /etc/vizd: Contains initial configuration and seednodes; copied into the data directory at first run. - -Practical guidance: -- Bind-mount a host directory to /var/lib/vizd for persistent storage across container restarts. -- Place a custom config.ini into /etc/vizd to override defaults; it will be copied into the data directory on first run. - -**Section sources** -- [Dockerfile-production:97-98](file://share/vizd/docker/Dockerfile-production#L97-L98) -- [Dockerfile-testnet:97-98](file://share/vizd/docker/Dockerfile-testnet#L97-L98) -- [Dockerfile-lowmem:79-80](file://share/vizd/docker/Dockerfile-lowmem#L79-L80) -- [Dockerfile-mongo:108-109](file://share/vizd/docker/Dockerfile-mongo#L108-L109) -- [vizd.sh:40-43](file://share/vizd/vizd.sh#L40-L43) - -### Network Configuration and Connectivity -- Exposed ports: 8090 (HTTP RPC), 8091 (WebSocket RPC), 2001 (P2P). -- Seed nodes: Provided via /etc/vizd/seednodes; overridden by VIZD_SEED_NODES environment variable. -- P2P endpoint binding: Defaults to 0.0.0.0:2001; overrideable via VIZD_P2P_ENDPOINT. -- RPC endpoint binding: Defaults to 0.0.0.0:8090; overrideable via VIZD_RPC_ENDPOINT. - -For MongoDB-enabled deployments, the configuration template includes a MongoDB URI suitable for connecting to a MongoDB instance reachable from the container's network namespace. - -**Section sources** -- [Dockerfile-production:89-95](file://share/vizd/docker/Dockerfile-production#L89-L95) -- [Dockerfile-testnet:89-95](file://share/vizd/docker/Dockerfile-testnet#L89-L95) -- [Dockerfile-lowmem:71-77](file://share/vizd/docker/Dockerfile-lowmem#L71-L77) -- [Dockerfile-mongo:100-106](file://share/vizd/docker/Dockerfile-mongo#L100-L106) -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) -- [seednodes:1-6](file://share/vizd/seednodes#L1-L6) -- [config_mongo.ini:71-72](file://share/vizd/config/config_mongo.ini#L71-L72) - -### Practical Examples - -- Run a production node locally: - - docker run -d \ - --name viz-prod \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /srv/viz/data:/var/lib/vizd \ - -v /srv/viz/etc:/etc/vizd \ - vizblockchain/vizd:latest - -- Connect to the testnet: - - docker run -d \ - --name viz-testnet \ - -e VIZD_SEED_NODES="seed1.testnet.viz:2001,seed2.testnet.viz:2001" \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /srv/viz/testnet-data:/var/lib/vizd \ - vizblockchain/vizd:testnet - -- Deploy a MongoDB-enabled node: - - docker run -d \ - --name viz-mongo \ - -e VIZD_SEED_NODES="..." \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /srv/viz/mongo-data:/var/lib/vizd \ - -v /srv/viz/mongo-etc:/etc/vizd \ - vizblockchain/vizd:mongo - -- Run a validator node: - - docker run -d \ - --name viz-validator \ - -e VIZD_WITNESS_NAME="your-validator" \ - -e VIZD_PRIVATE_KEY="5...your-private-key" \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /srv/viz/validator-data:/var/lib/vizd \ - vizblockchain/vizd:latest - -[No sources needed since this section provides practical examples without analyzing specific files] - -## Dependency Analysis -The Docker build depends on CMake options to select features and plugins. The CI/CD pipeline depends on Docker Hub credentials and the presence of the Dockerfiles with enhanced authentication security. - -**Updated** Enhanced with improved authentication and build action versions - -```mermaid -graph LR -CMake["CMakeLists.txt
BUILD_TESTNET / LOW_MEMORY_NODE / ENABLE_MONGO_PLUGIN"] -DP["Dockerfile-production"] -DT["Dockerfile-testnet"] -DL["Dockerfile-lowmem"] -DM["Dockerfile-mongo"] -CMake --> DP -CMake --> DT -CMake --> DL -CMake --> DM -GHMain["docker-main.yml
v4 checkout + v6 build-push + v3 login"] --> DP -GHMain --> DT -GHPR["docker-pr-build.yml
v4 checkout + v6 build-push + v3 login"] --> DT -``` - -**Diagram sources** -- [CMakeLists.txt:56-89](file://CMakeLists.txt#L56-L89) -- [Dockerfile-production:56-61](file://share/vizd/docker/Dockerfile-production#L56-L61) -- [Dockerfile-testnet:56-62](file://share/vizd/docker/Dockerfile-testnet#L56-L62) -- [Dockerfile-lowmem:43-48](file://share/vizd/docker/Dockerfile-lowmem#L43-L48) -- [Dockerfile-mongo:72-77](file://share/vizd/docker/Dockerfile-mongo#L72-L77) -- [docker-main.yml:17-31](file://.github/workflows/docker-main.yml#L17-L31) -- [docker-pr-build.yml:15-29](file://.github/workflows/docker-pr-build.yml#L15-L29) - -**Section sources** -- [CMakeLists.txt:56-89](file://CMakeLists.txt#L56-L89) -- [docker-main.yml:1-53](file://.github/workflows/docker-main.yml#L1-L53) -- [docker-pr-build.yml:1-30](file://.github/workflows/docker-pr-build.yml#L1-L30) - -## Performance Considerations -- Multi-stage builds minimize final image size by discarding build tools and build artifacts after installation. -- Using Release builds and disabling shared libraries reduces binary size and improves runtime performance characteristics. -- Low-memory builds reduce shared memory sizing and related overhead for constrained environments. -- MongoDB builds include static drivers to avoid runtime dependency resolution overhead. -- Caching: ccache is enabled during CMake configuration to speed up rebuilds when iterating on changes. -- Enhanced CI/CD: Latest GitHub Actions versions provide improved build performance and reliability. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Ports already in use: - - Change published ports or stop conflicting services. -- Permission denied on data directory: - - Ensure the container user owns /var/lib/vizd and mounted volumes are writable. -- No connectivity to peers: - - Verify VIZD_SEED_NODES or rely on default seednodes; confirm firewall/NAT rules allow inbound P2P traffic on port 2001. -- Slow startup due to replay: - - Provide a cached snapshot or pre-seeded blockchain data in /var/lib/vizd to accelerate synchronization. -- validator production not starting: - - Confirm VIZD_WITNESS_NAME and VIZD_PRIVATE_KEY are set and match the configured validator and private key in the configuration. -- MongoDB plugin errors: - - Ensure the MongoDB URI is reachable from the container network and matches the configuration template. -- CI/CD build failures: - - Verify Docker Hub credentials are properly configured; check docker-login-action@v3 authentication. - - Ensure actions/checkout@v4 and docker/build-push-action@v6 are compatible with your repository structure. - -Operational checks: -- Inspect container logs for startup errors. -- Verify configuration file presence in /var/lib/vizd after first run. -- Monitor RPC and P2P endpoints availability. -- Check GitHub Actions workflow logs for authentication and build errors. - -**Section sources** -- [vizd.sh:1-98](file://share/vizd/vizd.sh#L1-L98) -- [config_mongo.ini:71-72](file://share/vizd/config/config_mongo.ini#L71-L72) -- [docker-main.yml:21-24](file://.github/workflows/docker-main.yml#L21-L24) -- [docker-pr-build.yml:19-22](file://.github/workflows/docker-pr-build.yml#L19-L22) - -## Conclusion -The Docker integration for VIZ CPP Node provides flexible, reproducible deployments across production, testnet, low-memory, and MongoDB-enabled scenarios. Enhanced multi-stage builds, modernized CI/CD automation with improved security and reliability, and robust runtime configuration enable efficient development and operations. By leveraging volumes, environment variables, and configuration templates, teams can deploy reliable nodes with predictable performance and minimal operational overhead. The recent upgrades to GitHub Actions workflows ensure better build reliability and security for automated Docker image creation. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Appendix A: Environment Variables Reference -- VIZD_SEED_NODES: Comma-separated P2P endpoints to bootstrap connections. -- VIZD_WITNESS_NAME: Name of the validator to produce blocks. -- VIZD_PRIVATE_KEY: Private key for validator signing. -- VIZD_RPC_ENDPOINT: RPC endpoint binding (host:port). -- VIZD_P2P_ENDPOINT: P2P endpoint binding (host:port). -- VIZD_EXTRA_OPTS: Additional CLI options appended to the node process. - -**Section sources** -- [vizd.sh:17-37](file://share/vizd/vizd.sh#L17-L37) -- [vizd.sh:62-72](file://share/vizd/vizd.sh#L62-L72) - -### Appendix B: CMake Options and Docker Variants Mapping -- BUILD_TESTNET: Selects testnet configuration and snapshot. -- LOW_MEMORY_NODE: Reduces memory footprint for constrained environments. -- ENABLE_MONGO_PLUGIN: Enables MongoDB plugin and installs drivers. -- BUILD_SHARED_LIBRARIES=OFF: Produces static binaries for portability. -- CMAKE_BUILD_TYPE=Release: Optimized release build. - -**Section sources** -- [CMakeLists.txt:56-89](file://CMakeLists.txt#L56-L89) -- [Dockerfile-production:56-61](file://share/vizd/docker/Dockerfile-production#L56-L61) -- [Dockerfile-testnet:56-62](file://share/vizd/docker/Dockerfile-testnet#L56-L62) -- [Dockerfile-lowmem:43-48](file://share/vizd/docker/Dockerfile-lowmem#L43-L48) -- [Dockerfile-mongo:72-77](file://share/vizd/docker/Dockerfile-mongo#L72-L77) - -### Appendix C: Enhanced GitHub Actions Workflow Versions -**Updated** Current workflow versions and security enhancements - -- actions/checkout@v4: Latest checkout action with improved performance and security -- docker/login-action@v3: Enhanced authentication with better credential handling -- docker/build-push-action@v6: Latest build and push action with improved reliability -- Error handling: Better failure detection and reporting in CI/CD pipelines -- Authentication: Secure Docker Hub credentials management through GitHub Secrets - -**Section sources** -- [docker-main.yml:17-21](file://.github/workflows/docker-main.yml#L17-L21) -- [docker-pr-build.yml:19-22](file://.github/workflows/docker-pr-build.yml#L19-L22) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/GitHub Actions CI_CD Pipeline.md b/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/GitHub Actions CI_CD Pipeline.md deleted file mode 100644 index 7eadc9dfd8..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/GitHub Actions CI_CD Pipeline.md +++ /dev/null @@ -1,350 +0,0 @@ -# GitHub Actions CI/CD Pipeline - - -**Referenced Files in This Document** -- [docker-main.yml](file://.github/workflows/docker-main.yml) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [CMakeLists.txt](file://CMakeLists.txt) -- [vizd.sh](file://share/vizd/vizd.sh) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [testing.md](file://documentation/testing.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the GitHub Actions CI/CD pipeline that automates Docker image builds and testing for the VIZ C++ Node. It covers workflow triggers for main branch pushes and pull requests, Docker build matrix variants, automated testing and code quality checks, artifact management, Docker image publishing, and deployment automation. It also provides practical examples for modifying workflow configurations, adding new build stages, and troubleshooting pipeline failures, with best practices tailored for blockchain development. - -## Project Structure -The CI/CD pipeline is defined by two GitHub Actions workflow files: -- A main branch build workflow that produces production and testnet images. -- A pull request build workflow that builds a testnet image and optionally tags it with the ref for traceability. - -Docker images are built from multi-stage Dockerfiles located under share/vizd/docker. The build leverages CMake options to toggle features such as testnet mode, low-memory mode, and MongoDB plugin support. - -```mermaid -graph TB -GH[".github/workflows"] --> MAIN["docker-main.yml"] -GH --> PR["docker-pr-build.yml"] -MAIN --> PROD["Dockerfile-production"] -MAIN --> TESTNET["Dockerfile-testnet"] -PR --> TESTNET_PR["Dockerfile-testnet"] -PROD --> CMAKE["CMakeLists.txt"] -TESTNET --> CMAKE -TESTNET_PR --> CMAKE -CMAKE --> DOCKERS["Dockerfiles"] -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [CMakeLists.txt](file://CMakeLists.txt#L46-L89) - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -## Core Components -- Workflow triggers: - - Main branch push: Builds and publishes production and testnet images. - - Pull request: Builds a testnet image and tags it with the ref for traceability. -- Docker build matrix: - - Production image: Built from Dockerfile-production. - - Testnet image: Built from Dockerfile-testnet. - - Low-memory variant: Dockerfile-lowmem enables LOW_MEMORY_NODE. - - MongoDB-enabled variant: Dockerfile-mongo enables ENABLE_MONGO_PLUGIN. -- Artifact management and publishing: - - Images are pushed to a Docker registry using credentials from GitHub secrets. - - Tagging strategy differs between main and PR workflows. -- Testing and code quality: - - Unit tests can be executed via a documented target. - - Code coverage can be enabled via CMake flags and lcov. -- Runtime configuration: - - Container entrypoint script configures endpoints, seeds, and optional replay from cache. - - Multiple config templates are provided for mainnet and testnet. - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L3-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L3-L24) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L46-L53) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L82) -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [documentation/testing.md](file://documentation/testing.md#L3-L43) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L62-L81) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -## Architecture Overview -The CI/CD pipeline orchestrates Docker builds and publishes images to a registry. The main workflow runs on master pushes and produces two images: production and testnet. The PR workflow runs on pull requests and produces a testnet image tagged with the ref. The Dockerfiles use CMake to configure build-time features and produce minimal runtime images. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant GH as "GitHub Actions" -participant Repo as "Repository" -participant Docker as "Docker Registry" -Dev->>GH : Push to master -GH->>Repo : Checkout code -GH->>GH : Build production image -GH->>Docker : Push "latest" tag -GH->>GH : Build testnet image -GH->>Docker : Push "testnet" tag -Dev->>GH : Open Pull Request -GH->>Repo : Checkout PR code -GH->>GH : Build testnet image -GH->>Docker : Push "" tag -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L3-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L3-L24) - -## Detailed Component Analysis - -### Main Branch Workflow -- Triggers: Pushes to master branch with documentation and Markdown files excluded from triggering. -- Jobs: - - Build testnet image using Dockerfile-testnet and tag as testnet. - - Build production image using Dockerfile-production and tag as latest. -- Secrets: Uses DOCKER_USERNAME and DOCKER_PASSWORD for registry authentication. - -```mermaid -flowchart TD -Start(["Push to master"]) --> ExcludeDocs["paths-ignore documentation/** and *.md"] -ExcludeDocs --> BuildTestnet["Job: build_testnet
Dockerfile-testnet -> tag: testnet"] -ExcludeDocs --> BuildProd["Job: build_prod
Dockerfile-production -> tag: latest"] -BuildTestnet --> Publish["Publish to registry"] -BuildProd --> Publish -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L3-L41) - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L3-L41) - -### Pull Request Workflow -- Triggers: Pull requests with documentation and Markdown files excluded from triggering. -- Job: - - Build testnet image using Dockerfile-testnet and tag with ref for traceability. -- Secrets: Uses DOCKER_USERNAME and DOCKER_PASSWORD for registry authentication. - -```mermaid -flowchart TD -PRStart(["Pull Request opened"]) --> ExcludeDocsPR["paths-ignore documentation/** and *.md"] -ExcludeDocsPR --> BuildPR["Job: build_testnet
Dockerfile-testnet -> tag: "] -BuildPR --> PublishPR["Publish to registry"] -``` - -**Diagram sources** -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L3-L24) - -**Section sources** -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L3-L24) - -### Docker Build Matrix and Variants -- Production: Built with release flags and standard plugins. -- Testnet: Built with BUILD_TESTNET enabled and testnet-specific config. -- Low-memory: Built with LOW_MEMORY_NODE enabled to reduce resource usage. -- MongoDB: Built with ENABLE_MONGO_PLUGIN enabled and MongoDB driver installation. - -```mermaid -graph LR -CMake["CMake options"] --> Prod["Production"] -CMake --> Testnet["Testnet"] -CMake --> LowMem["Low Memory"] -CMake --> Mongo["MongoDB"] -Prod --- DFProd["Dockerfile-production"] -Testnet --- DFTNet["Dockerfile-testnet"] -LowMem --- DFLow["Dockerfile-lowmem"] -Mongo --- DFMongo["Dockerfile-mongo"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L46-L53) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L82) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L46-L53) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L82) - -### Automated Testing and Code Quality -- Unit tests: - - Target: chain_test. - - Execution: ./tests/chain_test. - - Configuration options include log level, report level, and selective test execution. -- Code coverage: - - Enable via CMake flag. - - Capture baseline and test coverage with lcov and generate HTML reports. - -```mermaid -flowchart TD -Build["Build with CMake"] --> Tests["Run chain_test"] -Tests --> Report["Generate test report"] -Build --> Coverage["Enable coverage in CMake"] -Coverage --> Lcov["Capture coverage with lcov"] -Lcov --> Html["Generate HTML report"] -``` - -**Diagram sources** -- [documentation/testing.md](file://documentation/testing.md#L3-L43) - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L3-L43) - -### Runtime Configuration and Entrypoint -- Entrypoint script configures RPC and P2P endpoints, seed nodes, optional replay from cached snapshot, and passes extra options. -- Config templates: - - Mainnet: config.ini. - - Testnet: config_testnet.ini. - -```mermaid -flowchart TD -Start(["Container start"]) --> Seed["Resolve seed nodes"] -Seed --> Args["Compose arguments"] -Args --> Replay{"Snapshot exists?"} -Replay --> |Yes| Init["Initialize from cached snapshot"] -Replay --> |No| Skip["Skip replay"] -Init --> Run["Start vizd with configured endpoints"] -Skip --> Run -``` - -**Diagram sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L13-L81) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L13-L81) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -## Dependency Analysis -- Workflow-to-Dockerfile dependencies: - - docker-main.yml depends on Dockerfile-production and Dockerfile-testnet. - - docker-pr-build.yml depends on Dockerfile-testnet. -- Build-time configuration: - - Dockerfiles depend on CMake options to enable/disable features and select variants. -- Runtime configuration: - - Entrypoint script depends on config templates and snapshot availability. - -```mermaid -graph TB -MAIN[".github/workflows/docker-main.yml"] --> PROD["Dockerfile-production"] -MAIN --> TESTNET["Dockerfile-testnet"] -PR[".github/workflows/docker-pr-build.yml"] --> TESTNET -PROD --> CMAKE["CMakeLists.txt"] -TESTNET --> CMAKE -LOWMEM["Dockerfile-lowmem"] --> CMAKE -MONGO["Dockerfile-mongo"] --> CMAKE -ENTRY["vizd.sh"] --> CFG["config.ini / config_testnet.ini"] -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [share/vizd/docker/Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [share/vizd/docker/Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) -- [CMakeLists.txt](file://CMakeLists.txt#L46-L89) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [CMakeLists.txt](file://CMakeLists.txt#L46-L89) - -## Performance Considerations -- Build performance: - - Use ccache via CMake to speed up rebuilds. - - Multi-stage builds minimize runtime image size and improve cold start. -- Resource usage: - - Low-memory variant reduces memory footprint for constrained environments. -- Network and storage: - - Entrypoint supports replay from cached snapshot to accelerate initial sync. -- Parallelism: - - Use matrix strategies to run multiple variants concurrently when extending the pipeline. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Authentication failures: - - Verify DOCKER_USERNAME and DOCKER_PASSWORD secrets are set in repository settings. -- Build failures due to missing dependencies: - - Ensure Dockerfiles install required packages and CMake options match intended variant. -- Test failures: - - Confirm chain_test target is available and test runner options are correctly passed. -- Coverage reporting: - - Ensure lcov is installed and CMake coverage flag is enabled during build. -- Image tagging: - - For PR builds, confirm tag_with_ref is enabled to attach the ref to the image tag. - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L19-L25) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L17-L23) -- [documentation/testing.md](file://documentation/testing.md#L3-L43) - -## Conclusion -The CI/CD pipeline automates robust Docker image builds for VIZ C++ Node across multiple variants, integrates testing and code coverage, and publishes images to a registry. By leveraging CMake options and multi-stage Dockerfiles, the pipeline supports production, testnet, low-memory, and MongoDB-enabled deployments. Extending the pipeline involves adding jobs, Dockerfiles, and CMake toggles while maintaining consistent tagging and secret management. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Examples - -- Modify workflow configurations: - - Add a new job to build a variant image by referencing a Dockerfile and setting appropriate tags. - - Adjust paths-ignore to include/exclude specific directories from triggering builds. - - Reference: [docker-main.yml](file://.github/workflows/docker-main.yml#L11-L41), [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L9-L24) - -- Add a new build stage: - - Extend an existing Dockerfile or create a new Dockerfile variant. - - Introduce a corresponding CMake option and update the workflow to consume it. - - Reference: [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54), [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L46-L53), [CMakeLists.txt](file://CMakeLists.txt#L56-L89) - -- Troubleshoot pipeline failures: - - Check logs for authentication errors and verify secrets. - - Validate Docker build steps and ensure dependencies are installed. - - Confirm test targets and coverage tools are available and configured. - - Reference: [docker-main.yml](file://.github/workflows/docker-main.yml#L19-L25), [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L17-L23), [testing.md](file://documentation/testing.md#L3-L43) - -### Best Practices for Blockchain CI/CD -- Reproducible builds: - - Pin Docker base images and toolchain versions; rely on CMake to enforce consistent flags. -- Security: - - Store registry credentials as encrypted secrets; limit permissions; scan images post-build. -- Performance: - - Use caching (ccache, Docker layers); parallelize independent jobs; prune unused artifacts. -- Reliability: - - Validate images with smoke tests; publish multiple tags for traceability; document rollback procedures. - -[No sources needed since this section provides general guidance] \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Low-Memory Dockerfile.md b/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Low-Memory Dockerfile.md deleted file mode 100644 index 184cafaf4e..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Low-Memory Dockerfile.md +++ /dev/null @@ -1,316 +0,0 @@ -# Low-Memory Dockerfile - - -**Referenced Files in This Document** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [CMakeLists.txt](file://CMakeLists.txt) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [vizd.sh](file://share/vizd/vizd.sh) -- [database.cpp](file://libraries/chain/database.cpp) -- [account_api_object.cpp](file://libraries/api/account_api_object.cpp) -- [plugin.cpp](file://plugins/tags/plugin.cpp) -- [social_network.cpp](file://plugins/social_network/social_network.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the low-memory Dockerfile optimized for resource-constrained environments. It details the LOW_MEMORY_NODE build option, its impact on memory usage, reduced functionality, and performance trade-offs. It also documents minimal dependency requirements, the optimized build process, and the reduced feature set compared to production builds. Practical examples for building low-memory containers, resource monitoring, and performance optimization techniques are provided, along with limitations, supported operations, and migration paths to full-featured builds. - -## Project Structure -The low-memory container is built from a dedicated Dockerfile that mirrors the production build process but enables the LOW_MEMORY_NODE CMake option. The Dockerfile sets up a minimal build environment, clones the repository, initializes submodules, configures CMake with low-memory flags, compiles, installs, and packages a runtime image with a non-root user and essential volumes. - -```mermaid -graph TB -A["Dockerfile-lowmem
Defines base image, build deps, CMake flags"] --> B["Builder Stage
Clone repo, init submodules, cmake -DLOW_MEMORY_NODE=TRUE"] -B --> C["Install artifacts to /usr/local"] -C --> D["Runtime Stage
Copy /usr/local, create non-root user, expose ports, mount volumes"] -D --> E["Container Entrypoint
vizd.sh starts node with config and optional snapshot"] -``` - -**Diagram sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) - -**Section sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) - -## Core Components -- Low-memory Dockerfile: Builds with LOW_MEMORY_NODE enabled, disabling extra indexes and metadata storage to reduce memory footprint. -- CMake configuration: Adds -DIS_LOW_MEM and toggles related compile-time flags. -- Runtime configuration: Minimal RPC endpoints, reduced thread pools, and conservative shared memory sizing. -- Entrypoint script: Initializes data directory, optionally replays a cached snapshot, and launches the node with configurable endpoints. - -Key differences from production: -- LOW_MEMORY_NODE=TRUE vs FALSE -- Reduced plugin surface and indexes -- Smaller shared memory defaults and cautious growth thresholds - -**Section sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L53) -- [CMakeLists.txt](file://CMakeLists.txt#L66-L74) -- [config.ini](file://share/vizd/config/config.ini#L49-L67) -- [vizd.sh](file://share/vizd/vizd.sh#L44-L53) - -## Architecture Overview -The low-memory container architecture separates build-time and runtime concerns: -- Build stage: Installs minimal toolchain and dependencies, configures CMake with LOW_MEMORY_NODE, compiles, and installs binaries. -- Runtime stage: Copies installed binaries, creates a non-root user, exposes RPC and P2P ports, mounts persistent volumes for blockchain data and config, and runs via an init service wrapper. - -```mermaid -graph TB -subgraph "Build Stage" -L1["apt-get install minimal deps"] --> L2["git submodule update"] -L2 --> L3["cmake -DLOW_MEMORY_NODE=TRUE ..."] -L3 --> L4["make -j$(nproc)"] -L4 --> L5["make install"] -end -subgraph "Runtime Stage" -R1["FROM baseimage"] --> R2["COPY --from=builder /usr/local"] -R2 --> R3["useradd vizd, chown cache dir"] -R3 --> R4["COPY config.ini, seednodes, vizd.sh"] -R4 --> R5["EXPOSE 8090, 8091, 2001"] -R5 --> R6["VOLUME [/var/lib/vizd, /etc/vizd]"] -R6 --> R7["Entrypoint: vizd.sh"] -end -L5 --> R2 -``` - -**Diagram sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L7-L59) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L40-L59) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Detailed Component Analysis - -### Low-Memory Build Option Impact -The LOW_MEMORY_NODE option adds -DIS_LOW_MEM and disables certain indexes and metadata storage to reduce memory usage. Conditional compilation blocks appear across chain logic and plugins. - -```mermaid -flowchart TD -Start(["Configure CMake"]) --> Check{"LOW_MEMORY_NODE enabled?"} -Check --> |Yes| AddFlag["Add -DIS_LOW_MEM"] -AddFlag --> DisableIndexes["Disable extra indexes and metadata storage"] -DisableIndexes --> Compile["Compile with reduced feature set"] -Check --> |No| FullBuild["Full feature build"] -Compile --> Install["Install artifacts"] -FullBuild --> Install -Install --> Runtime["Runtime image"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L66-L74) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L66-L74) -- [database.cpp](file://libraries/chain/database.cpp#L2221-L2225) -- [database.cpp](file://libraries/chain/database.cpp#L2269-L2278) -- [database.cpp](file://libraries/chain/database.cpp#L3063-L3067) -- [database.cpp](file://libraries/chain/database.cpp#L3078-L3082) -- [database.cpp](file://libraries/chain/database.cpp#L3098-L3103) -- [database.cpp](file://libraries/chain/database.cpp#L3120-L3124) -- [database.cpp](file://libraries/chain/database.cpp#L3144-L3148) -- [database.cpp](file://libraries/chain/database.cpp#L3170-L3174) -- [account_api_object.cpp](file://libraries/api/account_api_object.cpp#L40-L43) -- [plugin.cpp](file://plugins/tags/plugin.cpp#L42-L52) -- [plugin.cpp](file://plugins/tags/plugin.cpp#L211-L220) -- [social_network.cpp](file://plugins/social_network/social_network.cpp#L372-L392) - -### Memory-Efficient Chain Initialization -Genesis initialization and account creation are guarded by low-memory conditionals to skip metadata objects when memory is constrained. - -```mermaid -flowchart TD -A["Initialize genesis accounts"] --> B{"IS_LOW_MEM?"} -B --> |Yes| C["Skip metadata objects for accounts"] -B --> |No| D["Create metadata objects for accounts"] -C --> E["Continue initialization"] -D --> E -``` - -**Diagram sources** -- [database.cpp](file://libraries/chain/database.cpp#L3063-L3067) -- [database.cpp](file://libraries/chain/database.cpp#L3078-L3082) -- [database.cpp](file://libraries/chain/database.cpp#L3098-L3103) -- [database.cpp](file://libraries/chain/database.cpp#L3120-L3124) -- [database.cpp](file://libraries/chain/database.cpp#L3144-L3148) -- [database.cpp](file://libraries/chain/database.cpp#L3170-L3174) - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3060-L3182) - -### Tags Plugin Index Management -The tags plugin conditionally registers indexes and hooks to reduce memory overhead under low-memory mode. - -```mermaid -sequenceDiagram -participant Init as "tags_plugin : : plugin_initialize" -participant DB as "database" -Init->>Init : "#ifndef IS_LOW_MEM" -Init->>DB : "add_plugin_index(tag_index)" -Init->>DB : "add_plugin_index(tag_stats_index)" -Init->>DB : "add_plugin_index(author_tag_stats_index)" -Init->>DB : "add_plugin_index(language_index)" -Init-->>Init : "#endif" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/tags/plugin.cpp#L211-L220) - -**Section sources** -- [plugin.cpp](file://plugins/tags/plugin.cpp#L42-L52) -- [plugin.cpp](file://plugins/tags/plugin.cpp#L211-L220) - -### Social Network Discussions Retrieval -Discussion retrieval APIs are guarded by low-memory conditionals to avoid heavy indexing operations. - -```mermaid -sequenceDiagram -participant API as "social_network : : get_replies_by_last_update" -participant Impl as "impl : : get_replies_by_last_update" -participant DB as "database" -API->>Impl : "validate args and limits" -Impl->>Impl : "#ifndef IS_LOW_MEM" -Impl->>DB : "iterate by_last_update index" -Impl-->>API : "return discussions" -Impl->>Impl : "#endif" -``` - -**Diagram sources** -- [social_network.cpp](file://plugins/social_network/social_network.cpp#L372-L392) - -**Section sources** -- [social_network.cpp](file://plugins/social_network/social_network.cpp#L370-L394) - -### Runtime Entrypoint and Snapshot Replay -The entrypoint script prepares the data directory, optionally replays a cached snapshot, and launches the node with configurable endpoints. - -```mermaid -flowchart TD -S["Start vizd.sh"] --> U["Ensure ownership of /var/lib/vizd"] -U --> C["Copy /etc/vizd/config.ini -> $HOME/config.ini"] -C --> D{"blockchain dir exists?"} -D --> |No| E{"blocks.tbz2 present in cache?"} -E --> |Yes| F["Replay cached snapshot"] -E --> |No| G["Proceed without snapshot"] -D --> |Yes| H["Proceed without snapshot"] -F --> I["Launch vizd with endpoints and seed nodes"] -G --> I -H --> I -``` - -**Diagram sources** -- [vizd.sh](file://share/vizd/vizd.sh#L44-L81) - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Dependency Analysis -The low-memory Dockerfile depends on: -- Base image for build and runtime stages -- Minimal toolchain and libraries for compilation -- CMake configuration toggling LOW_MEMORY_NODE and related flags -- Runtime configuration files and entrypoint script - -```mermaid -graph LR -CM["CMakeLists.txt
LOW_MEMORY_NODE option"] --> DF["Dockerfile-lowmem
cmake -DLOW_MEMORY_NODE=TRUE"] -DF --> IMG["Built Image"] -CFG["config.ini
shared memory defaults"] --> IMG -SH["vizd.sh
entrypoint"] --> IMG -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L66-L74) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L45-L53) -- [config.ini](file://share/vizd/config/config.ini#L49-L67) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L1-L82) -- [CMakeLists.txt](file://CMakeLists.txt#L66-L74) -- [config.ini](file://share/vizd/config/config.ini#L49-L67) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Performance Considerations -- Shared memory sizing: Defaults are tuned to reduce initial allocation and growth frequency. -- Thread pool sizing: Conservative thread counts for RPC clients. -- Lock contention: Single write thread reduces database lock contention. -- Plugin notifications: Disabled on push transactions to reduce overhead. -- MongoDB plugin: Explicitly disabled in low-memory builds. - -Practical tips: -- Monitor shared memory free space and adjust growth increments if needed. -- Keep RPC thread pool size minimal for constrained systems. -- Disable non-essential plugins to further reduce memory usage. -- Use snapshot replay to bootstrap quickly without replaying entire chain. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L13-L47) -- [config.ini](file://share/vizd/config/config.ini#L49-L67) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L50-L50) - -## Troubleshooting Guide -Common issues and remedies: -- Out-of-memory during startup: Reduce shared memory growth increments or disable non-essential plugins. -- Slow RPC response: Lower thread pool size or disable plugin notifications on push. -- Missing metadata in API responses: Expected in low-memory mode; metadata is intentionally omitted. -- Snapshot replay failures: Verify cached snapshot presence and permissions. - -Operational checks: -- Confirm LOW_MEMORY_NODE is enabled in the build. -- Validate runtime configuration overrides via environment variables. -- Inspect logs written to configured appenders. - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L112-L130) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L81) - -## Conclusion -The low-memory Dockerfile provides a compact, resource-efficient node suitable for constrained environments. By enabling LOW_MEMORY_NODE, the build reduces indexes and metadata storage, lowers shared memory defaults, and simplifies operational overhead. While some features are intentionally disabled, it remains capable of serving basic RPC needs and can be migrated to full-featured builds when resources permit. - -## Appendices - -### Building Low-Memory Containers -- Build command: Use the low-memory Dockerfile to produce a minimal image. -- Environment overrides: Configure endpoints and seed nodes via environment variables injected by the entrypoint script. -- Persistent volumes: Mount data and config directories to preserve state across restarts. - -**Section sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L60-L82) -- [vizd.sh](file://share/vizd/vizd.sh#L13-L39) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L81) - -### Supported Operations Under Low-Memory Mode -- Basic RPC endpoints remain available. -- Genesis initialization proceeds without metadata objects. -- Some plugin indexes and operations are disabled to conserve memory. - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L3063-L3067) -- [database.cpp](file://libraries/chain/database.cpp#L3078-L3082) -- [plugin.cpp](file://plugins/tags/plugin.cpp#L211-L220) -- [social_network.cpp](file://plugins/social_network/social_network.cpp#L372-L392) - -### Migration Paths to Full-Featured Builds -- Switch to the production Dockerfile to enable full feature set. -- Re-enable MongoDB plugin if persistence to MongoDB is required. -- Increase shared memory defaults and thread pool sizes for higher throughput. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L52) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L74-L82) -- [config.ini](file://share/vizd/config/config.ini#L49-L67) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/MongoDB Integration Dockerfile.md b/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/MongoDB Integration Dockerfile.md deleted file mode 100644 index 2bc98da404..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/MongoDB Integration Dockerfile.md +++ /dev/null @@ -1,434 +0,0 @@ -# MongoDB Integration Dockerfile - - -**Referenced Files in This Document** -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [CMakeLists.txt](file://CMakeLists.txt) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_writer.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_writer.hpp) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp) -- [plugins/mongo_db/mongo_db_types.cpp](file://plugins/mongo_db/mongo_db_types.cpp) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [share/vizd/config/config_debug_mongo.ini](file://share/vizd/config/config_debug_mongo.ini) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the MongoDB-enabled Dockerfile variant that integrates VIZ CPP Node with MongoDB for enhanced indexing and query capabilities. It covers the ENABLE_MONGO_PLUGIN CMake option, the MongoDB plugin architecture, data synchronization mechanisms, and performance characteristics. It also documents container setup, connection configuration, persistence strategies, and practical examples for enabling MongoDB integration, configuring replica sets, and optimizing queries. Finally, it compares the trade-offs between traditional SQLite-based storage and MongoDB integration, including storage requirements, backups, and migration procedures. - -## Project Structure -The MongoDB integration spans Docker build configuration, CMake build options, and the MongoDB plugin implementation. The Dockerfile variant installs MongoDB C drivers and enables the MongoDB plugin during build. The plugin connects to MongoDB, listens to blockchain events, transforms state and operations into documents, and writes them to collections with secondary indexes. - -```mermaid -graph TB -subgraph "Docker Build" -DF["Dockerfile-mongo
Installs MongoDB C/C++ drivers
Builds with ENABLE_MONGO_PLUGIN=TRUE"] -CFG["config_mongo.ini
Enables mongo_db plugin
Sets mongodb-uri"] -end -subgraph "CMake Build" -CM["CMakeLists.txt
ENABLE_MONGO_PLUGIN option"] -PM["plugins/mongo_db/CMakeLists.txt
Finds libmongocxx/libbsoncxx
Links targets"] -end -subgraph "Plugin Runtime" -PLUG["mongo_db_plugin
App base plugin"] -WR["mongo_db_writer
Connects to MongoDB
Bulk writes"] -STATE["state_writer
Formats docs per operation"] -TYPES["mongo_db_types
named_document, helpers"] -end -DF --> CM -CM --> PM -CFG --> PLUG -PLUG --> WR -WR --> STATE -WR --> TYPES -``` - -**Diagram sources** -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L1-L81) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L54-L125) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L133) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp#L25-L29) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp#L40-L85) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L72) - -**Section sources** -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L1-L111) -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L1-L81) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L72) - -## Core Components -- ENABLE_MONGO_PLUGIN CMake option: Controls whether the MongoDB plugin is built and linked into the node. When enabled, the plugin target is added and linked against libmongocxx and libbsoncxx. -- Dockerfile-mongo: Installs MongoDB C drivers (mongo-c-driver and mongo-cxx-driver), clones and builds the C++ driver, and passes -DENABLE_MONGO_PLUGIN=TRUE to cmake. -- mongo_db_plugin: An appbase plugin that registers CLI/config options, initializes the MongoDB writer, subscribes to applied_block and post_apply_operation signals, and coordinates writes. -- mongo_db_writer: Manages MongoDB connection, bulk writes, document formatting, and index creation on first use per collection. -- state_writer: Visitor that formats blockchain state and operations into named_document entries with upsert semantics and optional removal updates. -- mongo_db_types: Provides document metadata (collection name, key, indexes), hashing helpers, and a multi-index container for efficient deduplication and updates. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L1-L81) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L31-L58) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L54-L125) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_writer.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_writer.hpp#L42-L90) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L133) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp#L19-L77) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp#L25-L29) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp#L40-L85) - -## Architecture Overview -The MongoDB integration is layered: -- Build-time: ENABLE_MONGO_PLUGIN toggles inclusion of the mongo_db target and links MongoDB C++ drivers. -- Runtime: The plugin initializes a MongoDB client, subscribes to chain events, formats documents via state_writer, and performs bulk writes with upserts and selective index creation. - -```mermaid -sequenceDiagram -participant Node as "vizd node" -participant Plugin as "mongo_db_plugin" -participant Writer as "mongo_db_writer" -participant DB as "MongoDB" -Node->>Plugin : Initialize plugin with options -Plugin->>Writer : initialize(uri, write_raw, ops) -Writer->>DB : Connect via URI -Note over Writer,DB : Connection established -Node->>Plugin : applied_block(block) -Plugin->>Writer : on_block(block) -Writer->>Writer : Buffer blocks until LRB -Writer->>Writer : Format docs via state_writer -Writer->>DB : Bulk insert/update per collection -DB-->>Writer : Acknowledged write -Node->>Plugin : post_apply_operation(op) -Plugin->>Writer : on_operation(op) -Writer->>Writer : Track virtual ops for rollback -``` - -**Diagram sources** -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L71-L113) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L68-L133) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L272-L294) - -## Detailed Component Analysis - -### ENABLE_MONGO_PLUGIN CMake Option -- Purpose: Enables MongoDB plugin compilation and links against libmongocxx and libbsoncxx. -- Behavior: - - When TRUE: Adds the mongo_db target, finds libmongocxx and libbsoncxx, links them, and exports the target. - - When FALSE: Skips plugin build and linking. -- Impact on dependencies: Requires MongoDB C drivers to be installed in the build environment for successful linking. - -```mermaid -flowchart TD -Start(["Configure CMake"]) --> CheckOpt["ENABLE_MONGO_PLUGIN = TRUE?"] -CheckOpt --> |Yes| FindDrivers["Find libmongocxx and libbsoncxx"] -FindDrivers --> AddTarget["Add mongo_db target
Link drivers and deps"] -AddTarget --> Export["Export graphene::mongo_db"] -CheckOpt --> |No| Skip["Skip mongo_db build"] -Export --> End(["Build Complete"]) -Skip --> End -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L15-L67) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L1-L81) - -### Dockerfile-mongo: MongoDB Driver Installation and Build -- Installs mongo-c-driver and mongo-cxx-driver from source. -- Configures and builds the C++ driver with a static prefix. -- Builds the project with -DENABLE_MONGO_PLUGIN=TRUE and installs artifacts. - -```mermaid -flowchart TD -DStart(["Dockerfile-mongo build"]) --> InstallC["Install mongo-c-driver"] -InstallC --> InstallCpp["Clone mongo-cxx-driver
Build and install"] -InstallCpp --> CopySrc["Copy repo sources"] -CopySrc --> Configure["cmake -DENABLE_MONGO_PLUGIN=TRUE .."] -Configure --> Make["make -j$(nproc)"] -Make --> Install["make install"] -Install --> DEnd(["Image ready"]) -``` - -**Diagram sources** -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L31-L87) - -**Section sources** -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L31-L87) - -### mongo_db_plugin: Lifecycle and Event Subscription -- Registers CLI options for mongodb-uri, mongodb-write-raw-blocks, and mongodb-write-operations. -- Initializes the writer with parsed options and subscribes to: - - applied_block: triggers block processing. - - post_apply_operation: tracks virtual operations for rollback safety. - -```mermaid -classDiagram -class mongo_db_plugin { -+set_program_options(cli, cfg) -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() --pimpl_ : mongo_db_plugin_impl -} -class mongo_db_plugin_impl { -+initialize(uri, write_raw, ops) bool -+on_block(block) -+on_operation(notification) --writer : mongo_db_writer --database() : database& -} -mongo_db_plugin --> mongo_db_plugin_impl : "owns" -``` - -**Diagram sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L54-L125) - -**Section sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L54-L125) - -### mongo_db_writer: Connection, Buffering, and Bulk Writes -- Establishes connection from URI, selects database, configures unordered bulk operations. -- Buffers blocks until last irreversible block advances, formats documents, and writes via bulk operations. -- Creates secondary indexes on first use per collection and supports removal updates. - -```mermaid -flowchart TD -Init(["initialize(uri, write_raw, ops)"]) --> Conn["Connect to MongoDB"] -Conn --> Loop["on_block(block)"] -Loop --> Buffer["Buffer by block_num"] -Buffer --> LRB{"Block <= LRB?"} -LRB --> |Yes| Format["Format docs via state_writer"] -Format --> Upsert["Upsert or Insert per doc"] -Upsert --> MaybeIndex{"Index created?"} -MaybeIndex --> |No| CreateIdx["Create indexes"] -MaybeIndex --> |Yes| Next["Next block"] -LRB --> |No| Wait["Wait for LRB advance"] -CreateIdx --> Flush["write_data() bulk write"] -Next --> Flush -Flush --> Done(["Processed"]) -``` - -**Diagram sources** -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L133) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L206-L233) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L272-L294) - -**Section sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_writer.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_writer.hpp#L42-L90) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L133) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L206-L233) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L272-L294) - -### state_writer: Operation-to-Document Formatting -- Implements visitor operators for various operations (e.g., vote, content, author_reward, curation_reward, content_reward, content_benefactor_reward). -- Produces named_document entries with: - - collection_name: target MongoDB collection. - - key/keyval: composite key for deduplication/upsert. - - indexes_to_create: secondary index specs (e.g., content root content, content votes). - - $set body: fields to update. -- Supports removal documents for soft-deletion semantics. - -```mermaid -classDiagram -class state_writer { -+operator()(vote_operation) -+operator()(content_operation) -+operator()(author_reward_operation) -+operator()(curation_reward_operation) -+operator()(content_reward_operation) -+operator()(content_benefactor_reward_operation) --create_document(name, key, keyval) named_document --create_removal_document(name, key, keyval) named_document --db_ : database& --state_block : signed_block --all_docs : db_map -} -class named_document { -+string collection_name -+document doc -+bool is_removal -+vector indexes_to_create -+string key -+string keyval -} -state_writer --> named_document : "produces" -``` - -**Diagram sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp#L19-L77) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp#L31-L49) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp#L40-L85) - -**Section sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_state.hpp#L19-L77) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp#L203-L248) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp#L411-L473) -- [plugins/mongo_db/mongo_db_state.cpp](file://plugins/mongo_db/mongo_db_state.cpp#L501-L530) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp#L40-L85) - -### mongo_db_types: Data Structures and Helpers -- named_document: carries collection metadata, BSON document payload, keying, and index specs. -- db_map: multi_index container with random access and hashed unique index for efficient deduplication and replacement. -- Hashing and formatting helpers: SHA1-based OID generation and typed field formatters for BSON. - -```mermaid -classDiagram -class named_document { -+string collection_name -+document doc -+bool is_removal -+vector indexes_to_create -+string key -+string keyval -} -class db_map { -<> -+random_access -+hashed_unique(composite_key) -} -class helpers { -+hash_oid(value) string -+format_oid(doc, name, value) -+format_value(doc, name, value) -} -db_map --> named_document : "stores" -``` - -**Diagram sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp#L40-L85) -- [plugins/mongo_db/mongo_db_types.cpp](file://plugins/mongo_db/mongo_db_types.cpp#L7-L14) - -**Section sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp#L40-L85) -- [plugins/mongo_db/mongo_db_types.cpp](file://plugins/mongo_db/mongo_db_types.cpp#L7-L14) - -## Dependency Analysis -- Build dependencies: - - Dockerfile-mongo installs mongo-c-driver and mongo-cxx-driver and passes -DENABLE_MONGO_PLUGIN=TRUE. - - plugins/mongo_db/CMakeLists.txt requires libmongocxx and libbsoncxx to be found and links them to the mongo_db target. - - Root CMakeLists.txt defines ENABLE_MONGO_PLUGIN and conditionally sets compiler flags and library linkage. -- Runtime dependencies: - - mongo_db_plugin depends on chain plugin and JSON RPC plugin. - - mongo_db_writer depends on mongocxx client and BSON builders. - -```mermaid -graph LR -RootCMake["Root CMakeLists.txt
ENABLE_MONGO_PLUGIN"] --> PluginCMake["plugins/mongo_db/CMakeLists.txt"] -PluginCMake --> Drivers["libmongocxx / libbsoncxx"] -Docker["Dockerfile-mongo"] --> Drivers -PluginCMake --> Target["graphene::mongo_db target"] -Target --> AppBase["appbase / fc / graphene libs"] -Plugin["mongo_db_plugin"] --> Chain["chain plugin"] -Plugin --> Writer["mongo_db_writer"] -Writer --> Mongo["MongoDB server"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L15-L67) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L31-L58) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L17-L19) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L66) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L82-L89) -- [plugins/mongo_db/CMakeLists.txt](file://plugins/mongo_db/CMakeLists.txt#L15-L67) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L31-L58) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L17-L19) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L66) - -## Performance Considerations -- Bulk writes: Unordered bulk operations reduce latency by batching inserts/updates per collection. -- Index creation: Indexes are created on first use per collection to avoid repeated overhead. -- Last irreversible block (LRB) buffering: Ensures writes occur only after blocks become irreversible, preventing rollback costs. -- Optional raw block writes: Can be disabled to reduce storage and improve performance when only state/operation indexing is needed. -- Virtual operations: Only virtual operations are written to plugins, reducing unnecessary processing for non-virtual ops. -- Shared memory sizing and thread pool tuning: Configuration options in the MongoDB config help balance concurrency and lock contention. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- MongoDB plugin disabled: - - Cause: mongodb-uri not provided or initialization fails. - - Action: Verify config_mongo.ini has a valid mongodb-uri and plugin list includes mongo_db. -- Driver installation failures: - - Cause: Missing system packages or incompatible driver versions. - - Action: Ensure Dockerfile-mongo installs mongo-c-driver and mongo-cxx-driver; confirm cmake can find libmongocxx and libbsoncxx. -- Connection errors: - - Cause: Incorrect URI, unreachable host, or authentication issues. - - Action: Validate URI in config_mongo.ini; check container networking and firewall rules. -- Bulk write failures: - - Cause: Temporary server issues or malformed documents. - - Action: Review logs around write_data(); ensure indexes are compatible with document shapes. -- Index creation errors: - - Cause: Conflicting index definitions or server-side constraints. - - Action: Inspect index specs generated by state_writer and adjust as needed. - -**Section sources** -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L84-L109) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L39-L66) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L272-L294) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L72) - -## Conclusion -The MongoDB-enabled Dockerfile variant integrates VIZ CPP Node with MongoDB through a dedicated plugin that listens to blockchain events, formats documents, and performs efficient bulk writes with secondary indexing. The ENABLE_MONGO_PLUGIN CMake option controls build inclusion and links required drivers. Container setup ensures drivers are present and the node is configured to enable the plugin and connect to MongoDB. While MongoDB adds query flexibility and indexing capabilities, it introduces operational overhead and storage considerations compared to native storage. Proper configuration, index planning, and monitoring are essential for reliable performance. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Examples - -- Enabling MongoDB integration in Docker: - - Use the MongoDB Dockerfile to build an image with drivers and ENABLE_MONGO_PLUGIN=TRUE. - - Mount persistent volumes for /var/lib/vizd and /etc/vizd. - - Expose ports 8090 (HTTP), 8091 (WebSocket), and 2001 (P2P) as defined in the Dockerfile. - -- Connecting to MongoDB: - - Set mongodb-uri in config_mongo.ini to point to your MongoDB instance or replica set. - - Example: mongodb://host:port/dbname or mongodb://user:pass@host:port/dbname with appropriate credentials. - -- Configuring replica sets: - - Deploy MongoDB with replica set configuration. - - Update mongodb-uri to include replica set hosts and set appropriate read/write concerns in the MongoDB client options if needed. - -- Optimizing query performance: - - Leverage secondary indexes created per collection by state_writer (e.g., content root content, content votes). - - Limit tracked operations via mongodb-write-operations to reduce write volume. - - Disable raw block writes if only state/operation indexing is required. - -- Trade-offs and migration: - - Storage requirements: MongoDB documents can be larger than native storage depending on normalization; monitor disk usage. - - Backup strategies: Use MongoDB-native backup tools (e.g., logical dumps or snapshot-based backups) alongside chain snapshots. - - Migration: To switch from native storage to MongoDB, deploy the MongoDB-enabled node, allow indexing to complete, and validate queries. Back up MongoDB collections before major upgrades. - -**Section sources** -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L97-L111) -- [share/vizd/config/config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L72) -- [plugins/mongo_db/mongo_db_writer.cpp](file://plugins/mongo_db/mongo_db_writer.cpp#L227-L232) -- [plugins/mongo_db/mongo_db_plugin.cpp](file://plugins/mongo_db/mongo_db_plugin.cpp#L58-L67) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Production Dockerfile.md b/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Production Dockerfile.md deleted file mode 100644 index 7843edceee..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Production Dockerfile.md +++ /dev/null @@ -1,347 +0,0 @@ -# Production Dockerfile - - -**Referenced Files in This Document** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [vizd.sh](file://share/vizd/vizd.sh) -- [config.ini](file://share/vizd/config/config.ini) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini) -- [README.md](file://README.md) -- [building.md](file://documentation/building.md) -- [docker-main.yml](file://.github/workflows/docker-main.yml) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the production Dockerfile configuration used to deploy the VIZ C++ node. It covers the multi-stage build process, base image selection, build environment setup, CMake configuration options, dependency installation, build optimization techniques, runtime image construction, user and volumes, and exposed ports. Practical examples for building and running the production container are included, along with guidance for persistent data and network configuration. - -## Project Structure -The production Dockerfile resides under the share/vizd/docker directory and is complemented by runtime scripts and configuration files. The CI/CD pipeline automates building and publishing the production image. - -```mermaid -graph TB -A["Dockerfile-production
Multi-stage build"] --> B["Builder stage
Ubuntu-based build env"] -A --> C["Runtime stage
Production image"] -D["vizd.sh
Entry script"] --> E["Runtime configuration
config.ini"] -F["CI/CD Workflow
docker-main.yml"] --> A -G["Low-memory variant
Dockerfile-lowmem"] --> C -H["MongoDB variant
Dockerfile-mongo"] --> C -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) - -## Core Components -- Multi-stage build: Separates build dependencies from the runtime image for smaller footprint and improved security. -- Base image: Uses phusion/baseimage:bionic-1.0.0 for both stages, ensuring a consistent, minimal Linux environment. -- Build environment: Installs essential build tools, Boost, OpenSSL, Python, and ccache. -- CMake configuration: Sets production defaults for shared libraries, memory profile, locking checks, and plugin enablement. -- Runtime setup: Creates a dedicated user, prepares cache and data directories, installs entry script and configuration, exposes ports, and defines volumes. -- CI/CD automation: Builds and pushes the production image tagged as latest. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [building.md](file://documentation/building.md#L3-L16) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L27-L41) - -## Architecture Overview -The production Dockerfile implements a two-stage build: -- Builder stage: Installs build dependencies, clones the repository, initializes submodules, configures CMake with production flags, compiles with parallel jobs, and installs artifacts. -- Runtime stage: Copies installed artifacts from the builder, creates a non-root user, sets up directories, installs the entry script and configuration, exposes ports, and declares volumes. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant CI as "CI/CD Pipeline" -participant Builder as "Builder Stage" -participant Runtime as "Runtime Stage" -participant Container as "Container Runtime" -Dev->>CI : Trigger build (master branch) -CI->>Builder : Build image with Dockerfile-production -Builder->>Builder : Install deps, clone repo, init submodules -Builder->>Builder : CMake configure (production flags) -Builder->>Builder : make -j$(nproc) -Builder->>Builder : make install -Builder-->>CI : Built artifact layer -CI->>Runtime : Build runtime image -Runtime->>Runtime : Copy artifacts, create user, setup dirs -Runtime->>Runtime : Install entry script & config -Runtime-->>CI : Push image to registry (tag : latest) -CI-->>Container : Pull latest image -Container->>Runtime : Start container -Runtime->>Container : Run entry script (vizd.sh) -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L27-L41) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Detailed Component Analysis - -### Multi-Stage Build Process -- Builder stage: - - Sets environment variables for locale, application directory, and home. - - Installs build tools, Boost, OpenSSL, Python, ccache, and auxiliary utilities. - - Copies only necessary source files to minimize rebuild triggers. - - Initializes Git submodules and configures CMake with production flags. - - Compiles with parallel jobs and installs artifacts. - - Cleans package cache to reduce image size. -- Runtime stage: - - Copies installed artifacts from the builder. - - Creates a non-root user and prepares cache/data directories. - - Installs the entry script and copies configuration files. - - Exposes ports and declares volumes for persistence. - -```mermaid -flowchart TD -Start(["Build Start"]) --> Env["Set ENV vars"] -Env --> InstallDeps["Install build deps
Boost, OpenSSL, Python, ccache"] -InstallDeps --> CopySrc["Copy minimal source tree"] -CopySrc --> Submodule["Init Git submodules"] -Submodule --> Configure["CMake configure
Production flags"] -Configure --> Build["make -j$(nproc)"] -Build --> Install["make install"] -Install --> Clean["Clean package cache"] -Clean --> Runtime["Runtime stage"] -Runtime --> CreateUser["Create non-root user"] -CreateUser --> SetupDirs["Setup cache/data dirs"] -SetupDirs --> InstallScript["Install entry script & config"] -InstallScript --> ExposePorts["Expose ports"] -ExposePorts --> Volumes["Declare volumes"] -Volumes --> End(["Image Ready"]) -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) - -### Base Image Selection -- phusion/baseimage:bionic-1.0.0 is used for both stages to ensure a stable, minimal Ubuntu 18.04-based environment with a service supervisor suitable for long-running containers. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) - -### Build Environment Setup -- Essential tools: autoconf, automake, autotools-dev, binutils, bsdmainutils, build-essential, cmake, doxygen, git, libtool, ncurses-dev, pbzip2, pkg-config. -- Libraries: libboost-all-dev, libreadline-dev, libssl-dev. -- Python: python3, python3-dev, python3-pip; gcovr installed via pip. -- ccache enabled for faster incremental builds. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L7-L30) - -### CMake Configuration Options -The production Dockerfile configures CMake with the following flags: -- CMAKE_BUILD_TYPE=Release -- BUILD_SHARED_LIBRARIES=FALSE -- LOW_MEMORY_NODE=FALSE -- CHAINBASE_CHECK_LOCKING=FALSE -- ENABLE_MONGO_PLUGIN=FALSE - -These choices optimize for a production node that: -- Builds optimized binaries. -- Links statically to reduce runtime dependencies. -- Does not enable low-memory mode. -- Disables extra locking checks. -- Disables the MongoDB plugin. - -```mermaid -flowchart TD -A["CMake Configure"] --> B["CMAKE_BUILD_TYPE=Release"] -A --> C["BUILD_SHARED_LIBRARIES=FALSE"] -A --> D["LOW_MEMORY_NODE=FALSE"] -A --> E["CHAINBASE_CHECK_LOCKING=FALSE"] -A --> F["ENABLE_MONGO_PLUGIN=FALSE"] -B --> G["Optimized binary"] -C --> H["Static linking"] -D --> I["Full-feature node"] -E --> J["Reduced overhead"] -F --> K["No MongoDB plugin"] -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L52) - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L52) -- [building.md](file://documentation/building.md#L3-L16) - -### Dependency Installation Process -- Boost: Installed via libboost-all-dev to satisfy library requirements. -- OpenSSL: Installed via libssl-dev for cryptographic operations. -- Python: Installed via python3, python3-dev, python3-pip; gcovr installed for coverage reporting. -- Development tools: cmake, git, build-essential, doxygen, autotools, etc. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L19-L30) - -### Build Optimization Techniques -- ccache: Enabled during build to accelerate incremental builds. -- Parallel compilation: make -j$(nproc) uses all CPU cores for faster builds. -- Static linking: BUILD_SHARED_LIBRARIES=FALSE reduces runtime dependencies. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L19-L30) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L54-L54) - -### Runtime Image Construction -- Artifact copy: Copies /usr/local from the builder to the runtime image. -- User creation: Creates a non-root user with a home directory under /var/lib/vizd. -- Cache/data directories: Prepares /var/cache/vizd and sets ownership. -- Entry script: Installs /etc/service/vizd/run to launch the node. -- Configuration: Copies config.ini to /etc/vizd/config.ini and seednodes to /etc/vizd/seednodes. -- Ports: Exposes 8090 (HTTP), 8091 (WebSocket), 2001 (P2P). -- Volumes: Declares persistent volumes for /var/lib/vizd and /etc/vizd. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -### Volume Mounting Configuration -- /var/lib/vizd: Contains blockchain data, configuration overrides, and logs. -- /etc/vizd: Contains initial configuration and seednode lists. -- The entry script ensures proper ownership and can initialize from a cached snapshot if present. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L87-L87) -- [vizd.sh](file://share/vizd/vizd.sh#L7-L53) - -### Exposed Ports -- 8090: HTTP RPC endpoint. -- 8091: WebSocket RPC endpoint. -- 2001: P2P endpoint. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L79-L85) -- [config.ini](file://share/vizd/config/config.ini#L16-L20) - -### Practical Examples - -#### Building the Production Container -- Build locally: - - docker build -t viz:latest -f share/vizd/docker/Dockerfile-production . -- Build with CI/CD: - - The workflow builds and pushes the image tagged as latest. - -**Section sources** -- [README.md](file://README.md#L40-L52) -- [docker-main.yml](file://.github/workflows/docker-main.yml#L27-L41) - -#### Running the Production Container -- Basic run: - - docker run -d -p 2001:2001 -p 8090:8090 -p 8091:8091 --name vizd vizblockchain/vizd:latest -- Attach logs: - - docker logs -f vizd -- Override RPC/P2P endpoints: - - Set VIZD_RPC_ENDPOINT and VIZD_P2P_ENDPOINT environment variables. -- Provide custom seed nodes: - - Set VIZD_SEED_NODES to a whitespace-delimited list of seed nodes. -- Enable validator mode: - - Set VIZD_WITNESS_NAME and VIZD_PRIVATE_KEY environment variables. - -**Section sources** -- [README.md](file://README.md#L21-L29) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L72) - -#### Volume Management for Persistent Blockchain Data -- Mount /var/lib/vizd to persist blockchain data and logs across container restarts. -- Mount /etc/vizd to override configuration and seednodes. -- The entry script initializes from a cached snapshot if present and sets proper ownership. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L87-L87) -- [vizd.sh](file://share/vizd/vizd.sh#L44-L53) - -#### Network Configuration for Node Connectivity -- Publish host ports 2001 (P2P), 8090 (HTTP), and 8091 (WebSocket) to allow external clients and peers to connect. -- Seed nodes are loaded from /etc/vizd/seednodes by default; override via VIZD_SEED_NODES. -- The entry script allows overriding RPC and P2P endpoints via environment variables. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L79-L85) -- [vizd.sh](file://share/vizd/vizd.sh#L11-L29) - -## Dependency Analysis -The production Dockerfile depends on: -- phusion/baseimage:bionic-1.0.0 for both stages. -- Build dependencies installed in the builder stage. -- CMake configuration flags controlling node behavior. -- Runtime configuration files and entry script. - -```mermaid -graph LR -DF["Dockerfile-production"] --> IMG["phusion/baseimage:bionic-1.0.0"] -DF --> DEPS["Build deps
Boost, OpenSSL, Python, ccache"] -DF --> CMAKE["CMake flags
Production defaults"] -DF --> RUNTIME["Runtime setup
User, dirs, script, config"] -RUNTIME --> CFG["config.ini"] -RUNTIME --> SCRIPT["vizd.sh"] -``` - -**Diagram sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) - -## Performance Considerations -- Static linking reduces runtime dependencies and improves portability. -- Parallel compilation accelerates builds using all available CPU cores. -- ccache speeds up incremental builds by caching compiled objects. -- Production defaults disable extra locking checks and MongoDB plugin to reduce overhead. - -**Section sources** -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) -- [building.md](file://documentation/building.md#L3-L16) - -## Troubleshooting Guide -- Port conflicts: Ensure host ports 2001, 8090, and 8091 are free when running the container. -- Permission issues: The entry script sets ownership for /var/lib/vizd; ensure mounted volumes have correct permissions. -- Configuration overrides: Place a custom config.ini in /etc/vizd to override defaults. -- Seed nodes: Provide VIZD_SEED_NODES to override the default seednodes list. -- validator mode: Set VIZD_WITNESS_NAME and VIZD_PRIVATE_KEY to enable validator participation. - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L7-L53) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -## Conclusion -The production Dockerfile provides a robust, secure, and efficient way to deploy the VIZ C++ node. Its multi-stage build minimizes attack surface and image size, while CMake flags tailor the node for production. The runtime setup ensures non-root execution, persistent storage, and straightforward networking. The CI/CD pipeline automates building and publishing the latest image, and the entry script simplifies customization via environment variables. - -## Appendices - -### Additional Variants -- Low-memory variant: Enables LOW_MEMORY_NODE=TRUE for resource-constrained environments. -- MongoDB variant: Installs MongoDB drivers and enables ENABLE_MONGO_PLUGIN=TRUE with a dedicated configuration. - -**Section sources** -- [Dockerfile-lowmem](file://share/vizd/docker/Dockerfile-lowmem#L47-L50) -- [Dockerfile-mongo](file://share/vizd/docker/Dockerfile-mongo#L78-L79) -- [config_mongo.ini](file://share/vizd/config/config_mongo.ini#L69-L72) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Testnet Dockerfile.md b/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Testnet Dockerfile.md deleted file mode 100644 index 1d38025472..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Build System/Docker Integration/Testnet Dockerfile.md +++ /dev/null @@ -1,369 +0,0 @@ -# Testnet Dockerfile - - -**Referenced Files in This Document** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [config.ini](file://share/vizd/config/config.ini) -- [vizd.sh](file://share/vizd/vizd.sh) -- [seednodes](file://share/vizd/seednodes) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json) -- [CMakeLists.txt](file://CMakeLists.txt) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp) -- [get_config.cpp](file://libraries/protocol/get_config.cpp) -- [testnet.md](file://documentation/testnet.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the testnet Dockerfile variant designed for deploying VIZ test network nodes. It highlights differences from the production configuration, focusing on testnet-specific CMake options, configuration files, bootstrap behavior, seed node setup, and RPC endpoints. It also covers automated snapshot loading, genesis-like initialization, and practical operational guidance for running and connecting to testnet networks. - -## Project Structure -The testnet Dockerfile variant resides under share/vizd/docker and integrates with testnet-specific configuration and scripts: -- Dockerfile-testnet builds the node with BUILD_TESTNET enabled and packages testnet-specific assets. -- config_testnet.ini sets testnet RPC endpoints, P2P ports, logging, and plugin selection. -- vizd.sh orchestrates container startup, seed node resolution, optional replay, and runtime arguments. -- snapshot-testnet.json provides pre-defined test accounts for quick testing. -- seednodes lists known testnet seed nodes for initial connectivity. - -```mermaid -graph TB -subgraph "Docker Build" -DF["Dockerfile-testnet"] -CMake["CMakeLists.txt
BUILD_TESTNET=TRUE"] -CFG["config_testnet.ini"] -SH["vizd.sh"] -SNAP["snapshot-testnet.json"] -SEED["seednodes"] -end -DF --> CMake -DF --> CFG -DF --> SH -DF --> SNAP -DF --> SEED -``` - -**Diagram sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [CMakeLists.txt](file://CMakeLists.txt#L55-L64) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L88) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -## Core Components -- Testnet Dockerfile: Builds with BUILD_TESTNET enabled, installs testnet config, snapshot, and seed nodes, exposes testnet RPC and P2P ports, and declares persistent volumes for data and config. -- Testnet configuration: Defines RPC endpoints, P2P settings, plugin list, and validator production parameters tuned for testnet. -- Startup script: Resolves seed nodes, applies environment overrides, optionally replays from cached blockchain, and launches the node with proper data directory ownership. -- Snapshot: Pre-populates test accounts for immediate testing without manual account creation. -- Seed nodes: Provides initial peers for testnet connectivity. - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L9-L29) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -## Architecture Overview -The testnet container architecture centers on the testnet Dockerfile’s build-time and runtime behavior, testnet configuration, and startup orchestration. - -```mermaid -graph TB -subgraph "Container Runtime" -RUN["vizd.sh"] -VIZD["vizd binary"] -CFG["/etc/vizd/config.ini"] -DATA["/var/lib/vizd"] -SNAP["/var/lib/vizd/snapshot.json"] -SEED["/etc/vizd/seednodes"] -end -subgraph "Build-time" -DFT["Dockerfile-testnet"] -CMAKE["CMake BUILD_TESTNET=TRUE"] -CTI["config_testnet.ini"] -VSH["vizd.sh"] -SST["snapshot-testnet.json"] -SDN["seednodes"] -end -DFT --> CMAKE -DFT --> CTI -DFT --> VSH -DFT --> SST -DFT --> SDN -RUN --> CFG -RUN --> DATA -RUN --> SNAP -RUN --> SEED -RUN --> VIZD -``` - -**Diagram sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -## Detailed Component Analysis - -### Testnet Dockerfile Differences from Production -- Build option: Enables BUILD_TESTNET during CMake configuration. -- Assets: Copies testnet-specific config, snapshot, and seed nodes. -- Ports: Exposes testnet RPC endpoints (HTTP and WS) and P2P port. -- Volumes: Declares persistent volumes for data and config directories. - -```mermaid -flowchart TD -Start(["Build Start"]) --> CopyFiles["Copy Source & Config"] -CopyFiles --> Submodules["Initialize Git Submodules"] -Submodules --> Configure["cmake -DBUILD_TESTNET=TRUE ..."] -Configure --> Make["make -j$(nproc)"] -Make --> Install["make install"] -Install --> FinalStage["Final Stage Image"] -FinalStage --> CopyAssets["Copy testnet config, snapshot, seednodes"] -CopyAssets --> ExposePorts["Expose RPC and P2P"] -ExposePorts --> VolumeDecl["Declare /var/lib/vizd and /etc/vizd volumes"] -VolumeDecl --> End(["Ready"]) -``` - -**Diagram sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L32-L65) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L75-L87) - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L75-L87) -- [Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L46-L54) - -### Testnet CMake Configuration and Build Flags -- BUILD_TESTNET: When enabled, adds a preprocessor definition and includes testnet-specific protocol constants and configuration. -- Impact: Selects testnet chain parameters, address prefix, block interval, and other consensus settings. - -```mermaid -flowchart TD -A["CMakeLists.txt
BUILD_TESTNET option"] --> B{"Enabled?"} -B --> |Yes| C["Add -DBUILD_TESTNET to compiler flags"] -C --> D["Include config_testnet.hpp in protocol"] -D --> E["Use testnet chain parameters"] -B --> |No| F["Default production config"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L55-L64) -- [get_config.cpp](file://libraries/protocol/get_config.cpp#L2-L6) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L55-L64) -- [get_config.cpp](file://libraries/protocol/get_config.cpp#L2-L6) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -### Testnet Configuration and RPC Endpoints -- P2P endpoint: Listens on a testnet-specific port. -- RPC endpoints: HTTP and WebSocket endpoints configured for testnet. -- Plugins: Includes chain, p2p, json_rpc, webserver, and other plugins suitable for testnet development and testing. -- validator production: Enabled with configurable participation thresholds and validator name/key for testnet block production. - -```mermaid -flowchart TD -CFG["config_testnet.ini"] --> P2P["p2p-endpoint"] -CFG --> RPC["webserver-http-endpoint
webserver-ws-endpoint"] -CFG --> PLUGINS["plugin list"] -CFG --> validator["validator and private-key"] -``` - -**Diagram sources** -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -**Section sources** -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) - -### Testnet Bootstrap and Snapshot Loading -- Snapshot availability: The startup script checks for a cached blockchain archive and, if present, replays it to initialize the chain quickly. -- Snapshot content: Testnet snapshot includes predefined accounts with initial balances, enabling immediate testing without manual account creation. -- Seed nodes: The script reads seednodes and passes them to the node as seed peers. - -```mermaid -sequenceDiagram -participant Script as "vizd.sh" -participant FS as "Filesystem" -participant Node as "vizd" -participant Seed as "Seed Nodes" -Script->>FS : Check /var/cache/vizd/blocks.tbz2 -alt Found -Script->>Script : Append --replay-blockchain -Script->>FS : Extract blocks.tbz2 to /var/lib/vizd/blockchain -else Not Found -Script->>Script : Proceed without replay -end -Script->>FS : Read /etc/vizd/seednodes -Script->>Seed : Resolve seed nodes -Script->>Node : exec vizd with args and env overrides -``` - -**Diagram sources** -- [vizd.sh](file://share/vizd/vizd.sh#L44-L53) -- [vizd.sh](file://share/vizd/vizd.sh#L17-L29) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L44-L53) -- [vizd.sh](file://share/vizd/vizd.sh#L17-L29) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) - -### Testnet RPC Endpoint Setup and Environment Overrides -- RPC endpoint override: The script allows overriding the RPC endpoint via an environment variable. -- P2P endpoint override: Similarly supports overriding the P2P endpoint. -- validator customization: Allows setting the validator name and private key via environment variables. - -```mermaid -flowchart TD -ENV["Environment Variables"] --> RPC["VIZD_RPC_ENDPOINT"] -ENV --> P2P["VIZD_P2P_ENDPOINT"] -ENV --> WIT["VIZD_WITNESS_NAME"] -ENV --> KEY["VIZD_PRIVATE_KEY"] -RPC --> ARG["--rpc-endpoint"] -P2P --> ARG -WIT --> ARG -KEY --> ARG -``` - -**Diagram sources** -- [vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [vizd.sh](file://share/vizd/vizd.sh#L31-L37) - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [vizd.sh](file://share/vizd/vizd.sh#L31-L37) - -### Testnet Volume Mounts and Persistence -- Data directory: Persistent storage for blockchain data and runtime state. -- Config directory: Persistent storage for configuration overrides and logs. - -```mermaid -graph LR -Host["Host Machine"] --> Vol1["/var/lib/vizd"] -Host --> Vol2["/etc/vizd"] -Vol1 --> Container["Container Data Dir"] -Vol2 --> Container["Container Config Dir"] -``` - -**Diagram sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L87-L87) - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L87-L87) - -### Practical Examples: Running Testnet Containers -- Build the testnet image from the repository. -- Run a container and tail logs to observe bootstrapping and synchronization progress. -- Use the pre-built image from the registry. - -Refer to the documentation for exact commands and additional users available in the testnet snapshot. - -**Section sources** -- [testnet.md](file://documentation/testnet.md#L21-L37) - -## Dependency Analysis -The testnet Dockerfile depends on: -- CMake configuration enabling BUILD_TESTNET to select testnet protocol constants. -- Testnet configuration file for RPC and plugin settings. -- Startup script for seed node resolution and runtime argument assembly. -- Snapshot and seed nodes for bootstrap convenience. - -```mermaid -graph TB -DFT["Dockerfile-testnet"] --> CMake["CMakeLists.txt
BUILD_TESTNET"] -DFT --> CTI["config_testnet.ini"] -DFT --> VSH["vizd.sh"] -DFT --> SST["snapshot-testnet.json"] -DFT --> SDN["seednodes"] -CMake --> Proto["config_testnet.hpp"] -CTI --> NodeCfg["Node Runtime Config"] -VSH --> Node["vizd Binary"] -``` - -**Diagram sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [CMakeLists.txt](file://CMakeLists.txt#L55-L64) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L46-L55) -- [CMakeLists.txt](file://CMakeLists.txt#L55-L64) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [seednodes](file://share/vizd/seednodes#L1-L6) -- [config_testnet.hpp](file://libraries/protocol/include/graphene/protocol/config_testnet.hpp#L1-L170) - -## Performance Considerations -- Testnet block interval is shorter than production, accelerating iteration cycles for developers. -- Shared memory sizing and growth parameters are tuned for testnet throughput and stability. -- Single write thread and reduced plugin notifications on push transactions can improve performance under load. -- Logging levels are set to aid debugging during testnet runs. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- No seed nodes provided: The startup script automatically loads seed nodes from the built-in seed list if none are supplied via environment variables. -- RPC endpoint conflicts: Override the RPC endpoint using the environment variable to avoid port conflicts. -- P2P endpoint conflicts: Override the P2P endpoint similarly. -- validator production: Ensure the validator name and private key match the testnet configuration when attempting to produce blocks. -- Snapshot not applied: Verify the presence of the snapshot file and that the node has permission to read it. -- Connectivity delays: Allow time for the node to discover peers and synchronize; monitor logs for peer connection and block sync progress. - -**Section sources** -- [vizd.sh](file://share/vizd/vizd.sh#L17-L29) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L72) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L105-L112) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) - -## Conclusion -The testnet Dockerfile variant streamlines deployment of VIZ test network nodes by enabling BUILD_TESTNET, packaging testnet-specific configuration, snapshot, and seed nodes, and exposing appropriate RPC and P2P endpoints. The startup script automates seed node resolution, optional replay, and runtime customization, making it straightforward to run and connect to the testnet for development and testing. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Appendix A: Testnet RPC and P2P Port Exposure -- HTTP RPC: Exposed on the testnet port. -- WebSocket RPC: Exposed on the testnet port. -- P2P: Exposed on the testnet port. - -**Section sources** -- [Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L79-L85) - -### Appendix B: Testnet Genesis and Snapshot Accounts -- Snapshot includes predefined accounts with initial balances for immediate testing. -- Additional users are available for testing without manual account creation. - -**Section sources** -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L35) -- [testnet.md](file://documentation/testnet.md#L39-L54) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Debug Node Plugin.md b/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Debug Node Plugin.md deleted file mode 100644 index 86e170874b..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Debug Node Plugin.md +++ /dev/null @@ -1,348 +0,0 @@ -# Debug Node Plugin - - -**Referenced Files in This Document** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp) -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [1.hf](file://libraries/chain/hardfork.d/1.hf) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -The debug node plugin is a development and debugging tool designed to manipulate blockchain state locally for testing, reproducibility, and experimentation. It enables: -- Generating blocks programmatically for rapid scenario testing -- Importing existing blockchain data via binary block logs or JSON arrays -- Inspecting and modifying validator scheduling and hardfork state -- Applying targeted database edits during block application - -It integrates with the chain plugin for database access and the JSON-RPC plugin for API exposure, allowing controlled manipulation of chain state without affecting consensus on public networks. - -## Project Structure -The debug node plugin resides under plugins/debug_node and exposes a set of APIs registered via the JSON-RPC plugin. It depends on the chain plugin for database operations and uses the JSON-RPC plugin to serve API methods. - -```mermaid -graph TB -subgraph "Plugins" -DN["debug_node plugin
plugin.cpp"] -CH["chain plugin
plugin.cpp"] -JR["json_rpc plugin
plugin.cpp"] -end -subgraph "Libraries" -DBH["database.hpp
database interface"] -HF["hardfork.d/*.hf
hardfork definitions"] -end -DN --> CH -DN --> JR -DN --> DBH -CH --> DBH -CH --> HF -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L1-L668) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L1-L200) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L1-L200) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L1-L668) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L1-L200) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L1-L200) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) - -## Core Components -- Plugin lifecycle and registration - - Initializes program options, connects to chain plugin database signals, and registers JSON-RPC APIs. -- Public APIs exposed - - Block generation: debug_generate_blocks, debug_generate_blocks_until - - Block import: debug_push_blocks, debug_push_json_blocks - - State inspection: debug_pop_block, debug_get_witness_schedule - - Hardfork control: debug_set_hardfork, debug_has_hardfork -- Internal mechanisms - - Uses database flags to skip validations for faster import - - Applies targeted database edits per block head to simulate state changes - - Logs and conditionally modifies validator signing keys to enable block production - -Key API definitions and declarations are declared in the plugin header and implemented in the plugin source. - -**Section sources** -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L25-L108) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L31-L60) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L475-L556) - -## Architecture Overview -The debug node plugin orchestrates block generation and import through the chain plugin’s database interface and serves methods via the JSON-RPC plugin. It listens to applied-block events to apply debug updates against the current head block. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant RPC as "JSON-RPC Plugin" -participant DBG as "debug_node plugin" -participant CH as "chain plugin" -participant DB as "database" -Client->>RPC : "call , , " -RPC->>DBG : "dispatch to debug_* API" -DBG->>CH : "access database()" -CH->>DB : "execute operation (generate/push/pop/schedule/hardfork)" -DB-->>CH : "result or exception" -CH-->>DBG : "result or exception" -DBG-->>RPC : "result" -RPC-->>Client : "JSON-RPC response" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L117-L136) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L479-L555) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L182) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L180-L200) - -## Detailed Component Analysis - -### API Surface and Responsibilities -- debug_push_blocks - - Imports blocks from a binary block log starting after the current head block. - - Returns the number of blocks successfully imported. -- debug_push_json_blocks - - Imports blocks from a JSON file containing an array of signed blocks. - - Supports skip flags to bypass validations for faster import. -- debug_generate_blocks - - Generates a given number of blocks by scheduling the current head time and modifying validator signing keys if needed. - - Accepts skip flags and optional key editing to align validator keys with the provided private key. -- debug_generate_blocks_until - - Generates blocks until the chain head reaches a specified absolute time, optionally skipping intermediate slots. -- debug_pop_block - - Returns the last block without popping it from the chain (useful for inspection). -- debug_get_witness_schedule - - Retrieves the current validator schedule object for inspection. -- debug_set_hardfork - - Sets the active hardfork to a given ID (no-op if beyond supported range). -- debug_has_hardfork - - Checks whether the chain has reached or exceeded a given hardfork ID. - -```mermaid -flowchart TD -Start(["Call debug_push_json_blocks"]) --> CheckCount["count == 0?"] -CheckCount --> |Yes| ReturnZero["Return 0"] -CheckCount --> |No| ValidateFile["Validate JSON file exists and is readable"] -ValidateFile --> |Fail| LogError["Log error and return 0"] -ValidateFile --> |Success| LoadArray["Load array of signed blocks"] -LoadArray --> Iterate["Iterate up to 'count' blocks"] -Iterate --> PushBlock["Push each block with skip_flags"] -PushBlock --> NextOrDone{"More blocks?"} -NextOrDone --> |Yes| Iterate -NextOrDone --> |No| Done(["Return total pushed"]) -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L374-L420) - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L31-L60) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L321-L420) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L479-L555) - -### Block Generation Workflow -The block generation process selects the scheduled validator for the next slot, compares its signing key with the provided debug key, optionally edits the validator object to match, and generates a block signed by the debug key. - -```mermaid -flowchart TD -Enter(["debug_generate_blocks"]) --> ZeroCheck{"count == 0?"} -ZeroCheck --> |Yes| Exit0["Return 0"] -ZeroCheck --> |No| ParseKey["Parse WIF private key"] -ParseKey --> ValidKey{"Valid key?"} -ValidKey --> |No| Exit0 -ValidKey --> |Yes| Loop["While produced < count"] -Loop --> SlotCalc["Compute slot and scheduled validator"] -SlotCalc --> CompareKey{"Scheduled key == debug key?"} -CompareKey --> |No| EditNeeded{"edit_if_needed?"} -EditNeeded --> |No| ExitLoop["Break loop"] -EditNeeded --> |Yes| ApplyEdit["Apply debug_update to modify validator signing key"] -ApplyEdit --> Loop -CompareKey --> |Yes| GenBlock["Generate block at scheduled time"] -GenBlock --> Inc["Increment produced and slot"] -Inc --> Loop -Loop --> ExitLoop -ExitLoop --> Return(["Return produced count"]) -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L222-L288) - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L222-L288) - -### validator Schedule Inspection -Retrieves the current validator schedule object from the database for inspection and debugging. - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L427-L430) - -### Hardfork Management -- debug_set_hardfork - - Sets the active hardfork ID if within supported range. -- debug_has_hardfork - - Checks if the stored hardfork property indicates the chain has reached or exceeded the given hardfork ID. - -These methods rely on the database’s hardfork property object and internal hardfork arrays. - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L441-L454) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L429-L524) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) - -### Integration with Chain and JSON-RPC Plugins -- Chain plugin - - Provides the database instance and handles block acceptance/validation. - - Offers skip flags to bypass expensive checks during import. -- JSON-RPC plugin - - Registers the debug_node API methods and dispatches requests to the plugin. - - Manages request parsing, argument validation, and response formatting. - -```mermaid -classDiagram -class DebugNodePlugin { -+set_program_options(cli, cfg) -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+debug_push_blocks(src, count) -+debug_push_json_blocks(file, count, skip) -+debug_generate_blocks(key, count, skip, miss, edit) -+debug_generate_blocks_until(key, time, sparse, skip) -+debug_pop_block() -+debug_get_witness_schedule() -+debug_set_hardfork(id) -+debug_has_hardfork(id) -} -class ChainPlugin { -+db() database& -+accept_block(block, currently_syncing, skip) -+accept_transaction(trx) -} -class JsonRpcPlugin { -+add_api_method(api_name, method_name, api) -+find_api_method(api, method) -+process_params(...) -} -DebugNodePlugin --> ChainPlugin : "uses database()" -DebugNodePlugin --> JsonRpcPlugin : "registers APIs" -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L104-L136) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L182) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L159-L178) - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L104-L136) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L169-L182) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp#L159-L178) - -## Dependency Analysis -- Internal dependencies - - debug_node plugin requires chain plugin for database access and signals. - - Uses fc utilities for filesystem, JSON parsing, and logging. -- External dependencies - - JSON-RPC plugin for API registration and request handling. - - Hardfork definitions under hardfork.d for version/time constants. - -```mermaid -graph LR -DN["debug_node plugin"] --> CH["chain plugin"] -DN --> JR["json_rpc plugin"] -CH --> HF["hardfork.d/*.hf"] -DN --> FC["fc utilities"] -``` - -**Diagram sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L1-L20) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L1-L20) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L1-L20) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L1-L20) -- [1.hf](file://libraries/chain/hardfork.d/1.hf#L1-L7) - -## Performance Considerations -- Skip flags - - Use skip flags (e.g., skip_witness_signature, skip_authority_check, skip_witness_schedule_check) when importing blocks to avoid heavy validations. -- Batch operations - - Prefer debug_push_blocks or debug_push_json_blocks with reasonable counts to minimize repeated I/O. -- Logging overhead - - Disable logging in performance-sensitive scenarios to reduce I/O and CPU overhead. -- Block generation - - Limit the number of generated blocks and avoid unnecessary key edits to reduce database modifications. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Empty or invalid JSON file - - Ensure the JSON file contains an array of signed blocks and is readable. -- Block log not found or incomplete - - Verify the block log path and index exist and contain sufficient blocks. -- Validation failures during import - - Use appropriate skip flags to bypass checks; confirm compatibility with mainnet/testnet block formats. -- validator key mismatch - - Provide a valid WIF private key and allow key editing if needed; otherwise, block generation will halt. -- Hardfork ID out of range - - Set hardfork ID within supported bounds; exceeding the maximum has no effect. - -Operational tips: -- Bind RPC and P2P endpoints to localhost for development. -- Restrict public API exposure when debug_node_api is enabled. -- Use sparse generation for large time jumps to reduce intermediate blocks. - -**Section sources** -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L374-L420) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L321-L372) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L222-L288) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L441-L454) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) - -## Conclusion -The debug node plugin provides powerful tools for blockchain state manipulation in development environments. It supports rapid block generation, efficient import of existing data, validator schedule inspection, and hardfork control. Proper use of skip flags, careful key management, and restricted API exposure ensures safe and effective debugging without compromising production integrity. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Debugging Workflows -- Creating a test environment from live chain data - - Export blocks from a live node, start a new node with debug_node enabled, and import blocks via debug_push_blocks or debug_push_json_blocks. -- Reproducing edge cases - - Generate blocks until a specific time, adjust hardfork state, and simulate validator key changes to reproduce timing-sensitive issues. -- Validating state changes - - Inspect validator schedules and hardfork properties, then generate blocks to observe behavioral differences. - -**Section sources** -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) - -### Security Considerations -- Development vs production - - Enable debug_node only in isolated development environments; restrict RPC access to localhost and avoid exposing debug_node_api publicly. -- Database edits - - Treat debug updates as ephemeral and local; they do not affect consensus and are not propagated to other nodes. -- Private key handling - - Use disposable keys for debugging; never deploy debug keys in production configurations. - -**Section sources** -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L18-L71) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Debugging Tools.md b/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Debugging Tools.md deleted file mode 100644 index 439138bc26..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Debugging Tools.md +++ /dev/null @@ -1,523 +0,0 @@ -# Debugging Tools - - -**Referenced Files in This Document** -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp) -- [api_helper.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [node.cpp](file://libraries/network/node.cpp) - - -## Update Summary -**Changes Made** -- Enhanced P2P plugin logging section to document ANSI color code improvements -- Added detailed explanation of color-coded logging for different network event types -- Updated network debugging section with specific color coding examples -- Added console readability and debugging efficiency benefits -- Updated troubleshooting guide with color-based log analysis techniques - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the debugging tooling available in the VIZ C++ Node, focusing on: -- The debug node plugin for state inspection, transaction tracing, and blockchain state visualization -- Transaction serialization utilities for diagnosing signing issues -- Network debugging and peer connection monitoring with enhanced ANSI color-coded logging -- Performance profiling and memory analysis utilities -- Practical debugging workflows for unit testing to production troubleshooting - -The goal is to provide a practical guide for developers and operators to diagnose and resolve issues efficiently, with references to concrete source files and configuration examples. - -**Updated** Enhanced P2P plugin logging now provides visual distinction through ANSI color codes for different types of network events, significantly improving console readability and debugging efficiency. - -## Project Structure -The debugging tooling spans several areas: -- The debug node plugin that enables "what-if" experiments and block generation/state manipulation -- Utilities for signing transactions and digests to validate signing logic -- P2P and network components that surface peer connection and message handling details with enhanced color-coded logging -- Configuration templates optimized for debugging and performance tuning - -```mermaid -graph TB -subgraph "Debug Node Plugin" -DNP["plugin.hpp"] -DNPImpl["plugin.cpp"] -AH["api_helper.hpp"] -end -subgraph "Utilities" -ST["sign_transaction.cpp"] -SD["sign_digest.cpp"] -end -subgraph "Network" -P2P["p2p_plugin.cpp"] -NETNode["node.hpp"] -PeerConn["peer_connection.hpp"] -end -CFG["config_debug.ini"] -DNP --> DNPImpl -DNPImpl --> AH -ST --> DNPImpl -SD --> DNPImpl -P2P --> NETNode -P2P --> PeerConn -CFG --> DNPImpl -CFG --> P2P -``` - -**Diagram sources** -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) -- [plugin.cpp:25-94](file://plugins/debug_node/plugin.cpp#L25-L94) -- [api_helper.hpp:1-108](file://plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp#L1-L108) -- [sign_transaction.cpp:12-26](file://programs/util/sign_transaction.cpp#L12-L26) -- [sign_digest.cpp:12-24](file://programs/util/sign_digest.cpp#L12-L24) -- [p2p_plugin.cpp:1-200](file://plugins/p2p/p2p_plugin.cpp#L1-L200) -- [node.hpp:190-200](file://libraries/network/include/graphene/network/node.hpp#L190-L200) -- [peer_connection.hpp:79-200](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L200) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) - -**Section sources** -- [plugin.hpp:1-111](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L1-L111) -- [plugin.cpp:1-668](file://plugins/debug_node/plugin.cpp#L1-L668) -- [api_helper.hpp:1-108](file://plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp#L1-L108) -- [sign_transaction.cpp:1-54](file://programs/util/sign_transaction.cpp#L1-L54) -- [sign_digest.cpp:1-49](file://programs/util/sign_digest.cpp#L1-L49) -- [p2p_plugin.cpp:1-200](file://plugins/p2p/p2p_plugin.cpp#L1-L200) -- [node.hpp:1-200](file://libraries/network/include/graphene/network/node.hpp#L1-L200) -- [peer_connection.hpp:1-200](file://libraries/network/include/graphene/network/peer_connection.hpp#L1-L200) -- [config_debug.ini:1-126](file://share/vizd/config/config_debug.ini#L1-L126) - -## Core Components -- Debug node plugin - - Provides APIs to push blocks from disk or JSON, generate blocks locally, pop blocks, inspect validator schedule, and control hardfork state - - Supports applying database updates at specific block heights and logging decisions - - Exposes program options for initial database edit scripts -- Transaction signing utilities - - Standalone CLI tools to compute transaction digests, signature digests, and signatures given WIF keys -- Network debugging with enhanced visual distinction - - P2P plugin logs block acceptance and transaction ingestion with ANSI color codes - - Network node and peer connection abstractions expose peer status and message propagation metadata - - Color-coded logging improves console readability and debugging efficiency - -Key capabilities: -- State inspection via database access and validator schedule retrieval -- Transaction tracing by generating blocks and observing accepted transactions -- Blockchain state visualization by replaying blocks from logs or JSON -- Signing diagnostics using deterministic signing utilities -- Enhanced network debugging through visual color coding - -**Updated** Enhanced P2P plugin logging now uses ANSI color codes to provide visual distinction for different types of network events, including block processing messages in white color, peer statistics in cyan color, and detailed debugging information in gray color. - -**Section sources** -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) -- [plugin.cpp:25-94](file://plugins/debug_node/plugin.cpp#L25-L94) -- [plugin.cpp:222-288](file://plugins/debug_node/plugin.cpp#L222-L288) -- [plugin.cpp:321-420](file://plugins/debug_node/plugin.cpp#L321-L420) -- [plugin.cpp:489-555](file://plugins/debug_node/plugin.cpp#L489-L555) -- [sign_transaction.cpp:12-26](file://programs/util/sign_transaction.cpp#L12-L26) -- [sign_digest.cpp:12-24](file://programs/util/sign_digest.cpp#L12-L24) -- [p2p_plugin.cpp:118-170](file://plugins/p2p/p2p_plugin.cpp#L118-L170) -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) - -## Architecture Overview -The debug node plugin integrates with the chain plugin and JSON-RPC to expose a set of debugging APIs. It manipulates the database to simulate conditions and replay blocks from external sources. Network debugging leverages the P2P plugin's enhanced logging hooks with ANSI color codes and the network node's peer management. - -```mermaid -sequenceDiagram -participant Client as "RPC Client" -participant JSONRPC as "JSON-RPC Layer" -participant DNP as "debug_node plugin" -participant DB as "Chain Database" -participant P2P as "P2P Plugin" -Client->>JSONRPC : "call debug_push_blocks(src,count)" -JSONRPC->>DNP : "debug_push_blocks(...)" -DNP->>DB : "push_block(block, skip_flags)" -DB-->>DNP : "accepted or exception" -DNP-->>JSONRPC : "count of blocks pushed" -JSONRPC-->>Client : "result" -Note over Client,P2P : "Enhanced P2P plugin logs with ANSI color codes for improved readability" -``` - -**Diagram sources** -- [plugin.cpp:489-511](file://plugins/debug_node/plugin.cpp#L489-L511) -- [plugin.cpp:321-372](file://plugins/debug_node/plugin.cpp#L321-L372) -- [p2p_plugin.cpp:118-170](file://plugins/p2p/p2p_plugin.cpp#L118-L170) - -## Detailed Component Analysis - -### Debug Node Plugin -The debug node plugin offers: -- Block replay from block log and JSON arrays -- Local block generation with configurable validator key and skipping of validations -- Database update hooks applied at specific block heights -- Hardfork state control and validator schedule inspection - -```mermaid -classDiagram -class plugin { -+set_program_options(cli, cfg) -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+DECLARE_API(...) -+debug_update(callback, skip) -+set_logging(islogging) -} -class plugin_impl { -+debug_push_blocks(src, count) uint32_t -+debug_push_json_blocks(file, count, skip) uint32_t -+debug_generate_blocks(key, count, skip, miss, edit) uint32_t -+debug_generate_blocks_until(key, time, sparse, skip) uint32_t -+debug_pop_block() optional -+debug_get_witness_schedule() witness_schedule_object -+debug_set_hardfork(id) void -+debug_has_hardfork(id) bool -+apply_debug_updates() void -+on_applied_block(b) void -} -plugin --> plugin_impl : "owns" -``` - -**Diagram sources** -- [plugin.hpp:38-108](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) -- [plugin.cpp:25-94](file://plugins/debug_node/plugin.cpp#L25-L94) -- [plugin.cpp:222-555](file://plugins/debug_node/plugin.cpp#L222-L555) - -Key behaviors: -- Block replay honors skip flags to bypass expensive validations when needed -- Local block generation modifies validator signing keys to accept self-signed blocks -- Hardfork state can be set programmatically for testing activation logic -- Logging toggles help reduce noise during automated tests - -Practical usage patterns: -- Replay historical blocks from a block log to reproduce state -- Generate blocks deterministically for consensus timing tests -- Inspect validator schedule and hardfork state during debugging sessions - -**Section sources** -- [plugin.cpp:321-420](file://plugins/debug_node/plugin.cpp#L321-L420) -- [plugin.cpp:222-288](file://plugins/debug_node/plugin.cpp#L222-L288) -- [plugin.cpp:441-454](file://plugins/debug_node/plugin.cpp#L441-L454) -- [plugin.cpp:422-430](file://plugins/debug_node/plugin.cpp#L422-L430) -- [plugin.cpp:117-136](file://plugins/debug_node/plugin.cpp#L117-L136) - -### Transaction Serialization Utilities -Two standalone utilities support signing diagnostics: -- sign_transaction: computes digest and signature digest, signs a transaction, and prints results -- sign_digest: signs a raw digest with a WIF key and prints the signature - -```mermaid -flowchart TD -Start(["Read JSON line"]) --> Parse["Parse JSON to signing request"] -Parse --> Compute["Compute tx.digest() and tx.sig_digest(CHAIN_ID)"] -Compute --> LoadKey["Load private key from WIF"] -LoadKey --> Sign["Sign sig_digest to produce signature"] -Sign --> Emit["Emit JSON result with digest, sig_digest, key, signature"] -Emit --> End(["Next line"]) -``` - -**Diagram sources** -- [sign_transaction.cpp:28-53](file://programs/util/sign_transaction.cpp#L28-L53) -- [sign_digest.cpp:26-48](file://programs/util/sign_digest.cpp#L26-L48) - -Common debugging scenarios: -- Verifying transaction signing failures by comparing computed sig_digest against wallet-produced signatures -- Confirming chain ID correctness by ensuring sig_digest matches expected chain ID -- Isolating signature malleability or encoding issues by printing compact signatures - -**Section sources** -- [sign_transaction.cpp:12-26](file://programs/util/sign_transaction.cpp#L12-L26) -- [sign_transaction.cpp:28-53](file://programs/util/sign_transaction.cpp#L28-L53) -- [sign_digest.cpp:12-24](file://programs/util/sign_digest.cpp#L12-L24) -- [sign_digest.cpp:26-48](file://programs/util/sign_digest.cpp#L26-L48) - -### Network Debugging and Peer Monitoring with Enhanced Visual Distinction - -**Updated** The P2P plugin now provides enhanced logging with ANSI color codes for improved console readability and debugging efficiency. - -The P2P plugin logs block acceptance and transaction ingestion with ANSI color codes, and the network node/peer connection abstractions expose peer status and message propagation metadata. The enhanced logging system uses color codes to visually distinguish different types of network events: - -- **White color (CLOG_WHITE)**: Block processing messages including transaction counts and latency information -- **Cyan color (CLOG_CYAN)**: Peer statistics and connection status information -- **Gray color (CLOG_GRAY)**: Detailed debugging information and operational context -- **Orange color (CLOG_ORANGE)**: Connection-related warnings and termination notices -- **Red color (CLOG_RED)**: Critical connection termination events - -```mermaid -sequenceDiagram -participant Peer as "Remote Peer" -participant P2P as "p2p_plugin" -participant Chain as "Chain DB" -participant Log as "Color-Coded Logs" -Peer->>P2P : "block_message" -P2P->>Log : "WHITE : Block processing with transaction count" -P2P->>Chain : "accept_block(..., skip_flags)" -Chain-->>P2P : "result" -P2P-->>Peer : "acknowledge or error" -Peer->>P2P : "trx_message" -P2P->>Chain : "accept_transaction(trx)" -Chain-->>P2P : "accepted or rejected" -Note over Log : "CYAN : Peer statistics and connection status" -Note over Log : "GRAY : Detailed debugging context" -``` - -**Diagram sources** -- [p2p_plugin.cpp:118-170](file://plugins/p2p/p2p_plugin.cpp#L118-L170) -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:169-172](file://plugins/p2p/p2p_plugin.cpp#L169-L172) -- [p2p_plugin.cpp:605-652](file://plugins/p2p/p2p_plugin.cpp#L605-L652) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) -- [node.cpp:5091-5108](file://libraries/network/node.cpp#L5091-L5108) - -Operational insights with enhanced visual distinction: -- **White logs**: Immediately highlight block processing activity with transaction counts and latency measurements -- **Cyan logs**: Provide clear peer statistics including connection counts, latency, and bandwidth metrics -- **Gray logs**: Offer detailed debugging context for DLT mode operations and synchronization status -- **Orange/red logs**: Clearly indicate connection warnings, terminations, and critical network events -- Logs indicate block ingestion latency and transaction counts per block -- Sync vs normal mode logs differentiate between catching-up and live operation -- Peer connection states and metrics (round-trip delay, clock offset) aid in diagnosing connectivity issues - -**Section sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:169-172](file://plugins/p2p/p2p_plugin.cpp#L169-L172) -- [p2p_plugin.cpp:298-365](file://plugins/p2p/p2p_plugin.cpp#L298-L365) -- [p2p_plugin.cpp:521-530](file://plugins/p2p/p2p_plugin.cpp#L521-L530) -- [p2p_plugin.cpp:605-686](file://plugins/p2p/p2p_plugin.cpp#L605-L686) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) -- [node.cpp:5091-5108](file://libraries/network/node.cpp#L5091-L5108) - -## Dependency Analysis -The debug node plugin depends on the chain plugin and JSON-RPC infrastructure. It interacts with the database to push blocks, modify validator keys, and manage hardfork state. The P2P plugin depends on the chain plugin for validation and delegates block/trx handling to it. The enhanced logging system relies on ANSI color code definitions and the underlying logging framework. - -```mermaid -graph LR -DNP["debug_node plugin"] --> Chain["chain plugin"] -DNP --> JSONRPC["json_rpc plugin"] -P2P["p2p_plugin"] --> Chain -P2P --> NetNode["network::node"] -NetNode --> PeerConn["peer_connection"] -P2P --> ColorCodes["ANSI Color Codes"] -ColorCodes --> Console["Console Output"] -``` - -**Diagram sources** -- [plugin.hpp:40-41](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L40-L41) -- [plugin.cpp:117-136](file://plugins/debug_node/plugin.cpp#L117-L136) -- [p2p_plugin.cpp:1-200](file://plugins/p2p/p2p_plugin.cpp#L1-L200) -- [node.hpp:190-200](file://libraries/network/include/graphene/network/node.hpp#L190-L200) -- [peer_connection.hpp:79-200](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L200) -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) - -**Section sources** -- [plugin.hpp:40-41](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L40-L41) -- [plugin.cpp:117-136](file://plugins/debug_node/plugin.cpp#L117-L136) -- [p2p_plugin.cpp:1-200](file://plugins/p2p/p2p_plugin.cpp#L1-L200) - -## Performance Considerations -- Shared memory sizing and growth thresholds impact replay performance and stability -- Single write thread and reduced plugin notifications can improve throughput during bulk operations -- Read/write lock contention affects RPC responsiveness under load -- **Updated** Enhanced logging with color codes provides better visual scanning efficiency without impacting performance significantly - -Recommendations: -- Increase shared memory size and thresholds for long replays -- Enable single write thread for deterministic block generation -- Tune read/write wait retries to avoid transient lock errors -- **Updated** Leverage color-coded logs for faster identification of network events and debugging scenarios - -**Section sources** -- [config_debug.ini:36-47](file://share/vizd/config/config_debug.ini#L36-L47) -- [config_debug.ini:49-67](file://share/vizd/config/config_debug.ini#L49-L67) - -## Troubleshooting Guide - -### Transaction Validation Failures -Symptoms: -- Transactions rejected with signature or authority errors -- Mismatch between expected and computed digests - -Workflow: -- Use sign_transaction to compute digest and sig_digest for the transaction -- Compare sig_digest with the transaction's sig_digest(CHAIN_ID) -- Verify WIF corresponds to the claimed signing key -- Reproduce by pushing blocks that include the transaction and observe logs - -```mermaid -flowchart TD -A["Transaction rejected"] --> B["Run sign_transaction on JSON input"] -B --> C{"sig_digest matches?"} -C --> |No| D["Fix chain ID or transaction fields"] -C --> |Yes| E["Verify WIF and key derivation"] -E --> F["Replay blocks with skip flags to isolate issue"] -``` - -**Diagram sources** -- [sign_transaction.cpp:28-53](file://programs/util/sign_transaction.cpp#L28-L53) -- [plugin.cpp:321-372](file://plugins/debug_node/plugin.cpp#L321-L372) - -**Section sources** -- [sign_transaction.cpp:12-26](file://programs/util/sign_transaction.cpp#L12-L26) -- [sign_transaction.cpp:28-53](file://programs/util/sign_transaction.cpp#L28-L53) -- [plugin.cpp:321-372](file://plugins/debug_node/plugin.cpp#L321-L372) - -### Consensus Issues -Symptoms: -- Blocks not accepted or chain stalls -- validator participation thresholds not met - -Workflow: -- Use debug_generate_blocks to advance the chain deterministically -- Temporarily modify validator signing keys to accept self-signed blocks -- Inspect validator schedule and hardfork state via debug APIs - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant DNP as "debug_node" -participant DB as "Database" -Dev->>DNP : "debug_generate_blocks(key,count,skip)" -DNP->>DB : "modify validator signing_key if needed" -DNP->>DB : "generate_block(slot,validator,priv_key,skip)" -DB-->>DNP : "new head block" -``` - -**Diagram sources** -- [plugin.cpp:222-288](file://plugins/debug_node/plugin.cpp#L222-L288) -- [plugin.cpp:489-511](file://plugins/debug_node/plugin.cpp#L489-L511) - -**Section sources** -- [plugin.cpp:222-288](file://plugins/debug_node/plugin.cpp#L222-L288) -- [plugin.cpp:489-511](file://plugins/debug_node/plugin.cpp#L489-L511) - -### Network Connectivity Problems with Enhanced Visual Analysis - -**Updated** Network connectivity problems can now be diagnosed more efficiently using the enhanced color-coded logging system. - -Symptoms: -- Peers disconnect frequently -- No blocks received or delayed propagation -- Sudden connection drops or reconnections - -Workflow with color-coded analysis: -- **Monitor CYAN logs**: Check peer statistics and connection status for immediate issues -- **Review WHITE logs**: Analyze block processing latency and transaction counts -- **Examine GRAY logs**: Investigate DLT mode operations and synchronization context -- **Watch ORANGE/RED logs**: Identify connection warnings and critical termination events -- Adjust seed nodes and connection limits in configuration - -Enhanced diagnostic approach: -- **Cyan peer statistics**: Look for sudden spikes in bytes_in or latency changes -- **White block processing**: Monitor transaction counts per block for network congestion -- **Gray debugging info**: Check DLT mode operations during snapshot sync failures -- **Orange/red warnings**: Identify connection health issues and peer blocking status - -**Section sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:605-686](file://plugins/p2p/p2p_plugin.cpp#L605-L686) -- [p2p_plugin.cpp:169-172](file://plugins/p2p/p2p_plugin.cpp#L169-L172) -- [p2p_plugin.cpp:298-365](file://plugins/p2p/p2p_plugin.cpp#L298-L365) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) -- [node.cpp:5091-5108](file://libraries/network/node.cpp#L5091-L5108) - -### Log Analysis Techniques with Color-Coded System - -**Updated** Enhanced log analysis techniques leveraging ANSI color codes for improved debugging efficiency. - -- **White logs**: Quickly identify block processing activity and transaction volume -- **Cyan logs**: Monitor peer statistics and connection health in real-time -- **Gray logs**: Access detailed debugging context for complex operations -- **Orange/red logs**: Immediately spot critical connection issues and warnings -- Use the debug node plugin's logging toggle to reduce noise during scripted runs -- Inspect P2P logs for sync vs live modes and block ingestion latency -- Correlate error backtraces from block push operations with the specific block number and ID - -**Section sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:605-686](file://plugins/p2p/p2p_plugin.cpp#L605-L686) -- [p2p_plugin.cpp:169-172](file://plugins/p2p/p2p_plugin.cpp#L169-L172) -- [plugin.cpp:244-248](file://plugins/debug_node/plugin.cpp#L244-L248) -- [plugin.cpp:363-366](file://plugins/debug_node/plugin.cpp#L363-L366) -- [p2p_plugin.cpp:124-133](file://plugins/p2p/p2p_plugin.cpp#L124-L133) - -### Integration with External Tools and IDEs -- Build and run the signing utilities from the programs/util directory to pipe transaction JSON into them -- Use the debug node plugin's JSON-RPC API from IDE REST clients or scripts -- Configure logging appenders and endpoints in the debug configuration template -- **Updated** Leverage color-coded console output for better integration with terminal-based debugging tools - -**Section sources** -- [sign_transaction.cpp:1-54](file://programs/util/sign_transaction.cpp#L1-L54) -- [sign_digest.cpp:1-49](file://programs/util/sign_digest.cpp#L1-L49) -- [config_debug.ini:107-126](file://share/vizd/config/config_debug.ini#L107-L126) - -### Debugging Workflows Across Development Phases -- Unit testing: Use sign_transaction and sign_digest to validate signing logic in isolation -- Integration testing: Replay blocks from JSON logs with skip flags to accelerate tests -- Staging: Enable debug node plugin with restricted RPC access and targeted edit scripts -- Production troubleshooting: Temporarily enable debug APIs on loopback, replay problematic blocks, and inspect validator schedule and hardfork state -- **Updated** Utilize enhanced color-coded logging for rapid identification of network issues in production environments - -**Section sources** -- [plugin.cpp:489-511](file://plugins/debug_node/plugin.cpp#L489-L511) -- [plugin.cpp:374-420](file://plugins/debug_node/plugin.cpp#L374-L420) -- [debug_node_plugin.md:50-134](file://documentation/debug_node_plugin.md#L50-L134) - -## Conclusion -The VIZ C++ Node provides robust debugging tooling centered around the debug node plugin, transaction signing utilities, and network introspection with enhanced ANSI color-coded logging. By combining deterministic block generation, replay capabilities, structured logging with visual distinction, and the enhanced P2P plugin logging system, teams can systematically diagnose transaction validation failures, consensus issues, and network connectivity problems more efficiently. The color-coded console output significantly improves debugging speed and accuracy, especially for complex network debugging scenarios. Proper configuration and disciplined workflows ensure efficient troubleshooting from development to production. - -**Updated** The enhanced P2P plugin logging with ANSI color codes provides substantial improvements in console readability and debugging efficiency, allowing developers and operators to quickly identify and resolve network issues through visual distinction of different event types. - -## Appendices - -### API Reference Summary -- debug_push_blocks: Load blocks from a block log -- debug_push_json_blocks: Load blocks from a JSON array -- debug_generate_blocks: Produce blocks with a specified key -- debug_generate_blocks_until: Advance time-based to a target head block time -- debug_pop_block: Remove the head block -- debug_get_witness_schedule: Retrieve validator schedule object -- debug_set_hardfork / debug_has_hardfork: Control and query hardfork state - -**Section sources** -- [plugin.hpp:62-90](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L62-L90) -- [plugin.cpp:489-555](file://plugins/debug_node/plugin.cpp#L489-L555) - -### Enhanced P2P Logging Color Codes Reference - -**Updated** Color-coded logging system for improved network debugging: - -- **CLOG_WHITE** ("\033[97m"): Block processing messages including transaction counts and latency -- **CLOG_CYAN** ("\033[96m"): Peer statistics and connection status information -- **CLOG_GRAY** ("\033[90m"): Detailed debugging information and operational context -- **CLOG_RESET** ("\033[0m"): Reset color formatting to default - -Common usage patterns: -- White logs for immediate block processing visibility -- Cyan logs for peer monitoring and connection health -- Gray logs for detailed operational context during complex operations -- Automatic reset ensures proper console formatting - -**Section sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:169-172](file://plugins/p2p/p2p_plugin.cpp#L169-L172) -- [p2p_plugin.cpp:605-686](file://plugins/p2p/p2p_plugin.cpp#L605-L686) -- [p2p_plugin.cpp:298-365](file://plugins/p2p/p2p_plugin.cpp#L298-L365) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) -- [node.cpp:5091-5108](file://libraries/network/node.cpp#L5091-L5108) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Network Debugging Capabilities.md b/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Network Debugging Capabilities.md deleted file mode 100644 index b823e7e20a..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Network Debugging Capabilities.md +++ /dev/null @@ -1,490 +0,0 @@ -# Network Debugging Capabilities - - -**Referenced Files in This Document** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [peer_database.cpp](file://libraries/network/peer_database.cpp) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp) -- [config_debug.ini](file://share/vizd/config/config_debug.ini) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document explains the network debugging capabilities in VIZ CPP Node with a focus on: -- Peer connection monitoring and debugging (establishment, handshake, and protocol issues) -- Message propagation debugging for blocks and transactions -- Network performance monitoring and latency analysis -- Peer database inspection for topology and connection quality diagnostics -- Practical scenarios: peer discovery failures, block propagation delays, and network partitions -- Logging configuration for network debugging, connection state monitoring, and traffic analysis -- Troubleshooting connectivity and optimizing performance - -## Project Structure -The network stack is implemented in the network library and integrated via the P2P plugin. Debugging and logging are configured via the debug configuration file and the debug_node plugin. - -```mermaid -graph TB -subgraph "Network Library" -N["node.hpp
node.cpp"] -PC["peer_connection.hpp
peer_connection.cpp"] -PD["peer_database.hpp
peer_database.cpp"] -MOC["message_oriented_connection.hpp"] -end -subgraph "Plugins" -P2P["p2p_plugin.hpp
p2p_plugin.cpp"] -DNP["debug_node plugin.hpp
debug_node plugin.cpp"] -end -subgraph "Config" -CFG["config_debug.ini"] -end -P2P --> N -N --> PC -N --> PD -PC --> MOC -DNP -. "debugging utilities" .- N -CFG -. "logging config" .- N -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [node.cpp](file://libraries/network/node.cpp#L106-L176) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L68-L162) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L186) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L41-L103) -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L38-L108) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L104-L156) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -## Core Components -- Node: Manages P2P connections, broadcasting, syncing, and propagation metrics. Provides APIs to inspect peers, bandwidth limits, and usage statistics. -- PeerConnection: Encapsulates per-peer state, negotiation status, inventory tracking, and timing metrics. -- PeerDatabase: Persistent storage of potential peers, connection attempts, and outcomes. -- MessageOrientedConnection: Low-level transport abstraction for message streams. -- P2P Plugin: Integrates the node with the chain plugin, handles blocks/transactions, and exposes broadcast APIs. -- Debug Node Plugin: Adds debugging hooks and APIs for “what-if” experiments and logging controls. -- Logging Config: Routes logs to console and file appenders, including a dedicated p2p logger. - -Key debugging APIs and structures: -- Propagation data tracking for blocks and transactions -- Peer status and connection counts -- Bandwidth limiting and usage stats -- Peer database inspection and clearing -- Firewall and negotiation state visibility - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L276-L278) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L292-L294) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L296-L296) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L288-L288) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L175-L198) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L200-L225) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L227-L268) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L40-L46) -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L101-L101) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -## Architecture Overview -The P2P subsystem is layered: -- Application layer (P2P plugin) delegates to Node for networking. -- Node manages PeerConnections and PeerDatabase. -- PeerConnection composes MessageOrientedConnection for transport. -- Debug Node plugin augments runtime behavior for diagnostics. - -```mermaid -classDiagram -class Node { -+broadcast(message) -+sync_from(item_id, forks) -+get_connected_peers() -+get_connection_count() -+get_transaction_propagation_data(id) -+get_block_propagation_data(id) -+clear_peer_database() -+network_get_info() -+network_get_usage_stats() -+get_potential_peers() -} -class PeerConnection { -+accept_connection() -+connect_to(endpoint) -+send_message(msg) -+close_connection() -+get_total_bytes_sent() -+get_total_bytes_received() -+get_last_message_sent_time() -+get_last_message_received_time() -+busy()/idle() -} -class PeerDatabase { -+open(path) -+update_entry(record) -+lookup_or_create_entry_for_endpoint(ep) -+begin()/end() -+size() -} -class MessageOrientedConnection { -+accept() -+bind(endpoint) -+connect_to(remote) -+send_message(msg) -+get_total_bytes_sent() -+get_total_bytes_received() -} -class P2PPlugin { -+broadcast_block(signed_block) -+broadcast_transaction(signed_transaction) -} -Node --> PeerConnection : "manages" -Node --> PeerDatabase : "uses" -PeerConnection --> MessageOrientedConnection : "uses" -P2PPlugin --> Node : "delegates" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) - -## Detailed Component Analysis - -### Node: Peer Monitoring and Propagation Metrics -- Propagation tracking: message_propagation_data captures received and validated timestamps and originating peer for blocks and transactions. -- Peer inspection: get_connected_peers returns peer_status with version, endpoint, and variant info. -- Stats and limits: network_get_info and network_get_usage_stats expose bandwidth and call statistics; set_total_bandwidth_limit caps throughput. -- Peer DB operations: clear_peer_database resets persistent peer records to aid in diagnosing discovery/connectivity issues. -- Sync and broadcast: sync_from initiates sync; broadcast dispatches messages to peers. - -```mermaid -sequenceDiagram -participant App as "Application" -participant P2P as "P2P Plugin" -participant Node as "Node" -participant Peer as "PeerConnection" -App->>P2P : broadcast_block / broadcast_transaction -P2P->>Node : broadcast(message) -Node->>Peer : send_message(message) per connected peer -Peer-->>Node : bytes counters updated -Node-->>App : stats via network_get_usage_stats() -``` - -**Diagram sources** -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L40-L46) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L258-L258) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L305-L305) - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L249-L253) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L276-L278) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L292-L294) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L296-L296) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L288-L288) - -### PeerConnection: Negotiation States and Timing -- Negotiation lifecycle: connection_negotiation_status enumerates states from connecting to accepted and hello exchange. -- Per-peer metrics: bytes sent/received, last message times, busy/idle detection, and inhibition flags for fetching. -- Inventory tracking: maintains sets of advertised items, requested items, and sync queues. -- Transport: wraps MessageOrientedConnection for accept/bind/connect and send operations. - -```mermaid -stateDiagram-v2 -[*] --> disconnected -disconnected --> connecting : "initiate outbound" -disconnected --> accepting : "accept inbound" -connecting --> hello_sent : "exchange hello" -accepting --> accepted : "exchange hello" -hello_sent --> negotiation_complete : "ready" -accepted --> negotiation_complete : "ready" -negotiation_complete --> closing : "graceful close" -closing --> closed : "destroy" -``` - -**Diagram sources** -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L94-L106) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L169-L200) - -**Section sources** -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L82-L106) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L175-L198) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L227-L268) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L169-L200) - -### PeerDatabase: Diagnosing Topology and Quality -- Records: endpoint, last seen, disposition of last connection (success/failure/rejected/handshake failed), attempt counts, and last error. -- Iteration: begin/end iterators traverse entries sorted by last_seen_time. -- Persistence: open loads from JSON; close saves to JSON; clear removes all entries. - -```mermaid -flowchart TD -Start(["Open PeerDB"]) --> Load["Load JSON file if exists"] -Load --> Prune{"Size > max?"} -Prune --> |Yes| Trim["Trim to maximum size"] -Prune --> |No| Ready["Ready"] -Ready --> Update["update_entry()"] -Update --> Lookup["lookup_or_create_entry_for_endpoint()"] -Lookup --> Iterate["begin()/end() iteration"] -Iterate --> Save["close(): save to JSON"] -Save --> End(["Close"]) -``` - -**Diagram sources** -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L151-L174) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L176-L182) - -**Section sources** -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L151-L174) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L176-L182) - -### MessageOrientedConnection: Transport Abstraction -- Accepts inbound connections, binds local endpoints, connects to remotes, sends messages, and tracks bytes and last message times. -- Used by PeerConnection to encapsulate transport concerns. - -**Section sources** -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) - -### P2P Plugin: Integration and Broadcast -- Implements node_delegate to integrate with the chain plugin. -- Exposes broadcast_block and broadcast_transaction for application-level propagation. -- Handles block and transaction ingestion and logs sync latencies. - -**Section sources** -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L40-L46) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L106-L170) - -### Debug Node Plugin: Logging Controls and Debug Utilities -- Provides APIs to generate or push blocks and to control logging behavior. -- Useful for isolating network behavior under controlled conditions. - -**Section sources** -- [plugin.hpp](file://plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#L101-L101) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L104-L156) - -## Dependency Analysis -- P2P plugin depends on Node for networking and on Chain plugin for blockchain operations. -- Node depends on PeerConnection and PeerDatabase for peer lifecycle and topology persistence. -- PeerConnection depends on MessageOrientedConnection for transport. -- Logging configuration routes network events to the p2p logger. - -```mermaid -graph LR -P2P["p2p_plugin.cpp"] --> NodeHdr["node.hpp"] -NodeCpp["node.cpp"] --> PeerConnHdr["peer_connection.hpp"] -NodeCpp --> PeerDBHdr["peer_database.hpp"] -PeerConnHdr --> MOC["message_oriented_connection.hpp"] -CFG["config_debug.ini"] --> NodeHdr -DNP["debug_node plugin.cpp"] -. "logging control" .- NodeHdr -``` - -**Diagram sources** -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L41-L103) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [node.cpp](file://libraries/network/node.cpp#L106-L176) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L351) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) -- [plugin.cpp](file://plugins/debug_node/plugin.cpp#L104-L156) - -**Section sources** -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L41-L103) -- [node.cpp](file://libraries/network/node.cpp#L106-L176) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L68-L162) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L100-L138) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -## Performance Considerations -- Bandwidth limiting: set_total_bandwidth_limit can throttle upload/download to stabilize resource-constrained environments. -- Usage stats: network_get_usage_stats provides byte counters and call statistics to identify hotspots. -- Queueing and throttling: PeerConnection busy()/idle() and transaction_fetching_inhibited_until help prevent overload during floods. -- Latency measurement: P2P plugin logs sync latency for blocks, aiding diagnosis of propagation bottlenecks. - -Practical tips: -- Monitor bytes sent/received per peer to detect misbehaving nodes. -- Temporarily lower bandwidth limits to stabilize a congested network. -- Use clear_peer_database to remove stale peers and improve discovery. - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L290-L290) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L294-L294) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L327-L329) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp#L144-L148) - -## Troubleshooting Guide - -### Peer Discovery Failures -Symptoms: -- Low connection count despite configured seeds. -- Repeated failed connection attempts. - -Actions: -- Inspect potential peers: use get_potential_peers to enumerate endpoints and last_connection_disposition. -- Clear peer database: call clear_peer_database to reset persistent records and retry discovery. -- Review logs: ensure p2p logger is enabled and review handshake/connection rejection reasons. - -Evidence in code: -- Potential peer disposition includes handshaking failure and rejected outcomes. -- Peer DB persists last error and attempt counts. - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L296-L296) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L288-L288) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L39-L45) -- [peer_database.cpp](file://libraries/network/peer_database.cpp#L151-L174) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -### Handshake and Protocol Issues -Symptoms: -- Peers connect then close quickly. -- Negotiation remains stuck at hello_sent or accepted. - -Actions: -- Check negotiation status: monitor connection_negotiation_status transitions. -- Verify chain_id compatibility and user agent/version fields exchanged during hello. -- Inspect firewall and NAT traversal: is_firewalled and endpoint fields capture relevant state. - -Evidence in code: -- Negotiation states and connection times are tracked in PeerConnection. -- Hello fields include node identifiers and capabilities. - -**Section sources** -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L94-L106) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L175-L198) -- [peer_connection.cpp](file://libraries/network/peer_connection.cpp#L169-L200) - -### Block Propagation Delays -Symptoms: -- Blocks arrive late or out of order. -- Sync stalls with peers. - -Actions: -- Measure propagation: use get_block_propagation_data to retrieve received/validated timestamps and originating peer. -- Inspect peer inventory: review advertised and requested sets to detect missing items. -- Reduce bandwidth pressure: temporarily lower limits to improve responsiveness. - -Evidence in code: -- message_propagation_data stores propagation timestamps and origin. -- Peer inventory tracking and sync queues are maintained in PeerConnection. - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L54) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L278-L278) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp#L227-L268) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L290-L290) - -### Network Partition Detection -Symptoms: -- Partial connectivity with isolated subset of peers. -- No progress on sync despite multiple peers. - -Actions: -- Compare get_connected_peers and get_potential_peers to identify partitions. -- Clear peer database to force re-discovery and rebuild topology. -- Increase verbosity of p2p logger to capture partition events. - -Evidence in code: -- Peer status includes endpoint and variant info for diagnostics. -- Peer DB records last seen and disposition to infer partition health. - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L249-L253) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L47-L71) -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -### Logging Configuration for Network Debugging -- Configure loggers: default and p2p loggers route to console and file appenders. -- File appender writes to logs/p2p/p2p.log. -- Adjust levels to capture detailed P2P events. - -Evidence in code: -- Logger and appender configuration in debug config. - -**Section sources** -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) - -### Practical Scenarios and Examples -- Peer discovery failures: Clear peer database and observe reconnection behavior; inspect potential peers for repeated handshaking failures. -- Block propagation delays: Retrieve propagation data for recent blocks and compare timestamps across peers to locate slow links. -- Network partitions: Use get_connected_peers and get_potential_peers to map connectivity; clear peer database to recover. - -Evidence in code: -- APIs for propagation data, peer inspection, and peer DB enumeration. - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L276-L278) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L249-L253) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L296-L296) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L288-L288) - -## Conclusion -The VIZ CPP Node provides robust primitives for network debugging: -- Node exposes propagation metrics, peer inspection, and usage stats. -- PeerConnection tracks negotiation and per-peer timing. -- PeerDatabase persists topology and outcomes for diagnosis. -- P2P plugin integrates with the chain and broadcasts messages. -- Logging and debug_node plugin support controlled experiments and logging control. - -These capabilities enable systematic diagnosis of connection issues, propagation bottlenecks, and topology problems, along with actionable performance tuning. - -## Appendices - -### Appendix A: Key APIs and Where They Are Defined -- Propagation data retrieval: get_block_propagation_data, get_transaction_propagation_data -- Peer inspection: get_connected_peers, get_potential_peers -- Peer DB operations: clear_peer_database, update_entry, lookup_or_create_entry_for_endpoint -- Bandwidth and stats: set_total_bandwidth_limit, network_get_info, network_get_usage_stats -- Transport: MessageOrientedConnection methods for accept/bind/connect/send -- P2P broadcast: broadcast_block, broadcast_transaction - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L249-L253) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L276-L278) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L288-L288) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L292-L294) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L296-L296) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp#L104-L134) -- [message_oriented_connection.hpp](file://libraries/network/include/graphene/network/message_oriented_connection.hpp#L45-L79) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L40-L46) - -### Appendix B: Logging Configuration Reference -- Console and file appenders for default and p2p loggers -- Log file location: logs/p2p/p2p.log - -**Section sources** -- [config_debug.ini](file://share/vizd/config/config_debug.ini#L107-L126) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Performance Profiling Utilities.md b/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Performance Profiling Utilities.md deleted file mode 100644 index 96553f475b..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Performance Profiling Utilities.md +++ /dev/null @@ -1,495 +0,0 @@ -# Performance Profiling Utilities - - -**Referenced Files in This Document** -- [inflation_plot.py](file://programs/util/inflation_plot.py) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp) -- [main.cpp](file://programs/size_checker/main.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [testing.md](file://documentation/testing.md) -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes performance profiling and analysis utilities available in the VIZ CPP Node. It focuses on: -- The inflation_plot.py script for analyzing blockchain economic metrics and inflation patterns -- The size_checker utility for examining memory usage and object sizing within the blockchain database -- Test utilities test_block_log and test_shared_mem for performance benchmarking and memory testing -- Practical performance analysis workflows for identifying memory bottlenecks, measuring transaction processing throughput, and analyzing database performance -- Profiling techniques for blockchain processing, network operations, and database performance -- Guidance on interpreting performance metrics and identifying optimization opportunities -- Integration with external profiling tools and system monitoring approaches - -## Project Structure -The performance-related utilities are located under programs/util and programs/size_checker. They complement the core blockchain libraries under libraries/chain and libraries/network, and are integrated into the node via plugins. - -```mermaid -graph TB -subgraph "Utilities" -U1["programs/util/inflation_plot.py"] -U2["programs/util/test_block_log.cpp"] -U3["programs/util/test_shared_mem.cpp"] -U4["programs/size_checker/main.cpp"] -end -subgraph "Core Libraries" -L1["libraries/chain/include/graphene/chain/database.hpp"] -L2["libraries/chain/include/graphene/chain/block_log.hpp"] -L3["libraries/network/include/graphene/network/node.hpp"] -end -subgraph "Plugins" -P1["plugins/chain/plugin.cpp"] -end -U1 --> L1 -U2 --> L2 -U3 --> L1 -U4 --> L1 -P1 --> L1 -P1 --> L2 -``` - -**Diagram sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L200) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L23-L322) - -**Section sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L200) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L23-L322) - -## Core Components -- inflation_plot.py: Reads per-block JSON metrics and plots monetary supply projections with inflection points for economic parameters. -- size_checker: Computes memory and wire sizes for protocol types and blocks, aiding memory footprint analysis. -- test_block_log: Exercises block_log append/read operations for I/O and serialization performance checks. -- test_shared_mem: Validates shared memory container behavior and object sizing for chainbase/shared memory usage. -- Chain database and block_log APIs: Provide the foundational interfaces for performance-sensitive operations. -- Network node interface: Exposes hooks for message propagation timing and peer statistics relevant to network performance. - -**Section sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L200) - -## Architecture Overview -The performance utilities operate at different layers: -- Economic metrics plotting relies on external JSON input and visualization -- Memory sizing operates on protocol types and block structures -- Block log tests exercise the block storage layer -- Shared memory tests validate chainbase/shared memory behavior -- Plugins integrate these capabilities into the node lifecycle - -```mermaid -graph TB -A["inflation_plot.py
Economic metrics plotting"] --> B["External JSON metrics"] -C["size_checker
Memory sizing"] --> D["Protocol types and blocks"] -E["test_block_log
Block I/O tests"] --> F["block_log API"] -G["test_shared_mem
Shared memory tests"] --> H["Chainbase/shared memory"] -I["plugins/chain/plugin.cpp
Node integration"] --> J["database.hpp"] -I --> K["block_log.hpp"] -J --> L["Core blockchain processing"] -K --> M["Block storage and retrieval"] -``` - -**Diagram sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L23-L322) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) - -## Detailed Component Analysis - -### inflation_plot.py -Purpose: -- Parse per-block JSON records containing block number, supply, and revenue vector -- Plot cumulative supply growth and mark inflection points for economic parameters - -Key behaviors: -- Reads a JSON stream line-by-line -- Filters blocks at regular intervals (every 10,000 blocks) -- Converts block numbers to years using a constant blocks-per-year value -- Plots supply on a logarithmic scale with custom tick labels -- Identifies inflection points by detecting changes in revenue vector components - -```mermaid -flowchart TD -Start(["Start"]) --> ReadFile["Open JSON input file"] -ReadFile --> Loop["Iterate lines"] -Loop --> Parse["Parse JSON line"] -Parse --> Filter{"Block divisible by 10000?"} -Filter --> |No| Loop -Filter --> |Yes| Compute["Compute x=block/BLOCKS_PER_YEAR, y=supply/1000"] -Compute --> Store["Append to x,y series"] -Store --> Inflection{"First non-zero revenue component change?"} -Inflection --> |No| Loop -Inflection --> |Yes| Mark["Mark inflection point with color and shape"] -Mark --> Loop -Loop --> DonePlot["Plot curve and markers"] -DonePlot --> Save["Save figure to 'myfig.png'"] -Save --> End(["End"]) -``` - -**Diagram sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) - -Interpretation tips: -- Peaks in the supply curve indicate policy changes or hardforks -- Inflection markers highlight shifts in revenue distribution among curators, content creators, producers, liquidity, and proof-of-work -- Logarithmic scale helps visualize long-term trends - -Practical usage: -- Generate per-block metrics JSON from node logs or debug outputs -- Run the script to produce a supply projection chart - -**Section sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) - -### size_checker -Purpose: -- Measure memory footprint and serialized size of protocol types and blocks -- Aid in optimizing data structures and reducing serialization overhead - -Key behaviors: -- Iterates through operation types and computes in-memory and packed sizes -- Sorts types by memory size to highlight heavy-weight structures -- Prints JSON array of type metadata for further analysis -- Reports block header and signed block sizes - -```mermaid -flowchart TD -Start(["Start"]) --> Init["Initialize operation type iterator"] -Init --> Visit["Visit each operation type"] -Visit --> Measure["Measure sizeof(Type) and pack_size(Type)"] -Measure --> Record["Record name, mem_size, wire_size"] -Record --> Next{"More types?"} -Next --> |Yes| Visit -Next --> |No| Sort["Sort by mem_size descending"] -Sort --> Print["Print JSON array"] -Print --> Sizes["Report block_header and signed_block sizes"] -Sizes --> End(["End"]) -``` - -**Diagram sources** -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) - -Interpretation tips: -- Types with large memory or wire sizes are candidates for optimization -- Compare mem_size vs wire_size to identify over-allocation or inefficient packing -- Use reported block sizes to estimate transaction and block overhead - -Practical usage: -- Build and run the utility to generate a size report -- Review sorted types to prioritize refactoring efforts - -**Section sources** -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) - -### test_block_log -Purpose: -- Exercise block_log append and read operations to evaluate I/O and serialization performance -- Validate block storage behavior under controlled conditions - -Key behaviors: -- Opens a temporary block log -- Appends two test blocks, flushes, and verifies head updates -- Reads back stored blocks and compares packed sizes -- Demonstrates typical block_log usage patterns - -```mermaid -sequenceDiagram -participant T as "test_block_log.cpp" -participant BL as "block_log" -participant FS as "Filesystem" -T->>BL : open(temp_path) -T->>BL : append(b1) -BL->>FS : write block + index -T->>BL : flush() -T->>BL : append(b2) -BL->>FS : write block + index -T->>BL : flush() -T->>BL : read_block(0) -BL->>FS : seek + read -T->>BL : read_block(offset) -BL->>FS : seek + read -T->>BL : read_head() -BL->>FS : read head pointer -``` - -**Diagram sources** -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) - -Interpretation tips: -- Monitor flush frequency and block sizes to tune I/O performance -- Use read timings to estimate storage latency and throughput -- Validate that offsets and indices are correctly maintained - -Practical usage: -- Run the test to verify block_log operations -- Extend with timing measurements for performance profiling - -**Section sources** -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) - -### test_shared_mem -Purpose: -- Validate shared memory container behavior and object sizing for chainbase/shared memory usage -- Aid in diagnosing memory allocation and fragmentation issues - -Key behaviors: -- Creates or opens a managed mapped file segment -- Constructs a multi-index container of shared_string-backed book objects -- Emplaces entries and manipulates a deque within shared memory -- Uses named mutex for synchronization - -```mermaid -flowchart TD -Start(["Start"]) --> Open["Open or create managed segment './book_container.db'"] -Open --> Construct["Construct book_container and book deque"] -Construct --> Iterate["Iterate container contents"] -Iterate --> Emplace["Emplace new book entry"] -Emplace --> DequeOp["Operate on shared deque"] -DequeOp --> Sync["Named mutex usage"] -Sync --> End(["End"]) -``` - -**Diagram sources** -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) - -Interpretation tips: -- Verify that shared memory limits are respected and allocations succeed -- Monitor container growth and memory usage patterns -- Ensure proper synchronization with named mutexes - -Practical usage: -- Run the test to validate shared memory setup -- Extend with memory profiling and allocation tracing - -**Section sources** -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) - -### Blockchain Processing and Database Performance Hooks -The chain database exposes several performance-relevant controls and flags: -- Validation step flags for selective validation to reduce overhead during reindex or specialized operations -- Shared memory management functions to monitor and adjust free memory thresholds -- Methods to open/reindex databases with configurable shared memory sizes - -```mermaid -classDiagram -class database { -+open(data_dir, shared_mem_dir, initial_supply, shared_file_size, chainbase_flags) -+reindex(data_dir, shared_mem_dir, from_block_num, shared_file_size) -+set_min_free_shared_memory_size(size_t) -+set_inc_shared_memory_size(size_t) -+set_block_num_check_free_size(uint32_t) -+check_free_memory(skip_print, current_block_num) -+wipe(data_dir, shared_mem_dir, include_blocks) -+close(rewind) -+push_block(b, skip) -+push_transaction(trx, skip) -+validate_block(b, skip) -} -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [database.cpp](file://libraries/chain/database.cpp#L351-L413) - -Interpretation tips: -- Use validation step flags to disable expensive checks during benchmarking -- Tune shared memory parameters to avoid frequent resizing and improve cache locality -- Monitor free memory checks to detect potential bottlenecks - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L56-L108) -- [database.cpp](file://libraries/chain/database.cpp#L351-L413) - -### Network Operations Performance Hooks -The network node interface supports message propagation timing and peer statistics: -- Message propagation data structure captures received and validated timestamps -- Peer status provides endpoint and variant information for diagnostics -- Node delegate callbacks for block and transaction handling expose timing-sensitive entry points - -```mermaid -classDiagram -class node { -+set_node_delegate(del) -+load_configuration(path) -+close() -} -class node_delegate { -+has_item(id) bool -+handle_block(blk_msg, sync_mode, contained_transaction_message_ids) bool -+handle_transaction(trx_msg) void -+handle_message(message) void -+get_block_ids(synopsis, remaining_item_count, limit) std : : vector -+get_item(id) message -+get_blockchain_synopsis(reference_point, number_of_blocks_after_reference_point) std : : vector -+sync_status(item_type, item_count) void -+connection_count_changed(c) void -+get_block_number(block_id) uint32_t -+get_block_time(block_id) fc : : time_point_sec -+get_blockchain_now() fc : : time_point_sec -+get_head_block_id() item_hash_t -+estimate_last_known_fork_from_git_revision_timestamp(unix_timestamp) uint32_t -+error_encountered(message, error) void -} -node --> node_delegate : "uses" -``` - -**Diagram sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L1-L200) - -Interpretation tips: -- Track message propagation delays to identify network bottlenecks -- Monitor peer counts and sync status to assess network health -- Use delegate callbacks to instrument timing around block and transaction processing - -**Section sources** -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L166) - -## Dependency Analysis -The performance utilities depend on core library APIs and are integrated via plugins: -- inflation_plot.py depends on external JSON metrics and matplotlib -- size_checker depends on protocol type definitions and fc serialization -- test_block_log depends on block_log API -- test_shared_mem depends on chainbase and boost interprocess -- Plugins initialize and configure database and block log behavior - -```mermaid -graph LR -IP["inflation_plot.py"] --> METRICS["Per-block metrics JSON"] -SC["size_checker"] --> PROT["Protocol types"] -TBL["test_block_log"] --> BLAPI["block_log.hpp"] -TSM["test_shared_mem"] --> CB["chainbase/shared memory"] -PLG["plugins/chain/plugin.cpp"] --> DBH["database.hpp"] -PLG --> BLH["block_log.hpp"] -``` - -**Diagram sources** -- [inflation_plot.py](file://programs/util/inflation_plot.py#L1-L74) -- [main.cpp](file://programs/size_checker/main.cpp#L1-L85) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) -- [plugin.cpp](file://plugins/chain/plugin.cpp#L23-L322) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) - -**Section sources** -- [plugin.cpp](file://plugins/chain/plugin.cpp#L23-L322) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) -- [block_log.hpp](file://libraries/chain/include/graphene/chain/block_log.hpp#L1-L75) - -## Performance Considerations -- Memory footprint - - Use size_checker to identify large protocol types and blocks - - Optimize data structures and reduce unnecessary fields - - Monitor shared memory growth and tune increment sizes -- Storage I/O - - Profile block_log append/read operations with test_block_log - - Adjust flush policies and block sizes to balance durability and throughput -- Network latency - - Instrument message propagation timing via node delegate callbacks - - Monitor peer counts and sync status to detect network congestion -- Validation overhead - - Apply validation step flags selectively during benchmarks - - Disable non-essential checks for synthetic workloads - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Memory pressure during reindex - - Increase shared memory size and adjust minimum free memory thresholds - - Use validation step flags to reduce overhead -- Slow block log operations - - Verify flush policies and filesystem performance - - Benchmark with test_block_log to isolate bottlenecks -- Shared memory allocation failures - - Validate segment creation and capacity - - Use test_shared_mem to confirm container behavior -- Network performance anomalies - - Inspect message propagation delays and peer status - - Use node delegate hooks to capture timing data - -**Section sources** -- [database.cpp](file://libraries/chain/database.cpp#L351-L413) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [test_shared_mem.cpp](file://programs/util/test_shared_mem.cpp#L1-L169) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L166) - -## Conclusion -The VIZ CPP Node provides focused utilities for performance profiling across economic metrics, memory sizing, block storage, and shared memory behavior. Combined with plugin-driven configuration and core library APIs, these tools enable targeted analysis and optimization of blockchain processing, network operations, and database performance. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Workflows - -- Identifying memory bottlenecks - - Run size_checker to obtain type size reports - - Focus on top-heavy types and reduce field sizes or packing overhead - - Monitor shared memory usage and adjust thresholds via database configuration - -- Measuring transaction processing throughput - - Use debug_node plugin to generate synthetic blocks and transactions - - Instrument node delegate callbacks to capture timing around block and transaction handling - - Compare metrics across different validation step configurations - -- Analyzing database performance - - Use test_block_log to profile append/read latencies - - Evaluate block sizes and flush frequencies - - Monitor free memory checks and shared memory growth during extended runs - -**Section sources** -- [debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp#L48-L166) -- [test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [database.cpp](file://libraries/chain/database.cpp#L351-L413) - -### Integration with External Tools and Monitoring -- Code coverage and profiling - - Use lcov-based workflow for coverage capture and reporting - - Combine with unit test targets for comprehensive coverage analysis - -- System monitoring - - Track CPU, memory, and I/O metrics during utility runs - - Correlate metrics with block log and shared memory operations - -**Section sources** -- [testing.md](file://documentation/testing.md#L26-L43) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Transaction Debugging Tools.md b/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Transaction Debugging Tools.md deleted file mode 100644 index d6c5438bfc..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Debugging Tools/Transaction Debugging Tools.md +++ /dev/null @@ -1,366 +0,0 @@ -# Transaction Debugging Tools - - -**Referenced Files in This Document** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp) -- [main.cpp](file://programs/js_operation_serializer/main.cpp) -- [transaction.cpp](file://libraries/protocol/transaction.cpp) -- [sign_state.hpp](file://libraries/protocol/sign_state.hpp) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [get_config.cpp](file://libraries/protocol/get_config.cpp) -- [transaction_object.hpp](file://libraries/chain/transaction_object.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive documentation for transaction debugging utilities in the VIZ C++ Node. It focuses on three primary tools: -- sign_transaction: a command-line utility to debug transaction signing issues by computing digests and generating signatures from WIF keys. -- sign_digest: a command-line utility to debug cryptographic operations and signature verification processes by signing arbitrary SHA256 digests. -- JavaScript operation serializer: a utility to convert between JSON and binary operation formats for inspection and debugging. - -It also covers transaction validation, authority verification, and common error scenarios encountered during transaction construction, signing, and network transmission. - -## Project Structure -The transaction debugging tools reside under the programs/util directory and the programs/js_operation_serializer directory. Supporting protocol and chain logic is located under libraries/protocol and libraries/chain. - -```mermaid -graph TB -subgraph "Utilities" -ST["programs/util/sign_transaction.cpp"] -SD["programs/util/sign_digest.cpp"] -JS["programs/js_operation_serializer/main.cpp"] -end -subgraph "Protocol Layer" -TXCPP["libraries/protocol/transaction.cpp"] -SIGSTATE["libraries/protocol/sign_state.hpp"] -EXC["libraries/protocol/exceptions.hpp"] -CFG["libraries/protocol/config.hpp"] -GCFG["libraries/protocol/get_config.cpp"] -end -subgraph "Chain Layer" -TXOBJ["libraries/chain/transaction_object.hpp"] -end -ST --> TXCPP -SD --> TXCPP -JS --> TXCPP -TXCPP --> SIGSTATE -TXCPP --> EXC -TXCPP --> CFG -GCFG --> CFG -TXOBJ --> TXCPP -``` - -**Diagram sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L1-L54) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L1-L49) -- [main.cpp](file://programs/js_operation_serializer/main.cpp#L1-L531) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [sign_state.hpp](file://libraries/protocol/sign_state.hpp#L1-L45) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp#L1-L116) -- [config.hpp](file://libraries/protocol/config.hpp#L1-L169) -- [get_config.cpp](file://libraries/protocol/get_config.cpp#L1-L78) -- [transaction_object.hpp](file://libraries/chain/transaction_object.hpp#L1-L56) - -**Section sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L1-L54) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L1-L49) -- [main.cpp](file://programs/js_operation_serializer/main.cpp#L1-L531) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L361) -- [sign_state.hpp](file://libraries/protocol/sign_state.hpp#L1-L45) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp#L1-L116) -- [config.hpp](file://libraries/protocol/config.hpp#L1-L169) -- [get_config.cpp](file://libraries/protocol/get_config.cpp#L1-L78) -- [transaction_object.hpp](file://libraries/chain/transaction_object.hpp#L1-L56) - -## Core Components -- sign_transaction: Reads transaction and WIF key from stdin, computes transaction digest and signature digest bound to the chain ID, signs with the private key, and prints a JSON result containing the original transaction, computed digests, public key, and signature. -- sign_digest: Reads a SHA256 digest and WIF key from stdin, signs the digest with the private key, and prints a JSON result containing the digest, public key, and signature. -- JavaScript operation serializer: Generates JavaScript serializers for operations and chain properties, enabling inspection of binary-to-JSON conversion behavior. - -Key input/output formats: -- Both utilities consume one JSON object per line via stdin and emit one JSON object per line to stdout. -- sign_transaction expects an object with a transaction field and a WIF key string; it emits an object with transaction, digest, sig_digest, key, and sig. -- sign_digest expects an object with a hex SHA256 digest string and a WIF key string; it emits an object with the digest, key, and sig. - -Common error scenarios: -- Malformed JSON input lines. -- Invalid WIF key or mismatched chain ID. -- Transaction validation failures (e.g., missing operations, invalid authority combinations). -- Duplicate or irrelevant signatures during authority verification. - -**Section sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L12-L26) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L12-L24) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp#L54-L115) - -## Architecture Overview -The signing utilities integrate with the protocol layer to compute digests and signatures, and leverage the chain layer for transaction storage and duplicate detection. - -```mermaid -sequenceDiagram -participant User as "User" -participant ST as "sign_transaction" -participant TX as "transaction.cpp" -participant KEYS as "key_conversion.hpp" -participant OUT as "stdout" -User->>ST : "stdin line with {tx, wif}" -ST->>TX : "tx.digest()" -ST->>TX : "tx.sig_digest(CHAIN_ID)" -ST->>KEYS : "wif_to_key(wif)" -KEYS-->>ST : "private_key" -ST->>ST : "sign_compact(sig_digest)" -ST-->>OUT : "JSON {tx, digest, sig_digest, key, sig}" -``` - -**Diagram sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L28-L51) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L17-L28) -- [config.hpp](file://libraries/protocol/config.hpp#L9-L9) - -```mermaid -sequenceDiagram -participant User as "User" -participant SD as "sign_digest" -participant KEYS as "key_conversion.hpp" -participant OUT as "stdout" -User->>SD : "stdin line with {dig, wif}" -SD->>KEYS : "wif_to_key(wif)" -KEYS-->>SD : "private_key" -SD->>SD : "sign_compact(dig)" -SD-->>OUT : "JSON {dig, key, sig}" -``` - -**Diagram sources** -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L26-L46) - -## Detailed Component Analysis - -### sign_transaction Tool -Purpose: -- Debug transaction signing by computing digests and verifying signatures against a given chain ID. - -Command-line usage: -- Standard input: newline-separated JSON objects with fields: - - tx: a transaction object - - wif: a WIF-encoded private key string -- Standard output: newline-separated JSON objects with fields: - - tx: the original transaction - - digest: transaction digest - - sig_digest: signature digest bound to chain ID - - key: public key derived from WIF - - sig: compact signature - -Processing logic: -- Parse each input line as JSON. -- Convert WIF to a private key. -- Compute transaction digest and signature digest bound to the chain ID. -- Sign the signature digest with the private key. -- Emit the result as JSON. - -```mermaid -flowchart TD -Start(["Read stdin line"]) --> Parse["Parse JSON variant"] -Parse --> Valid{"Valid JSON?"} -Valid --> |No| Error["Skip/Log error"] -Valid --> |Yes| WIF["Convert WIF to private key"] -WIF --> Digests["Compute tx.digest()
and tx.sig_digest(CHAIN_ID)"] -Digests --> Sign["Sign sig_digest with private key"] -Sign --> PubKey["Derive public key"] -PubKey --> Emit["Emit JSON {tx, digest, sig_digest, key, sig}"] -Emit --> End(["Exit loop"]) -Error --> End -``` - -**Diagram sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L28-L51) - -**Section sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L12-L26) -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L28-L51) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L17-L28) -- [config.hpp](file://libraries/protocol/config.hpp#L9-L9) - -### sign_digest Tool -Purpose: -- Debug cryptographic operations and signature verification by signing arbitrary SHA256 digests. - -Command-line usage: -- Standard input: newline-separated JSON objects with fields: - - dig: a hex-encoded SHA256 digest string - - wif: a WIF-encoded private key string -- Standard output: newline-separated JSON objects with fields: - - dig: the input digest - - key: public key derived from WIF - - sig: compact signature - -Processing logic: -- Parse each input line as JSON. -- Convert WIF to a private key. -- Sign the digest with the private key. -- Emit the result as JSON. - -```mermaid -flowchart TD -Start(["Read stdin line"]) --> Parse["Parse JSON variant"] -Parse --> Valid{"Valid JSON?"} -Valid --> |No| Error["Skip/Log error"] -Valid --> |Yes| WIF["Convert WIF to private key"] -WIF --> Sign["Sign digest with private key"] -Sign --> PubKey["Derive public key"] -PubKey --> Emit["Emit JSON {dig, key, sig}"] -Emit --> End(["Exit loop"]) -Error --> End -``` - -**Diagram sources** -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L26-L46) - -**Section sources** -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L12-L24) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L26-L46) - -### JavaScript Operation Serializer -Purpose: -- Generate JavaScript serializers for operations and chain properties to inspect binary-to-JSON conversion behavior. - -Command-line usage: -- No command-line options; runs and prints generated JavaScript serializer definitions to stdout. - -Processing logic: -- Iterates through operation types and chain properties. -- Generates serializer definitions for each type, printing them to stdout. - -```mermaid -flowchart TD -Start(["Run main"]) --> Ops["Iterate operation types"] -Ops --> Props["Iterate chain properties"] -Props --> Init["Initialize serializers"] -Init --> Print["Print serializer definitions"] -Print --> End(["Exit"]) -``` - -**Diagram sources** -- [main.cpp](file://programs/js_operation_serializer/main.cpp#L495-L531) - -**Section sources** -- [main.cpp](file://programs/js_operation_serializer/main.cpp#L495-L531) - -### Transaction Validation and Authority Verification -Validation: -- Transactions must contain at least one operation. -- Each operation undergoes validation checks. - -Authority verification: -- The verify_authority function computes required authorities and validates provided signatures and approvals. -- It distinguishes between regular, active, and master authorities and enforces mutual exclusivity rules. -- It reports missing authorities and unused signatures/approvals. - -```mermaid -flowchart TD -Start(["verify_authority(ops, sigs, getters)"]) --> Required["Collect required authorities"] -Required --> Branch{"Regular authority present?"} -Branch --> |Yes| Reg["Check regular authority
and approvals"] -Branch --> |No| ActMaster["Check active/master authorities"] -Reg --> Unused["Assert no unused signatures/approvals"] -ActMaster --> Unused -Unused --> End(["Success"]) -``` - -**Diagram sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L94-L222) -- [sign_state.hpp](file://libraries/protocol/sign_state.hpp#L10-L42) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp#L58-L115) - -## Dependency Analysis -The signing utilities depend on the protocol layer for digest computation and signature generation. The chain layer provides transaction storage and duplicate detection mechanisms. - -```mermaid -graph LR -ST["sign_transaction.cpp"] --> TXCPP["transaction.cpp"] -SD["sign_digest.cpp"] --> TXCPP -TXCPP --> SIGSTATE["sign_state.hpp"] -TXCPP --> EXC["exceptions.hpp"] -TXCPP --> CFG["config.hpp"] -GCFG["get_config.cpp"] --> CFG -TXOBJ["transaction_object.hpp"] --> TXCPP -``` - -**Diagram sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L10-L10) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L10-L10) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L3) -- [sign_state.hpp](file://libraries/protocol/sign_state.hpp#L1-L45) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp#L1-L116) -- [config.hpp](file://libraries/protocol/config.hpp#L1-L169) -- [get_config.cpp](file://libraries/protocol/get_config.cpp#L1-L78) -- [transaction_object.hpp](file://libraries/chain/transaction_object.hpp#L1-L56) - -**Section sources** -- [sign_transaction.cpp](file://programs/util/sign_transaction.cpp#L10-L10) -- [sign_digest.cpp](file://programs/util/sign_digest.cpp#L10-L10) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L1-L3) -- [transaction_object.hpp](file://libraries/chain/transaction_object.hpp#L1-L56) - -## Performance Considerations -- Both utilities process input line-by-line, suitable for streaming large sets of transactions or digests. -- Digest computations and ECDSA signatures are CPU-bound; batching multiple inputs per invocation reduces overhead. -- For large-scale debugging, consider precomputing digests and reusing WIF keys to minimize repeated conversions. - -## Troubleshooting Guide -Common transaction validation failures: -- Missing operations: Ensure the transaction contains at least one operation. -- Invalid authority combinations: Regular authority cannot be mixed with active/master authority in the same transaction. - -Signature verification problems: -- Duplicate signatures: Detected during signature key extraction; remove duplicates. -- Irrelevant signatures: Unused signatures cause verification failure; remove before submission. -- Missing required authorities: Add signatures from required authorities or adjust approvals. - -Serialization issues: -- Use the JavaScript operation serializer to inspect operation and chain property serializers and confirm binary-to-JSON mapping. - -Network transmission issues: -- Verify the chain ID matches the target network; mismatches prevent successful broadcast. -- Confirm transaction size does not exceed limits and expiration is set appropriately. - -Practical examples: -- Transaction construction errors: Validate operations individually and ensure correct field types/values. -- Authority verification failures: Use sign_transaction to generate expected signatures and compare with provided ones. -- Serialization issues: Compare JSON vs. binary representation using the JavaScript operation serializer. - -**Section sources** -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L30-L36) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L225-L237) -- [transaction.cpp](file://libraries/protocol/transaction.cpp#L76-L92) -- [exceptions.hpp](file://libraries/protocol/exceptions.hpp#L58-L115) -- [config.hpp](file://libraries/protocol/config.hpp#L9-L9) -- [get_config.cpp](file://libraries/protocol/get_config.cpp#L28-L28) - -## Conclusion -The transaction debugging utilities provide focused capabilities to validate signing, cryptographic operations, and serialization. By leveraging the protocol and chain layers, developers can isolate issues in transaction construction, authority verification, and network compatibility. Use the troubleshooting guide to systematically address common problems and improve reliability in production environments. - -## Appendices -- Chain ID: The chain ID is derived from the chain name and is used to bind signatures to the correct network. -- Transaction storage: Duplicate detection relies on storing packed transactions with expiration timestamps. - -**Section sources** -- [config.hpp](file://libraries/protocol/config.hpp#L9-L9) -- [transaction_object.hpp](file://libraries/chain/transaction_object.hpp#L19-L35) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Development Tools.md b/.qoder/repowiki/en/content/Development Tools/Development Tools.md deleted file mode 100644 index 55d91c8010..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Development Tools.md +++ /dev/null @@ -1,346 +0,0 @@ -# Development Tools - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [CMakeLists.txt](file://CMakeLists.txt) -- [documentation/building.md](file://documentation/building.md) -- [documentation/testing.md](file://documentation/testing.md) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md) -- [documentation/plugin.md](file://documentation/plugin.md) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) -- [programs/util/newplugin.py](file://programs/util/newplugin.py) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp) -- [programs/util/pretty_schema.py](file://programs/util/pretty_schema.py) -- [programs/js_operation_serializer/main.cpp](file://programs/js_operation_serializer/main.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the complete development toolkit for VIZ CPP Node, focusing on the CMake build system, cross-platform compilation, Docker-based development environments, testing frameworks, debugging tools, and development workflow. It also covers code generation utilities, schema validation helpers, and practical examples for common development tasks such as building custom plugins, running tests, and profiling performance. The goal is to make the development toolchain accessible to contributors while providing sufficient technical depth for advanced tasks. - -## Project Structure -The repository is organized around a layered structure: -- Top-level CMake configuration orchestrates thirdparty, libraries, plugins, and programs. -- Libraries implement core blockchain logic (chain, protocol, network, utilities, wallet). -- Plugins provide modular features (e.g., chain, debug_node, p2p, webserver). -- Programs include the node daemon, CLI wallet, utilities, and build helpers. -- Documentation provides build, testing, plugin, and debugging guides. -- Dockerfiles and CI workflows support reproducible builds and automated testing. - -```mermaid -graph TB -Root["Top-level CMakeLists.txt"] -ThirdParty["thirdparty/"] -Libs["libraries/"] -Plugins["plugins/"] -Programs["programs/"] -Docs["documentation/"] -Docker["share/vizd/docker/"] -Root --> ThirdParty -Root --> Libs -Root --> Plugins -Root --> Programs -Root --> Docs -Root --> Docker -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L214) -- [README.md](file://README.md#L1-L53) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L277) -- [README.md](file://README.md#L1-L53) - -## Core Components -- Build system: CMake with platform-specific flags, coverage, and optional components (e.g., MongoDB plugin). -- Cross-compilation helper: configure_build.py supports Windows cross-compilation and custom Boost/OpenSSL roots. -- Plugin system: automatic discovery of internal plugins and support for external plugins. -- Testing: unit tests via chain_test with configurable runtime options and coverage reporting. -- Debugging: debug_node plugin for state simulation and API experimentation. -- Utilities: schema inspection, pretty-printing JSON schema, JS operation serializer, and plugin scaffolding. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L52-L89) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L143-L196) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L1-L57) -- [programs/util/pretty_schema.py](file://programs/util/pretty_schema.py#L1-L28) -- [programs/js_operation_serializer/main.cpp](file://programs/js_operation_serializer/main.cpp#L1-L531) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) - -## Architecture Overview -The development architecture integrates build orchestration, modular plugins, and reproducible environments: -- CMake discovers libraries, plugins, and programs, enabling conditional compilation and platform-specific flags. -- Dockerfiles encapsulate build environments for production and testnet scenarios. -- CI workflows automate Docker image builds for main and PR contexts. -- Utilities and scripts streamline plugin creation, reflection checks, and schema validation. - -```mermaid -graph TB -subgraph "Build Orchestration" -CMake["CMakeLists.txt"] -ConfigPy["configure_build.py"] -end -subgraph "Runtime Modules" -Libs["libraries/"] -Plugins["plugins/"] -Programs["programs/"] -end -subgraph "Dev Tools" -NewPlugin["newplugin.py"] -SchemaTest["schema_test.cpp"] -PrettySchema["pretty_schema.py"] -ReflectCheck["check_reflect.py"] -end -subgraph "Reproducibility" -DockerProd["Dockerfile-production"] -GHMain[".github/workflows/docker-main.yml"] -GHPR[".github/workflows/docker-pr-build.yml"] -end -CMake --> Libs -CMake --> Plugins -CMake --> Programs -ConfigPy --> CMake -NewPlugin --> Plugins -SchemaTest --> Libs -PrettySchema --> Plugins -ReflectCheck --> Libs -DockerProd --> Programs -GHMain --> DockerProd -GHPR --> DockerProd -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L214) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L143-L196) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L225-L247) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L44-L56) -- [programs/util/pretty_schema.py](file://programs/util/pretty_schema.py#L9-L27) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L160) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) - -## Detailed Component Analysis - -### CMake Build System -Key characteristics: -- Enforces minimum compiler versions for GCC and Clang. -- Supports compile-time options such as BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, and ENABLE_MONGO_PLUGIN. -- Enables ccache globally when available. -- Adds subdirectories for thirdparty, libraries, plugins, and programs. -- Provides coverage flags via ENABLE_COVERAGE_TESTING. - -Common build options and flags: -- CMAKE_BUILD_TYPE: Release or Debug. -- LOW_MEMORY_NODE: Build a consensus-only node. -- BUILD_TESTNET: Configure for test network. -- CHAINBASE_CHECK_LOCKING: Enable lock checking in chainbase. -- ENABLE_MONGO_PLUGIN: Include MongoDB plugin. -- ENABLE_COVERAGE_TESTING: Enable coverage instrumentation. - -Cross-platform flags: -- Windows (MSVC/Mingw): Compiler and linker flags, static linking options, and MSVC-specific settings. -- macOS/Linux: Standard C++14 flags, platform-specific libraries, and Ninja diagnostics. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L11-L20) -- [CMakeLists.txt](file://CMakeLists.txt#L56-L89) -- [CMakeLists.txt](file://CMakeLists.txt#L106-L110) -- [CMakeLists.txt](file://CMakeLists.txt#L112-L202) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [CMakeLists.txt](file://CMakeLists.txt#L210-L214) - -### Cross-Platform Compilation and Docker Environments -- Platform-specific instructions and dependencies are documented for Ubuntu, macOS, and Windows (where applicable). -- Dockerfiles define reproducible builds for production and testnet, including dependency installation, submodule initialization, and staged builds. - -Practical steps: -- Use configure_build.py to simplify cross-compilation and environment setup. -- Build Docker images for production or testnet using provided Dockerfiles. - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L143-L196) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) - -### Testing Framework -- Unit tests are built via chain_test and executed to validate basic, block, operation, serialization, and time-dependent functionality. -- Runtime configuration supports log levels, report levels, and selective test execution. -- Coverage testing is supported with lcov integration. - -Recommended workflow: -- Build chain_test with CMake. -- Run tests with desired runtime options. -- Capture coverage data and generate HTML reports. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L1-L43) - -### Debugging Tools -- debug_node plugin enables “what-if” simulations by editing chain state locally, generating blocks, and manipulating accounts for testing. -- Provides RPC methods for loading blocks, generating blocks, and updating objects. -- Use with caution: changes are local and do not affect the live network. - -Typical usage: -- Configure RPC and plugin exposure carefully. -- Load historical blocks and simulate conditions. -- Experiment with account keys and transactions. - -**Section sources** -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L1-L134) - -### Plugin Development Toolkit -- Internal plugins are discovered automatically; external plugins can be added to a dedicated directory and built seamlessly. -- newplugin.py generates boilerplate for custom plugins with API registration and factory registration. - -Workflow: -- Run newplugin.py to scaffold a plugin. -- Implement plugin lifecycle hooks and API methods. -- Register APIs and connect to chain events. - -**Section sources** -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L225-L247) - -### Transaction Serialization Utilities -- js_operation_serializer produces JavaScript-friendly serializers for operations and chain objects. -- Useful for front-end tooling and schema validation. - -Usage: -- Build and run the utility to emit serializers for operations and chain properties. - -**Section sources** -- [programs/js_operation_serializer/main.cpp](file://programs/js_operation_serializer/main.cpp#L495-L531) - -### Schema Validation and Reflection Checks -- schema_test.cpp inspects and prints schema metadata for chain objects. -- pretty_schema.py fetches and prettifies JSON schema from a running node’s debug_node API. -- check_reflect.py validates FC_REFLECT declarations against Doxygen class member lists. - -Integration: -- Use schema_test.cpp to inspect object schemas during development. -- Use pretty_schema.py to obtain human-readable schema definitions. -- Use check_reflect.py to ensure reflection and documentation remain synchronized. - -**Section sources** -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L44-L56) -- [programs/util/pretty_schema.py](file://programs/util/pretty_schema.py#L9-L27) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L160) - -### Development Workflow and CI -- Git branching follows a model with master and develop, with strict policies for pull requests, reviews, and tagging. -- CI workflows build Docker images for main and PR contexts to ensure reproducibility. - -Best practices: -- Branch from develop, keep commits focused, and ensure tests pass. -- Use PRs for code review and automated checks. -- Leverage Docker images for consistent environments. - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) - -## Dependency Analysis -The build system composes the project from multiple subprojects. The top-level CMake adds subdirectories for thirdparty, libraries, plugins, and programs. Conditional options influence which components are compiled and linked. - -```mermaid -graph TB -Root["CMakeLists.txt"] -TP["thirdparty"] -LIB["libraries"] -PLG["plugins"] -PRG["programs"] -Root --> TP -Root --> LIB -Root --> PLG -Root --> PRG -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L214) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L214) - -## Performance Considerations -- Use Release builds for performance-sensitive tasks. -- Enable coverage only when profiling or auditing code coverage. -- Consider LOW_MEMORY_NODE for resource-constrained environments (e.g., validators). -- Use Docker images to standardize environments and avoid performance regressions caused by local toolchain differences. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -Common issues and resolutions: -- Compiler version mismatch: Ensure GCC >= 4.8 or Clang >= 3.3 as enforced by CMake. -- Boost version: Use compatible Boost versions; configure_build.py helps locate custom Boost roots. -- Missing dependencies: Follow platform-specific installation steps in the building guide. -- Reflection mismatches: Run check_reflect.py to compare Doxygen-derived members with FC_REFLECT declarations. -- Docker build failures: Verify submodule initialization and environment variables; use provided Dockerfiles for reproducibility. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L11-L20) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L122-L140) -- [documentation/building.md](file://documentation/building.md#L25-L137) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py#L107-L160) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L32-L54) - -## Conclusion -The VIZ CPP Node development toolkit combines a robust CMake build system, cross-platform support, Docker-based reproducibility, comprehensive testing, and powerful debugging utilities. Together with plugin scaffolding and schema validation tools, it enables efficient and reliable development across platforms. Following the documented workflow and leveraging the provided utilities ensures consistent progress and high-quality contributions. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Examples - -- Build a Release binary on Linux/macOS: - - Configure with CMake and build targets as described in the building guide. - - Example targets include the node daemon and CLI wallet. - -- Build a Windows cross-compiled binary: - - Use configure_build.py with the Windows cross-compilation option and appropriate toolchain. - -- Run unit tests: - - Build chain_test and execute with desired runtime options. - -- Generate a custom plugin: - - Run newplugin.py to scaffold a plugin under libraries/plugins/. - - Implement plugin lifecycle and API methods, then rebuild. - -- Inspect chain object schemas: - - Build and run schema_test.cpp to print schema metadata. - - Use pretty_schema.py to fetch and format JSON schema from a node. - -- Profile performance: - - Build with coverage enabled and capture lcov data as described in the testing guide. - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L190-L201) -- [programs/build_helpers/configure_build.py](file://programs/build_helpers/configure_build.py#L168-L179) -- [documentation/testing.md](file://documentation/testing.md#L30-L42) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L225-L247) -- [programs/util/schema_test.cpp](file://programs/util/schema_test.cpp#L44-L56) -- [programs/util/pretty_schema.py](file://programs/util/pretty_schema.py#L9-L27) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Development Workflow.md b/.qoder/repowiki/en/content/Development Tools/Development Workflow.md deleted file mode 100644 index ad630bc910..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Development Workflow.md +++ /dev/null @@ -1,394 +0,0 @@ -# Development Workflow - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md) -- [documentation/plugin.md](file://documentation/plugin.md) -- [programs/util/newplugin.py](file://programs/util/newplugin.py) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) -- [documentation/building.md](file://documentation/building.md) -- [documentation/testing.md](file://documentation/testing.md) -- [.travis.yml](file://.travis.yml) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the complete development lifecycle for VIZ CPP Node contributors. It covers code style expectations, commit and branch conventions, pull request processes, plugin development workflow using the newplugin.py template, continuous integration pipelines (GitHub Actions and legacy CI), testing requirements, code review and merge criteria, collaboration and issue tracking, and how development practices tie into quality assurance. - -## Project Structure -The repository is organized around a CMake-based build system, with libraries, plugins, programs, and documentation. Key areas for contributors: -- Libraries: core blockchain logic and APIs -- Plugins: modular features enabling APIs and services -- Programs: utilities and executables (vizd, cli_wallet, helpers) -- Documentation: build, plugin, testing, and Git guidelines -- CI: GitHub Actions for Docker builds and PR validation - -```mermaid -graph TB -A["Root"] --> B["libraries"] -A --> C["plugins"] -A --> D["programs"] -A --> E["documentation"] -A --> F[".github/workflows"] -A --> G[".travis.yml"] -B --> B1["chain"] -B --> B2["api"] -B --> B3["network"] -B --> B4["protocol"] -B --> B5["utilities"] -B --> B6["wallet"] -C --> C1["chain"] -C --> C2["account_history"] -C --> C3["debug_node"] -C --> C4["json_rpc"] -C --> C5["mongo_db"] -C --> C6["tags"] -C --> C7["webserver"] -C --> C8["witness_api"] -C --> C9["... others"] -D --> D1["util"] -D --> D2["build_helpers"] -D --> D3["cli_wallet"] -D --> D4["vizd"] -D --> D5["js_operation_serializer"] -D --> D6["size_checker"] -D --> D7["util/*"] -E --> E1["building.md"] -E --> E2["plugin.md"] -E --> E3["testing.md"] -E --> E4["git_guildelines.md"] -E --> E5["testnet.md"] -E --> E6["debug_node_plugin.md"] -E --> E7["api_notes.md"] -E --> E8["doxygen/*"] -F --> F1["docker-main.yml"] -F --> F2["docker-pr-build.yml"] -``` - -**Section sources** -- [README.md](file://README.md#L1-L53) -- [documentation/building.md](file://documentation/building.md#L1-L212) - -## Core Components -- Git and branching model: defines branches, PR process, and policies for master and develop. -- Plugin system: how plugins are registered, configured, and enabled. -- CI pipelines: automated Docker builds for PRs and releases. -- Testing: unit tests, coverage, and configuration options. -- Build system: CMake options and platform-specific instructions. - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L1-L111) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/building.md](file://documentation/building.md#L1-L212) - -## Architecture Overview -The development workflow integrates local development, automated CI, and release automation. The diagram below maps the primary components and their interactions during development and release. - -```mermaid -graph TB -Dev["Developer"] --> Repo["Repository"] -Repo --> GH["GitHub Actions
docker-main.yml"] -Repo --> PR["Pull Request Validation
docker-pr-build.yml"] -GH --> DockerHub["Docker Hub Images"] -PR --> DockerHub -Repo --> Travis["Legacy CI
.travis.yml"] -Travis --> DockerHub -Repo --> Docs["Documentation
building.md, testing.md, plugin.md"] -Repo --> Plugins["Plugin System
newplugin.py"] -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [.travis.yml](file://.travis.yml#L1-L46) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) - -## Detailed Component Analysis - -### Git and Pull Request Workflow -- Branches: - - master: release branch; PRs validated by CI before merge. - - develop: active development branch; PRs validated by CI. -- Branch naming: - - Issue-driven patches: issue-number-short-description. - - Non-issue patches: YYYYMMDD-shortname. -- Pull Requests: - - All changes to develop and master are submitted via PRs. - - Automated testing is mandatory; manual code review required. - - Merge conflicts must be resolved by rebasing against origin/develop. -- Policies: - - Force-push policy: master and develop are protected; patch branches may be force-pushed at discretion. - - Tagging policy: releases tagged as vMajor.Hardfork.Release on master. - -```mermaid -flowchart TD -Start(["Start Feature/Hotfix"]) --> Branch["Create Branch from develop
Issue-driven or YYYYMMDD naming"] -Branch --> Develop["Implement Changes
Follow style and tests"] -Develop --> Commit["Commit Conventions
Clear messages, single focus"] -Commit --> Push["Push Branch"] -Push --> PR["Open Pull Request to develop"] -PR --> CI["Automated Testing"] -CI --> |Pass| Review["Code Review by 2 Developers"] -CI --> |Fail| Fix["Fix Issues and Update PR"] -Review --> |Approved| Merge["Merge to develop or master per policy"] -Review --> |Changes Requested| Fix -Merge --> End(["Complete"]) -``` - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L8-L111) - -### Plugin Development Workflow Using newplugin.py -The newplugin.py script generates a complete plugin skeleton with boilerplate for headers, implementation files, and CMake configuration. After generation, contributors register signals, add API methods, and integrate with the application’s API factory. - -```mermaid -flowchart TD -A["Run newplugin.py provider name"] --> B["Generate files:
CMakeLists.txt,
*_plugin.hpp/cpp,
*_api.hpp/cpp"] -B --> C["Add plugin to CMake environment variable CHAIN_INTERNAL_PLUGINS"] -C --> D["Enable plugin via config: enable-plugin=name"] -D --> E["Register API factory in plugin_startup()
register_api_factory_api>"] -E --> F["Reflect API methods in FC_API declaration"] -F --> G["Optionally enable public API via config:
public-api, api-user"] -G --> H["Replay chain if enabling a DB-affecting plugin"] -``` - -**Diagram sources** -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) -- [documentation/plugin.md](file://documentation/plugin.md#L21-L28) - -**Section sources** -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) -- [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) - -### Continuous Integration Pipelines -- GitHub Actions: - - docker-main.yml: builds and pushes Docker images for master (testnet/latest). - - docker-pr-build.yml: builds a testnet image for PRs with ref tagging. -- Legacy CI (.travis.yml): - - Matrix builds multiple Dockerfiles (standard, test, testnet, lowmem, mongo). - - On master or tag, pushes images to Docker Hub. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant GH as "GitHub Actions" -participant Hub as "Docker Hub" -Dev->>GH : Push to master or open PR -GH->>GH : Run docker-main.yml or docker-pr-build.yml -GH->>Hub : Build and push image (latest/testnet/ref) -Hub-->>Dev : Image available for deployment -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [.travis.yml](file://.travis.yml#L1-L46) - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [.travis.yml](file://.travis.yml#L1-L46) - -### Testing Requirements and Coverage -- Unit tests: - - Target: make chain_test - - Categories include basic_tests, block_tests, operation_tests, serialization_tests, etc. -- Runtime configuration: - - log_level, report_level, run_test flags for controlling verbosity and selection. -- Code coverage: - - Enable coverage in CMake, run initial capture, run tests, combine lcov traces, and generate HTML report. - -```mermaid -flowchart TD -A["Configure CMake
CMAKE_BUILD_TYPE, coverage flags"] --> B["Build"] -B --> C["Run initial lcov capture"] -C --> D["Execute tests/chain_test"] -D --> E["Run lcov capture"] -E --> F["Combine base.info + test.info -> total.info"] -F --> G["Filter out tests/* -> interesting.info"] -G --> H["Generate HTML report in lcov/"] -``` - -**Diagram sources** -- [documentation/testing.md](file://documentation/testing.md#L26-L43) - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L1-L43) - -### Build System and Platform Notes -- CMake options: - - CMAKE_BUILD_TYPE=[Release/Debug] - - LOW_MEMORY_NODE=[FALSE/TRUE] for consensus-only nodes -- Platform-specific instructions: - - Ubuntu 16.04, 14.04, macOS X with specific package lists and caveats. -- Docker builds: - - Prebuilt images available; manual builds supported via Dockerfiles in share/vizd/docker. - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [README.md](file://README.md#L1-L53) - -### Code Review and Merge Criteria -- Review policy: - - Two developers must review every release before merging into master. - - Two developers must review consensus-breaking changes before merging into develop. - - Patches should be reviewed by at least one developer other than the author. -- Quality checks: - - Automated tests must pass. - - Style and correctness must be verified (no trailing whitespace, single focus per patch, no mixing unrelated changes). -- Collaboration: - - External contributions should be reviewed by two internal developers. - - PRs should reference related issues to maintain traceability. - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L93-L111) - -### Issue Tracking and Collaboration -- Branches: - - master and develop define the cadence for releases and development. -- Branch creation: - - From develop; name according to issue number or date-based naming. -- PRs: - - Always reference related issues. - - Resolve merge conflicts by rebasing against origin/develop. - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L8-L76) - -### Relationship Between Development Workflow and QA -- CI ensures builds and Docker images are produced consistently for PRs and releases. -- Automated tests validate core functionality; coverage analysis helps identify gaps. -- Plugin scaffolding and reflection checks support API completeness and correctness. -- Release tagging and protected branches enforce stability gates for merges. - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py#L1-L160) - -## Dependency Analysis -The development workflow depends on: -- CI systems for automated validation and image publishing -- CMake and platform toolchains for building -- Plugin scaffolding for consistent API development -- Test suites for regression prevention - -```mermaid -graph LR -CI["CI Pipelines"] --> Build["Build & Test"] -Build --> Docker["Docker Images"] -Build --> Tests["Unit Tests & Coverage"] -Plugins["Plugin Scaffolding"] --> Build -Docs["Documentation"] --> Dev["Developer Workflow"] -Dev --> CI -Dev --> Plugins -Dev --> Tests -``` - -**Diagram sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [documentation/plugin.md](file://documentation/plugin.md#L1-L28) - -## Performance Considerations -- Build type: prefer Release for production; Debug with coverage for development and analysis. -- Low-memory nodes: use LOW_MEMORY_NODE for validator and seed-node deployments to reduce resource usage. -- Docker builds: leverage prebuilt images for faster iteration; build locally when debugging CI issues. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- CI failures: - - Verify Dockerfile selection and credentials for GitHub Actions. - - For Travis, confirm matrix variables and Docker Hub credentials. -- Build issues: - - Follow platform-specific dependency lists and version requirements. - - Use CMAKE_BUILD_TYPE=Release for production builds. -- Test failures: - - Use run_test and report_level to narrow down failing categories. - - Re-run with increased verbosity to locate failing test cases. -- Reflection mismatches: - - Use check_reflect.py to compare Doxygen XML and FC_REFLECT declarations. - -**Section sources** -- [.github/workflows/docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [.github/workflows/docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) -- [.travis.yml](file://.travis.yml#L1-L46) -- [documentation/building.md](file://documentation/building.md#L1-L212) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [programs/build_helpers/check_reflect.py](file://programs/build_helpers/check_reflect.py#L1-L160) - -## Conclusion -This workflow ensures reliable development, robust CI validation, and consistent releases. Contributors should follow branch naming, PR procedures, and review criteria; use the plugin scaffolding for API development; adhere to testing and coverage practices; and rely on CI pipelines for automated Docker builds and validations. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Practical Examples - -- Implementing a new plugin - - Use newplugin.py to generate boilerplate. - - Register API factory in plugin_startup(). - - Reflect API methods in FC_API declaration. - - Enable via config and optionally expose via public-api. - - **Section sources** - - [programs/util/newplugin.py](file://programs/util/newplugin.py#L1-L251) - - [documentation/plugin.md](file://documentation/plugin.md#L21-L28) - -- Fixing a bug - - Create a branch from develop with a descriptive name. - - Write targeted tests; run make chain_test and adjust flags as needed. - - Open a PR; address reviewer feedback; re-run CI. - - **Section sources** - - [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L25-L76) - - [documentation/testing.md](file://documentation/testing.md#L1-L43) - -- Contributing to an existing plugin - - Identify the plugin directory under plugins/. - - Follow the same scaffolding and reflection rules if adding new API methods. - - Ensure compatibility with DB state if applicable and document replay requirements. - - **Section sources** - - [documentation/plugin.md](file://documentation/plugin.md#L11-L20) - -### Code Style and Commit Conventions -- Keep commits focused and atomic. -- Use clear, descriptive commit messages. -- Avoid mixing unrelated changes in a single commit. -- Reference related issues in PR descriptions. - -**Section sources** -- [documentation/git_guildelines.md](file://documentation/git_guildelines.md#L62-L76) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Testing Framework/Code Coverage Analysis.md b/.qoder/repowiki/en/content/Development Tools/Testing Framework/Code Coverage Analysis.md deleted file mode 100644 index a8569b22ee..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Testing Framework/Code Coverage Analysis.md +++ /dev/null @@ -1,265 +0,0 @@ -# Code Coverage Analysis - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [testing.md](file://documentation/testing.md) -- [docker-main.yml](file://.github/workflows/docker-main.yml) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides a comprehensive guide to code coverage analysis for the VIZ CPP Node testing framework. It documents the complete lcov integration workflow, including prerequisites, debug build configuration, and the multi-step coverage capture process. It also explains cmake configuration options for enabling coverage testing in Debug builds, details lcov command-line options, and offers practical examples for generating coverage reports, interpreting metrics, and integrating coverage analysis into CI/CD pipelines. - -## Project Structure -The repository organizes the build and testing infrastructure around a top-level cmake configuration and documentation that describes the coverage workflow. The testing documentation outlines the lcov commands and the chain_test target used to exercise the test suite. The main cmake file defines the option to enable coverage testing and injects compiler flags accordingly. - -```mermaid -graph TB -Root["Repository Root"] -Docs["documentation/testing.md"] -CMakeRoot["CMakeLists.txt"] -Workflows["GitHub Workflows"] -Root --> Docs -Root --> CMakeRoot -Root --> Workflows -``` - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L26-L42) - -## Core Components -- Coverage build configuration via cmake option ENABLE_COVERAGE_TESTING -- Debug build requirement for coverage instrumentation -- lcov capture workflow with base.info, test execution, test.info, total.info, and filtered interesting.info -- HTML report generation with genhtml - -Key implementation points: -- The cmake option ENABLE_COVERAGE_TESTING controls whether coverage instrumentation flags are added to the build. -- The lcov workflow documented in testing.md shows the step-by-step process for capturing and combining tracefiles and generating HTML reports. -- The chain_test target is used to execute the test suite during coverage capture. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L26-L42) - -## Architecture Overview -The coverage workflow integrates cmake, lcov, and the test runner to produce an HTML coverage report. The diagram below maps the documented steps to actual files and targets. - -```mermaid -sequenceDiagram -participant Dev as "Developer" -participant CMake as "CMake (ENABLE_COVERAGE_TESTING)" -participant Lcov as "lcov" -participant Test as "chain_test" -participant GenHTML as "genhtml" -Dev->>CMake : Configure with -D ENABLE_COVERAGE_TESTING=true -D CMAKE_BUILD_TYPE=Debug -CMake-->>Dev : Compiler flags include --coverage -Dev->>Lcov : lcov --capture --initial --directory . --output-file base.info --no-external -Dev->>Test : Run tests/chain_test -Dev->>Lcov : lcov --capture --directory . --output-file test.info --no-external -Dev->>Lcov : lcov --add-tracefile base.info --add-tracefile test.info --output-file total.info -Dev->>Lcov : lcov -o interesting.info -r total.info tests/* -Dev->>GenHTML : genhtml interesting.info --output-directory lcov --prefix pwd -GenHTML-->>Dev : Open lcov/index.html in a browser -``` - -**Diagram sources** -- [testing.md](file://documentation/testing.md#L26-L42) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) - -## Detailed Component Analysis - -### cmake Coverage Configuration -- Option definition: ENABLE_COVERAGE_TESTING is defined as a cmake option with a cache type of BOOL. -- Conditional instrumentation: When enabled, the --coverage flag is prepended to CMAKE_CXX_FLAGS. -- Build type requirement: The documented workflow requires CMAKE_BUILD_TYPE=Debug. - -Practical implications: -- Enabling coverage adds instrumentation to all targets built under the project. -- The Debug build type ensures debug symbols are present for accurate line-level coverage. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) - -### lcov Workflow Steps -The documented workflow consists of the following stages: - -1. Initial capture with base.info - - Purpose: Initialize the coverage recorder with baseline counts. - - Command: lcov --capture --initial --directory . --output-file base.info --no-external - -2. Test execution with chain_test - - Purpose: Run the test suite to collect coverage data. - - Target: tests/chain_test - -3. Secondary capture with test.info - - Purpose: Capture coverage after running tests. - - Command: lcov --capture --directory . --output-file test.info --no-external - -4. Tracefile combination with total.info - - Purpose: Merge base and test tracefiles into a single total.info. - - Command: lcov --add-tracefile base.info --add-tracefile test.info --output-file total.info - -5. Filtering of test directories with interesting.info - - Purpose: Remove entries from tests/* to focus on library and plugin code. - - Command: lcov -o interesting.info -r total.info tests/* - -6. HTML report generation with genhtml - - Purpose: Produce an HTML report from the filtered tracefile. - - Command: genhtml interesting.info --output-directory lcov --prefix `pwd` - -7. Viewing the report - - Purpose: Inspect coverage metrics in a browser. - - Path: lcov/index.html - -**Section sources** -- [testing.md](file://documentation/testing.md#L26-L42) - -### lcov Command-Line Options -The workflow uses the following lcov options: -- --capture: Collects execution counts from the current working directory. -- --initial: Initializes the recorder for a clean baseline. -- --directory: Specifies the root directory for coverage data collection. -- --output-file: Sets the output filename for tracefiles. -- --no-external: Filters out coverage contributions from external sources. -- --add-tracefile: Combines multiple tracefiles into one. -- -r (remove): Removes matched patterns from the tracefile (used to exclude tests/*). - -These options are applied as documented in the testing guide. - -**Section sources** -- [testing.md](file://documentation/testing.md#L30-L42) - -### CI/CD Integration -While the repository does not include a dedicated coverage job in the GitHub Actions workflows, the existing Docker build workflows demonstrate how to integrate build and packaging steps in CI. To add coverage reporting to CI: - -- Add a job that configures the project with ENABLE_COVERAGE_TESTING and CMAKE_BUILD_TYPE=Debug. -- Run the lcov capture and report generation steps as documented. -- Publish artifacts (lcov/) for review. - -The Docker workflows show the general pattern of building images on pushes and pull requests, which can be extended to include coverage jobs. - -**Section sources** -- [docker-main.yml](file://.github/workflows/docker-main.yml#L1-L41) -- [docker-pr-build.yml](file://.github/workflows/docker-pr-build.yml#L1-L24) - -## Dependency Analysis -Coverage depends on: -- Cmake option ENABLE_COVERAGE_TESTING to inject --coverage flags. -- Debug build type for debug symbols. -- lcov tool availability (installed via brew on macOS as documented). -- The chain_test target to exercise the test suite. - -```mermaid -graph TB -CMakeOpt["ENABLE_COVERAGE_TESTING"] -Flags["--coverage in CMAKE_CXX_FLAGS"] -BuildType["CMAKE_BUILD_TYPE=Debug"] -LcovTool["lcov"] -ChainTest["tests/chain_test"] -TraceBase["base.info"] -TraceTest["test.info"] -TraceTotal["total.info"] -TraceFiltered["interesting.info"] -HtmlReport["lcov/index.html"] -CMakeOpt --> Flags -BuildType --> Flags -Flags --> ChainTest -LcovTool --> TraceBase -ChainTest --> TraceTest -TraceBase --> TraceTotal -TraceTest --> TraceTotal -TraceTotal --> TraceFiltered -TraceFiltered --> HtmlReport -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L26-L42) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L26-L42) - -## Performance Considerations -- Coverage instrumentation adds overhead; expect slower test execution and larger binary sizes. -- Using --no-external reduces noise from external dependencies, focusing on project code. -- Filtering tests/* with -r improves report readability by excluding test harness code. -- Keep the build in Debug to ensure accurate line-level coverage. - -## Troubleshooting Guide -Common issues and resolutions: -- Missing lcov tool - - Symptom: Commands fail with "command not found". - - Resolution: Install lcov using the documented package manager command. - -- No coverage data captured - - Symptom: Empty or minimal coverage in HTML report. - - Causes: - - ENABLE_COVERAGE_TESTING not enabled during configuration. - - CMAKE_BUILD_TYPE not set to Debug. - - Tests not executed or failing before coverage capture. - - Resolutions: - - Reconfigure with -D ENABLE_COVERAGE_TESTING=true -D CMAKE_BUILD_TYPE=Debug. - - Ensure tests/chain_test runs successfully. - - Verify --directory points to the build root and includes compiled sources. - -- Incorrect or missing debug symbols - - Symptom: Lines not attributed to source files. - - Resolution: Confirm Debug build and that --coverage flags are applied. - -- HTML report shows external paths - - Symptom: Coverage includes third-party or system libraries. - - Resolution: Use --no-external during capture and -r to remove unwanted patterns. - -- Tracefile combination errors - - Symptom: lcov fails to merge tracefiles. - - Resolution: Ensure base.info and test.info exist and are valid; confirm correct filenames and paths. - -- genhtml output location - - Symptom: Report not found or paths incorrect. - - Resolution: Use --output-directory lcov and --prefix `pwd` as documented. - -**Section sources** -- [testing.md](file://documentation/testing.md#L26-L42) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) - -## Conclusion -The VIZ CPP Node testing framework supports code coverage analysis through a straightforward cmake option and a well-defined lcov workflow. By enabling coverage instrumentation in Debug builds, capturing baseline and test tracefiles, combining and filtering them, and generating an HTML report, teams can gain actionable insights into test coverage. Integrating these steps into CI/CD pipelines enables continuous monitoring of coverage trends and helps maintain high-quality test suites. - -## Appendices - -### Practical Examples -- Enabling coverage and building: - - Configure with cmake -D ENABLE_COVERAGE_TESTING=true -D CMAKE_BUILD_TYPE=Debug . -- Capturing baseline coverage: - - Run lcov --capture --initial --directory . --output-file base.info --no-external -- Executing tests: - - Run tests/chain_test -- Capturing test coverage: - - Run lcov --capture --directory . --output-file test.info --no-external -- Combining tracefiles: - - Run lcov --add-tracefile base.info --add-tracefile test.info --output-file total.info -- Filtering test directories: - - Run lcov -o interesting.info -r total.info tests/* -- Generating HTML report: - - Run genhtml interesting.info --output-directory lcov --prefix `pwd` -- Viewing results: - - Open lcov/index.html in a browser - -**Section sources** -- [testing.md](file://documentation/testing.md#L26-L42) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Testing Framework/Testing Framework.md b/.qoder/repowiki/en/content/Development Tools/Testing Framework/Testing Framework.md deleted file mode 100644 index 711bee8e2d..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Testing Framework/Testing Framework.md +++ /dev/null @@ -1,336 +0,0 @@ -# Testing Framework - - -**Referenced Files in This Document** -- [testing.md](file://documentation/testing.md) -- [.travis.yml](file://.travis.yml) -- [CMakeLists.txt](file://CMakeLists.txt) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) -- [share/vizd/snapshot-testnet.json](file://share/vizd/snapshot-testnet.json) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the testing framework for VIZ CPP Node, focusing on unit tests, integration tests, and performance benchmarks. It explains the test categories, Boost.Test configuration, execution commands, reporting options, code coverage with lcov, and CI workflows. It also covers test data management, mock objects, environment setup, and practical examples for writing and running tests. - -## Project Structure -The testing infrastructure spans documentation, CMake build configuration, utility executables, and a dedicated test API plugin. The primary unit test target is generated via CMake and executed as an executable that runs all unit tests. Test categories are organized under a single test binary and filtered at runtime. - -```mermaid -graph TB -A["Root CMakeLists.txt"] --> B["libraries/CMakeLists.txt"] -B --> C["libraries/chain/CMakeLists.txt"] -A --> D["programs/util/CMakeLists.txt"] -D --> E["programs/util/test_block_log.cpp"] -A --> F["plugins/test_api/test_api_plugin.cpp"] -G["programs/vizd/main.cpp"] --> F -H["documentation/testing.md"] --> I["Unit test target and categories"] -J[".travis.yml"] --> K["CI container builds"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L200) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L1-L142) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L1-L120) -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [.travis.yml](file://.travis.yml#L1-L46) - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L1-L43) -- [CMakeLists.txt](file://CMakeLists.txt#L1-L200) -- [libraries/CMakeLists.txt](file://libraries/CMakeLists.txt#L1-L8) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L1-L142) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L1-L120) -- [.travis.yml](file://.travis.yml#L1-L46) - -## Core Components -- Unit test target and categories: - - The unit test target is built via CMake and produces an executable that runs all unit tests. - - Test categories include basic_tests, block_tests, live_tests, operation_tests, operation_time_tests, and serialization_tests. -- Boost.Test configuration: - - Runtime configuration supports log_level, report_level, and run_test filters. - - Refer to Boost.Test documentation for advanced options. -- Code coverage: - - lcov integration is supported with a dedicated CMake option to enable coverage in Debug builds. - - The documented workflow captures baseline and test tracefiles, merges them, removes test artifacts, and generates HTML reports. - -Practical usage examples: -- Run all unit tests: execute the built test binary. -- Filter by category or test case: use the run_test runtime option. -- Enable coverage: configure with the coverage flag and follow the lcov capture and HTML generation steps. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L1-L43) - -## Architecture Overview -The test architecture centers on a single test executable that aggregates multiple test suites. The test API plugin exposes JSON-RPC endpoints useful for integration-style tests. Utility programs exercise subsystems like block logging and signing. - -```mermaid -graph TB -subgraph "Test Executable" -T1["Unit tests
basic_tests, block_tests,
live_tests, operation_tests,
operation_time_tests, serialization_tests"] -end -subgraph "Test API Plugin" -P1["JSON-RPC APIs"] -P2["test_api_a"] -P2a["test_api_b"] -end -subgraph "Utilities" -U1["test_block_log"] -U2["sign_transaction"] -U3["sign_digest"] -end -T1 --> P1 -P1 --> P2 -P1 --> P2a -T1 --> U1 -T1 --> U2 -T1 --> U3 -``` - -**Diagram sources** -- [documentation/testing.md](file://documentation/testing.md#L6-L14) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L6-L14) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) - -## Detailed Component Analysis - -### Test Categories -- basic_tests: Validates “basic” functionality. -- block_tests: Validates blockchain-specific logic. -- live_tests: Validates against live chain data (e.g., past hardfork scenarios). -- operation_tests: Validates operations. -- operation_time_tests: Validates time-dependent operations (e.g., vesting withdrawals). -- serialization_tests: Validates serialization logic. - -Filtering and execution: -- Use the run_test runtime option to select specific suites or test cases. -- Configure log_level and report_level for verbosity and detail. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L6-L23) - -### Boost.Test Configuration and Execution -- Log level options include all, success, test_suite, message, warning, error, cpp_exception, system_error, fatal_error, nothing. -- Report level options include no, confirm, short, detailed. -- run_test supports selecting specific suites or test cases. - -Execution command: -- Build the test target and run the resulting executable to execute all tests. -- Use runtime options to filter and tune output. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L16-L23) - -### Code Coverage with lcov -- Enable coverage in Debug builds via a CMake option. -- Workflow: - - Capture initial tracefile. - - Run the test executable. - - Capture test tracefile. - - Merge and post-process tracefiles. - - Generate HTML report and open it in a browser. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L26-L43) - -### Test Data Management and Environment Setup -- Testnet configuration: - - A dedicated testnet configuration file exists for isolated testing environments. - - A Dockerfile is provided for building a testnet image. - - A snapshot file is available for quick testnet initialization. -- Test API plugin: - - The plugin registers JSON-RPC APIs and is loaded by the node process. - -```mermaid -sequenceDiagram -participant Node as "Node Process" -participant Main as "programs/vizd/main.cpp" -participant Plugin as "plugins/test_api/test_api_plugin.cpp" -Node->>Main : "Initialize application" -Main->>Plugin : "register_plugin()" -Plugin-->>Main : "plugin_initialize(options)" -Main-->>Node : "Startup complete" -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L1-L120) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) - -**Section sources** -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L200) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L1-L200) -- [share/vizd/snapshot-testnet.json](file://share/vizd/snapshot-testnet.json#L1-L200) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L1-L120) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) - -### Continuous Integration Testing Workflows -- CI builds Docker images for multiple variants (standard, test, testnet, low memory, mongo). -- The matrix defines environment variables and Dockerfile selection. -- Builds are triggered per branch and tag, enabling automated artifact publishing. - -```mermaid -flowchart TD -Start(["CI Trigger"]) --> SetEnv["Set DOCKERFILE and DOCKERNAME"] -SetEnv --> Build["docker build -t viz-world/viz-world: -f "] -Build --> Success{"Build Success?"} -Success --> |Yes| Push["Push to registry if credentials present"] -Success --> |No| Fail["Report failure"] -Push --> Deploy{"Master + latest?"} -Deploy --> |Yes| Release["Run deployment script"] -Deploy --> |No| End(["Done"]) -Release --> End -Fail --> End -``` - -**Diagram sources** -- [.travis.yml](file://.travis.yml#L1-L46) - -**Section sources** -- [.travis.yml](file://.travis.yml#L1-L46) - -### Practical Examples -- Writing a new test suite: - - Add tests to the existing test target using Boost.Test macros. - - Group tests into categories (basic_tests, block_tests, etc.) as appropriate. -- Running a specific suite: - - Use the run_test runtime option to execute a named suite or specific test case. -- Interpreting results: - - Adjust report_level to control output detail. - - Use log_level to focus on successes, warnings, errors, or disable logs. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L16-L23) - -### Integration Test Utilities -- Block log utility: - - Demonstrates opening a block log, appending signed blocks, flushing, and reading blocks. - - Useful for validating block storage and retrieval logic. -- Signing utilities: - - Provide helpers for signing digests and transactions, useful for integration tests requiring cryptographic operations. - -```mermaid -sequenceDiagram -participant App as "test_block_log" -participant Log as "block_log" -participant FS as "Filesystem" -App->>Log : "open(temp_dir)" -App->>Log : "append(signed_block)" -Log->>FS : "write" -App->>Log : "flush()" -App->>Log : "read_block(index)" -Log->>FS : "read" -Log-->>App : "block data" -``` - -**Diagram sources** -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) - -**Section sources** -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L58-L69) - -## Dependency Analysis -The test framework relies on: -- CMake configuration to build the test target and link required libraries. -- The test API plugin for JSON-RPC-based integration tests. -- Utility programs for exercising subsystems like block logging and signing. - -```mermaid -graph LR -CMake["CMakeLists.txt"] --> ChainLib["libraries/chain/CMakeLists.txt"] -CMake --> UtilCMake["programs/util/CMakeLists.txt"] -CMake --> TestAPI["plugins/test_api/test_api_plugin.cpp"] -UtilCMake --> TestBlockLog["programs/util/test_block_log.cpp"] -TestAPI --> NodeMain["programs/vizd/main.cpp"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L200) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L1-L142) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L1-L120) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L1-L200) -- [libraries/chain/CMakeLists.txt](file://libraries/chain/CMakeLists.txt#L1-L142) -- [programs/util/CMakeLists.txt](file://programs/util/CMakeLists.txt#L1-L69) -- [programs/util/test_block_log.cpp](file://programs/util/test_block_log.cpp#L1-L54) -- [plugins/test_api/test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L1-L40) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L1-L120) - -## Performance Considerations -- Performance benchmarks are not explicitly defined in the repository’s testing documentation. -- Recommended approach: - - Use the existing test categories to isolate performance-sensitive areas (e.g., operation_time_tests). - - Integrate timing measurements around critical operations within unit tests. - - Use external benchmarking tools if needed, ensuring they integrate with the test runner and produce machine-readable results for CI. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- Coverage report generation fails: - - Ensure lcov is installed and the CMake coverage option is enabled in Debug builds. - - Verify tracefile capture steps and that the test executable path is correct. -- Test filtering does not work: - - Confirm the run_test runtime option syntax and that the selected suite or test case names match the registered tests. -- CI build failures: - - Review Docker build logs and environment variable assignments in the CI configuration. - - Validate that required credentials are configured for pushing images. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L26-L43) -- [.travis.yml](file://.travis.yml#L1-L46) - -## Conclusion -The VIZ CPP Node testing framework leverages a unified test executable with categorized suites, Boost.Test runtime configuration, and lcov-based coverage reporting. Integration tests benefit from the test API plugin and utility programs. CI automation builds multiple Docker variants to support diverse testing scenarios. Extending the framework involves adding tests to existing suites, configuring runtime options, and integrating coverage and CI pipelines. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices -- Example runtime options: - - log_level: set to desired verbosity level. - - report_level: set to control detail. - - run_test: filter suites or specific test cases. -- Coverage workflow summary: - - Enable coverage in Debug builds. - - Capture baseline and test tracefiles. - - Merge and post-process tracefiles. - - Generate and open HTML report. - -**Section sources** -- [documentation/testing.md](file://documentation/testing.md#L16-L43) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Development Tools/Testing Framework/Unit Testing Infrastructure.md b/.qoder/repowiki/en/content/Development Tools/Testing Framework/Unit Testing Infrastructure.md deleted file mode 100644 index 7ed9151701..0000000000 --- a/.qoder/repowiki/en/content/Development Tools/Testing Framework/Unit Testing Infrastructure.md +++ /dev/null @@ -1,261 +0,0 @@ -# Unit Testing Infrastructure - - -**Referenced Files in This Document** -- [CMakeLists.txt](file://CMakeLists.txt) -- [testing.md](file://documentation/testing.md) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp) -- [main.cpp](file://programs/vizd/main.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document describes the unit testing infrastructure for VIZ CPP Node with a focus on the Boost.Test framework setup and configuration used across the testing suite. It explains the test categories, execution mechanisms via the chain_test target, runtime configuration options, and best practices for test development, data management, mocking, and environment isolation. - -## Project Structure -The testing infrastructure is primarily driven by the top-level build configuration and a dedicated test API plugin that supports test harnesses. The key elements are: -- Top-level CMake configuration enabling Boost unit_test_framework and defining the chain_test target -- Test category documentation and runtime configuration options -- A minimal test API plugin used by the node to support test scenarios - -```mermaid -graph TB -Root["Top-level CMakeLists.txt
Defines chain_test target and Boost unit_test_framework"] --> Tests["Generated chain_test executable"] -Tests --> Categories["Test Categories:
basic_tests, block_tests, live_tests,
operation_tests, operation_time_tests, serialization_tests"] -Tests --> RuntimeCfg["Runtime Config:
log_level, report_level, run_test"] -Tests --> Coverage["Coverage Testing:
ENABLE_COVERAGE_TESTING option"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L3-L23) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L3-L23) - -## Core Components -- Boost.Test framework integration: - - Boost unit_test_framework is included as a required component for building the test target. - - The chain_test target is produced by the build system and runs all unit tests. -- Test categories: - - basic_tests: Fundamental functionality tests - - block_tests: Blockchain operation tests - - live_tests: Live chain data validation (historical/past hardfork testing) - - operation_tests: Individual operation validation - - operation_time_tests: Time-dependent operations (e.g., vesting withdrawals) - - serialization_tests: Data encoding/decoding tests -- Runtime configuration: - - log_level: Controls logging verbosity (all, success, test_suite, message, warning, error, cpp_exception, system_error, fatal_error, nothing) - - report_level: Controls reporting detail (no, confirm, short, detailed) - - run_test: Filters which test units to execute (supports selecting suites and individual test cases) -- Code coverage: - - ENABLE_COVERAGE_TESTING option enables coverage flags during Debug builds - -Practical execution: -- Build and run: make chain_test followed by ./tests/chain_test -- Selective execution: pass --run_test= to filter tests -- Reporting customization: pass --report_level= and --log-level= - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [testing.md](file://documentation/testing.md#L3-L23) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) - -## Architecture Overview -The testing architecture centers on the Boost.Test framework integrated through CMake. The chain_test executable aggregates all registered tests across categories and executes them according to runtime configuration. The test API plugin is part of the node’s plugin ecosystem and can be leveraged by tests requiring RPC-like interactions. - -```mermaid -graph TB -subgraph "Build System" -CMake["CMakeLists.txt
unit_test_framework, chain_test target"] -end -subgraph "Test Harness" -ChainTest["chain_test executable"] -Categories["Test Suites
basic/block/live/operation/operation_time/serialization"] -Runtime["Boost.Test Runtime Args
log-level, report_level, run_test"] -end -subgraph "Node Plugin Ecosystem" -TestAPI["test_api_plugin
JSON_RPC_REGISTER_API"] -Main["vizd main.cpp
registers test_api_plugin"] -end -CMake --> ChainTest -ChainTest --> Categories -ChainTest --> Runtime -Main --> TestAPI -TestAPI -. "RPC-like test support" .-> ChainTest -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [testing.md](file://documentation/testing.md#L3-L23) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L16) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L52) -- [main.cpp](file://programs/vizd/main.cpp#L11-L71) - -## Detailed Component Analysis - -### Boost.Test Integration and chain_test Target -- The build configuration includes Boost unit_test_framework as a required component, ensuring the test framework is available. -- The chain_test target produces an executable that runs all unit tests. -- Optional coverage instrumentation is enabled via ENABLE_COVERAGE_TESTING, which injects coverage flags in Debug builds. - -```mermaid -flowchart TD -Start(["Configure with CMake"]) --> FindBoost["Find Boost with unit_test_framework"] -FindBoost --> DefineTarget["Define chain_test target"] -DefineTarget --> Build["Build chain_test"] -Build --> Run["Run ./tests/chain_test"] -Run --> Filter["Apply --run_test filter"] -Run --> Report["Apply --report_level and --log-level"] -Report --> Results["Output test results"] -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L3-L23) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [testing.md](file://documentation/testing.md#L3-L23) - -### Test Categories -The testing suite organizes tests into six categories: -- basic_tests: Validates basic functionality -- block_tests: Validates blockchain operations -- live_tests: Validates against live chain data (historical hardfork checks) -- operation_tests: Validates individual operations -- operation_time_tests: Validates time-dependent operations -- serialization_tests: Validates data encoding/decoding - -These categories are executed by the chain_test target and can be filtered using the run_test runtime argument. - -**Section sources** -- [testing.md](file://documentation/testing.md#L6-L14) - -### Runtime Configuration Options -- log_level: Controls logging verbosity. Values include all, success, test_suite, message, warning, error, cpp_exception, system_error, fatal_error, nothing. -- report_level: Controls reporting detail. Values include no, confirm, short, detailed. -- run_test: Selectively executes test suites or individual test cases. Examples: - - Run a whole suite: --run_test=operation_tests - - Run a specific case: --run_test=operation_tests/delegation - -These options are passed to the chain_test executable and align with Boost.Test’s documented runtime configuration. - -**Section sources** -- [testing.md](file://documentation/testing.md#L16-L23) - -### Test Data Management, Mock Objects, and Environment Isolation -- Test data management: - - Use temporary directories and isolated databases per test case to prevent cross-contamination. - - For blockchain-related tests, initialize a clean database state and reset forks as needed. -- Mock objects: - - Utilize lightweight mocks for external dependencies (e.g., network, storage) to isolate unit under test. - - Prefer dependency injection to swap real implementations with test doubles. -- Environment isolation: - - Run tests in separate processes or containers when necessary. - - Avoid global mutable state; prefer per-test fixtures and deterministic initialization. - -[No sources needed since this section provides general guidance] - -### Adding New Test Cases -- Create a new test suite or add to existing ones (e.g., operation_tests) using Boost.Test macros. -- Register test cases with meaningful names and organize them by category. -- Use run_test to selectively execute new cases during development. -- Keep tests deterministic and fast; avoid heavy I/O or external dependencies. - -[No sources needed since this section provides general guidance] - -## Dependency Analysis -The testing infrastructure depends on: -- Boost.Test framework (unit_test_framework) for test discovery and execution -- CMake configuration to produce the chain_test target -- Optional coverage flags controlled by ENABLE_COVERAGE_TESTING -- The test API plugin for RPC-like interactions within tests - -```mermaid -graph LR -Boost["Boost unit_test_framework"] --> CMake["CMakeLists.txt"] -CMake --> ChainTest["chain_test target"] -ChainTest --> Tests["All Test Suites"] -Coverage["ENABLE_COVERAGE_TESTING"] --> CMake -TestAPI["test_api_plugin"] --> ChainTest -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L16) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L52) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [CMakeLists.txt](file://CMakeLists.txt#L204-L208) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L16) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L52) - -## Performance Considerations -- Keep tests fast and deterministic; avoid unnecessary I/O or network calls. -- Use small, focused test cases and minimize fixture setup/teardown overhead. -- Leverage run_test to execute only relevant suites during development. -- Enable coverage only when needed to reduce build and runtime overhead. - -[No sources needed since this section provides general guidance] - -## Troubleshooting Guide -- If chain_test does not appear after building, verify the chain_test target exists and that Boost unit_test_framework is properly linked. -- If tests fail due to missing runtime configuration, ensure you pass --log-level, --report_level, and/or --run_test as needed. -- For coverage issues, confirm ENABLE_COVERAGE_TESTING is enabled and that lcov steps are executed correctly. - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L38-L50) -- [testing.md](file://documentation/testing.md#L26-L42) - -## Conclusion -The VIZ CPP Node testing infrastructure leverages Boost.Test through a well-defined CMake configuration that produces the chain_test executable. Tests are categorized for clarity and can be selectively executed using runtime arguments. The test API plugin integrates with the node to support RPC-like interactions in tests. By following best practices for test data management, mocking, and environment isolation, developers can maintain a robust and reliable testing suite. - -[No sources needed since this section summarizes without analyzing specific files] - -## Appendices - -### Appendix A: Test API Plugin Integration -The test_api_plugin registers a JSON-RPC API surface and is initialized by the node. While primarily intended for runtime node operations, it can also serve as a controlled interface for tests that require deterministic RPC-like behavior. - -```mermaid -sequenceDiagram -participant Node as "vizd main.cpp" -participant TestAPI as "test_api_plugin" -participant ChainTest as "chain_test" -Node->>TestAPI : register_plugin() -TestAPI->>TestAPI : plugin_initialize(options) -ChainTest->>TestAPI : invoke test_api_a/test_api_b (via RPC) -TestAPI-->>ChainTest : return values -``` - -**Diagram sources** -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L16) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L25-L35) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L52) -- [main.cpp](file://programs/vizd/main.cpp#L11-L71) - -**Section sources** -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L15-L16) -- [test_api_plugin.cpp](file://plugins/test_api/test_api_plugin.cpp#L25-L35) -- [test_api_plugin.hpp](file://plugins/test_api/include/graphene/plugins/test_api/test_api_plugin.hpp#L35-L52) -- [main.cpp](file://programs/vizd/main.cpp#L11-L71) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Getting Started.md b/.qoder/repowiki/en/content/Getting Started.md deleted file mode 100644 index b5342c6936..0000000000 --- a/.qoder/repowiki/en/content/Getting Started.md +++ /dev/null @@ -1,376 +0,0 @@ -# Getting Started - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [CMakeLists.txt](file://CMakeLists.txt) -- [documentation/building.md](file://documentation/building.md) -- [documentation/testnet.md](file://documentation/testnet.md) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini) -- [share/vizd/seednodes](file://share/vizd/seednodes) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This guide helps you install, configure, and run a VIZ node quickly. It covers: -- Prerequisites and environment setup -- Multiple installation approaches: Docker, manual compilation, and package installation -- First-time setup, configuration, and initial synchronization -- Practical scenarios: full node, testnet node, and validator node -- Security considerations, monitoring, and troubleshooting - -## Project Structure -At a high level, the repository provides: -- A production-ready node binary (vizd) -- Configuration templates for mainnet and testnet -- Dockerfiles for containerized deployment -- Build scripts and documentation for manual compilation - -```mermaid -graph TB -A["Root"] --> B["programs/vizd"] -A --> C["share/vizd"] -A --> D["documentation"] -A --> E["libraries"] -A --> F["plugins"] -C --> C1["config/*.ini"] -C --> C2["seednodes"] -C --> C3["docker/*.Dockerfile"] -C --> C4["vizd.sh"] -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -**Section sources** -- [README.md](file://README.md#L1-L53) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L158) - -## Core Components -- Node binary: vizd, the core blockchain node -- Configuration: config.ini for mainnet, config_testnet.ini for testnet, config_witness.ini for validator-only setups -- Seed nodes: curated list to bootstrap P2P connectivity -- Docker images: prebuilt or self-built images for quick deployment - -Key runtime entry point and plugin registration are defined in the node’s main program. - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L69-L73) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L73) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L68-L86) -- [share/vizd/seednodes](file://share/vizd/seednodes#L1-L6) - -## Architecture Overview -The node exposes: -- P2P endpoint for peer-to-peer communication -- JSON-RPC HTTP and WebSocket endpoints for API access -- Optional validator production controls - -```mermaid -graph TB -subgraph "Node Runtime" -N["vizd"] -P["P2P Plugin"] -W["Webserver Plugin"] -C["Chain Plugin"] -NA["Network Broadcast API"] -WA["validator API"] -end -subgraph "External" -S["Seed Nodes"] -U["Users/Apps"] -end -N --> P -N --> W -N --> C -N --> NA -N --> WA -S --> P -U --> W -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L90) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L20) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L20) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L20) - -## Detailed Component Analysis - -### Prerequisites and Environment Setup -Supported platforms: -- Linux Ubuntu LTS -- macOS -- Windows (compilation notes are provided; official CI focuses on Linux/macOS) - -Dependencies: -- Boost 1.57+ -- OpenSSL -- CMake -- Compiler toolchains (GCC/Clang) - -Build-time options: -- Release vs Debug -- Low-memory node mode -- Testnet vs mainnet builds -- MongoDB plugin toggle - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [CMakeLists.txt](file://CMakeLists.txt#L56-L64) -- [CMakeLists.txt](file://CMakeLists.txt#L66-L74) -- [CMakeLists.txt](file://CMakeLists.txt#L83-L89) -- [documentation/building.md](file://documentation/building.md#L3-L16) -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [documentation/building.md](file://documentation/building.md#L138-L189) - -### Installation Approaches - -#### Option A: Docker Deployment (Recommended for beginners) -- Use prebuilt images or build locally -- Exposed ports: P2P (2001), HTTP RPC (8090), WebSocket RPC (8091) -- Volume mounts for persistent data and config - -```mermaid -sequenceDiagram -participant User as "Operator" -participant Docker as "Docker Engine" -participant Image as "vizd Image" -participant Script as "vizd.sh" -participant Node as "vizd" -User->>Docker : Run container with env vars and volumes -Docker->>Image : Start entrypoint -Image->>Script : Execute vizd.sh -Script->>Node : Launch vizd with args and config -Node-->>User : Logs and RPC endpoints ready -``` - -**Diagram sources** -- [README.md](file://README.md#L12-L29) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L66-L88) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L74-L81) - -Practical steps: -- Pull or build the production image -- Run with mapped ports and volumes -- Optionally set seed nodes via environment variables -- Inspect logs and verify RPC availability - -**Section sources** -- [README.md](file://README.md#L12-L29) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L1-L88) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -#### Option B: Manual Compilation from Source -- Ubuntu LTS: install required packages, then build with CMake and make -- macOS: install dependencies via Homebrew, set environment variables for OpenSSL and Boost, then build -- Build targets include vizd and cli_wallet - -```mermaid -flowchart TD -Start(["Start"]) --> Deps["Install Boost 1.57+, OpenSSL, CMake, Compiler"] -Deps --> Clone["Clone repo and init submodules"] -Clone --> Configure["Configure with CMake"] -Configure --> Build["Build vizd and optional cli_wallet"] -Build --> Install["Optionally install to system"] -Install --> End(["Done"]) -``` - -**Diagram sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [documentation/building.md](file://documentation/building.md#L138-L189) - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [documentation/building.md](file://documentation/building.md#L138-L189) - -#### Option C: Package Installation -- Not documented in this repository; Docker and manual builds are the supported paths -- If packaging is desired, use the build artifacts produced by the CMake pipeline - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L210-L213) - -### First-Time Setup and Configuration - -#### Initial Configuration Files -- Mainnet: config.ini -- Testnet: config_testnet.ini -- validator-only: config_witness.ini - -Key areas to review: -- P2P endpoint and seed nodes -- RPC endpoints (HTTP and WebSocket) -- Shared memory sizing and growth thresholds -- Plugin selection and enablement -- Logging configuration - -```mermaid -flowchart TD -A["Copy template config"] --> B["Set p2p-endpoint and webserver-* endpoints"] -B --> C["Add p2p-seed-node entries"] -C --> D["Adjust shared-file-size and growth params"] -D --> E["Enable required plugins"] -E --> F["Review logging config"] -F --> G["Save and start node"] -``` - -**Diagram sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L20) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L69-L73) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L111-L130) - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L1-L107) -- [share/vizd/seednodes](file://share/vizd/seednodes#L1-L6) - -#### Initial Synchronization -- The node connects to seed nodes and downloads blocks -- Docker images may preload a snapshot to accelerate first sync -- Monitor logs to confirm peers and block progress - -**Section sources** -- [README.md](file://README.md#L31-L38) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L44-L53) - -### Practical Scenarios - -#### Full Node (Mainnet) -- Use config.ini -- Expose P2P and RPC ports -- Optionally set custom seed nodes via environment variables - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L20) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L17-L29) - -#### Testnet Node -- Use config_testnet.ini or the testnet Docker image -- Preloaded snapshot accelerates sync -- Additional test users and keys are documented - -**Section sources** -- [share/vizd/config/config_testnet.ini](file://share/vizd/config/config_testnet.ini#L1-L132) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L75-L77) -- [documentation/testnet.md](file://documentation/testnet.md#L21-L37) - -#### validator Node -- Use config_witness.ini -- Enable validator and witness_api plugins -- Configure validator name and private key - -**Section sources** -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L68-L86) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L106-L111) - -## Dependency Analysis -- Build-time dependencies: Boost, OpenSSL, CMake, compiler toolchain -- Runtime dependencies: shared libraries linked at build time -- Docker images encapsulate dependencies and expose ports - -```mermaid -graph LR -A["CMakeLists.txt"] --> B["Boost 1.57+"] -A --> C["OpenSSL"] -A --> D["Compiler Toolchain"] -E["Dockerfile-production"] --> B -E --> C -E --> D -F["Dockerfile-testnet"] --> B -F --> C -F --> D -``` - -**Diagram sources** -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L9-L30) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L9-L30) - -**Section sources** -- [CMakeLists.txt](file://CMakeLists.txt#L97-L104) -- [share/vizd/docker/Dockerfile-production](file://share/vizd/docker/Dockerfile-production#L9-L30) -- [share/vizd/docker/Dockerfile-testnet](file://share/vizd/docker/Dockerfile-testnet#L9-L30) - -## Performance Considerations -- Shared memory sizing and growth thresholds impact stability during high load -- Single write thread reduces lock contention for write-heavy workloads -- Plugin notification toggles can reduce overhead on push_transaction -- Thread pool sizing for RPC clients should match CPU cores - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L36-L47) - -## Troubleshooting Guide - -Common issues and resolutions: -- Network connectivity - - Verify P2P endpoint and firewall rules - - Confirm seed nodes are reachable - - Override seed nodes via environment variables in Docker - -- Configuration errors - - Validate config.ini sections and plugin lists - - Ensure endpoints are not conflicting with other services - -- Docker-specific - - Check container logs for initialization errors - - Confirm volume mounts for persistent data and config - - Rebuild images if dependencies change - -- validator setup - - Ensure validator name and private key are configured - - Adjust participation thresholds for testnet if needed - -**Section sources** -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L17-L29) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L20) -- [share/vizd/config/config_witness.ini](file://share/vizd/config/config_witness.ini#L82-L86) -- [documentation/testnet.md](file://documentation/testnet.md#L21-L37) - -## Conclusion -You now have multiple paths to run a VIZ node: -- Docker for quick start -- Manual build for customization -- validator configuration for block production - -Follow the configuration and troubleshooting sections to ensure a smooth setup and ongoing operation. - -## Appendices - -### Security Considerations -- Limit RPC exposure to trusted networks or use reverse proxies -- Use strong private keys for validator nodes -- Regularly update the node and monitor logs for anomalies - -### Monitoring Node Health -- Observe logs for peer connections and block progress -- Verify RPC endpoints are reachable -- Track shared memory usage and growth thresholds - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L111-L130) -- [share/vizd/vizd.sh](file://share/vizd/vizd.sh#L74-L81) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Logging System.md b/.qoder/repowiki/en/content/Logging System.md deleted file mode 100644 index fc6afff9fb..0000000000 --- a/.qoder/repowiki/en/content/Logging System.md +++ /dev/null @@ -1,490 +0,0 @@ -# Logging System - - -**Referenced Files in This Document** -- [logger.hpp](file://thirdparty/fc/include/fc/log/logger.hpp) -- [logger.cpp](file://thirdparty/fc/src/log/logger.cpp) -- [appender.hpp](file://thirdparty/fc/include/fc/log/appender.hpp) -- [appender.cpp](file://thirdparty/fc/src/log/appender.cpp) -- [logger_config.hpp](file://thirdparty/fc/include/fc/log/logger_config.hpp) -- [logger_config.cpp](file://thirdparty/fc/src/log/logger_config.cpp) -- [log_message.hpp](file://thirdparty/fc/include/fc/log/log_message.hpp) -- [console_appender.hpp](file://thirdparty/fc/include/fc/log/console_appender.hpp) -- [console_appender.cpp](file://thirdparty/fc/src/log/console_appender.cpp) -- [file_appender.hpp](file://thirdparty/fc/include/fc/log/file_appender.hpp) -- [file_appender.cpp](file://thirdparty/fc/src/log/file_appender.cpp) -- [gelf_appender.hpp](file://thirdparty/fc/include/fc/log/gelf_appender.hpp) -- [gelf_appender.cpp](file://thirdparty/fc/src/log/gelf_appender.cpp) -- [json_console_appender.hpp](file://thirdparty/fc/include/fc/log/json_console_appender.hpp) -- [json_console_appender.cpp](file://thirdparty/fc/src/log/json_console_appender.cpp) -- [main.cpp](file://programs/vizd/main.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Configuration System](#configuration-system) -7. [Log Levels and Filtering](#log-levels-and-filtering) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction - -The VIZ logging system is built on the fc (Fast Crypto) library's logging framework, providing a flexible and extensible architecture for capturing application events, errors, and informational messages. This system supports multiple output destinations through appenders, hierarchical logger organization, and configurable log levels with filtering capabilities. - -The logging system follows a layered architecture where loggers represent named logging channels, appenders handle the actual output formatting and destination, and log messages carry contextual information about the source and content of each log event. - -## Project Structure - -The logging system is organized across several key directories and files: - -```mermaid -graph TB -subgraph "Logging Core" -A[logger.hpp] --> B[logger.cpp] -C[appender.hpp] --> D[appender.cpp] -E[log_message.hpp] -F[logger_config.hpp] --> G[logger_config.cpp] -end -subgraph "Appenders" -H[console_appender.hpp] --> I[console_appender.cpp] -J[file_appender.hpp] --> K[file_appender.cpp] -L[gelf_appender.hpp] --> M[gelf_appender.cpp] -N[json_console_appender.hpp] --> O[json_console_appender.cpp] -end -subgraph "Application Integration" -P[main.cpp] -end -A --> H -A --> J -A --> L -A --> N -F --> H -F --> J -F --> L -F --> N -``` - -**Diagram sources** -- [logger.hpp:1-195](file://thirdparty/fc/include/fc/log/logger.hpp#L1-L195) -- [appender.hpp:1-51](file://thirdparty/fc/include/fc/log/appender.hpp#L1-L51) -- [logger_config.hpp:1-53](file://thirdparty/fc/include/fc/log/logger_config.hpp#L1-L53) - -**Section sources** -- [logger.hpp:1-195](file://thirdparty/fc/include/fc/log/logger.hpp#L1-L195) -- [appender.hpp:1-51](file://thirdparty/fc/include/fc/log/appender.hpp#L1-L51) -- [logger_config.hpp:1-53](file://thirdparty/fc/include/fc/log/logger_config.hpp#L1-L53) - -## Core Components - -### Logger Hierarchy - -The logging system centers around the `logger` class, which provides named logging channels with hierarchical inheritance: - -```mermaid -classDiagram -class Logger { --string _name --Logger _parent --bool _enabled --bool _additivity --LogLevel _level --vector~AppenderPtr~ _appenders -+get(name) Logger -+set_log_level(level) Logger& -+set_parent(parent) Logger& -+add_appender(appender) void -+log(message) void -+is_enabled(level) bool -} -class LogLevel { -+values all, debug, info, warn, error, off -+operator int() int -} -class LogMessage { -+LogContext context -+string format -+VariantObject data -+get_message() string -+get_context() LogContext -} -Logger --> LogLevel : "uses" -Logger --> LogMessage : "receives" -Logger --> Logger : "parent-child relationship" -``` - -**Diagram sources** -- [logger.hpp:22-72](file://thirdparty/fc/include/fc/log/logger.hpp#L22-L72) -- [log_message.hpp:116-141](file://thirdparty/fc/include/fc/log/log_message.hpp#L116-L141) - -### Appender Architecture - -The appender system provides pluggable output mechanisms through a factory pattern: - -```mermaid -classDiagram -class Appender { -<> -+create(name, type, args) AppenderPtr -+get(name) AppenderPtr -+register_appender(type, factory) bool -+log(message) void -} -class AppenderFactory { -<> -+create(args) AppenderPtr -} -class ConsoleAppender { --Config cfg --Color[] level_colors -+log(message) void -+configure(config) void -+print(text, color) void -} -class FileAppender { --Config cfg --ofstream out --Mutex slock -+log(message) void --rotate_files() void -} -class GelfAppender { --Config cfg --Endpoint gelf_endpoint --UdpSocket gelf_socket -+log(message) void -} -class JsonConsoleAppender { -+log(message) void -} -Appender <|-- ConsoleAppender -Appender <|-- FileAppender -Appender <|-- GelfAppender -Appender <|-- JsonConsoleAppender -Appender --> AppenderFactory : "factory pattern" -``` - -**Diagram sources** -- [appender.hpp:33-49](file://thirdparty/fc/include/fc/log/appender.hpp#L33-L49) -- [console_appender.hpp:8-65](file://thirdparty/fc/include/fc/log/console_appender.hpp#L8-L65) -- [file_appender.hpp:10-33](file://thirdparty/fc/include/fc/log/file_appender.hpp#L10-L33) -- [gelf_appender.hpp:10-27](file://thirdparty/fc/include/fc/log/gelf_appender.hpp#L10-L27) - -**Section sources** -- [logger.hpp:22-72](file://thirdparty/fc/include/fc/log/logger.hpp#L22-L72) -- [appender.hpp:33-49](file://thirdparty/fc/include/fc/log/appender.hpp#L33-L49) -- [log_message.hpp:116-141](file://thirdparty/fc/include/fc/log/log_message.hpp#L116-L141) - -## Architecture Overview - -The logging system implements a publish-subscribe pattern where loggers act as publishers and appenders as subscribers to log events: - -```mermaid -sequenceDiagram -participant Application as "Application Code" -participant Logger as "Logger" -participant Message as "LogMessage" -participant Appender1 as "ConsoleAppender" -participant Appender2 as "FileAppender" -participant Appender3 as "GelfAppender" -Application->>Logger : fc_ilog(logger, format, args) -Logger->>Logger : is_enabled(level) -alt Level enabled -Logger->>Message : create log_message -Logger->>Appender1 : log(message) -Logger->>Appender2 : log(message) -Logger->>Appender3 : log(message) -Appender1->>Appender1 : format to console -Appender2->>Appender2 : write to file -Appender3->>Appender3 : send via UDP -else Level disabled -Logger->>Application : return immediately -end -``` - -**Diagram sources** -- [logger.cpp:72-82](file://thirdparty/fc/src/log/logger.cpp#L72-L82) -- [console_appender.cpp:90-130](file://thirdparty/fc/src/log/console_appender.cpp#L90-L130) -- [file_appender.cpp:161-197](file://thirdparty/fc/src/log/file_appender.cpp#L161-L197) -- [gelf_appender.cpp:73-182](file://thirdparty/fc/src/log/gelf_appender.cpp#L73-L182) - -The architecture supports multiple output destinations simultaneously, with each appender handling its own formatting and output mechanism independently. - -## Detailed Component Analysis - -### Logger Implementation - -The logger implementation provides thread-safe access to named logging channels with hierarchical inheritance: - -```mermaid -flowchart TD -A[Logger Request] --> B{Logger Exists?} -B --> |No| C[Create New Logger] -B --> |Yes| D[Get Existing Logger] -C --> E[Initialize Logger Properties] -D --> E -E --> F[Set Parent Relationship] -F --> G[Apply Log Level] -G --> H[Add Appenders] -H --> I[Return Logger Instance] -J[Log Message] --> K[Check Level Enabled] -K --> |No| L[Skip Message] -K --> |Yes| M[Format Context] -M --> N[Distribute to Appenders] -N --> O[Parent Propagation] -``` - -**Diagram sources** -- [logger.cpp:102-106](file://thirdparty/fc/src/log/logger.cpp#L102-L106) -- [logger.cpp:126-135](file://thirdparty/fc/src/log/logger.cpp#L126-L135) - -Key features include: -- Thread-safe logger registry using spin locks -- Hierarchical parent-child relationships for log level inheritance -- Additivity property for propagating messages to parent loggers -- Configurable log levels with filtering - -**Section sources** -- [logger.cpp:14-26](file://thirdparty/fc/src/log/logger.cpp#L14-L26) -- [logger.cpp:102-141](file://thirdparty/fc/src/log/logger.cpp#L102-L141) - -### Console Appender - -The console appender provides formatted output to standard error and standard out with color support: - -```mermaid -flowchart TD -A[Log Message] --> B[Extract Context Info] -B --> C[Format Timestamp] -C --> D[Format Thread Name] -D --> E[Format File:Line] -E --> F[Format Method] -F --> G[Format Message Content] -G --> H[Apply Color Based on Level] -H --> I[Write to Console Stream] -I --> J[Optional Flush] -K[Configuration] --> L[Stream Selection] -L --> M[Level Color Mapping] -M --> N[Format Template] -N --> O[Flush Setting] -``` - -**Diagram sources** -- [console_appender.cpp:90-130](file://thirdparty/fc/src/log/console_appender.cpp#L90-L130) -- [console_appender.hpp:30-39](file://thirdparty/fc/include/fc/log/console_appender.hpp#L30-L39) - -**Section sources** -- [console_appender.cpp:21-64](file://thirdparty/fc/src/log/console_appender.cpp#L21-L64) -- [console_appender.cpp:90-162](file://thirdparty/fc/src/log/console_appender.cpp#L90-L162) - -### File Appender - -The file appender handles persistent log storage with rotation capabilities: - -```mermaid -flowchart TD -A[Log Message] --> B[Format Complete Message] -B --> C[Acquire Write Lock] -C --> D[Write to File] -D --> E[Check Rotation Needed] -E --> |Yes| F[Rotate Files] -E --> |No| G[Release Lock] -F --> H[Close Current File] -F --> I[Create New Timestamped File] -F --> J[Create Hard Link] -F --> K[Schedule Next Rotation] -K --> G -G --> L[Flush if Enabled] -L --> M[Complete Write] -N[Rotation Configuration] --> O[Interval Settings] -O --> P[Limit Settings] -P --> Q[Auto-Rotation Task] -``` - -**Diagram sources** -- [file_appender.cpp:60-131](file://thirdparty/fc/src/log/file_appender.cpp#L60-L131) -- [file_appender.cpp:161-197](file://thirdparty/fc/src/log/file_appender.cpp#L161-L197) - -**Section sources** -- [file_appender.cpp:16-58](file://thirdparty/fc/src/log/file_appender.cpp#L16-L58) -- [file_appender.cpp:161-197](file://thirdparty/fc/src/log/file_appender.cpp#L161-L197) - -### GELF Appender - -The Graylog Extended Log Format (GELF) appender enables integration with centralized logging systems: - -```mermaid -sequenceDiagram -participant Logger as "Logger" -participant GelfAppender as "GELF Appender" -participant Socket as "UDP Socket" -participant Graylog as "Graylog Server" -Logger->>GelfAppender : log(message) -GelfAppender->>GelfAppender : Extract Context Data -GelfAppender->>GelfAppender : Build GELF JSON -GelfAppender->>GelfAppender : Compress with ZLIB -alt Message Within Payload Limit -GelfAppender->>Socket : send_to(endpoint) -else Message Exceeds Limit -GelfAppender->>GelfAppender : Split into Chunks -loop For Each Chunk -GelfAppender->>Socket : send_chunk() -end -end -Socket->>Graylog : Deliver Log Message -``` - -**Diagram sources** -- [gelf_appender.cpp:73-182](file://thirdparty/fc/src/log/gelf_appender.cpp#L73-L182) - -**Section sources** -- [gelf_appender.cpp:22-33](file://thirdparty/fc/src/log/gelf_appender.cpp#L22-L33) -- [gelf_appender.cpp:73-182](file://thirdparty/fc/src/log/gelf_appender.cpp#L73-L182) - -## Configuration System - -The logging system supports dynamic configuration through structured configuration files: - -```mermaid -flowchart TD -A[Configuration File] --> B[Parse JSON] -B --> C[Load Appenders] -C --> D[Register Appenders] -D --> E[Load Loggers] -E --> F[Configure Logger Hierarchy] -F --> G[Set Log Levels] -G --> H[Attach Append to Loggers] -H --> I[Enable Logging System] -J[Default Configuration] --> K[Console Appenders] -K --> L[Default Logger] -L --> M[Standard Error/Out Streams] -N[Runtime Configuration] --> O[Command Line Options] -O --> P[Log Console Appender Options] -P --> Q[File Appender Options] -Q --> R[Logger Routing Options] -``` - -**Diagram sources** -- [logger_config.cpp:29-67](file://thirdparty/fc/src/log/logger_config.cpp#L29-L67) -- [logger_config.cpp:69-89](file://thirdparty/fc/src/log/logger_config.cpp#L69-L89) - -The configuration system supports: -- JSON-based configuration files -- Runtime configuration updates -- Hierarchical logger relationships -- Multiple appender types with custom parameters - -**Section sources** -- [logger_config.hpp:35-46](file://thirdparty/fc/include/fc/log/logger_config.hpp#L35-L46) -- [logger_config.cpp:25-67](file://thirdparty/fc/src/log/logger_config.cpp#L25-L67) - -## Log Levels and Filtering - -The logging system implements a hierarchical level-based filtering mechanism: - -```mermaid -flowchart TD -A[Log Level Request] --> B{Compare with Logger Level} -B --> |Request >= Logger Level| C[Message Enabled] -B --> |Request < Logger Level| D[Message Disabled] -C --> E[Check Parent Propagation] -E --> |Additivity Enabled| F[Forward to Parent] -E --> |Additivity Disabled| G[Complete] -F --> H[Parent Decision] -H --> |Enabled| I[Propagate Message] -H --> |Disabled| G -D --> J[Skip Processing] -K[Level Hierarchy] --> L[all < debug < info < warn < error < off] -L --> M[Each Level Includes Higher Levels] -``` - -**Diagram sources** -- [logger.cpp:68-70](file://thirdparty/fc/src/log/logger.cpp#L68-L70) -- [log_message.hpp:29-31](file://thirdparty/fc/include/fc/log/log_message.hpp#L29-L31) - -Supported log levels include: -- `all`: Capture all messages (lowest level) -- `debug`: Debug information and development messages -- `info`: General operational information -- `warn`: Warning conditions requiring attention -- `error`: Error conditions affecting functionality -- `off`: Disable all logging (highest level) - -**Section sources** -- [log_message.hpp:21-44](file://thirdparty/fc/include/fc/log/log_message.hpp#L21-L44) -- [logger.cpp:68-70](file://thirdparty/fc/src/log/logger.cpp#L68-L70) - -## Performance Considerations - -The logging system incorporates several performance optimization strategies: - -### Thread Safety and Concurrency -- Spin locks for logger registry access -- Mutex protection for console output streams -- Separate mutexes for different appenders to minimize contention -- Asynchronous file rotation tasks - -### Memory Management -- Smart pointer usage for automatic resource cleanup -- RAII-based file handle management -- Efficient string formatting with pre-allocated buffers - -### I/O Optimization -- Optional flushing to reduce disk writes -- Buffered file operations -- Asynchronous rotation to avoid blocking log operations - -### Network Efficiency -- UDP-based transmission for GELF appender -- Message compression to reduce bandwidth -- Chunked transmission for large messages - -**Section sources** -- [logger.cpp:102-106](file://thirdparty/fc/src/log/logger.cpp#L102-L106) -- [console_appender.cpp:123-125](file://thirdparty/fc/src/log/console_appender.cpp#L123-L125) -- [file_appender.cpp:191-196](file://thirdparty/fc/src/log/file_appender.cpp#L191-L196) - -## Troubleshooting Guide - -### Common Issues and Solutions - -**Log Messages Not Appearing** -- Verify log level configuration matches intended verbosity -- Check logger hierarchy for proper parent-child relationships -- Ensure appenders are properly attached to target loggers - -**File Appender Issues** -- Confirm file path permissions and directory existence -- Verify rotation configuration parameters are valid -- Check for file locking issues on Windows systems - -**Network Appender Problems** -- Validate GELF endpoint connectivity and port accessibility -- Verify DNS resolution for hostname-based endpoints -- Check firewall settings for UDP traffic - -**Performance Degradation** -- Review log level settings to reduce message volume -- Consider disabling unnecessary appenders -- Evaluate flush frequency settings for file appenders - -**Section sources** -- [file_appender.cpp:144-155](file://thirdparty/fc/src/log/file_appender.cpp#L144-L155) -- [gelf_appender.cpp:65-67](file://thirdparty/fc/src/log/gelf_appender.cpp#L65-L67) - -## Conclusion - -The VIZ logging system provides a robust, extensible foundation for application monitoring and debugging. Its modular architecture supports multiple output destinations, hierarchical organization, and flexible configuration options. The system balances performance with functionality through careful concurrency management and efficient I/O operations. - -Key strengths include: -- Pluggable appender architecture supporting diverse output formats -- Hierarchical logger organization with intelligent level propagation -- Comprehensive configuration system supporting runtime modifications -- Performance optimizations for production environments -- Integration capabilities with external logging systems - -The system serves as a solid foundation for both development debugging and production monitoring, with clear extension points for custom logging requirements. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/P2p Plugin.md b/.qoder/repowiki/en/content/P2p Plugin.md deleted file mode 100644 index 8a10541c59..0000000000 --- a/.qoder/repowiki/en/content/P2p Plugin.md +++ /dev/null @@ -1,2215 +0,0 @@ -# P2P Plugin - - -**Referenced Files in This Document** -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [peer_connection.hpp](file://libraries/network/include/graphene/network/peer_connection.hpp) -- [peer_database.hpp](file://libraries/network/include/graphene/network/peer_database.hpp) -- [core_messages.hpp](file://libraries/network/include/graphene/network/core_messages.hpp) -- [message.hpp](file://libraries/network/include/graphene/network/message.hpp) -- [config.hpp](file://libraries/network/include/graphene/network/config.hpp) -- [node.cpp](file://libraries/network/node.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [chainbase.hpp](file://thirdparty/chainbase/include/chainbase/chainbase.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [CMakeLists.txt](file://plugins/p2p/CMakeLists.txt) -- [config.ini](file://share/vizd/config/config.ini) - - -## Update Summary -**Changes Made** -- Enhanced peer information logging with comprehensive metrics including bytes sent, connection direction, user agent strings, fork revision information, head block numbers and timestamps, firewall status indicators, and connection timing information -- Added cross-referencing capabilities with peer database entries to show failed/rejected peers and their current connection status -- Implemented comprehensive gap detection with automatic peer soft-banning for sync spam -- Enhanced DLT mode support with integrity verification, coverage gap monitoring, and Windows compatibility enhancements -- Integrated ANSI color coding system (white, cyan, gray, orange, red) throughout the logging system for improved console readability -- Added enhanced peer ahead-of-us detection mechanism with sophisticated DLT mode logic -- Implemented comprehensive startup block storage diagnostics with gap detection -- Enhanced minority fork recovery with improved peer interaction handling -- Replaced bool block-processing flags with `std::atomic` to allow thread-safe pause/resume from any thread -- Fixed atomic flag ordering in resume: `_catchup_after_pause` is set before clearing `snapshot_in_progress` to prevent stale-head fork -- Refactored resume_block_processing() into two-phase design: flags set immediately (Phase 1), drain posted async without wait (Phase 2) to eliminate a fiber deadlock -- Handle `shared_memory_corruption_exception` in block acceptance path: triggers auto-recovery instead of peer soft-ban -- Clear `_dlt_syncing` flag on SYNC→FORWARD transition to unblock validator production after sync completes -- Added node uptime field (`uptime=Xh Ym Zs`) to DLT Status and DLT P2P Stats log lines - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Enhanced Peer Information Logging](#enhanced-peer-information-logging) -7. [Comprehensive Gap Detection System](#comprehensive-gap-detection-system) -8. [Enhanced DLT Mode Block Range Management](#enhanced-dlt-mode-block-range-management) -9. [Improved Gap Detection and Recovery](#improved-gap-detection-and-recovery) -10. [Sophisticated Clamping Logic](#sophisticated-clamping-logic) -11. [Enhanced Peer Interaction Handling](#enhanced-peer-interaction-handling) -12. [Comprehensive Logging Throughout Sync Process](#comprehensive-logging-throughout-sync-process) -13. [ANSI Color Code Implementation](#ansi-color-code-implementation) -14. [Enhanced DLT Mode Debug Logging](#enhanced-dlt-mode-debug-logging) -15. [Improved Console Readability](#improved-console-readability) -16. [Conditional Block Processing Latency Logging](#conditional-block-processing-latency-logging) -17. [Graceful Degradation Capabilities](#graceful-degradation-capabilities) -18. [Minority Fork Recovery](#minority-fork-recovery) -19. [Enhanced Block Validation](#enhanced-block-validation) -20. [Concurrent Access Safety](#concurrent-access-safety) -21. [Logging Level Consistency](#logging-level-consistency) -22. [Dependency Analysis](#dependency-analysis) -23. [Performance Considerations](#performance-considerations) -24. [Troubleshooting Guide](#troubleshooting-guide) -25. [Conclusion](#conclusion) - -## Introduction - -The P2P (Peer-to-Peer) Plugin is a critical component of the VIZ blockchain node that enables decentralized communication between nodes in the network. This plugin provides the foundation for blockchain synchronization, transaction propagation, and peer discovery mechanisms that keep the entire network synchronized and functional. - -The plugin implements a sophisticated networking layer built on top of the Graphene network library, providing features such as automatic peer discovery, blockchain synchronization protocols, transaction broadcasting, and advanced peer management capabilities including soft-ban mechanisms and connection monitoring. - -**Updated** The plugin now includes enhanced monitoring capabilities with comprehensive ANSI color codes for improved console readability. DLT mode debug messages are displayed in gray color, while peer statistics and other informational messages use cyan and white color codes. The conditional block processing latency logging ensures that latency information is only displayed for successful block processing in non-sync mode, reducing log volume while maintaining operational visibility. These enhancements provide better visual distinction between different types of log messages and improve troubleshooting capabilities during network operations. - -## Project Structure - -The P2P plugin follows a modular architecture with clear separation of concerns: - -```mermaid -graph TB -subgraph "P2P Plugin Layer" -P2P[p2p_plugin.hpp/cpp] -Impl[p2p_plugin_impl] -DLT[DLT Mode Integration] -Stats[P2P Stats Task] -Stale[Stale Sync Detection] -Resync[resync_from_lib method] -Guard[operation_guard integration] -Colors[ANSI Color Codes] -Latency[Conditional Latency Logging] -Visibility[Enhanced Block Processing Visibility] -PeerDB[Enhanced Peer Database Logging] -StorageDiag[Block Storage Diagnostics] -DLTIntegrity[DLT Integrity Verification] -GapDetection[Comprehensive Gap Detection] -AheadOfUs[Enhanced Peer Ahead Detection] -StartupDiag[Startup Block Storage Diagnostics] -WindowsCompat[Windows Compatibility Enhancements] -``` - -**Diagram sources** -- [p2p_plugin.hpp:18-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L55) -- [p2p_plugin.cpp:910-979](file://plugins/p2p/p2p_plugin.cpp#L910-L979) -- [node.hpp:190-320](file://libraries/network/include/graphene/network/node.hpp#L190-L320) - -**Section sources** -- [p2p_plugin.hpp:1-57](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L1-L57) -- [CMakeLists.txt:1-49](file://plugins/p2p/CMakeLists.txt#L1-L49) - -## Core Components - -### P2P Plugin Interface - -The main plugin class provides a clean interface for managing P2P networking functionality: - -```mermaid -classDiagram -class p2p_plugin { -+set_program_options(cli, cfg) -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+broadcast_block(block) -+broadcast_block_post_validation(block_id, witness_account, signature) -+broadcast_transaction(tx) -+set_block_production(producing_blocks) -+resync_from_lib() --my : p2p_plugin_impl -} -class p2p_plugin_impl { -+has_item(id) -+handle_block(blk_msg, sync_mode, contained_tx_ids) -+handle_transaction(trx_msg) -+handle_message(message) -+get_block_ids(synopsis, remaining_count, limit) -+get_item(id) -+get_blockchain_synopsis(reference_point, num_blocks) -+sync_status(item_type, item_count) -+connection_count_changed(count) -+get_block_number(block_id) -+get_block_time(block_id) -+get_head_block_id() -+get_chain_id() -+error_encountered(message, error) -+get_blockchain_now() -+p2p_stats_task() -+stale_sync_check_task() -+is_included_block(block_id) -+ANSI Color Codes -+Conditional Latency Logging -+Enhanced Block Processing Visibility -+DLT Mode Debug Logging -+Enhanced Peer Stats -+Enhanced Peer Database Logging -+Block Storage Diagnostics -+DLT Integrity Verification -+Automatic Peer Soft-Banning -+Enhanced Gap Detection -+Comprehensive Gap Reporting -+Periodic DLT Integrity Scans -+Gap-Aware Recovery Mechanisms -+DLT Coverage Gap Monitoring -+Orange Color Coding for Warnings -+Red Color Coding for Critical Alerts -+Enhanced Peer Ahead-of-Us Detection -+Startup Block Storage Diagnostics -+Windows Compatibility Enhancements -``` - -**Diagram sources** -- [p2p_plugin.hpp:18-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L55) -- [p2p_plugin.cpp:49-126](file://plugins/p2p/p2p_plugin.cpp#L49-L126) - -### Network Node Architecture - -The plugin integrates with the underlying network infrastructure through a sophisticated node abstraction: - -```mermaid -classDiagram -class node { -+listen_to_p2p_network() -+connect_to_p2p_network() -+add_node(endpoint) -+connect_to_endpoint(endpoint) -+listen_on_endpoint(endpoint, wait) -+broadcast(message) -+sync_from(item_id, hard_fork_nums) -+resync() -+set_advanced_node_parameters(params) -+set_trusted_peer_endpoints(endpoints) -+get_connected_peers() -+get_connection_count() -+clear_peer_database() -+set_allowed_peers(allowed_peers) -+get_potential_peers() -} -class node_delegate { -<> -+has_item(id) -+handle_block(blk_msg, sync_mode, contained_tx_ids) -+handle_transaction(trx_msg) -+handle_message(message) -+get_block_ids(synopsis, remaining_count, limit) -+get_item(id) -+get_blockchain_synopsis(reference_point, num_blocks) -+sync_status(item_type, item_count) -+connection_count_changed(count) -+get_block_number(block_id) -+get_block_time(block_id) -+get_head_block_id() -+get_chain_id() -+error_encountered(message, error) -+get_blockchain_now() -} -class peer_connection { -+send_message(message) -+send_item(item_id) -+close_connection() -+destroy_connection() -+get_remote_endpoint() -+get_total_bytes_sent() -+get_total_bytes_received() -+busy() -+idle() -} -node ..|> node_delegate : "implements" -node --> peer_connection : "manages" -``` - -**Diagram sources** -- [node.hpp:190-320](file://libraries/network/include/graphene/network/node.hpp#L190-L320) -- [node.hpp:60-167](file://libraries/network/include/graphene/network/node.hpp#L60-L167) -- [peer_connection.hpp:79-354](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L354) - -**Section sources** -- [p2p_plugin.hpp:18-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L55) -- [node.hpp:190-320](file://libraries/network/include/graphene/network/node.hpp#L190-L320) - -## Architecture Overview - -The P2P plugin architecture implements a layered approach to blockchain networking: - -```mermaid -sequenceDiagram -participant App as Application -participant P2P as P2P Plugin -participant Node as Network Node -participant Peer as Remote Peer -participant Chain as Chain Database -App->>P2P : Initialize plugin -P2P->>Node : Create node instance -P2P->>Node : Load configuration -P2P->>Node : Set delegate (p2p_plugin_impl) -P2P->>Node : Listen on endpoint -P2P->>Node : Connect to seed nodes -Note over P2P,Node : Network initialization complete -Peer->>Node : Connect request -Node->>P2P : handle_message(hello) -P2P->>Node : Accept/reject connection -Node->>Peer : Connection accepted -Peer->>Node : Request blockchain synopsis -Node->>P2P : get_blockchain_synopsis() -P2P->>Chain : Query chain state -P2P->>Node : Return synopsis -Node->>Peer : Send synopsis -Peer->>Node : Request blocks/transactions -Node->>P2P : handle_message(fetch_items) -P2P->>Chain : Fetch items -P2P->>Node : Return items -Node->>Peer : Send items -Note over P2P,Chain : Enhanced DLT Mode with Gap Detection -P2P->>Chain : Check DLT availability -Chain-->>P2P : Earliest available block -P2P->>Node : Clamp block range with gap detection -P2P->>Node : Advertise only available blocks -Peer->>Node : New block/transaction -Node->>P2P : handle_block()/handle_transaction() -P2P->>Chain : Accept/validate block -P2P->>Node : Broadcast to peers -Note over P2P : Enhanced logging with ANSI color codes -P2P->>Node : Log DLT mode operations in gray -P2P->>Node : Log peer stats in cyan -P2P->>Node : Log latency in white only on successful processing -P2P->>Node : Log storage diagnostics with gap detection -P2P->>Node : Log DLT integrity verification -P2P->>Node : Log DLT coverage gaps in orange -P2P->>Node : Log critical errors in red -Note over P2P : Enhanced peer ahead-of-us detection -P2P->>Node : Detect peers ahead of us in DLT mode -P2P->>Node : Return empty response for ahead peers -Note over P2P : Startup block storage diagnostics -P2P->>Node : Log comprehensive storage diagnostics -P2P->>Node : Detect gaps and integrity issues -``` - -**Diagram sources** -- [p2p_plugin.cpp:758-823](file://plugins/p2p/p2p_plugin.cpp#L758-L823) -- [node.cpp:1-200](file://libraries/network/node.cpp#L1-L200) - -The architecture provides several key capabilities: - -1. **Automatic Peer Discovery**: The plugin automatically discovers and connects to seed nodes specified in configuration -2. **Blockchain Synchronization**: Implements efficient blockchain synchronization using selective block fetching with DLT mode awareness and gap detection -3. **Transaction Propagation**: Broadcasts transactions to connected peers with intelligent caching -4. **Peer Management**: Manages peer connections with soft-ban mechanisms and connection limits -5. **Monitoring and Statistics**: Provides comprehensive peer statistics and network health monitoring with colored console output -6. **Minority Fork Recovery**: Specialized recovery mechanism for handling minority fork scenarios -7. **Concurrent Access Safety**: Enhanced protection against concurrent access conflicts during block processing -8. **DLT Mode Support**: Intelligent block range management for snapshot-based nodes with sophisticated gap detection -9. **Graceful Degradation**: Handles peer unavailability with fallback mechanisms and automatic recovery -10. **Enhanced Logging**: Comprehensive logging system with ANSI color codes for improved console readability and conditional latency reporting -11. **DLT Storage Diagnostics**: Comprehensive block storage monitoring with gap detection and coverage analysis -12. **Peer Database Analytics**: Detailed peer interaction tracking with enhanced visibility into connection failures -13. **Automatic Peer Soft-Banning**: Intelligent peer management with automatic soft-banning for sync spam -14. **DLT Integrity Verification**: Periodic verification of DLT block log integrity with gap detection and continuity scanning -15. **Comprehensive Gap Detection**: Advanced gap detection reporting with detailed coverage gap monitoring -16. **Enhanced Peer Ahead-of-Us Detection**: Sophisticated detection of peers that are ahead of the local node in DLT mode -17. **Startup Block Storage Diagnostics**: Comprehensive diagnostics system that runs before synchronization begins -18. **Windows Compatibility Enhancements**: Enhanced compatibility with Windows operating systems for DLT block log operations - -## Detailed Component Analysis - -### Block Validation Protocol - -The P2P plugin implements a sophisticated block validation mechanism that enhances security and prevents malicious attacks: - -```mermaid -flowchart TD -Start([Block Received]) --> ValidateType{"Is block_post_validation_message?"} -ValidateType --> |No| StandardValidation["Standard block validation
via chain.accept_block()"] -ValidateType --> |Yes| ExtractParams["Extract block_id,
witness_account,
signature"] -ExtractParams --> VerifyWitness{"Verify validator exists?"} -VerifyWitness --> |No| RejectBlock["Reject block
(invalid validator)"] -VerifyWitness --> |Yes| VerifySignature["Verify signature
matches validator key"] -VerifySignature --> SignatureValid{"Signature valid?"} -SignatureValid --> |No| RejectBlock -SignatureValid --> |Yes| ApplyValidation["Apply block post-validation
chain.db().apply_block_post_validation()"] -ApplyValidation --> BroadcastBlock["Broadcast block to peers"] -StandardValidation --> BroadcastBlock -RejectBlock --> End([End]) -BroadcastBlock --> End -style RejectBlock fill:#ffcccc -style BroadcastBlock fill:#ccffcc -``` - -**Diagram sources** -- [p2p_plugin.cpp:216-245](file://plugins/p2p/p2p_plugin.cpp#L216-L245) -- [p2p_plugin.cpp:855-865](file://plugins/p2p/p2p_plugin.cpp#L855-L865) - -**Updated** The block validation protocol now includes enhanced concurrent access safety through operation guard protection and conditional latency logging: - -The block validation process incorporates operation guards to prevent concurrent access conflicts during validator key validation and block post-validation processing. This ensures thread-safe access to shared blockchain state during high-load conditions. - -The enhanced validation includes: - -1. **validator Signature Verification**: Validates that the block signature matches the claimed validator's public key -2. **Chain ID Consistency**: Ensures blocks belong to the correct blockchain instance -3. **Hard Fork Protection**: Handles different validation requirements across blockchain hard forks -4. **Post-Validation Processing**: Applies additional validation steps after initial acceptance -5. **Concurrent Access Protection**: Uses operation guards to prevent race conditions during validation -6. **Error Handling**: Comprehensive error handling for various failure scenarios -7. **Conditional Latency Reporting**: Only displays latency information for successful block processing in non-sync mode - -### Peer Connection Management - -The plugin manages peer connections through a sophisticated state machine: - -```mermaid -stateDiagram-v2 -[*] --> Disconnected -Disconnected --> Connecting : initiate_connection() -Connecting --> Connected : handshake_success -Connecting --> Disconnected : handshake_failed -Connected --> Negotiating : exchange_hello() -Negotiating --> Operating : negotiation_complete -Negotiating --> Rejected : negotiation_failed -Rejected --> Disconnected : close_connection() -Operating --> Syncing : start_sync() -Syncing --> Operating : sync_complete -Operating --> Closing : close_requested -Closing --> Disconnected : connection_closed -state Operating { -[*] --> NormalOperation -NormalOperation --> Broadcasting : broadcast_message -Broadcasting --> NormalOperation : broadcast_complete -NormalOperation --> Fetching : fetch_item -Fetching --> NormalOperation : item_fetched -} -``` - -**Diagram sources** -- [peer_connection.hpp:82-106](file://libraries/network/include/graphene/network/peer_connection.hpp#L82-L106) - -### Blockchain Synchronization Protocol - -The synchronization protocol efficiently handles blockchain state reconciliation with enhanced gap detection: - -```mermaid -sequenceDiagram -participant Local as Local Node -participant Remote as Remote Peer -participant Chain as Local Chain -Remote->>Local : hello_message -Local->>Remote : connection_accepted_message -Remote->>Local : fetch_blockchain_item_ids_message -Local->>Chain : get_blockchain_synopsis(reference_point, num_blocks) -Chain-->>Local : blockchain synopsis -Local->>Remote : blockchain_item_ids_inventory_message -Remote->>Local : fetch_items_message -Local->>Chain : get_item(item_id) -Chain-->>Local : item data -Note over Local,Chain : Enhanced DLT Mode with Gap Detection -Chain->>Local : earliest_available_block_num() -Local->>Remote : item_message (only available blocks) -Note over Local,Remote : Enhanced peer ahead-of-us detection -Local->>Remote : Empty response for peers ahead of us -Note over Local,Remote : Continue until synchronized with gap awareness -``` - -**Diagram sources** -- [core_messages.hpp:188-218](file://libraries/network/include/graphene/network/core_messages.hpp#L188-L218) -- [p2p_plugin.cpp:247-301](file://plugins/p2p/p2p_plugin.cpp#L247-L301) - -**Section sources** -- [p2p_plugin.cpp:129-208](file://plugins/p2p/p2p_plugin.cpp#L129-L208) -- [p2p_plugin.cpp:247-301](file://plugins/p2p/p2p_plugin.cpp#L247-L301) -- [peer_connection.hpp:79-354](file://libraries/network/include/graphene/network/peer_connection.hpp#L79-L354) - -## Enhanced Peer Information Logging - -**New** The P2P plugin now includes comprehensive peer information logging with detailed metrics for enhanced monitoring and troubleshooting capabilities. - -### Comprehensive Peer Metrics Collection - -The enhanced peer statistics system collects extensive information from connected peers: - -```mermaid -flowchart TD -PeerStats[Peer Statistics Collection] --> BasicInfo[Basic Connection Info] -BasicInfo --> IPInfo[IP Address and Port] -BasicInfo --> Direction[Connection Direction] -BasicInfo --> Latency[Latency Measurement] -BasicInfo --> Bytes[Bytes Sent/Received] -PeerStats --> ExtendedInfo[Extended Peer Info] -ExtendedInfo --> UA[User Agent String] -ExtendedInfo --> FW[Firewall Status] -ExtendedInfo --> HeadBlock[Head Block Info] -ExtendedInfo --> Timestamps[Timestamps] -PeerStats --> StatusInfo[Status Information] -StatusInfo --> Blocked[Blocked Status] -StatusInfo --> Reason[Block Reason] -StatusInfo --> ConnTime[Connection Time] -StatusInfo --> LastActivity[Last Activity Times] -PeerStats --> DLTInfo[DLT Mode Information] -DLTInfo --> DLTMode[DLT Mode Status] -DLTInfo --> DLTRev[DLT Revision Info] -DLTInfo --> EarliestAvail[Earliest Available Block] -``` - -**Diagram sources** -- [p2p_plugin.cpp:653-770](file://plugins/p2p/p2p_plugin.cpp#L653-L770) - -### Enhanced Peer Database Cross-Referencing - -The plugin provides comprehensive peer database logging with cross-referencing capabilities: - -```mermaid -flowchart TD -PeerDBDump[Peer Database Dump] --> PotentialPeers[Collect Potential Peers] -PotentialPeers --> FilterFailed[Filter Failed/Rejected Peers] -FilterFailed --> ExtractInfo[Extract Peer Info] -ExtractInfo --> Status[Extract Status] -ExtractInfo --> Attempts[Extract Attempt Counts] -ExtractInfo --> Error[Extract Error Info] -PeerDBDump --> CrossRef[Cross-Reference with Connected Peers] -CrossRef --> CheckConnected[Check Current Connection Status] -CheckConnected --> MarkConnected[Mark as Currently Connected] -CrossRef --> MarkDisconnected[Mark as Disconnected] -PeerDBDump --> LogResults[Log Results with Color Coding] -LogResults --> Cyan[Cyan for Peer Info] -LogResults --> Orange[Orange for Failed Peers] -LogResults --> Gray[Gray for Peer Database Info] -``` - -**Diagram sources** -- [p2p_plugin.cpp:773-813](file://plugins/p2p/p2p_plugin.cpp#L773-L813) - -### Detailed Peer Information Logging - -The enhanced peer logging system provides comprehensive information for each connected peer: - -```mermaid -sequenceDiagram -participant Logger as Logger -participant P2P as P2P Plugin -participant Peer as Peer Connection -Logger->>P2P : Collect peer info -P2P->>Peer : Extract connection metrics -Peer-->>P2P : Return metrics -P2P->>P2P : Format peer information -P2P->>Logger : Log peer stats with color coding -Note over P2P,Logger : Colored output :
- Cyan : Peer statistics
- Gray : Peer database info
- White : Transaction notifications -``` - -**Diagram sources** -- [p2p_plugin.cpp:760-768](file://plugins/p2p/p2p_plugin.cpp#L760-L768) - -### Peer Database Analytics - -The plugin provides detailed analytics on peer database entries with enhanced visibility: - -```mermaid -flowchart TD -PeerDBAnalytics[Peer Database Analytics] --> FailedCount[Failed Peer Count] -FailedCount --> StatusDist[Status Distribution] -StatusDist --> ErrorAnalysis[Error Pattern Analysis] -ErrorAnalysis --> RecoveryEffectiveness[Recovery Effectiveness] -PeerDBAnalytics --> CrossRefAnalysis[Cross-Reference Analysis] -CrossRefAnalysis --> ConnectedVsFailed[Connected vs Failed Analysis] -CrossRefAnalysis --> StatusTrends[Status Trends] -PeerDBAnalytics --> GapImpact[Gap Detection Impact] -GapImpact --> PeerSelection[Peer Selection Impact] -GapImpact --> RecoveryStrategies[Recovery Strategy Effectiveness] -``` - -**Diagram sources** -- [p2p_plugin.cpp:773-813](file://plugins/p2p/p2p_plugin.cpp#L773-L813) - -**Section sources** -- [p2p_plugin.cpp:653-770](file://plugins/p2p/p2p_plugin.cpp#L653-L770) -- [p2p_plugin.cpp:773-813](file://plugins/p2p/p2p_plugin.cpp#L773-L813) - -## Comprehensive Gap Detection System - -**New** The P2P plugin now includes a comprehensive gap detection system that monitors storage boundaries and provides automatic recovery mechanisms to prevent peer disconnections due to unavailable blocks. - -### Multi-Layer Gap Detection Architecture - -The enhanced gap detection system implements multiple layers of monitoring: - -```mermaid -flowchart TD -GapDetection[Gap Detection System] --> DLTMode{DLT Mode Active?} -DLTMode --> |No| NormalProcessing[Normal Processing] -DLTMode --> |Yes| DLTGapDetection[DLT Gap Detection] -DLTGapDetection --> StartBoundary[Check Start Boundary] -StartBoundary --> EarliestCheck{start_num < earliest?} -EarliestCheck --> |Yes| ClampEarliest[Clamp to Earliest] -EarliestCheck --> |No| StorageBoundary[Check Storage Boundary] -StorageBoundary --> StorageEnd[Calculate Storage End] -StorageEnd --> BeyondStorage{start_num > storage_end?} -BeyondStorage --> |Yes| ForkDBCheck[Check Fork DB] -BeyondStorage --> |No| ForkDBGap[Check Fork DB Gap] -ForkDBCheck --> ForkDBAvailable{Fork DB Available?} -ForkDBAvailable --> |Yes| UseForkDB[Use Fork DB Block] -ForkDBAvailable --> |No| GapDetected[Gap Detected] -ForkDBGap --> GapExists{Gap Exists?} -GapExists --> |Yes| ClampForkDB[Clamp to Fork DB] -GapExists --> |No| ContiguityCheck[Check Contiguity] -ClampEarliest --> LogClamp[Log Clamping] -ClampForkDB --> LogGap[Log Gap] -UseForkDB --> LogForkDB[Log Fork DB Usage] -GapDetected --> LogGap -LogClamp --> BuildRange[Build Range] -LogGap --> BuildRange -LogForkDB --> BuildRange -NormalProcessing --> End([End]) -BuildRange --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -### Automatic Peer Soft-Banning System - -**New** The plugin implements an automatic peer soft-banning system that responds to gap-related errors and sync spam: - -```mermaid -flowchart TD -PeerSoftBan[Peer Soft-Ban System] --> ErrorDetection[Error Detection] -ErrorDetection --> GapError{Gap Related Error?} -GapError --> |Yes| IncreasePenalty[Increase Penalty] -GapError --> |No| OtherError{Other Error Type?} -OtherError --> |Sync Spam| IncreasePenalty -OtherError --> |Linkable Block| ModeratePenalty -OtherError --> |Other| MaintainPenalty -IncreasePenalty --> CheckThreshold{Penalty Threshold?} -CheckThreshold --> |Exceeded| RemovePeer[Remove Peer] -CheckThreshold --> |Not Exceeded| Continue[Continue] -ModeratePenalty --> CheckTrusted{Trusted Peer?} -CheckTrusted --> |Yes| ReducedBan[Reduced Ban Duration] -CheckTrusted --> |No| Continue -RemovePeer --> FindAlternative[Find Alternative Peer] -ReducedBan --> FindAlternative -Continue --> End([End]) -FindAlternative --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) - -### Gap Detection Logging and Reporting - -The gap detection system provides comprehensive logging for troubleshooting: - -```mermaid -flowchart TD -GapLogging[Gap Detection Logging] --> GapTypes[Gap Type Classification] -GapTypes --> BelowEarliest[Below Earliest] -GapTypes --> BeyondStorage[Beyond Storage] -GapTypes --> ForkDBGap[Fork DB Gap] -GapTypes --> MissingBlock[Missing Block] -GapLogging --> ContextInfo[Context Information] -ContextInfo --> BlockNumbers[Block Numbers] -ContextInfo --> StorageBoundaries[Storage Boundaries] -ContextInfo --> ErrorContext[Error Context] -GapLogging --> RecoveryActions[Recovery Actions] -RecoveryActions --> RangeClamping[Range Clamping] -RecoveryActions --> PeerSwitching[Peer Switching] -RecoveryActions --> ParameterAdjustment[Parameter Adjustment] -GapLogging --> ImpactAssessment[Impact Assessment] -ImpactAssessment --> PeerDisconnectionRisk[Peer Disconnection Risk] -ImpactAssessment --> SyncDelay[Sync Delay] -ImpactAssessment --> DataAvailability[Data Availability] -``` - -**Diagram sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -**Section sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) - -## Enhanced DLT Mode Block Range Management - -**New** The P2P plugin now includes enhanced DLT (Data Ledger Technology) mode block range management that provides intelligent block serving capabilities for snapshot-based nodes with sophisticated gap detection and automatic recovery mechanisms. - -### Sophisticated Clamping Logic in get_block_ids() - -The `get_block_ids()` method has been enhanced with sophisticated clamping logic to prevent advertising blocks not available in node storage: - -```mermaid -flowchart TD -Start([get_block_ids Called]) --> CheckSynopsis{"Empty synopsis?"} -CheckSynopsis --> |Yes| UseZero["Use block 000000000"] -CheckSynopsis --> |No| IterateSyn["Iterate synopsis reverse"] -IterateSyn --> CheckKnown{"Known block AND included?"} -CheckKnown --> |Yes| FoundBlock["Set last_known_block_id"] -CheckKnown --> |No| ContinueLoop["Continue iteration"] -ContinueLoop --> CheckKnown -FoundBlock --> CalcStart["Calculate start_num from last_known_block_id"] -CalcStart --> CheckDLT{"In DLT mode?"} -CheckDLT --> |No| BuildRange["Build block range normally"] -CheckDLT --> |Yes| ClampStart["Clamp start to earliest_available_block_num"] -ClampStart --> LogClamp["Log DLT mode clamp operation
in gray ANSI color"] -ClampStart --> CheckStorageGaps["Check for storage gaps"] -CheckStorageGaps --> |Gap Detected| ClampEnd["Clamp end to storage boundary"] -CheckStorageGaps --> |No Gap| CheckUpperBound["Check upper bound gaps"] -CheckUpperBound --> |Gap Detected| ClampEnd -CheckUpperBound --> |No Gap| BuildRange -ClampEnd --> LogGap["Log gap detection and clamping
in gray ANSI color"] -LogGap --> BuildRange -BuildRange --> CheckLimit["Check block limit"] -CheckLimit --> |Exceeded| ReturnResult["Return partial result"] -CheckLimit --> |Within limit| ContinueBuild["Continue building range"] -ContinueBuild --> ReturnResult -ReturnResult --> End([End]) -LogClamp --> BuildRange -LogGap --> BuildRange -``` - -**Diagram sources** -- [p2p_plugin.cpp:290-364](file://plugins/p2p/p2p_plugin.cpp#L290-L364) - -### Enhanced Gap Detection and Recovery - -The plugin now includes comprehensive gap detection and automatic recovery mechanisms: - -```mermaid -flowchart TD -Start([Block Range Request]) --> CheckDLT{"DLT Mode Active?"} -CheckDLT --> |No| NormalRange["Build normal block range"] -CheckDLT --> |Yes| CheckStartGap["Check start_num vs earliest_available"] -CheckStartGap --> |Below Earliest| ClampToEarliest["Clamp start to earliest_available"] -CheckStartGap --> |Within Range| CheckStorageBoundary["Check storage boundaries"] -CheckStorageBoundary --> |Gap Found| ClampToBoundary["Clamp to storage boundary"] -CheckStorageBoundary --> |No Gap| CheckForkDBGap["Check fork_db gap"] -CheckForkDBGap --> |Gap Found| ClampToForkDB["Clamp to fork_db boundary"] -CheckForkDBGap --> |No Gap| BuildRange -ClampToEarliest --> LogClamp["Log clamping action
in gray ANSI color"] -ClampToBoundary --> LogGap["Log gap detection
in gray ANSI color"] -ClampToForkDB --> LogGap -LogClamp --> BuildRange -LogGap --> BuildRange -BuildRange --> CheckLimit["Check against block limit"] -CheckLimit --> |Exceeded| ReturnPartial["Return partial range"] -CheckLimit --> |Within Limit| ReturnFull["Return full range"] -ReturnPartial --> End([End]) -ReturnFull --> End -NormalRange --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -### Enhanced get_item() Method with Gap Awareness - -The `get_item()` method now provides comprehensive DLT mode error handling with gap detection: - -```mermaid -flowchart TD -Start([get_item Called]) --> CheckItemType{"Item type == block?"} -CheckItemType --> |No| FetchTx["Fetch transaction from chain"] -CheckItemType --> |Yes| CheckDLT{"In DLT mode?"} -CheckDLT --> |No| FetchBlock["Fetch block normally"] -CheckDLT --> |Yes| CheckAvailability["Check block availability"] -CheckAvailability --> |Available| FetchBlock -CheckAvailability --> |Not Available| CheckGap["Check if in DLT gap"] -CheckGap --> |In Gap| LogGapError["Log DLT gap error:
- Block number
- Available range
- DLT log bounds
in gray ANSI color"] -CheckGap --> |Not In Gap| LogMissingError["Log missing block error:
- Block not found anywhere
in gray ANSI color"] -LogGapError --> ThrowGapException["Throw key_not_found_exception"] -LogMissingError --> ThrowMissingException["Throw key_not_found_exception"] -FetchTx --> End([End]) -FetchBlock --> ReturnBlock["Return block_message"] -ReturnBlock --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:371-405](file://plugins/p2p/p2p_plugin.cpp#L371-L405) - -### Enhanced Peer Ahead-of-Us Detection - -**New** The plugin now includes sophisticated peer ahead-of-us detection mechanism that identifies peers who are ahead of the local node in DLT mode: - -```mermaid -flowchart TD -Start([Peer Synopsis Analysis]) --> CheckDLT{"DLT Mode Active?"} -CheckDLT --> |No| NormalForkCheck["Normal fork detection"] -CheckDLT --> |Yes| CheckAllAboveHead["Check if all synopsis entries > head"] -CheckAllAboveHead --> |All Above| LogAhead["Log peer ahead detection:
- All entries above head
- Peer is ahead, not on fork
in orange ANSI color"] -CheckAllAboveHead --> |Mixed| NormalForkCheck -LogAhead --> ReturnEmpty["Return empty block list"] -NormalForkCheck --> End([End]) -ReturnEmpty --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:307-327](file://plugins/p2p/p2p_plugin.cpp#L307-L327) - -### Startup Block Storage Diagnostics - -**New** The plugin provides comprehensive startup diagnostics that run before synchronization begins: - -```mermaid -flowchart TD -Start([Plugin Startup]) --> LogStart["Log startup diagnostics begin"] -LogStart --> GatherInfo["Gather storage information:
- Head block
- LIB
- Earliest available
- DLT log range
- Block log end
- Fork DB stats"] -GatherInfo --> CheckGaps["Check for gaps:
- DLT coverage gaps
- Integrity issues"] -CheckGaps --> |Gaps Found| LogGaps["Log gaps:
- Gap locations
- Missing blocks
in orange ANSI color"] -CheckGaps --> |No Gaps| LogOK["Log integrity OK"] -LogGaps --> LogEnd["Log startup diagnostics complete"] -LogOK --> LogEnd -LogEnd --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:1042-1113](file://plugins/p2p/p2p_plugin.cpp#L1042-L1113) - -### Windows Compatibility Enhancements - -**New** The plugin includes Windows compatibility enhancements for DLT block log operations: - -```mermaid -flowchart TD -Start([DLT Block Log Operation]) --> CheckPlatform{"Windows Platform?"} -CheckPlatform --> |No| NormalOperation["Normal operation"] -CheckPlatform --> |Yes| CheckMapping["Check mapping consistency"] -CheckMapping --> |Stale Mapping| HealMapping["Heal stale mapping:
- Close and reopen files
- Sync logical sizes
- Track resize count"] -CheckMapping --> |Valid Mapping| NormalOperation -HealMapping --> LogHeal["Log healing action:
- Stale mapping detected
- Healing performed
in gray ANSI color"] -LogHeal --> NormalOperation -NormalOperation --> End([End]) -``` - -**Diagram sources** -- [dlt_block_log.cpp:545-574](file://libraries/chain/dlt_block_log.cpp#L545-L574) - -### DLT Mode Integration Points - -The enhanced DLT mode integration affects multiple plugin methods with sophisticated gap detection: - -1. **Block ID Generation**: `get_block_ids()` clamps starting block numbers to available DLT range and detects storage gaps -2. **Item Serving**: `get_item()` provides detailed logging for unavailable DLT blocks and gap detection -3. **Synopsis Generation**: `get_blockchain_synopsis()` includes DLT availability context with gap awareness -4. **Earliest Block Calculation**: Database provides `earliest_available_block_num()` for DLT mode with gap detection -5. **Storage Boundary Detection**: Enhanced logic to detect gaps between dlt_block_log and fork_db -6. **Peer Ahead Detection**: Sophisticated detection of peers ahead of the local node in DLT mode -7. **Startup Diagnostics**: Comprehensive diagnostics system that runs before sync begins -8. **Windows Compatibility**: Enhanced compatibility with Windows operating systems for DLT operations - -### Database Integration - -The database provides DLT-specific functionality with gap detection: - -```mermaid -classDiagram -class database { -+bool _dlt_mode -+dlt_block_log _dlt_block_log -+uint32_t earliest_available_block_num() -+void set_dlt_mode(enabled) -+const dlt_block_log& get_dlt_block_log() -+uint32_t head_block_num() -+uint32_t last_non_undoable_block_num() -+optional fetch_block_by_number(num) -} -class dlt_block_log { -+uint32_t start_block_num() -+uint32_t head_block_num() -+uint32_t num_blocks() -+optional read_block_by_num(block_num) -+bool verify_mapping() -+std : : vector verify_continuity() -+uint64_t resize_count() -} -database --> dlt_block_log : "contains" -``` - -**Diagram sources** -- [database.hpp:57-78](file://libraries/chain/include/graphene/chain/database.hpp#L57-L78) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) - -**Section sources** -- [p2p_plugin.cpp:290-405](file://plugins/p2p/p2p_plugin.cpp#L290-L405) -- [p2p_plugin.cpp:307-327](file://plugins/p2p/p2p_plugin.cpp#L307-L327) -- [p2p_plugin.cpp:1042-1113](file://plugins/p2p/p2p_plugin.cpp#L1042-L1113) -- [database.hpp:57-78](file://libraries/chain/include/graphene/chain/database.hpp#L57-L78) -- [dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) - -## Improved Gap Detection and Recovery - -**New** The P2P plugin now includes sophisticated gap detection and automatic recovery mechanisms that prevent peer disconnections due to item_not_available responses. - -### Comprehensive Gap Detection Logic - -The enhanced gap detection system monitors multiple storage boundaries: - -```mermaid -flowchart TD -Start([Gap Detection]) --> CheckDLTMode{"DLT Mode Active?"} -CheckDLTMode --> |No| NormalProcessing["Normal processing"] -CheckDLTMode --> |Yes| CheckStartBoundary["Check start_num boundary"] -CheckStartBoundary --> GetEarliest["Get earliest_available_block_num()"] -GetEarliest --> CheckBelowEarliest{"start_num < earliest?"} -CheckBelowEarliest --> |Yes| ClampToEarliest["Clamp start to earliest"] -CheckBelowEarliest --> |No| CheckStorageBoundary["Check storage boundary"] -CheckStorageBoundary --> GetDLTEnd["Get dlt_block_log.head_block_num()"] -GetDLTEnd --> GetBlogEnd["Get block_log.head_block_num()"] -GetBlogEnd --> GetStorageEnd["storage_end = max(dlt_end, blog_end)"] -GetStorageEnd --> CheckBeyondStorage{"start_num > storage_end?"} -CheckBeyondStorage --> |Yes| CheckForkDB["Check fork_db availability"] -CheckBeyondStorage --> |No| CheckForkDBGap["Check fork_db gap"] -CheckForkDB --> |Available| UseForkDB["Use fork_db block"] -CheckForkDB --> |Not Available| LogGap["Log gap detected
in gray ANSI color"] -CheckForkDBGap --> |Gap Exists| ClampToForkDB["Clamp to fork_db boundary"] -CheckForkDBGap --> |No Gap| CheckContiguous["Check contiguity"] -ClampToEarliest --> LogClamp["Log clamping action
in gray ANSI color"] -ClampToForkDB --> LogGap -UseForkDB --> LogForkDB["Log fork_db usage
in gray ANSI color"] -LogClamp --> BuildRange["Build clamped range"] -LogGap --> BuildRange -LogForkDB --> BuildRange -BuildRange --> CheckLimit["Check against block limit"] -CheckLimit --> |Exceeded| ReturnPartial["Return partial range"] -CheckLimit --> |Within Limit| ReturnFull["Return full range"] -ReturnPartial --> End([End]) -ReturnFull --> End -NormalProcessing --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -### Automatic Recovery Mechanisms - -The plugin implements automatic recovery from gap-related synchronization issues: - -```mermaid -flowchart TD -Start([Gap Recovery Triggered]) --> DetectGap["Detect gap in block range"] -DetectGap --> LogGapInfo["Log gap information:
- Gap start/end
- Available ranges
- Storage boundaries
in gray ANSI color"] -LogGapInfo --> CheckPeerResponse["Check peer response type"] -CheckPeerResponse --> |item_not_available| LogPeerIssue["Log peer disconnection issue
in gray ANSI color"] -CheckPeerResponse --> |other_error| LogOtherError["Log other error
in gray ANSI color"] -LogPeerIssue --> SoftBanPeer["Soft-ban peer appropriately"] -LogOtherError --> SoftBanPeer -SoftBanPeer --> CheckRecoveryOptions["Check recovery options:
- Alternative peers
- Different sync strategy"] -CheckRecoveryOptions --> SwitchPeer["Switch to alternative peer"] -CheckRecoveryOptions --> AdjustSync["Adjust sync parameters"] -CheckRecoveryOptions --> WaitAndRetry["Wait and retry later"] -SwitchPeer --> ContinueSync["Continue synchronization"] -AdjustSync --> ContinueSync -WaitAndRetry --> ContinueSync -ContinueSync --> End([Recovery Complete]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:371-405](file://plugins/p2p/p2p_plugin.cpp#L371-L405) - -### Enhanced Error Handling and Logging - -The gap detection system provides comprehensive logging for troubleshooting: - -```mermaid -flowchart TD -Start([Gap Error]) --> ClassifyError["Classify gap type:
- Below earliest
- Beyond storage
- Fork_db gap
- Missing block"] -ClassifyError --> LogDetailedInfo["Log detailed gap info:
- Block numbers
- Available ranges
- Storage locations
- Error context
in gray ANSI color"] -LogDetailedInfo --> DetermineImpact["Determine impact:
- Peer disconnection risk
- Sync delay
- Data availability"] -DetermineImpact --> ApplyRecovery["Apply recovery:
- Range clamping
- Peer switching
- Parameter adjustment"] -ApplyRecovery --> LogRecovery["Log recovery actions:
- Actions taken
- Results
- Next steps
in gray ANSI color"] -LogRecovery --> ContinueSync["Continue sync process"] -ContinueSync --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -**Section sources** -- [p2p_plugin.cpp:295-405](file://plugins/p2p/p2p_plugin.cpp#L295-L405) - -## Sophisticated Clamping Logic - -**New** The P2P plugin now includes sophisticated clamping logic in the get_block_ids() method to prevent advertising blocks that aren't available in node storage, avoiding peer disconnections due to item_not_available responses. - -### Advanced Clamping Algorithm - -The enhanced clamping logic implements multiple layers of block availability validation: - -```mermaid -flowchart TD -Start([Block Range Request]) --> GetStartNum["Get calculated start_num"] -GetStartNum --> CheckDLTMode{"DLT Mode Active?"} -CheckDLTMode --> |No| BuildNormalRange["Build normal range"] -CheckDLTMode --> |Yes| CheckEarliest["Check against earliest_available"] -CheckEarliest --> GetEarliest["Get earliest_available_block_num()"] -GetEarliest --> CompareEarliest{"start_num < earliest?"} -CompareEarliest --> |Yes| ClampToEarliest["Clamp start_num = earliest"] -CompareEarliest --> |No| CheckStorageBoundary["Check storage boundary"] -CheckStorageBoundary --> GetStorageEnd["Get storage_end = max(dlt_end, blog_end)"] -GetStorageEnd --> CompareStorage{"start_num > storage_end?"} -CompareStorage --> |Yes| CheckForkDB["Check fork_db availability"] -CompareStorage --> |No| CheckForkDBGap["Check fork_db gap"] -CheckForkDB --> |Available| UseForkDB["Use fork_db block"] -CheckForkDB --> |Not Available| CheckForkDBGap -CheckForkDBGap --> |Gap Exists| ClampToForkDB["Clamp to fork_db boundary"] -CheckForkDBGap --> |No Gap| CheckContiguous["Check contiguity"] -ClampToEarliest --> LogClamp["Log clamping to earliest
in gray ANSI color"] -ClampToForkDB --> LogClamp -UseForkDB --> LogForkDB["Log fork_db usage
in gray ANSI color"] -LogClamp --> BuildClampedRange["Build clamped range"] -LogForkDB --> BuildClampedRange -CheckContiguous --> CheckGap["Check for gap between storage_end+1 and fork_db"] -CheckGap --> |Gap| ClampToStorageEnd["Clamp to storage_end"] -CheckGap --> |No Gap| BuildClampedRange -ClampToStorageEnd --> LogGap["Log gap detection
in gray ANSI color"] -LogGap --> BuildClampedRange -BuildNormalRange --> End([End]) -BuildClampedRange --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -### Storage Boundary Detection - -The clamping logic includes sophisticated storage boundary detection: - -```mermaid -flowchart TD -Start([Storage Boundary Check]) --> GetDLTInfo["Get DLT block log info:
- start_block_num()
- head_block_num()"] -GetDLTInfo --> GetBlogInfo["Get block log info:
- head_block_num()"] -GetBlogInfo --> CalcStorageEnd["Calculate storage_end:
storage_end = max(dlt_end, blog_end)"] -CalcStorageEnd --> CheckStartBeyond["Check: start_num > storage_end"] -CheckStartBeyond --> |Yes| CheckForkDBRange["Check fork_db range:
fetch_block_by_number(storage_end+1)"] -CheckStartBeyond --> |No| CheckForkDBGap["Check fork_db gap:
between storage_end and fork_db"] -CheckForkDBRange --> |Block Found| UseForkDB["Use fork_db block"] -CheckForkDBRange --> |No Block| LogNoBlock["Log no block found
in gray ANSI color"] -CheckForkDBGap --> |Gap Exists| ClampToStorage["Clamp to storage_end"] -CheckForkDBGap --> |No Gap| CheckContiguity["Check contiguity"] -UseForkDB --> LogForkDB["Log fork_db usage
in gray ANSI color"] -LogNoBlock --> LogError["Log error: block not found
in gray ANSI color"] -ClampToStorage --> LogClamp["Log clamping to storage boundary
in gray ANSI color"] -LogForkDB --> BuildRange["Build range up to boundary"] -LogError --> BuildEmpty["Build empty range"] -LogClamp --> BuildRange -CheckContiguity --> BuildRange -BuildEmpty --> End([End]) -BuildRange --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:308-340](file://plugins/p2p/p2p_plugin.cpp#L308-L340) - -### Enhanced Logging for Clamping Operations - -The clamping logic provides comprehensive logging for troubleshooting and monitoring: - -```mermaid -flowchart TD -Start([Clamping Operation]) --> LogClampStart["Log clamping start:
- Original start_num
- Reason for clamping
in gray ANSI color"] -LogClampStart --> PerformClamp["Perform clamping:
- Clamp to earliest
- Clamp to storage_end
- Clamp to fork_db"] -PerformClamp --> LogClampResult["Log clamping result:
- New start_num
- Effective head
- Range size
in gray ANSI color"] -LogClampResult --> LogContext["Log context:
- DLT mode active
- Earliest available
- Storage boundaries
in gray ANSI color"] -LogContext --> LogDecision["Log decision:
- Why clamping was needed
- Impact on sync
- Peer compatibility
in gray ANSI color"] -LogDecision --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:298-302](file://plugins/p2p/p2p_plugin.cpp#L298-L302) -- [p2p_plugin.cpp:335-338](file://plugins/p2p/p2p_plugin.cpp#L335-L338) - -**Section sources** -- [p2p_plugin.cpp:295-340](file://plugins/p2p/p2p_plugin.cpp#L295-L340) - -## Enhanced Peer Interaction Handling - -**New** The P2P plugin now includes enhanced peer interaction handling with improved error management, graceful degradation capabilities, and sophisticated gap-aware block serving to avoid item_not_available responses. - -### Comprehensive Peer Database Logging - -The plugin provides detailed peer database logging for troubleshooting with gap detection awareness: - -```mermaid -flowchart TD -Start([Peer Stats Task]) --> CheckPeers{"Any connected peers?"} -CheckPeers --> |No| LogNoPeers["Log 'no connected peers'
in cyan ANSI color"] -CheckPeers --> |Yes| IteratePeers["Iterate connected peers"] -IteratePeers --> ExtractInfo["Extract peer info:
- IP/port
- Latency
- Bytes received
- Blocked status"] -ExtractInfo --> LogPeer["Log individual peer stats
in cyan ANSI color"] -LogPeer --> CheckPotential["Check potential peers"] -CheckPotential --> IteratePotential["Iterate potential peers"] -IteratePotential --> CheckStatus{"Failed/rejected status?"} -CheckStatus --> |No| NextPeer["Next potential peer"] -CheckStatus --> |Yes| LogPotential["Log failed/rejected peer:
- Endpoint
- Last attempt time
- Failed attempts
- Error details
- Gap-related errors
in cyan ANSI color"] -LogPotential --> NextPeer -NextPeer --> CheckMore{"More potential peers?"} -CheckMore --> |Yes| IteratePotential -CheckMore --> |No| LogSummary["Log summary of failed peers
including gap detection results
in cyan ANSI color"] -LogSummary --> End([End]) -LogNoPeers --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) - -### Graceful Degradation on Peer Failure with Gap Awareness - -The plugin implements graceful degradation when peers cannot serve requested items with sophisticated gap detection: - -```mermaid -flowchart TD -Start([Peer Request Failed]) --> CheckError{"Error type?"} -CheckError --> |DLT Mode Error| CheckGapError["Check if gap-related error:
- item_not_available
- block not in dlt_block_log
- missing from storage"] -CheckGapError --> |Gap Error| LogDLTError["Log DLT availability error:
- Block number
- Available range
- DLT log bounds
- Gap detection results
in gray ANSI color"] -CheckGapError --> |Other Error| LogGenericError["Log generic error:
- Error details
- Peer endpoint
- Error context
in gray ANSI color"] -CheckError --> |Other Error Type| LogOtherError["Log other error type:
- Error classification
- Peer status
- Recovery actions
in gray ANSI color"] -LogDLTError --> CheckRecovery{"Check recovery options:
- Peer switching
- Range adjustment
- Wait and retry"} -LogGenericError --> CheckRecovery -LogOtherError --> CheckRecovery -CheckRecovery --> |Peer Switching| SwitchPeer["Switch to alternative peer"] -CheckRecovery --> |Range Adjustment| AdjustRange["Adjust block range
with gap detection"] -CheckRecovery --> |Wait and Retry| WaitRetry["Wait and retry later"] -SwitchPeer --> ResetTimer["Reset stale sync timer"] -AdjustRange --> ResetTimer -WaitRetry --> ResetTimer -ResetTimer --> ContinueSync["Continue synchronization"] -ContinueSync --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:371-405](file://plugins/p2p/p2p_plugin.cpp#L371-L405) - -### Enhanced Stale Sync Detection with Gap Awareness - -The stale sync detection has been enhanced with better peer interaction and gap detection: - -```mermaid -sequenceDiagram -participant Timer as Stale Sync Timer -participant Node as Network Node -participant Chain as Chain Database -participant Peers as Connected Peers -Timer->>Node : Check last_block_received_time -Node->>Chain : Get head_block_num and LIB -Chain-->>Node : Return chain state -Node->>Node : Compare elapsed time with timeout -alt Stale sync detected -Node->>Node : sync_from(LIB, []) -Node->>Node : resync() -Node->>Node : add_node(seed) for each seed -Node->>Node : connect_to_endpoint(seed) for each seed -Note over Node : Enhanced with gap detection -Node->>Chain : Check DLT gaps during recovery -Chain-->>Node : Return gap information -Node->>Node : Adjust sync parameters based on gaps -Node->>Timer : Reset _last_block_received_time -end -``` - -**Diagram sources** -- [p2p_plugin.cpp:701-765](file://plugins/p2p/p2p_plugin.cpp#L701-L765) - -**Section sources** -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) -- [p2p_plugin.cpp:371-405](file://plugins/p2p/p2p_plugin.cpp#L371-L405) -- [p2p_plugin.cpp:701-765](file://plugins/p2p/p2p_plugin.cpp#L701-L765) - -## Comprehensive Logging Throughout Sync Process - -**New** The P2P plugin now includes comprehensive logging throughout the sync process, providing detailed visibility into DLT mode operations, gap detection, and peer interactions with sophisticated gap-aware logging. - -### DLT Mode Logging Enhancements with Gap Detection - -The plugin provides detailed logging for DLT mode operations with gap detection awareness: - -```mermaid -flowchart TD -Start([DLT Mode Operation]) --> LogClamp["Log DLT clamp:
- Old start number
- New start number
- Earliest available
- Head block
- Gap detection results
in gray ANSI color"] -LogClamp --> LogIDs["Log get_block_ids result:
- Number of IDs
- Start block
- Head block
- Earliest available
- Gap information
in gray ANSI color"] -LogIDs --> LogSynopsis["Log get_blockchain_synopsis:
- Entry count
- Low/high blocks
- Head/LIB
- Earliest available
- Gap boundaries
in gray ANSI color"] -LogSynopsis --> LogAvailability["Log DLT availability:
- Block number
- Available range
- DLT log bounds
- Storage boundaries
in gray ANSI color"] -LogAvailability --> LogGap["Log gap detection:
- Gap location
- Gap size
- Available alternatives
- Recovery actions
in gray ANSI color"] -LogGap --> LogAhead["Log peer ahead detection:
- All entries above head
- Peer is ahead
- Empty response
in orange ANSI color"] -LogAhead --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:298-302](file://plugins/p2p/p2p_plugin.cpp#L298-L302) -- [p2p_plugin.cpp:355-364](file://plugins/p2p/p2p_plugin.cpp#L355-L364) -- [p2p_plugin.cpp:520-528](file://plugins/p2p/p2p_plugin.cpp#L520-L528) -- [p2p_plugin.cpp:321-327](file://plugins/p2p/p2p_plugin.cpp#L321-L327) - -### Enhanced Block Processing Logs with Gap Awareness - -**Updated** The block processing logging has been enhanced with conditional latency reporting and improved visibility: - -```mermaid -flowchart TD -Start([Handle Block]) --> LogGap["Log block gap:
- Block number
- Head block
- Gap size
- Gap detection context
in gray ANSI color"] -LogGap --> CheckSyncMode{"Sync mode?"} -CheckSyncMode --> |Yes| LogSync["Log sync block:
- Block number
- Head
- Gap
- Clamping info
in gray ANSI color"] -CheckSyncMode --> |No| LogNormal["Log normal block:
- Block number
- Transactions
- validator
- Gap context
in gray ANSI color"] -LogSync --> AcceptBlock["Accept block via chain.accept_block()"] -LogNormal --> AcceptBlock -AcceptBlock --> CheckResult{"Result successful?"} -CheckResult --> |No| End([End]) -CheckResult --> |Yes| CheckSyncMode2{"Sync mode?"} -CheckSyncMode2 --> |Yes| End -CheckSyncMode2 --> |No| LogLatency["Log latency:
- Transaction count
- Block number
- validator
- Latency in milliseconds
in white ANSI color"] -LogLatency --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:151-208](file://plugins/p2p/p2p_plugin.cpp#L151-L208) - -### Peer Interaction Logging with Gap Detection - -The plugin provides comprehensive peer interaction logging with gap detection awareness: - -```mermaid -flowchart TD -Start([Peer Interaction]) --> LogPeerStats["Log peer stats:
- IP/port
- Latency
- Bytes received
- Blocked status
- Reason
- Gap-related interactions
in cyan ANSI color"] -LogPeerStats --> LogPotential["Log potential peers:
- Endpoint
- Status
- Last attempt
- Failed attempts
- Error
- Gap detection results
in cyan ANSI color"] -LogPotential --> LogFailed["Log failed peers:
- Count
- Total peers
- Status distribution
- Gap-related failures
in cyan ANSI color"] -LogFailed --> LogRecovery["Log recovery actions:
- Peer switching
- Range adjustments
- Gap handling
- Success rates
in gray ANSI color"] -LogRecovery --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) - -### Block Storage Diagnostics with Gap Detection - -**New** The plugin provides comprehensive block storage diagnostics with gap detection and coverage monitoring: - -```mermaid -flowchart TD -Start([Storage Diagnostics]) --> LogStorage["Log block storage:
- Head block
- LIB
- Earliest available
- DLT log range
- Block log end
- Fork DB stats
- DLT mode status
- DLT resize count
in cyan ANSI color"] -LogStorage --> CheckGap["Check for DLT coverage gap:
- DLT end vs Fork DB start
- Gap detection
- Availability impact
in orange ANSI color"] -CheckGap --> LogGapWarning["Log DLT coverage gap:
- Gap start/end
- Blocks unavailable
- Impact on serving
in orange ANSI color"] -LogGapWarning --> LogIntegrity["Log DLT integrity:
- Gap count
- Missing blocks
- Gap locations
in orange ANSI color"] -LogIntegrity --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:722-771](file://plugins/p2p/p2p_plugin.cpp#L722-L771) -- [p2p_plugin.cpp:783-791](file://plugins/p2p/p2p_plugin.cpp#L783-L791) -- [p2p_plugin.cpp:812-816](file://plugins/p2p/p2p_plugin.cpp#L812-L816) - -### DLT Integrity Verification with Continuity Scanning - -**New** The plugin implements comprehensive DLT integrity verification with periodic continuity scanning: - -```mermaid -flowchart TD -Start([DLT Integrity Scan]) --> CheckDLTMode{"DLT Mode Active?"} -CheckDLTMode --> |No| End([End]) -CheckDLTMode --> |Yes| VerifyMapping["Call verify_mapping()
- Detect stale mapping
- Heal if needed
in gray ANSI color"] -VerifyMapping --> CheckContinuity["Call verify_continuity()
- Walk all blocks
- Report gaps
- Log missing blocks
in gray ANSI color"] -CheckContinuity --> CheckGaps{"Any gaps found?"} -CheckGaps --> |No| End -CheckGaps --> |Yes| LogGaps["Log DLT integrity warning:
- Gap count
- Missing blocks
- Gap locations
in orange ANSI color"] -LogGaps --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:773-795](file://plugins/p2p/p2p_plugin.cpp#L773-L795) - -### Startup Block Storage Diagnostics - -**New** The plugin provides comprehensive startup diagnostics that run before synchronization begins: - -```mermaid -flowchart TD -Start([Startup Diagnostics]) --> LogStart["Log startup diagnostics begin
in cyan ANSI color"] -LogStart --> GatherInfo["Gather storage information:
- Head block
- LIB
- Earliest available
- DLT log range
- Block log end
- Fork DB stats
- DLT mode status
- DLT resize count
in cyan ANSI color"] -GatherInfo --> CheckStartupGaps["Check for startup gaps:
- DLT coverage gaps
- Integrity issues
- Gap locations
in orange ANSI color"] -CheckStartupGaps --> LogGaps["Log startup gaps:
- Gap start/end
- Missing blocks
- Impact on serving
in orange ANSI color"] -LogGaps --> LogIntegrity["Log integrity status:
- Gap count
- Missing blocks
- OK status
in cyan ANSI color"] -LogIntegrity --> LogEnd["Log startup diagnostics complete
in cyan ANSI color"] -LogEnd --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:1042-1113](file://plugins/p2p/p2p_plugin.cpp#L1042-L1113) - -**Section sources** -- [p2p_plugin.cpp:298-302](file://plugins/p2p/p2p_plugin.cpp#L298-L302) -- [p2p_plugin.cpp:355-364](file://plugins/p2p/p2p_plugin.cpp#L355-L364) -- [p2p_plugin.cpp:520-528](file://plugins/p2p/p2p_plugin.cpp#L520-L528) -- [p2p_plugin.cpp:151-208](file://plugins/p2p/p2p_plugin.cpp#L151-L208) -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) -- [p2p_plugin.cpp:722-771](file://plugins/p2p/p2p_plugin.cpp#L722-L771) -- [p2p_plugin.cpp:773-795](file://plugins/p2p/p2p_plugin.cpp#L773-L795) -- [p2p_plugin.cpp:1042-1113](file://plugins/p2p/p2p_plugin.cpp#L1042-L1113) - -## ANSI Color Code Implementation - -**New** The P2P plugin now includes comprehensive ANSI color code implementation for enhanced console readability and visual distinction between different types of log messages. - -### ANSI Color Code Definitions - -The plugin defines ANSI color codes for consistent color usage throughout the logging system: - -```mermaid -flowchart TD -ColorCodes["ANSI Color Code Definitions"] --> Gray["CLOG_GRAY
\\033[90m
Gray color for DLT mode debug messages"] -ColorCodes --> Cyan["CLOG_CYAN
\\033[96m
Cyan color for peer statistics and informational messages"] -ColorCodes --> White["CLOG_WHITE
\\033[97m
White color for important transaction notifications and latency information"] -ColorCodes --> Reset["CLOG_RESET
\\033[0m
Reset color to default"] -ColorCodes --> Orange["CLOG_ORANGE
\\033[33m
Orange color for network-related warnings and peer connection status"] -ColorCodes --> Red["CLOG_RED
\\033[91m
Red color for critical errors and severe warnings"] -Gray --> DLTLogging["DLT Mode Debug Logging
in gray color"] -Cyan --> PeerStats["Peer Statistics Logging
in cyan color"] -White --> TransactionLogs["Transaction Notifications and Latency
in white color"] -Orange --> NetworkWarnings["Network Warnings and Peer Status
in orange color"] -Red --> CriticalErrors["Critical Errors
in red color"] -``` - -**Diagram sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) - -### Color Code Usage Patterns - -The ANSI color codes are applied consistently across different logging scenarios: - -1. **DLT Mode Debug Messages**: Gray color for detailed DLT mode operations, gap detection, and block range management -2. **Peer Statistics**: Cyan color for peer connection statistics, connection status, and peer database information -3. **Transaction Notifications**: White color for important transaction-related information and block processing notifications -4. **Network Warnings**: Orange color for peer connection warnings, network issues, and peer status changes -5. **Critical Errors**: Red color for severe errors, critical failures, and system emergencies -6. **Error and Warning Messages**: Default color scheme for error conditions and warnings (unchanged) - -### Implementation Examples - -The color codes are integrated throughout the plugin implementation: - -```mermaid -sequenceDiagram -participant Logger as Logger -participant P2P as P2P Plugin -participant Console as Console Output -Logger->>P2P : Log message with color code -P2P->>Console : Output colored text
CLOG_GRAY + message + CLOG_RESET -Console-->>P2P : Colored output displayed -Note over P2P,Console : Consistent color coding
throughout all logging operations -``` - -**Diagram sources** -- [p2p_plugin.cpp:169-171](file://plugins/p2p/p2p_plugin.cpp#L169-L171) -- [p2p_plugin.cpp:299-301](file://plugins/p2p/p2p_plugin.cpp#L299-L301) -- [p2p_plugin.cpp:522-528](file://plugins/p2p/p2p_plugin.cpp#L522-L528) - -**Section sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:169-171](file://plugins/p2p/p2p_plugin.cpp#L169-L171) -- [p2p_plugin.cpp:299-301](file://plugins/p2p/p2p_plugin.cpp#L299-L301) -- [p2p_plugin.cpp:522-528](file://plugins/p2p/p2p_plugin.cpp#L522-L528) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) - -## Enhanced DLT Mode Debug Logging - -**New** The P2P plugin now includes enhanced DLT (Data Ledger Technology) mode debug logging with comprehensive gap detection and block range management information displayed in gray ANSI color for improved troubleshooting capabilities. - -### Comprehensive DLT Mode Logging - -The enhanced DLT mode logging provides detailed information about block availability, gap detection, and synchronization operations: - -```mermaid -flowchart TD -DLTLogging["DLT Mode Debug Logging"] --> ClampStart["Clamp Start Logging
- Original start number
- New clamped start
- Earliest available block
- Head block information
in gray ANSI color"] -DLTLogging --> GapDetection["Gap Detection Logging
- Gap location identification
- Gap size calculation
- Storage boundary detection
- Fork database gap analysis
in gray ANSI color"] -DLTLogging --> BlockRange["Block Range Logging
- Number of blocks returned
- Start and end block numbers
- Effective head block
- Earliest available block
- DLT log end position
in gray ANSI color"] -DLTLogging --> Availability["Availability Logging
- Block number being served
- Available block range
- DLT log bounds
- Storage boundaries
in gray ANSI color"] -DLTLogging --> AheadDetection["Peer Ahead Detection Logging
- All synopsis entries above head
- Peer is ahead of us
- Empty response returned
in orange ANSI color"] -ClampStart --> DLTLogging -GapDetection --> DLTLogging -BlockRange --> DLTLogging -Availability --> DLTLogging -AheadDetection --> DLTLogging -``` - -**Diagram sources** -- [p2p_plugin.cpp:299-301](file://plugins/p2p/p2p_plugin.cpp#L299-L301) -- [p2p_plugin.cpp:321-327](file://plugins/p2p/p2p_plugin.cpp#L321-L327) -- [p2p_plugin.cpp:336-338](file://plugins/p2p/p2p_plugin.cpp#L336-L338) -- [p2p_plugin.cpp:357-364](file://plugins/p2p/p2p_plugin.cpp#L357-L364) -- [p2p_plugin.cpp:321-327](file://plugins/p2p/p2p_plugin.cpp#L321-L327) - -### DLT Mode Operation Logging - -The plugin provides comprehensive logging for all DLT mode operations: - -1. **get_block_ids() Operations**: Detailed logging of block ID generation with gap detection and clamping information -2. **get_blockchain_synopsis() Operations**: Logging of blockchain synopsis generation with DLT availability context -3. **get_item() Operations**: Logging of item serving operations with DLT mode error handling -4. **Gap Detection Operations**: Comprehensive logging of gap detection and recovery mechanisms -5. **Peer Ahead Detection**: Sophisticated logging of peer ahead-of-us detection in DLT mode -6. **Startup Diagnostics**: Comprehensive logging of startup block storage diagnostics -7. **Windows Compatibility**: Logging of Windows compatibility enhancements for DLT operations - -### Logging Context Information - -Each DLT mode log entry includes comprehensive context information: - -- **Block Numbers**: Current block, head block, earliest available block, and DLT log boundaries -- **Storage Information**: DLT log start and end positions, block log boundaries -- **Gap Information**: Gap locations, sizes, and detection results -- **Synchronization Context**: Effective head block, remaining item counts, and synchronization status -- **Peer Status**: Information about peers ahead of the local node in DLT mode - -**Section sources** -- [p2p_plugin.cpp:299-301](file://plugins/p2p/p2p_plugin.cpp#L299-L301) -- [p2p_plugin.cpp:321-327](file://plugins/p2p/p2p_plugin.cpp#L321-L327) -- [p2p_plugin.cpp:336-338](file://plugins/p2p/p2p_plugin.cpp#L336-L338) -- [p2p_plugin.cpp:357-364](file://plugins/p2p/p2p_plugin.cpp#L357-L364) -- [p2p_plugin.cpp:522-528](file://plugins/p2p/p2p_plugin.cpp#L522-L528) - -## Improved Console Readability - -**New** The P2P plugin now provides significantly improved console readability through the strategic use of ANSI color codes, allowing operators to quickly distinguish between different types of log messages and troubleshoot network operations more effectively. - -### Visual Distinction Between Log Types - -The color-coded logging system provides clear visual distinction between different categories of log messages: - -```mermaid -graph TB -subgraph "Console Readability Enhancement" -GrayLogs["Gray Logs
DLT Mode Debug
Gap Detection
Block Range Info
Windows Compatibility"] -CyanLogs["Cyan Logs
Peer Statistics
Connection Status
Peer Database Info
Startup Diagnostics"] -WhiteLogs["White Logs
Transaction Notifications
Block Processing
Latency Information"] -OrangeLogs["Orange Logs
Network Warnings
Peer Status
Connection Issues
Gap Warnings"] -RedLogs["Red Logs
Critical Errors
Severe Warnings
System Failures"] -DefaultLogs["Default Color
General Information
Debug Messages
Non-Critical Events"] -end -subgraph "Visual Benefits" -Benefit1["Quick Pattern Recognition"] -Benefit2["Faster Troubleshooting"] -Benefit3["Reduced Console Scrolling"] -Benefit4["Enhanced Multi-Tasking"] -end -GrayLogs --> Benefit1 -CyanLogs --> Benefit1 -WhiteLogs --> Benefit1 -OrangeLogs --> Benefit1 -RedLogs --> Benefit1 -DefaultLogs --> Benefit1 -``` - -**Diagram sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) - -### Operator Experience Improvements - -The enhanced console readability provides several operational benefits: - -1. **Quick Pattern Recognition**: Operators can instantly identify DLT mode operations (gray), peer statistics (cyan), transaction notifications (white), network warnings (orange), and critical errors (red) -2. **Faster Troubleshooting**: Color coding helps operators quickly locate relevant log entries during debugging sessions -3. **Reduced Console Scrolling**: Visual distinction makes it easier to scan through large amounts of log output -4. **Enhanced Multi-Tasking**: Operators can monitor multiple log streams simultaneously with color-based differentiation - -### Color Coding Strategy - -The color coding strategy is designed for optimal operator experience: - -- **Gray**: DLT mode debug information, gap detection details, block range management, and Windows compatibility enhancements -- **Cyan**: Peer statistics, connection status, peer database information, and startup diagnostics -- **White**: Important transaction notifications, block processing information, and latency details -- **Orange**: Network warnings, peer status changes, connection issues, and gap-related warnings -- **Red**: Critical errors, severe warnings, and system failures -- **Default**: General information, debug messages, and non-critical events (unchanged) - -**Section sources** -- [p2p_plugin.cpp:16-21](file://plugins/p2p/p2p_plugin.cpp#L16-L21) -- [p2p_plugin.cpp:169-171](file://plugins/p2p/p2p_plugin.cpp#L169-L171) -- [p2p_plugin.cpp:299-301](file://plugins/p2p/p2p_plugin.cpp#L299-L301) -- [p2p_plugin.cpp:522-528](file://plugins/p2p/p2p_plugin.cpp#L522-L528) -- [node.cpp:79-83](file://libraries/network/node.cpp#L79-L83) - -## Conditional Block Processing Latency Logging - -**New** The P2P plugin now implements conditional block processing latency logging that only executes when blocks are successfully processed in non-sync mode, significantly reducing log volume while maintaining operational visibility. - -### Conditional Latency Logging Implementation - -The enhanced block processing includes sophisticated conditional logging logic: - -```mermaid -flowchart TD -Start([Block Processing]) --> AcceptBlock["chain.accept_block()"] -AcceptBlock --> CheckResult{"Result successful?"} -CheckResult --> |No| End([End - No Latency Logging]) -CheckResult --> |Yes| CheckSyncMode{"Sync mode?"} -CheckSyncMode --> |Yes| End([End - No Latency Logging]) -CheckSyncMode --> |No| CalculateLatency["Calculate latency:
fc::time_point::now() - blk_msg.block.timestamp"] -CalculateLatency --> LogLatency["Log latency:
- Transaction count
- Block number
- validator
- Latency in milliseconds
in white ANSI color"] -LogLatency --> End([End - Latency Logged]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:159-175](file://plugins/p2p/p2p_plugin.cpp#L159-L175) - -### Benefits of Conditional Latency Logging - -The conditional approach provides several operational advantages: - -1. **Reduced Log Volume**: Latency information is only logged for successful block processing, significantly reducing log output during sync operations -2. **Maintained Visibility**: Critical latency information for successful normal operations is preserved for troubleshooting and performance monitoring -3. **Performance Impact**: Minimizes CPU overhead during high-volume sync operations while preserving diagnostic information -4. **Resource Efficiency**: Reduces I/O overhead and memory usage associated with excessive logging -5. **Operational Focus**: Ensures logs focus on meaningful operational events rather than routine sync activities - -### Implementation Details - -The conditional latency logging is implemented in the block processing method: - -```cpp -bool result = chain.accept_block(blk_msg.block, sync_mode, ...); - -if (!sync_mode && result) { - fc::microseconds latency = fc::time_point::now() - blk_msg.block.timestamp; - ilog(CLOG_WHITE "Got ${t} transactions on block ${b} by ${w} -- latency: ${l} ms" CLOG_RESET, - ("t", blk_msg.block.transactions.size())("b", blk_msg.block.block_num())("w", blk_msg.block.validator)("l", latency.count() / 1000)); -} -``` - -**Section sources** -- [p2p_plugin.cpp:159-175](file://plugins/p2p/p2p_plugin.cpp#L159-L175) - -## Graceful Degradation Capabilities - -**New** The P2P plugin now includes comprehensive graceful degradation capabilities when peers cannot serve requested items, ensuring network resilience and continued operation with sophisticated gap detection and automatic recovery. - -### DLT Mode Graceful Degradation with Gap Detection - -When peers cannot serve DLT-mode blocks, the plugin implements graceful degradation with comprehensive gap detection: - -```mermaid -flowchart TD -Start([DLT Block Request]) --> CheckAvailability["Check block availability:
- Block number
- Earliest available
- DLT log range
- Gap detection"] -CheckAvailability --> |Available| ServeBlock["Serve block normally"] -CheckAvailability --> |Not Available| CheckGap["Check if gap-related:
- Below earliest
- Beyond storage
- Fork_db gap"] -CheckGap --> |Gap Error| LogUnavailable["Log gap-related unavailability:
- Block number
- Available range
- DLT bounds
- Gap location
in gray ANSI color"] -CheckGap --> |Other Error| LogGenericUnavailable["Log generic unavailability:
- Error details
- Peer endpoint
- Context
in gray ANSI color"] -LogUnavailable --> CheckRecovery["Check recovery options:
- Peer switching
- Range adjustment
- Wait and retry"] -LogGenericUnavailable --> CheckRecovery -CheckRecovery --> |Peer Switching| SwitchPeer["Switch to alternative peer"] -CheckRecovery --> |Range Adjustment| AdjustRange["Adjust block range
with gap detection"] -CheckRecovery --> |Wait and Retry| WaitRetry["Wait and retry later"] -SwitchPeer --> LogRecovery["Log recovery actions:
- Peer switched
- Reason
- Success
in gray ANSI color"] -AdjustRange --> LogRecovery -WaitRetry --> LogRecovery -LogRecovery --> ContinueSync["Continue sync with available peers"] -ContinueSync --> End([End]) -ServeBlock --> End -``` - -**Diagram sources** -- [p2p_plugin.cpp:371-405](file://plugins/p2p/p2p_plugin.cpp#L371-L405) - -### Error Handling and Recovery with Gap Awareness - -The plugin implements comprehensive error handling and recovery mechanisms with gap detection: - -```mermaid -flowchart TD -Start([Error Occurred]) --> ClassifyError["Classify error:
- block_too_old_exception
- deferred_resize_exception
- unlinkable_block_exception
- network exceptions
- gap-related errors"] -ClassifyError --> HandleBlockTooOld["Handle block too old:
- Log warning
- Convert to network exception
- Soft-ban peer"] -ClassifyError --> HandleDeferredResize["Handle deferred resize:
- Log info
- Convert to network exception
- No peer penalty"] -ClassifyError --> HandleUnlinkable["Handle unlinkable block:
- Log warning
- Convert to network exception
- Peer soft-ban or resync"] -ClassifyError --> HandleGapError["Handle gap error:
- Log gap detection
- Adjust sync parameters
- Peer switching
in gray ANSI color"] -HandleBlockTooOld --> ContinueSync["Continue synchronization"] -HandleDeferredResize --> ContinueSync -HandleUnlinkable --> ContinueSync -HandleGapError --> CheckRecovery["Check recovery:
- Peer switching
- Range adjustment
- Wait and retry"] -CheckRecovery --> ContinueSync -ContinueSync --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:173-204](file://plugins/p2p/p2p_plugin.cpp#L173-L204) - -### Peer Soft-Ban Management with Gap Detection - -**New** The plugin manages peer soft-bans based on error severity with gap detection awareness and improved peer interaction: - -```mermaid -flowchart TD -Start([Peer Action]) --> CheckAction{"Action type?"} -CheckAction --> |Successful| DecreasePenalty["Decrease peer penalty"] -CheckAction --> |Minor Error| MaintainPenalty["Maintain current penalty"] -CheckAction --> |Major Error| IncreasePenalty["Increase penalty:
- Hard fork error
- Gap-related error
- Invalid block
- Sync spam detection
in gray ANSI color"] -CheckAction --> |Peer Disconnect| ResetPenalty["Reset penalty:
- Peer disconnected
- Handshake failed
- Rejected"] -IncreasePenalty --> CheckThreshold{"Penalty threshold exceeded?"} -CheckThreshold --> |No| Continue["Continue with current peer"] -CheckThreshold --> |Yes| CheckTrusted["Check if trusted peer:
- Trusted snapshot peer
- Reduced soft-ban duration"] -CheckTrusted --> |Trusted| ApplyReducedBan["Apply reduced soft-ban:
- 5 min instead of 1 hour
- For trusted peers only
in orange ANSI color"] -CheckTrusted --> |Not Trusted| RemovePeer["Remove peer:
- Add to banned list
- Clear from potential peers
- Log removal
- Gap detection context
in gray ANSI color"] -ApplyReducedBan --> FindAlternative["Find alternative peer:
- Check potential peers
- Consider gap compatibility
- Attempt reconnection
in gray ANSI color"] -RemovePeer --> FindAlternative -FindAlternative --> Continue -DecreasePenalty --> Continue -MaintainPenalty --> Continue -ResetPenalty --> Continue -Continue --> End([End]) -``` - -**Diagram sources** -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) - -**Section sources** -- [p2p_plugin.cpp:371-405](file://plugins/p2p/p2p_plugin.cpp#L371-L405) -- [p2p_plugin.cpp:173-204](file://plugins/p2p/p2p_plugin.cpp#L173-L204) -- [p2p_plugin.cpp:614-650](file://plugins/p2p/p2p_plugin.cpp#L614-L650) - -## Minority Fork Recovery - -**Updated** The minority fork recovery mechanism has been enhanced with improved peer interaction handling, comprehensive logging, and sophisticated gap detection throughout the recovery process. - -### Enhanced resync_from_lib() Method with Gap Detection - -The `resync_from_lib()` method now includes comprehensive logging, improved peer interaction, and gap detection: - -```mermaid -flowchart TD -Start([Minority Fork Detected]) --> CheckState{"Check LIB vs Head:
- LIB == 0?
- Head <= LIB?"} -CheckState --> |LIB == 0 or Head <= LIB| NoAction["No recovery needed:
- Already at/after LIB
- Log info message
in gray ANSI color"] -CheckState --> |Head > LIB| PopBlocks["Pop reversible blocks:
- While head > LIB
- db.pop_block()
- Clear pending
- Reset fork_db
- Log gap detection context
in gray ANSI color"] -PopBlocks --> RebuildForkDB["Re-seed fork DB:
- Fetch LIB block
- start_block(LIB_block)
- Log recovery step
- Check gap boundaries
in gray ANSI color"] -RebuildForkDB --> TriggerSync["Trigger P2P sync:
- sync_from(LIB_block_id)
- resync()
- Log sync initiation
- Include gap detection info
in gray ANSI color"] -TriggerSync --> ReconnectPeers["Reconnect to seed peers:
- add_node(seed)
- connect_to_endpoint(seed)
- Log peer switching
- Consider gap compatibility
in gray ANSI color"] -ReconnectPeers --> ResetTimer["Reset stale sync timer:
- _last_block_received_time = now
- Log timer reset
- Gap detection monitoring
in gray ANSI color"] -ResetTimer --> Complete([Recovery Complete]) -NoAction --> Complete -``` - -**Diagram sources** -- [p2p_plugin.cpp:992-1061](file://plugins/p2p/p2p_plugin.cpp#L992-L1061) - -### Enhanced Recovery Process Implementation with Gap Awareness - -The minority fork recovery process now includes several critical enhancements with gap detection: - -1. **State Analysis**: Improved comparison logic with comprehensive logging including gap detection context -2. **Block Popping**: Enhanced loop with proper error handling, logging, and gap boundary awareness -3. **Fork Database Reset**: Better error handling, state validation, and gap boundary detection -4. **Network Resynchronization**: Improved sync triggering with logging and gap-aware parameters -5. **Peer Reconnection**: Enhanced peer management with error handling and gap compatibility checking -6. **Timer Reset**: Proper timing management to prevent immediate re-trigger with gap monitoring - -### Integration with Validator Plugin and Gap Detection - -The minority fork recovery is triggered automatically by the Validator Plugin with enhanced logging and gap detection: - -```mermaid -sequenceDiagram -participant validator as Validator Plugin -participant P2P as P2P Plugin -participant Chain as Chain Database -participant Network as Network Layer -validator->>Chain : Check recent blocks -Chain-->>validator : Block validation results -validator->>validator : Analyze fork scenario
with gap detection -alt Minority fork detected -validator->>P2P : resync_from_lib() -Note over P2P : Enhanced logging with gap context
in gray ANSI color -P2P->>Chain : Pop blocks to LIB
with gap boundary awareness -P2P->>Chain : Reset fork database
including gap detection -P2P->>Network : Trigger sync from LIB
with gap-aware parameters -P2P->>Network : Reconnect to peers
considering gap compatibility -Note over P2P : Comprehensive recovery logging
with gap detection results
in gray ANSI color -end -``` - -**Diagram sources** -- [validator.cpp:540-552](file://plugins/validator/validator.cpp#L540-L552) -- [p2p_plugin.cpp:992-1061](file://plugins/p2p/p2p_plugin.cpp#L992-L1061) - -**Section sources** -- [p2p_plugin.cpp:992-1061](file://plugins/p2p/p2p_plugin.cpp#L992-L1061) -- [validator.cpp:540-552](file://plugins/validator/validator.cpp#L540-L552) - -## Enhanced Block Validation - -**Updated** The block validation process has been enhanced with operation guard protection to ensure concurrent access safety during critical validation operations with improved gap detection awareness. - -### Operation Guard Integration - -The enhanced block validation incorporates operation guards to prevent race conditions and ensure thread-safe access to shared blockchain state: - -```mermaid -flowchart TD -BlockReceived([Block Received]) --> ExtractWitness["Extract validator information"] -ExtractWitness --> AcquireGuard["Acquire operation guard"] -AcquireGuard --> VerifyWitness["Verify validator exists"] -VerifyWitness --> VerifySignature["Verify signature matches validator key"] -VerifySignature --> ReleaseGuard["Release operation guard"] -ReleaseGuard --> ApplyValidation["Apply block post-validation"] -ApplyValidation --> BroadcastBlock["Broadcast block to peers"] -``` - -**Diagram sources** -- [p2p_plugin.cpp:216-245](file://plugins/p2p/p2p_plugin.cpp#L216-L245) - -### Concurrent Access Protection with Gap Detection - -The operation guard system provides several layers of protection with gap detection awareness: - -1. **Resize Barrier Participation**: Operation guards participate in the shared memory resize barrier -2. **Lock Acquisition**: Automatically waits for resize operations to complete -3. **Thread Safety**: Prevents concurrent access conflicts during validator key validation -4. **Resource Management**: Ensures proper cleanup and release of resources -5. **Gap Detection Integration**: Operation guards work with gap detection mechanisms - -### Database Integration - -The enhanced validation leverages the chainbase database's operation guard functionality: - -```mermaid -classDiagram -class operation_guard { -+operation_guard(database& db) -+~operation_guard() -+release() -- database& _db -- bool _active -} -class database { -+make_operation_guard() operation_guard -+enter_operation() -+exit_operation() -} -operation_guard --> database : "guards access to" -``` - -**Diagram sources** -- [chainbase.hpp:1078-1115](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1115) - -**Section sources** -- [p2p_plugin.cpp:216-245](file://plugins/p2p/p2p_plugin.cpp#L216-L245) -- [chainbase.hpp:1078-1115](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1078-L1115) - -## Concurrent Access Safety - -**New** The P2P plugin now includes comprehensive concurrent access safety mechanisms to prevent data corruption and ensure thread-safe operations during high-load conditions with gap detection integration. - -### Operation Guard Implementation - -The operation guard system provides automatic protection against concurrent access conflicts: - -```mermaid -flowchart TD -Start([Operation Begins]) --> EnterOperation["Enter operation barrier"] -EnterOperation --> AcquireLock["Acquire database lock"] -AcquireLock --> PerformOperation["Perform database operation"] -PerformOperation --> ReleaseLock["Release database lock"] -ReleaseLock --> ExitOperation["Exit operation barrier"] -ExitOperation --> End([Operation Complete]) -``` - -**Diagram sources** -- [chainbase.hpp:1130-1137](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1130-L1137) - -### Thread Safety Enhancements with Gap Detection - -The concurrent access safety includes several key features with gap detection integration: - -1. **Automatic Lock Management**: Operation guards automatically manage database locks -2. **Resize Barrier Integration**: Participates in shared memory resize barriers -3. **Timeout Handling**: Implements timeout mechanisms for lock acquisition -4. **Resource Cleanup**: Ensures proper cleanup of resources on completion -5. **Gap Detection Integration**: Operation guards work seamlessly with gap detection mechanisms - -### Error Handling Improvements - -Enhanced error handling protects against various failure scenarios with gap detection: - -1. **Concurrent Resize Exceptions**: Proper handling of shared memory resize operations -2. **Deadlock Prevention**: Timeout mechanisms prevent indefinite blocking -3. **Graceful Degradation**: Fallback mechanisms for critical operations -4. **Diagnostic Information**: Comprehensive logging for debugging concurrent issues -5. **Gap Detection Logging**: Enhanced logging for concurrent gap detection scenarios - -**Section sources** -- [chainbase.hpp:1130-1137](file://thirdparty/chainbase/include/chainbase/chainbase.hpp#L1130-L1137) -- [p2p_plugin.cpp:173-208](file://plugins/p2p/p2p_plugin.cpp#L173-L208) - -## Logging Level Consistency - -**Updated** The P2P plugin has implemented improved logging level consistency to reduce verbosity during normal operation while maintaining appropriate log levels for different operational contexts with enhanced gap detection logging. - -### Sync Mode Logging Improvements - -**Updated** The plugin has undergone significant improvements in logging level management, particularly for synchronization operations with gap detection awareness: - -- **Sync Mode Downgrade**: Sync mode block processing logs were downgraded from info level to debug level -- **Normal Mode Preservation**: Normal block processing continues to use info level logging for visibility -- **Reduced Verbosity**: This change significantly reduces log volume during routine blockchain synchronization -- **Contextual Appropriateness**: Debug level logging is more appropriate for frequent sync operations while preserving info level for exceptional events -- **Gap Detection Logging**: Enhanced gap detection logs use appropriate levels for troubleshooting - -### Conditional Latency Logging Implementation - -**New** The conditional latency logging ensures that latency information is only displayed for successful block processing in non-sync mode: - -```cpp -if (!sync_mode && result) { - fc::microseconds latency = fc::time_point::now() - blk_msg.block.timestamp; - ilog(CLOG_WHITE "Got ${t} transactions on block ${b} by ${w} -- latency: ${l} ms" CLOG_RESET, - ("t", blk_msg.block.transactions.size())("b", blk_msg.block.block_num())("w", blk_msg.block.validator)("l", latency.count() / 1000)); -} -``` - -**Key Benefits:** -- **Reduced Log Volume**: Sync operations (which occur frequently during blockchain synchronization) now use debug level logging -- **Maintained Visibility**: Normal operations continue to use info level logging for operational visibility -- **Consistent Behavior**: Both sync and normal modes now consistently use debug level logging, improving overall logging consistency -- **Performance Impact**: Lower logging overhead during normal operation while preserving diagnostic information -- **Gap Detection Visibility**: Gap detection logs provide appropriate visibility for troubleshooting - -### Network Layer Integration - -The network layer maintains mixed logging levels for different operational contexts with gap detection awareness: - -- **Info Level**: Used for significant operational events and peer management actions -- **Debug Level**: Used for routine synchronization and connection maintenance -- **Warning/Error Levels**: Used for error conditions and exceptional circumstances -- **Gap Detection Levels**: Specialized logging for gap-related operations and recovery - -**Section sources** -- [p2p_plugin.cpp:151-156](file://plugins/p2p/p2p_plugin.cpp#L151-L156) -- [p2p_plugin.cpp:168-172](file://plugins/p2p/p2p_plugin.cpp#L168-L172) - -## Atomic Block Processing Flags and Two-Phase Resume - -Block processing pause/resume is now fully thread-safe using `std::atomic` flags in `dlt_p2p_node`, allowing the snapshot plugin to call `resume_block_processing()` from any thread without dispatching to the P2P thread. - -### Atomic Flag Design - -Three relevant flags are now atomic: -- `_block_processing_paused` — set by the P2P thread during a snapshot-triggered pause. -- `snapshot_in_progress` (conceptually cleared by the caller via a guard) — controls whether new blocks are accepted. -- `_catchup_after_pause` — tells the production gate that we just resumed and are catching up. - -**Critical ordering**: `_catchup_after_pause` must be set to `true` **before** `snapshot_in_progress` is cleared. If the order were reversed, the Validator Plugin could observe the snapshot complete but the catchup flag unset, mis-classify the head as a stale fork, and refuse to produce. - -This ordering is enforced in `p2p_plugin::resume_block_processing()` via `dlt_p2p_node::set_resume_flags()` (sets both atomics) followed by an async post for the P2P-thread-only drain. - -**Section sources** -- [p2p_plugin.cpp:692-723](file://plugins/p2p/p2p_plugin.cpp#L692-L723) -- [dlt_p2p_node.cpp](file://libraries/network/dlt_p2p_node.cpp) - -### Two-Phase Resume Implementation - -`resume_block_processing()` is split into two phases to avoid a deadlock that existed when calling `async().wait()` while holding a read lock: - -1. **Phase 1 (immediate, any thread)**: `set_resume_flags()` atomically sets `_catchup_after_pause = true` and clears `_block_processing_paused`. Because both are `std::atomic`, no P2P thread dispatch is required. -2. **Phase 2 (async, P2P thread)**: An async post to the P2P thread calls `run_resume_on_p2p_thread()` which logs the resume and drains the `_paused_block_queue`. No `.wait()` is used; the caller proceeds immediately after Phase 1. - -**Prior deadlock**: The old implementation dispatched Phase 1 via `async().wait()` while a read lock on the database was held. A second P2P fiber that had already passed the `_block_processing_paused` check would call `push_block()`, which blocked on the write lock (waiting for our read lock). The OS thread froze, the posted fiber never ran — deadlock. - -**Section sources** -- [p2p_plugin.cpp:708-722](file://plugins/p2p/p2p_plugin.cpp#L708-L722) - -### Shared Memory Corruption in Block Acceptance - -When `dlt_p2p_node` receives a block from the network and calls `push_block()`, it now catches `shared_memory_corruption_exception` separately from other exceptions. On this exception class the node triggers the same auto-recovery path as the chain plugin (reopen with replay), rather than simply soft-banning the peer that delivered the block. - -**Section sources** -- [p2p_plugin.cpp:692-706](file://plugins/p2p/p2p_plugin.cpp#L692-L706) -- [dlt_p2p_node.cpp](file://libraries/network/dlt_p2p_node.cpp) - -### Clear Syncing Flag on SYNC→FORWARD Transition - -`dlt_p2p_node` now explicitly clears the `_dlt_syncing` flag when transitioning from SYNC state to FORWARD state. Previously the flag could remain set after the sync completed, causing the Validator Plugin to see `chain().is_syncing() == true` indefinitely and refuse to produce blocks. - -**Section sources** -- [dlt_p2p_node.cpp](file://libraries/network/dlt_p2p_node.cpp) - -## DLT Status Log Uptime Field - -The periodic DLT Status log line now includes a node uptime field so operators can immediately see how long the node has been running without checking separate tooling. - -**Format**: `uptime=${uh}h${um}m${us}s` appended after the peer counts field. - -**Example**: `DLT Status | FORWARD | head=#1234567 lib=#1234500 | dlt_range=1000000-1234567 | peers=8active/10conn | uptime=2h15m43s | ...` - -**Implementation**: `_node_start_time` is recorded in the `dlt_p2p_node` constructor. Both the regular status log and the detailed stats log compute elapsed time as `(fc::time_point::now() - _node_start_time).count() / 1000000` seconds. - -**Section sources** -- [dlt_p2p_node.cpp:3126-3133](file://libraries/network/dlt_p2p_node.cpp#L3126-L3133) -- [dlt_p2p_node.cpp:3163-3169](file://libraries/network/dlt_p2p_node.cpp#L3163-L3169) - -## Dependency Analysis - -The P2P plugin has well-defined dependencies that enable modularity and maintainability with enhanced gap detection integration: - -```mermaid -graph TB -subgraph "Plugin Dependencies" -P2P[p2p_plugin] -Chain[chain::plugin] -AppBase[appbase] -Snapshot[snapshot_plugin] -validator[witness_plugin] -end -subgraph "Network Dependencies" -Node[node.hpp] -PeerConn[peer_connection.hpp] -Messages[core_messages.hpp] -PeerDB[peer_database.hpp] -Config[config.hpp] -end -subgraph "Protocol Dependencies" -Block[block.hpp] -Transaction[transaction.hpp] -Types[types.hpp] -end -subgraph "Database Dependencies" -Database[database.hpp] -Chainbase[chainbase.hpp] -OperationGuard[operation_guard] -DLTLog[dlt_block_log] -end -P2P --> Chain -P2P --> Node -P2P --> AppBase -P2P --> Snapshot -P2P --> validator -Node --> PeerConn -Node --> Messages -Node --> PeerDB -Node --> Config -Messages --> Block -Messages --> Transaction -Messages --> Types -Chain --> Database -Chain --> Chainbase -Chain --> OperationGuard -Chain --> DLTLog -Database --> OperationGuard -Database --> DLTLog -``` - -**Diagram sources** -- [CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) -- [p2p_plugin.cpp:1-13](file://plugins/p2p/p2p_plugin.cpp#L1-L13) - -Key dependency relationships: - -1. **Chain Integration**: Direct dependency on the chain plugin for blockchain state access with gap detection -2. **Network Foundation**: Relies on the network library for peer communication with gap-aware protocols -3. **Application Framework**: Uses appbase for plugin lifecycle management -4. **Snapshot Coordination**: Integrates with snapshot plugin for trusted peer management with gap detection -5. **validator Integration**: Works closely with Validator Plugin for fork detection and gap monitoring -6. **Database Protection**: Leverages chainbase operation guards for concurrent access safety with gap detection -7. **DLT Mode Support**: Integrates with dlt_block_log for snapshot-based block serving with sophisticated gap detection -8. **Gap Detection**: Enhanced integration with gap detection mechanisms throughout the plugin stack -9. **Windows Compatibility**: Integration with DLT block log operations for Windows platform support - -**Section sources** -- [CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) -- [p2p_plugin.cpp:1-13](file://plugins/p2p/p2p_plugin.cpp#L1-L13) - -## Performance Considerations - -The P2P plugin implements several performance optimization strategies with enhanced gap detection efficiency: - -### Connection Management -- **Connection Limits**: Configurable maximum connections to prevent resource exhaustion -- **Soft-Ban Mechanisms**: Automatic peer banning for misbehaving nodes with gap detection awareness -- **Trusted Peer System**: Reduced soft-ban duration for snapshot-provided trusted peers - -### Network Efficiency -- **Selective Synchronization**: Only fetches missing blockchain data with gap-aware range limiting -- **Message Caching**: Prevents redundant message propagation -- **Bandwidth Throttling**: Configurable upload/download limits - -### Monitoring and Diagnostics -- **Periodic Statistics**: Configurable logging intervals for peer statistics with gap detection -- **Stale Sync Detection**: Automatic recovery from stalled synchronization with gap monitoring -- **Connection Health Monitoring**: Real-time peer connection quality metrics with gap awareness - -### DLT Mode Performance with Gap Detection -**New** The DLT mode introduces several performance optimizations with gap detection: - -- **Intelligent Block Range Clamping**: Prevents requesting unavailable blocks with sophisticated gap detection -- **Early Availability Checking**: Reduces network requests for unavailable items with gap awareness -- **Optimized Peer Selection**: Better handling of DLT-capable peers with gap compatibility -- **Reduced Error Handling Overhead**: Graceful degradation minimizes performance impact with gap detection -- **Gap-Aware Recovery**: Automatic recovery mechanisms minimize performance impact during gap scenarios -- **Enhanced Peer Ahead Detection**: Sophisticated detection reduces unnecessary sync attempts -- **Startup Diagnostics Efficiency**: Optimized startup diagnostics minimize sync delay -- **Windows Compatibility Optimization**: Enhanced compatibility reduces performance impact - -### Logging Performance Impact -**Updated** The improved logging level consistency and conditional latency logging provide additional performance benefits: - -- **Reduced I/O Overhead**: Debug level logging produces less output than info level logging -- **Lower Memory Usage**: Reduced log buffer consumption during sync operations -- **Improved Throughput**: Less frequent logging reduces CPU overhead during normal operation -- **Better Resource Utilization**: More efficient use of system resources during routine operations -- **Conditional Latency Reporting**: Latency logging only occurs for successful operations, reducing unnecessary processing -- **Gap Detection Efficiency**: Optimized logging for gap detection scenarios - -### Concurrent Access Optimization -**New** The operation guard system provides performance benefits through gap detection integration: - -- **Reduced Contention**: Automatic lock management reduces thread contention -- **Efficient Resource Usage**: Operation guards minimize overhead during validation -- **Scalable Design**: Thread-safe operations scale better under load with gap detection -- **Graceful Degradation**: Timeout mechanisms prevent performance degradation -- **Gap Detection Optimization**: Integrated gap detection reduces unnecessary operations - -### DLT Storage Diagnostics Performance -**New** The enhanced DLT storage diagnostics provide performance monitoring with minimal overhead: - -- **Periodic Execution**: Diagnostics run at configurable intervals to balance accuracy and performance -- **Efficient Gap Detection**: Optimized algorithms for detecting DLT coverage gaps -- **Minimal I/O Impact**: Storage diagnostics use efficient queries to minimize disk access -- **Background Processing**: Diagnostics run in background threads to avoid blocking main operations -- **Startup Diagnostics Optimization**: Efficient startup diagnostics minimize sync delay - -### DLT Integrity Scanning Performance -**New** The periodic DLT integrity scanning provides comprehensive monitoring with performance considerations: - -- **Selective Scanning**: Continuity verification runs only when DLT mode is active -- **Efficient Gap Detection**: verify_continuity() algorithm optimized for performance -- **Limited Scope**: Scans only when gaps are detected, minimizing overhead -- **Background Execution**: Integrity scans run in background without impacting main operations -- **Windows Compatibility Optimization**: Enhanced compatibility reduces performance impact - -### Windows Compatibility Performance -**New** The Windows compatibility enhancements provide performance benefits: - -- **Stale Mapping Detection**: Efficient detection and healing of stale mappings -- **Logical Size Tracking**: Optimized tracking of file sizes to avoid performance issues -- **Memory-Mapped File Optimization**: Efficient handling of memory-mapped files on Windows -- **Resize Count Monitoring**: Minimal overhead for tracking resize operations - -**Section sources** -- [p2p_plugin.cpp:701-765](file://plugins/p2p/p2p_plugin.cpp#L701-L765) -- [p2p_plugin.cpp:596-699](file://plugins/p2p/p2p_plugin.cpp#L596-L699) -- [dlt_block_log.cpp:576-602](file://libraries/chain/dlt_block_log.cpp#L576-L602) - -## Troubleshooting Guide - -### Common Issues and Solutions - -#### Connection Problems -- **Symptom**: Unable to connect to seed nodes -- **Solution**: Verify network connectivity and check firewall settings -- **Configuration**: Review `p2p-seed-node` entries in configuration file - -#### Synchronization Delays -- **Symptom**: Slow blockchain synchronization -- **Solution**: Increase `p2p-max-connections` setting -- **Monitoring**: Enable P2P statistics to identify slow peers with gap detection awareness - -#### Peer Quality Issues -- **Symptom**: Frequent peer disconnections -- **Solution**: Check network stability and bandwidth limitations -- **Diagnostics**: Monitor peer statistics for connection patterns with gap-related errors - -### DLT Mode Troubleshooting with Gap Detection - -**New** For DLT mode-specific issues with gap detection: - -1. **Block Availability Errors**: Check `earliest_available_block_num()` and DLT log bounds with gap detection -2. **Peer Compatibility**: Verify peers support DLT mode block serving with gap awareness -3. **Recovery Actions**: Monitor graceful degradation logs for peer soft-bans with gap detection -4. **Sync Performance**: Use DLT-specific logging to identify block range issues with gap information -5. **Gap Detection**: Monitor gap detection logs for storage boundary issues -6. **Peer Ahead Detection**: Verify peer ahead detection is working correctly in DLT mode -7. **Startup Diagnostics**: Review startup diagnostics for gap and integrity issues - -### Minor Fork Recovery Procedures - -**Updated** For minority fork scenarios with gap detection: - -1. **Detection**: Monitor Validator Plugin logs for minority fork warnings with gap context -2. **Automatic Recovery**: The system automatically triggers `resync_from_lib()` with gap detection -3. **Manual Intervention**: Use RPC commands to trigger recovery if automatic detection fails -4. **Verification**: Monitor logs to confirm successful recovery and synchronization with gap awareness - -### Enhanced Peer Database Analysis - -**New** Use the enhanced peer database logging for troubleshooting with gap detection: - -1. **Failed Peer Analysis**: Review logs for failed/rejected peer status with gap-related errors -2. **Connection Attempts**: Monitor last connection attempt times and reasons with gap context -3. **Error Patterns**: Identify recurring error patterns across multiple peers with gap detection -4. **Recovery Effectiveness**: Track peer reconnection success rates with gap-aware metrics - -### Gap Detection Troubleshooting - -**New** Specific gap detection troubleshooting procedures: - -1. **Gap Detection Logs**: Review gap detection logs for storage boundary issues -2. **Clamping Operations**: Monitor clamping operations for proper gap handling -3. **Peer Compatibility**: Check peer compatibility with gap detection mechanisms -4. **Recovery Actions**: Verify automatic recovery actions for gap-related issues -5. **Peer Ahead Detection**: Verify peer ahead detection is working correctly - -### Conditional Latency Logging Troubleshooting - -**New** For conditional latency logging issues: - -1. **Latency Not Displayed**: Verify that blocks are being processed successfully in non-sync mode -2. **Log Volume**: Check that sync mode operations are not generating excessive latency logs -3. **Performance Impact**: Monitor system performance to ensure conditional logging is not causing overhead -4. **Color Coding**: Verify that latency logs are displayed in white color with proper ANSI formatting - -### ANSI Color Code Troubleshooting - -**New** For ANSI color code-related issues: - -1. **Console Compatibility**: Ensure terminal supports ANSI color codes -2. **Color Output Testing**: Test color output in different terminal environments -3. **Log Filtering**: Use log filtering to isolate specific color-coded log categories -4. **Operator Training**: Train operators to recognize different color-coded log categories - -### DLT Storage Diagnostics Troubleshooting - -**New** For DLT storage diagnostics issues: - -1. **Coverage Gaps**: Monitor DLT coverage gap warnings and investigate storage boundaries -2. **Integrity Verification**: Review DLT integrity warnings and investigate block log continuity -3. **Mapping Consistency**: Check DLT mapping verification results and address stale mappings -4. **Storage Performance**: Monitor storage diagnostics for optimal performance tuning -5. **Startup Diagnostics**: Review startup diagnostics for gap and integrity issues - -### Automatic Peer Soft-Banning Troubleshooting - -**New** For automatic peer soft-banning issues: - -1. **Soft-Ban Duration**: Verify soft-ban duration for trusted vs non-trusted peers -2. **Penalty Threshold**: Check penalty threshold calculations and enforcement -3. **Peer Recovery**: Monitor peer recovery mechanisms and automatic unbanning -4. **Sync Spam Detection**: Investigate sync spam detection and peer behavior analysis -5. **Gap Detection Impact**: Verify gap detection is not affecting soft-ban decisions - -### DLT Integrity Scanning Troubleshooting - -**New** For DLT integrity scanning issues: - -1. **Integrity Warnings**: Review DLT integrity warnings for gap detection and recovery -2. **Mapping Issues**: Investigate stale mapping detection and healing -3. **Performance Impact**: Monitor integrity scanning performance and adjust frequency -4. **Gap Reporting**: Verify gap reporting accuracy and completeness -5. **Windows Compatibility**: Verify Windows compatibility enhancements are working correctly - -### Enhanced Peer Ahead Detection Troubleshooting - -**New** For peer ahead detection issues: - -1. **Detection Accuracy**: Verify peer ahead detection is working correctly in DLT mode -2. **Empty Response**: Check that empty responses are returned for ahead peers -3. **Sync Performance**: Monitor sync performance with peer ahead detection enabled -4. **Logging**: Verify peer ahead detection logging is working correctly - -### Startup Diagnostics Troubleshooting - -**New** For startup diagnostics issues: - -1. **Diagnostics Timing**: Verify startup diagnostics run before synchronization begins -2. **Gap Detection**: Check startup gap detection and logging -3. **Integrity Scanning**: Verify startup integrity scanning is working correctly -4. **Performance Impact**: Monitor startup diagnostics performance impact - -### Windows Compatibility Troubleshooting - -**New** For Windows compatibility issues: - -1. **Stale Mapping Detection**: Verify stale mapping detection and healing are working -2. **File Operations**: Check Windows-specific file operations are working correctly -3. **Memory-Mapped Files**: Verify memory-mapped file operations on Windows -4. **Performance Impact**: Monitor Windows compatibility performance impact - -### Configuration Reference - -The P2P plugin supports extensive configuration options: - -| Configuration Option | Description | Default Value | -|---------------------|-------------|---------------| -| `p2p-endpoint` | Local IP address and port for incoming connections | 127.0.0.1:9876 | -| `p2p-max-connections` | Maximum incoming connections | 0 (unlimited) | -| `p2p-seed-node` | Seed node endpoints | None | -| `p2p-stats-enabled` | Enable peer statistics logging | true | -| `p2p-stats-interval` | Statistics logging interval (seconds) | 300 | -| `p2p-stale-sync-detection` | Enable stale sync detection | false | -| `p2p-stale-sync-timeout-seconds` | Stale sync timeout | 120 | - -### Concurrent Access Issues - -**New** For concurrent access problems with gap detection: - -1. **Monitor Operation Guards**: Check for operation guard timeouts in logs with gap context -2. **Check Shared Memory**: Verify shared memory resize operations are completing -3. **Adjust Timeouts**: Increase operation guard timeout values if needed -4. **Resource Monitoring**: Monitor system resources during high-load periods with gap detection -5. **Gap Detection Monitoring**: Monitor gap detection operations for performance impact - -### Color Coding Issues - -**New** For color coding problems: - -1. **Terminal Compatibility**: Ensure terminal supports ANSI color codes -2. **Color Output Verification**: Test color output in different environments -3. **Log Filtering**: Use log filtering to examine color-coded categories -4. **Operator Training**: Train staff to interpret color-coded log messages - -**Section sources** -- [p2p_plugin.cpp:701-765](file://plugins/p2p/p2p_plugin.cpp#L701-L765) -- [p2p_plugin.cpp:992-1061](file://plugins/p2p/p2p_plugin.cpp#L992-L1061) -- [config.ini:1-143](file://share/vizd/config/config.ini#L1-L143) - -## Conclusion - -The P2P Plugin represents a sophisticated implementation of blockchain networking infrastructure that provides essential functionality for distributed consensus systems. Its modular architecture, comprehensive peer management, and robust synchronization protocols make it a cornerstone component of the VIZ blockchain ecosystem. - -**Updated** Key enhancements include: - -1. **Security Focus**: Advanced block validation and validator verification mechanisms with gap detection -2. **Performance Optimization**: Efficient synchronization and connection management with gap-aware optimizations -3. **Operational Excellence**: Comprehensive monitoring and diagnostic capabilities with gap detection -4. **Extensibility**: Clean interfaces that support future enhancements with gap detection integration -5. **Enhanced Logging**: Improved logging level consistency with reduced verbosity while maintaining operational visibility -6. **Minority Fork Recovery**: Specialized recovery mechanism for handling fork scenarios with gap awareness -7. **Concurrent Access Safety**: Enhanced protection against race conditions and data corruption with gap detection -8. **Integration Capabilities**: Seamless coordination with validator and snapshot plugins with gap detection -9. **DLT Mode Support**: Intelligent block range management for snapshot-based nodes with sophisticated gap detection -10. **Graceful Degradation**: Robust error handling and peer interaction management with gap-aware recovery -11. **Enhanced Diagnostics**: Comprehensive logging throughout the sync process with gap detection -12. **Peer Database Analytics**: Detailed peer interaction tracking and troubleshooting with gap awareness -13. **ANSI Color Code Implementation**: Strategic use of color codes (white, cyan, gray, orange, red) for improved console readability and visual distinction -14. **Conditional Latency Logging**: Smart latency reporting that only displays successful block processing information in non-sync mode -15. **Enhanced Block Processing Visibility**: Improved visibility into block processing with detailed transaction and validator information -16. **DLT Storage Diagnostics**: Comprehensive block storage monitoring with gap detection and coverage analysis -17. **Automatic Peer Soft-Banning**: Intelligent peer management with automatic soft-banning for sync spam and improved peer interaction -18. **DLT Integrity Verification**: Periodic verification of DLT block log integrity with gap detection and continuity scanning -19. **Comprehensive Gap Detection**: Advanced gap detection reporting with detailed coverage gap monitoring -20. **Enhanced Peer Ahead-of-Us Detection**: Sophisticated detection of peers ahead of the local node in DLT mode -21. **Startup Block Storage Diagnostics**: Comprehensive diagnostics system that runs before synchronization begins -22. **Windows Compatibility Enhancements**: Enhanced compatibility with Windows operating systems for DLT block log operations - -The recent additions demonstrate ongoing attention to operational efficiency and user experience. The new DLT mode block range management with sophisticated gap detection provides intelligent support for snapshot-based nodes, while the enhanced peer interaction handling improves network resilience. The comprehensive logging throughout the sync process provides unprecedented visibility into network operations, and the graceful degradation capabilities ensure reliable operation even when peers cannot serve requested items. - -The plugin's design demonstrates best practices in distributed systems engineering, balancing security, performance, and maintainability while providing the foundation for scalable blockchain networks. The integration of DLT mode support, graceful degradation mechanisms, enhanced diagnostic capabilities, and sophisticated gap detection positions the P2P plugin to handle increasingly complex blockchain networking requirements with improved reliability and operability. - -The implementation of comprehensive ANSI color codes (white, cyan, gray, orange, red) further enhances the plugin's operational capabilities by providing visual distinction between different types of log messages, enabling operators to quickly identify and respond to different operational scenarios. The strategic use of color codes creates a clear visual hierarchy that improves troubleshooting efficiency and reduces operator workload during complex network operations. - -The conditional block processing latency logging ensures that operators receive timely feedback on successful block processing without being overwhelmed by log volume during sync operations. This balanced approach to logging provides the right amount of information at the right time, improving both operational efficiency and system performance. - -The enhanced peer database logging and DLT storage diagnostics provide unprecedented visibility into peer interactions and storage capabilities, enabling operators to quickly identify and resolve network issues. The automatic peer soft-banning system with gap detection awareness improves network resilience by intelligently managing problematic peers while maintaining service quality for legitimate users. - -The DLT integrity verification system provides continuous monitoring of data integrity, ensuring that snapshot-based nodes maintain reliable and consistent block storage. This comprehensive approach to diagnostics and monitoring positions the P2P plugin as a critical component in maintaining the health and reliability of the VIZ blockchain network. - -The enhanced peer ahead-of-us detection mechanism provides sophisticated peer management capabilities, preventing unnecessary sync attempts and improving overall network efficiency. The comprehensive startup diagnostics system ensures that potential issues are identified and resolved before synchronization begins, improving the reliability of new node deployments. - -The Windows compatibility enhancements ensure that the plugin operates reliably across different platforms, with special attention to DLT block log operations on Windows systems. This cross-platform compatibility is essential for the widespread deployment of VIZ blockchain nodes. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Plugin System/Plugin System.md b/.qoder/repowiki/en/content/Plugin System/Plugin System.md deleted file mode 100644 index 4bfc47dd4b..0000000000 --- a/.qoder/repowiki/en/content/Plugin System/Plugin System.md +++ /dev/null @@ -1,1152 +0,0 @@ -# Plugin System - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [documentation/plugin.md](file://documentation/plugin.md) -- [documentation/snapshot-plugin.md](file://documentation/snapshot-plugin.md) -- [plugins/CMakeLists.txt](file://plugins/CMakeLists.txt) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp) -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [plugins/p2p/CMakeLists.txt](file://plugins/p2p/CMakeLists.txt) -- [plugins/p2p/p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp) -- [plugins/snapshot/CMakeLists.txt](file://plugins/snapshot/CMakeLists.txt) -- [plugins/snapshot/plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [plugins/debug_node/plugin.cpp](file://plugins/debug_node/plugin.cpp) -- [libraries/protocol/include/graphene/protocol/operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [libraries/chain/chain_evaluator.cpp](file://libraries/chain/chain_evaluator.cpp) -- [libraries/chain/database.cpp](file://libraries/chain/database.cpp) -- [libraries/chain/dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [libraries/chain/include/graphene/chain/dlt_block_log.hpp](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp) -- [libraries/network/node.cpp](file://libraries/network/node.cpp) -- [libraries/network/peer_connection.hpp](file://libraries/network/peer_connection.hpp) -- [libraries/network/core_messages.hpp](file://libraries/network/core_messages.hpp) -- [thirdparty/fc/include/fc/network/ip.hpp](file://thirdparty/fc/include/fc/network/ip.hpp) -- [thirdparty/fc/src/network/ip.cpp](file://thirdparty/fc/src/network/ip.cpp) -- [programs/util/newplugin.py](file://programs/util/newplugin.py) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) - - -## Update Summary -**Changes Made** -- Enhanced P2P plugin integration with snapshot plugin through CMake linking and cross-plugin functionality -- Added automatic trusted peer endpoint registration from snapshot plugin to P2P layer -- Updated P2P plugin to utilize snapshot plugin's trusted peer management for reduced soft-ban duration -- Improved cross-plugin communication patterns between P2P and snapshot plugins - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [DLT Mode and Snapshot Integration](#dlt-mode-and-snapshot-integration) -7. [Enhanced P2P Plugin Integration with DLT Mode Awareness](#enhanced-p2p-plugin-integration-with-dlt-mode-awareness) -8. [Cross-Plugin Integration: P2P and Snapshot](#cross-plugin-integration-p2p-and-snapshot) -9. [Type Safety Improvements in IP Address Handling](#type-safety-improvements-in-ip-address-handling) -10. [Dependency Analysis](#dependency-analysis) -11. [Plugin Deprecation Status and Maintenance Practices](#plugin-deprecation-status-and-maintenance-practices) -12. [Performance Considerations](#performance-considerations) -13. [Troubleshooting Guide](#troubleshooting-guide) -14. [Conclusion](#conclusion) -15. [Appendices](#appendices) - -## Introduction -This document explains the VIZ C++ Node plugin system architecture, focusing on how the appbase-based framework enables modular functionality, the plugin lifecycle from registration to shutdown, and inter-plugin communication patterns. It documents the built-in plugin ecosystem (40+ plugins) ranging from core blockchain functions to specialized APIs and integrations, including the enhanced snapshot plugin capabilities for DLT (Distributed Ledger Technology) mode operations and integration with rolling block log features. - -**Updated** Enhanced with comprehensive DLT mode documentation covering snapshot-based node bootstrap, rolling block log integration, and P2P layer compatibility for distributed ledger deployments. The P2P plugin now includes sophisticated DLT mode awareness with improved error handling and logging capabilities for DLT mode block serving operations, featuring enhanced sync block processing visibility and better synchronization progress monitoring. Recent improvements include enhanced type safety in IP address handling to prevent implicit type conversion issues in peer management code. **Cross-plugin integration** now enables the P2P plugin to automatically register trusted peer endpoints from the snapshot plugin, improving peer trust management and reducing soft-ban durations for verified snapshot sources. - -## Project Structure -The plugin system is organized around the appbase application framework and a dedicated plugins directory. Each plugin is a self-contained module that registers APIs, subscribes to chain events, and optionally depends on other plugins. The top-level plugins build script enumerates subdirectories and exposes a runtime-accessible list of available plugins. Built-in plugins are located under libraries/plugins/, while external plugins can be added similarly. - -```mermaid -graph TB -A["AppBase Application
appbase::application"] --> B["JSON-RPC Plugin
json_rpc::plugin"] -B --> C["Chain Plugin
chain::plugin"] -B --> D["Webserver Plugin
webserver::plugin"] -B --> E["Database API Plugin
database_api::plugin"] -B --> F["Account History Plugin
account_history::plugin"] -B --> G["P2P Plugin
p2p::plugin"] -B --> H["MongoDB Plugin
mongo_db::plugin"] -B --> I["Snapshot Plugin
snapshot::plugin"] -C --> J["Chain Database
chainbase::database"] -I --> K["DLT Block Log
dlt_block_log"] -G --> L["DLT Mode Integration
P2P Layer"] -G --> M["Type Safety Enhancements
IP Address Handling"] -G --> N["Cross-Plugin Integration
Snapshot Trust Management"] -I --> O["Trusted Peer Management
Automatic Endpoint Registration"] -``` - -**Diagram sources** -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:84-118](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:32-57](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:179-403](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:59-97](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L97) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-52](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) -- [libraries/chain/include/graphene/chain/dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) -- [plugins/p2p/p2p_plugin.cpp:203-257](file://plugins/p2p/p2p_plugin.cpp#L203-L257) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -**Section sources** -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [documentation/plugin.md:1-28](file://documentation/plugin.md#L1-L28) - -## Core Components -- JSON-RPC Plugin: Central dispatcher for API method routing and request handling. It maintains a registry of API names and methods and delegates calls to registered APIs. -- Chain Plugin: Provides blockchain database access, block acceptance, transaction acceptance, and synchronization signals for other plugins. -- Webserver Plugin: Starts an HTTP/WebSocket server and dispatches JSON-RPC queries to registered handlers on the app's io_service thread. -- Database API Plugin: Exposes read-only database queries via JSON-RPC, including blocks, accounts, balances, and chain metadata. -- Account History Plugin: Tracks per-account operation histories and exposes retrieval APIs. -- P2P Plugin: Manages peer-to-peer networking, broadcasting blocks/transactions, and block production controls. **Enhanced** with DLT mode awareness, improved error handling, sophisticated logging for sync block processing, and **improved type safety** in IP address handling. **Enhanced** with cross-plugin integration for trusted peer management from the snapshot plugin. -- Mongo DB Plugin: Integrates with MongoDB for indexing and archival of chain data. -- **Snapshot Plugin**: Enables DLT (Distributed Ledger Technology) mode operations including snapshot creation, loading, P2P snapshot synchronization, and integration with rolling block logs. **Enhanced** with trusted peer endpoint management for cross-plugin integration. - -**Section sources** -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:84-118](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:32-57](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:179-403](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:59-97](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L97) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-52](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) - -## Architecture Overview -The plugin architecture follows appbase conventions: -- Plugins derive from appbase::plugin and declare dependencies via APPBASE_PLUGIN_REQUIRES. -- Plugins register APIs during startup and expose methods via JSON-RPC. -- Inter-plugin communication occurs through: - - Shared application services (e.g., chain database). - - Signals/slots (Boost.Signals2) for event-driven coordination. - - Explicit API calls across plugin boundaries. - - **Enhanced cross-plugin integration** through direct plugin access and shared configuration. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant WS as "Webserver Plugin" -participant RPC as "JSON-RPC Plugin" -participant DBAPI as "Database API Plugin" -participant CHAIN as "Chain Plugin" -participant SNAP as "Snapshot Plugin" -participant P2P as "P2P Plugin" -Client->>WS : "HTTP/WS JSON-RPC request" -WS->>RPC : "Dispatch body" -RPC->>DBAPI : "Resolve API name and method" -DBAPI->>CHAIN : "Read chain state" -CHAIN-->>DBAPI : "Database objects" -DBAPI-->>RPC : "Response variant" -RPC-->>WS : "Formatted JSON-RPC response" -WS-->>Client : "HTTP/WS response" -Note over SNAP,P2P : "Cross-plugin integration :
P2P registers trusted peers
from snapshot plugin" -Note over P2P,CHAIN : "DLT mode : Snapshot plugin manages state
and integrates with rolling block logs" -``` - -**Diagram sources** -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:32-57](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:109-113](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L109-L113) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:179-403](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:88-91](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:60-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L60-L76) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -## Detailed Component Analysis - -### JSON-RPC Plugin -- Role: Registers API method bindings and routes incoming JSON-RPC requests to the appropriate plugin API. -- Key behaviors: - - Maintains a registry of API names and methods. - - Dispatches calls using a visitor pattern that binds method pointers to variants. - - Supports error codes standardized for JSON-RPC. - -```mermaid -classDiagram -class JsonRpcPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+add_api_method(api_name, method_name, api) -+call(body, response_handler) -} -``` - -**Diagram sources** -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:84-118](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) - -**Section sources** -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:38-55](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L38-L55) -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:109-113](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L109-L113) - -### Chain Plugin -- Role: Core blockchain engine exposing database accessors, block/transaction acceptance, and synchronization signals. -- Lifecycle hooks: initialize, startup, shutdown. -- Public API surface includes helpers for indices and objects, plus a synchronization signal for dependent plugins. - -```mermaid -classDiagram -class ChainPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+accept_block(block, currently_syncing, skip) -+accept_transaction(trx) -+db() Database -+on_sync signal -} -``` - -**Diagram sources** -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-96](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L96) - -**Section sources** -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:36-42](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L36-L42) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:88-91](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) - -### Webserver Plugin -- Role: Starts an HTTP/WebSocket server and dispatches JSON-RPC queries to registered handlers on the application io_service thread. -- Dependencies: Requires JSON-RPC plugin. - -```mermaid -classDiagram -class WebserverPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -} -``` - -**Diagram sources** -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:32-57](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) - -**Section sources** -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:19-31](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L19-L31) - -### Database API Plugin -- Role: Exposes read-only database queries via JSON-RPC, including blocks, accounts, balances, and chain metadata. -- Dependencies: Requires JSON-RPC and Chain plugins. -- API coverage includes block retrieval, dynamic/global properties, account queries, and more. - -```mermaid -classDiagram -class DatabaseApiPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+DECLARE_API(...) -} -``` - -**Diagram sources** -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:179-403](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) - -**Section sources** -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:188-191](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:227-398](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L227-L398) - -### Account History Plugin -- Role: Tracks per-account operation histories and exposes retrieval APIs. -- Dependencies: Requires JSON-RPC, Chain, and Operation History plugins. - -```mermaid -classDiagram -class AccountHistoryPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+DECLARE_API(get_account_history) -} -``` - -**Diagram sources** -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:59-97](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L97) - -**Section sources** -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:61-65](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L61-L65) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:83-92](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L83-L92) - -### P2P Plugin -- Role: Manages peer-to-peer networking, broadcasting blocks/transactions, and block production controls. -- Dependencies: Requires Chain plugin. -- **Maintenance Note**: Uses deprecated configuration options with warnings for migration. -- **DLT Integration**: Automatically falls back to DLT block log when serving blocks not found in main block log. -- **Enhanced Error Handling**: Improved logging and error handling specifically for DLT mode scenarios with sophisticated debug logging capabilities. -- **Type Safety Improvements**: Enhanced IP address handling with explicit type conversion using static_cast() to prevent implicit type conversion issues in peer management code. -- **Cross-Plugin Integration**: **Enhanced** with automatic trusted peer endpoint registration from the snapshot plugin for improved peer trust management. - -```mermaid -classDiagram -class P2PPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+broadcast_block(block) -+broadcast_transaction(trx) -+set_block_production(flag) -+get_item(item_id) -+get_block_ids(synopsis, remaining, limit) -+register_trusted_snapshot_peers() -} -``` - -**Diagram sources** -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-52](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L52) -- [plugins/p2p/p2p_plugin.cpp:259-277](file://plugins/p2p/p2p_plugin.cpp#L259-L277) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) - -**Section sources** -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:20-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L20) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:40-49](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L40-L49) -- [plugins/p2p/p2p_plugin.cpp:259-277](file://plugins/p2p/p2p_plugin.cpp#L259-L277) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) - -### Mongo DB Plugin -- Role: Integrates with MongoDB for indexing and archival of chain data. -- Dependencies: Requires Chain plugin. - -```mermaid -classDiagram -class MongoDbPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -} -``` - -**Diagram sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:14-47](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L47) - -**Section sources** -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:17-19](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L17-L19) - -### Snapshot Plugin -- Role: Enables DLT (Distributed Ledger Technology) mode operations including snapshot creation, loading, P2P snapshot synchronization, and integration with rolling block logs. -- Dependencies: Requires Chain plugin. -- Key capabilities: - - Creates compressed state snapshots at specific blocks or periodically - - Loads state from snapshot files for fast node bootstrap - - Serves snapshots to other nodes via TCP protocol - - Manages automatic snapshot cleanup and rotation - - Integrates with DLT mode for distributed ledger deployments - - **Enhanced** with trusted peer endpoint management for cross-plugin integration - - **Enhanced** with trusted snapshot peer configuration for P2P integration - -```mermaid -classDiagram -class SnapshotPlugin { -+plugin_initialize(options) -+plugin_startup() -+plugin_shutdown() -+get_snapshot_path() string -+load_snapshot_from(path) -+create_snapshot_at(path) -+start_server() -+download_snapshot_from_peers() -+get_trusted_snapshot_peers() vector -} -``` - -**Diagram sources** -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -**Section sources** -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:46-87](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L46-L87) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -### Plugin Registration and Lifecycle -- Registration: Plugins are enumerated by the build system and registered with the application. Built-in plugins are discovered via the plugins directory traversal. -- Startup: Plugins initialize dependencies, set up program options, and register APIs with the JSON-RPC dispatcher. -- Shutdown: Plugins clean up resources and disconnect signals. - -```mermaid -flowchart TD -Start(["Application Start"]) --> Discover["Discover Plugins
CMake discovers subdirectories"] -Discover --> Init["Initialize Plugins
plugin_initialize()"] -Init --> Deps["Resolve Dependencies
APPBASE_PLUGIN_REQUIRES"] -Deps --> Startup["Startup Phase
plugin_startup()"] -Startup --> CrossPlugin["Cross-Plugin Integration
P2P registers trusted peers"] -CrossPlugin --> Run["Runtime
Serve APIs, handle events"] -Run --> DLTMode["DLT Mode Detection
Snapshot-based state"] -DLTMode --> Run -Run --> Shutdown["Shutdown Phase
plugin_shutdown()"] -Shutdown --> End(["Exit"]) -``` - -**Diagram sources** -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:84-118](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:36-42](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L36-L42) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:60-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L60-L76) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) - -**Section sources** -- [documentation/plugin.md:11-20](file://documentation/plugin.md#L11-L20) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) - -### Inter-Plugin Communication Patterns -- Shared Database Access: Plugins like Database API and Account History rely on Chain plugin's database interface. -- Event Synchronization: Plugins can subscribe to Chain plugin signals (e.g., on_sync) to coordinate startup behavior. -- Explicit API Calls: Plugins can call APIs exposed by other plugins via the JSON-RPC registry. -- **DLT Mode Coordination**: Snapshot plugin coordinates with Chain plugin for state management and with P2P plugin for snapshot distribution. -- **Enhanced Cross-Plugin Integration**: P2P plugin can directly access snapshot plugin to retrieve trusted peer endpoints for improved peer trust management. - -```mermaid -sequenceDiagram -participant CHAIN as "Chain Plugin" -participant AHIST as "Account History Plugin" -participant DBAPI as "Database API Plugin" -participant SNAP as "Snapshot Plugin" -participant P2P as "P2P Plugin" -CHAIN-->>AHIST : "on_sync signal" -AHIST->>CHAIN : "Subscribe to applied_block" -CHAIN-->>DBAPI : "Expose database via APIs" -CHAIN-->>SNAP : "State management for DLT mode" -SNAP->>CHAIN : "Load snapshot state" -P2P->>SNAP : "get_trusted_snapshot_peers()" -SNAP-->>P2P : "Vector of trusted peer endpoints" -P2P->>P2P : "set_trusted_peer_endpoints()" -``` - -**Diagram sources** -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:88-91](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:77-77](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L77-L77) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:179-403](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L403) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:60-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L60-L76) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) - -**Section sources** -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:88-91](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L88-L91) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:77-77](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L77-L77) - -### Built-in Plugins Catalog (Overview) -The following plugins are part of the built-in set. Each plugin exposes specific APIs and integrates with the appbase framework and JSON-RPC dispatcher. Consult individual plugin headers for API declarations and lifecycle hooks. - -- chain: Core blockchain database access and block/transaction acceptance. -- webserver: HTTP/WebSocket server for JSON-RPC. -- database_api: Read-only chain state queries. -- account_history: Per-account operation history. -- p2p: Peer-to-peer networking and broadcasting. -- mongo_db: MongoDB integration for archival/indexing. -- json_rpc: JSON-RPC dispatcher and method registry. -- **snapshot**: DLT mode snapshot management and P2P synchronization. -- Additional plugins include: account_by_key, auth_util, block_info, committee_api, custom_protocol_api, debug_node, follow, invite_api, network_broadcast_api, operation_history, paid_subscription_api, private_message, raw_block, social_network, tags, test_api, validator, witness_api. - -**Section sources** -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-42](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L42) -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:32-43](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L43) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:179-186](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L179-L186) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:59-70](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L59-L70) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:18-32](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L18-L32) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:14-41](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L14-L41) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:42-54](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L54) - -### Plugin Development Workflow -- Template-based Creation Tool: Use the provided Python script to generate a plugin skeleton with CMake targets, API headers, and implementation stubs. -- Steps: - - Run the generator with provider and plugin name to scaffold files. - - Implement plugin lifecycle methods and register API factory in startup. - - Declare API methods and integrate with JSON-RPC. - - Build and enable the plugin via configuration. - -```mermaid -flowchart TD -A["Run newplugin.py
generator"] --> B["Generate files
plugin.hpp/cpp, api.hpp/cpp"] -B --> C["Implement plugin methods
initialize/startup/shutdown"] -C --> D["Register API factory
register_api_factory"] -D --> E["Enable plugin in config
enable-plugin"] -E --> F["Build and run node"] -``` - -**Diagram sources** -- [programs/util/newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) -- [programs/util/newplugin.py:168-173](file://programs/util/newplugin.py#L168-L173) - -**Section sources** -- [documentation/plugin.md:21-28](file://documentation/plugin.md#L21-L28) -- [programs/util/newplugin.py:225-246](file://programs/util/newplugin.py#L225-L246) - -## DLT Mode and Snapshot Integration - -### DLT Mode Overview -DLT (Distributed Ledger Technology) mode enables snapshot-based node deployments where the main block log is empty and state is managed through snapshots. This approach significantly reduces bootstrap time and storage requirements for distributed ledger applications. - -### Snapshot-Based State Management -The snapshot plugin manages DLT mode operations including: -- **State Loading**: Loads complete blockchain state from snapshot files -- **Periodic Snapshots**: Automatic snapshot creation at specified intervals -- **Snapshot Distribution**: TCP-based P2P synchronization for node bootstrap -- **State Validation**: Integrity checking and verification during snapshot operations - -```mermaid -flowchart TD -A["DLT Mode Detection"] --> B["Open Database for Snapshot Import"] -B --> C["Wipe Shared Memory"] -C --> D["Initialize Schema and Indexes"] -D --> E["Open Block Logs
Main: block_log (empty)
DLT: dlt_block_log (rolling)"] -E --> F["Load Snapshot State"] -F --> G["Ready for P2P Operations"] -``` - -**Diagram sources** -- [libraries/chain/database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) -- [libraries/chain/database.cpp:292-292](file://libraries/chain/database.cpp#L292-L292) -- [libraries/chain/database.cpp:313-317](file://libraries/chain/database.cpp#L313-L317) - -**Section sources** -- [libraries/chain/database.cpp:281-324](file://libraries/chain/database.cpp#L281-L324) -- [libraries/chain/database.cpp:292-292](file://libraries/chain/database.cpp#L292-L292) -- [libraries/chain/database.cpp:313-317](file://libraries/chain/database.cpp#L313-L317) - -### DLT Rolling Block Log -In DLT mode, a separate rolling block log (`dlt_block_log`) serves as the primary storage for recent irreversible blocks: - -**Key Features:** -- **Offset-Aware Index**: 8-byte header storing first block number -- **Rolling Window**: Configurable size (default: 100,000 blocks) -- **Amortized Cost**: Truncation when window exceeds 2x limit -- **Fallback Mechanism**: P2P layer automatically falls back to DLT log - -**Implementation Details:** -- Same binary format as regular block_log -- Memory-mapped file access for performance -- Automatic reconstruction of corrupted indexes -- Temporary file swapping for efficient truncation - -```mermaid -classDiagram -class DLTBlockLog { -+open(file) -+append(block) -+read_block_by_num(block_num) optional -+truncate_before(new_start) -+start_block_num() uint32 -+head_block_num() uint32 -+num_blocks() uint32 -} -``` - -**Diagram sources** -- [libraries/chain/include/graphene/chain/dlt_block_log.hpp:35-72](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L72) - -**Section sources** -- [libraries/chain/include/graphene/chain/dlt_block_log.hpp:13-33](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L13-L33) -- [libraries/chain/dlt_block_log.cpp:1-200](file://libraries/chain/dlt_block_log.cpp#L1-L200) -- [libraries/chain/dlt_block_log.cpp:356-382](file://libraries/chain/dlt_block_log.cpp#L356-L382) - -### P2P Integration with DLT Mode -The P2P plugin seamlessly integrates with DLT mode through automatic fallback mechanisms: - -**Block Retrieval Flow:** -1. Check main block_log for requested block -2. If not found, check DLT block_log fallback -3. If still not found, check fork database -4. Return appropriate error if block unavailable - -**Configuration Options:** -- `dlt-block-log-max-blocks`: Rolling window size (default: 100,000) -- Automatic truncation when exceeding 2x limit -- Amortized cost distribution across block writes - -```mermaid -sequenceDiagram -participant P2P as "P2P Plugin" -participant DB as "Database" -participant BL as "Block Log" -participant DLT as "DLT Block Log" -P2P->>DB : "get_block_id_for_num(block_num)" -DB->>BL : "read_block_by_num(block_num)" -alt Block found in main log -BL-->>DB : "block_id" -DB-->>P2P : "block_id" -else Block not in main log -DB->>DLT : "read_block_by_num(block_num)" -alt Block found in DLT log -DLT-->>DB : "block_id" -DB-->>P2P : "block_id" -else Block not found anywhere -DB-->>P2P : "empty result" -end -end -``` - -**Diagram sources** -- [plugins/p2p/p2p_plugin.cpp:259-277](file://plugins/p2p/p2p_plugin.cpp#L259-L277) -- [libraries/chain/database.cpp:558-567](file://libraries/chain/database.cpp#L558-L567) -- [libraries/chain/database.cpp:596-600](file://libraries/chain/database.cpp#L596-L600) - -**Section sources** -- [libraries/chain/database.cpp:558-567](file://libraries/chain/database.cpp#L558-L567) -- [libraries/chain/database.cpp:596-600](file://libraries/chain/database.cpp#L596-L600) -- [libraries/chain/database.cpp:618-622](file://libraries/chain/database.cpp#L618-L622) - -### Snapshot P2P Synchronization -The snapshot plugin provides TCP-based synchronization for distributed deployments: - -**Protocol Features:** -- Binary request-response protocol over TCP -- Anti-spam protection (1 connection/IP, 3 connections/hour) -- Chunked transfer with progress reporting -- Integrity verification via checksums - -**Trust Model:** -- Public serving: Any IP can download snapshots -- Trusted-only serving: Only IPs from trusted list can download -- Client-side trust: Connects only to specified trusted peers - -**Section sources** -- [documentation/snapshot-plugin.md:104-164](file://documentation/snapshot-plugin.md#L104-L164) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:15-41](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L15-L41) - -## Enhanced P2P Plugin Integration with DLT Mode Awareness - -### Improved Error Handling and Logging -The P2P plugin now includes sophisticated DLT mode awareness with enhanced error handling and logging capabilities: - -**DLT Mode Detection Logic:** -- When serving blocks in DLT mode, the P2P plugin detects when block data is not available for blocks outside the DLT log range -- Instead of logging generic errors, it provides specific DLT mode context information using debug logging -- Uses debug logging (`dlog`) for DLT mode scenarios to avoid flooding error logs with expected behavior -- Maintains backward compatibility with error logging for non-DLT mode scenarios - -**Enhanced Block Serving Flow:** -1. Attempt to fetch block from main chain database -2. If block not found and DLT mode is active: - - Log specific DLT mode information using debug logging (`dlog`) - - Provide context about expected block unavailability in DLT mode - - Throw appropriate exception to indicate block unavailability -3. If not in DLT mode, log detailed error information with block ID correlation using error logging (`elog`) - -**Logging Improvements:** -- **Debug Logging**: `dlog` statements provide DLT mode context without polluting error logs -- **Error Logging**: `elog` statements provide detailed error information with block ID correlation -- **Context Information**: Both debug and error logs include block ID and expected block number context -- **Sync Block Processing**: Enhanced logging for sync block processing with better visibility into synchronization progress - -```mermaid -flowchart TD -A["Block Request Received"] --> B{"Is DLT Mode Active?"} -B --> |Yes| C["Check Main Block Log"] -B --> |No| D["Check Main Block Log"] -C --> E{"Block Found?"} -E --> |Yes| F["Return Block"] -E --> |No| G["Log DLT Context Info
dlog: 'Block ${id} not available in DLT mode'
Throw key_not_found_exception"] -D --> H{"Block Found?"} -H --> |Yes| F -H --> |No| I["Log Detailed Error
elog: 'Couldn't find block ${id}'
Include block ID correlation"] -``` - -**Diagram sources** -- [plugins/p2p/p2p_plugin.cpp:265-276](file://plugins/p2p/p2p_plugin.cpp#L265-L276) - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:265-276](file://plugins/p2p/p2p_plugin.cpp#L265-L276) - -### Enhanced Sync Block Processing Logging -The P2P plugin now provides improved logging for sync block processing with better visibility into synchronization progress: - -**Sync Block Logging Enhancements:** -- **Sync Mode Detection**: Distinguishes between sync mode and regular block processing -- **Head Block Context**: Logs current head block number alongside block processing -- **Progress Tracking**: Provides clear indication of synchronization progress -- **Performance Metrics**: Includes transaction count and validator information for non-sync blocks - -**Logging Categories:** -- **Sync Mode**: Uses `fc_ilog` with "sync" logger for synchronization operations -- **Regular Mode**: Uses standard `ilog` for normal block processing -- **Error Context**: Maintains detailed error logging with head block context - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:118-165](file://plugins/p2p/p2p_plugin.cpp#L118-L165) - -### DLT Mode Block Serving Operations -The P2P plugin now provides enhanced error handling specifically for DLT mode block serving operations: - -**Expected Behavior in DLT Mode:** -- Blocks outside the DLT log range are intentionally not available -- This is expected behavior and should not be logged as errors -- The P2P layer handles this gracefully by falling back to other peers -- Debug logging provides context without flooding error logs - -**Error Handling Strategy:** -- **DLT Mode**: Use debug logging with `dlog` to indicate expected unavailability -- **Non-DLT Mode**: Use error logging with `elog` to report unexpected failures -- **Exception Handling**: Throw appropriate exceptions to signal block unavailability -- **Context Preservation**: Maintain block ID and expected block number context in logs - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:265-276](file://plugins/p2p/p2p_plugin.cpp#L265-L276) - -### Database Integration Enhancements -The database layer provides comprehensive fallback mechanisms for DLT mode: - -**Multi-Level Block Retrieval:** -1. **TAPoS Buffer**: Fastest check for reversible blocks -2. **Main Block Log**: Irreversible blocks -3. **DLT Block Log**: Recent blocks in DLT mode -4. **Fork Database**: Alternative chain blocks - -**DLT Mode Fallback Logic:** -- Main block log fallback for DLT mode scenarios -- Automatic detection of DLT mode availability -- Graceful degradation when DLT log is empty or unavailable -- Enhanced logging for DLT mode block serving operations - -**Section sources** -- [libraries/chain/database.cpp:558-580](file://libraries/chain/database.cpp#L558-L580) -- [libraries/chain/database.cpp:599-620](file://libraries/chain/database.cpp#L599-L620) -- [libraries/chain/database.cpp:623-640](file://libraries/chain/database.cpp#L623-L640) - -## Cross-Plugin Integration: P2P and Snapshot - -### Enhanced P2P Plugin Integration with Snapshot Plugin -The P2P plugin now features enhanced integration with the snapshot plugin through automatic trusted peer endpoint registration and cross-plugin communication. - -**Integration Architecture:** -- **CMake Linking**: P2P plugin now links against the snapshot library (`graphene::snapshot`) -- **Direct Plugin Access**: P2P plugin can directly access snapshot plugin instance -- **Automatic Configuration**: P2P plugin automatically retrieves trusted peer endpoints from snapshot plugin -- **Reduced Soft-Ban Duration**: Trusted snapshot peers receive reduced soft-ban duration (5 minutes vs 1 hour) - -**Implementation Details:** -- P2P plugin searches for snapshot plugin instance during startup -- Retrieves trusted snapshot peer endpoints using `get_trusted_snapshot_peers()` API -- Registers endpoints with P2P node using `set_trusted_peer_endpoints()` -- Applies reduced soft-ban duration for registered trusted peers - -```mermaid -sequenceDiagram -participant SNAP as "Snapshot Plugin" -participant P2P as "P2P Plugin" -participant NODE as "Network Node" -SNAP->>SNAP : "parse trusted-snapshot-peer config" -SNAP->>SNAP : "store trusted endpoints" -P2P->>P2P : "plugin_startup()" -P2P->>SNAP : "find_plugin()" -SNAP-->>P2P : "snapshot plugin instance" -P2P->>SNAP : "get_trusted_snapshot_peers()" -SNAP-->>P2P : "vector trusted endpoints" -P2P->>NODE : "set_trusted_peer_endpoints(endpoints)" -NODE-->>P2P : "trusted peer endpoints registered" -P2P->>P2P : "apply reduced soft-ban (5 min)" -``` - -**Diagram sources** -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) -- [libraries/network/node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) -- [plugins/snapshot/plugin.cpp:3039-3045](file://plugins/snapshot/plugin.cpp#L3039-L3045) -- [libraries/network/node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) - -### CMake Integration and Build System Changes -The P2P plugin's CMake configuration has been updated to enable cross-plugin functionality: - -**P2P Plugin CMake Changes:** -- Added `graphene::snapshot` to target_link_libraries -- Enables direct access to snapshot plugin APIs -- Facilitates automatic trusted peer endpoint registration -- Supports enhanced peer trust management - -**Snapshot Plugin CMake (unchanged):** -- Maintains standalone library configuration -- Provides trusted peer management functionality -- Exposes `get_trusted_snapshot_peers()` API for cross-plugin access - -**Build System Benefits:** -- Direct plugin-to-plugin communication without intermediate layers -- Reduced runtime overhead for trusted peer management -- Improved configuration consistency across plugins -- Enhanced maintainability through explicit dependencies - -**Section sources** -- [plugins/p2p/CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) -- [plugins/snapshot/CMakeLists.txt:27-38](file://plugins/snapshot/CMakeLists.txt#L27-L38) - -### Trusted Peer Management Enhancement -The cross-plugin integration enables sophisticated trusted peer management: - -**Configuration Integration:** -- P2P plugin reads `trusted-snapshot-peer` configuration from snapshot plugin -- Automatic endpoint registration eliminates manual configuration duplication -- Consistent trust model across both plugins -- Dynamic trust management based on snapshot plugin configuration - -**Trust Enforcement Benefits:** -- Reduced soft-ban duration for verified snapshot sources (5 minutes vs 1 hour) -- Improved peer reputation management -- Enhanced security through verified snapshot peer sources -- Better resource allocation for trusted snapshot sources - -**Operational Improvements:** -- Automatic discovery of trusted snapshot peers -- Dynamic adjustment of trust levels based on peer behavior -- Enhanced monitoring and logging for trusted peer activities -- Improved network topology optimization - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) -- [libraries/network/node.cpp:5254-5274](file://libraries/network/node.cpp#L5254-L5274) - -### Type Safety Improvements in IP Address Handling - -### Enhanced Type Safety in Peer Statistics -Recent improvements to the P2P plugin include enhanced type safety in IP address handling to prevent implicit type conversion issues in peer management code. - -**The Issue**: The P2P plugin's peer statistics logging previously relied on implicit type conversions when extracting IP addresses from peer_info.host.get_address(). This could lead to unexpected behavior and type conversion errors in certain scenarios. - -**The Solution**: Implemented explicit type conversion using static_cast() to ensure reliable IP address extraction and formatting. - -**Key Changes**: -- Line 504: `ip = static_cast(peer_info.host.get_address());` -- This ensures explicit conversion from fc::ip::address to std::string -- Prevents potential implicit conversion issues and compiler warnings -- Provides consistent string formatting for IP addresses in logs - -**Impact**: -- Eliminates type conversion ambiguity in peer statistics logging -- Improves code reliability and prevents runtime errors -- Maintains backward compatibility while enhancing type safety -- Reduces potential security vulnerabilities from implicit conversions - -```mermaid -flowchart TD -A["Peer Connection Established"] --> B["Extract Peer Information"] -B --> C["Get IP Address from peer_info.host.get_address()"] -C --> D{"Type Conversion Required?"} -D --> |Yes| E["Apply static_cast()"] -D --> |No| F["Direct String Conversion"] -E --> G["Format IP Address for Logging"] -F --> G -G --> H["Output Peer Statistics"] -``` - -**Diagram sources** -- [plugins/p2p/p2p_plugin.cpp:500-508](file://plugins/p2p/p2p_plugin.cpp#L500-L508) - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:500-508](file://plugins/p2p/p2p_plugin.cpp#L500-L508) -- [thirdparty/fc/include/fc/network/ip.hpp:69-71](file://thirdparty/fc/include/fc/network/ip.hpp#L69-L71) -- [thirdparty/fc/src/network/ip.cpp:35-39](file://thirdparty/fc/src/network/ip.cpp#L35-L39) - -### IP Address Type System -The underlying fc::ip::address and fc::ip::endpoint classes provide a robust type system for network address handling: - -**Address Class Features**: -- Implicit conversion operators for different types -- String conversion via operator std::string() -- Numeric conversion via operator uint32_t() -- Range checking for private/public/multicast addresses - -**Endpoint Class Features**: -- Composite address and port management -- String parsing from "IP:PORT" format -- Comparison operators for sorting and matching -- Port extraction and manipulation - -**Type Safety Benefits**: -- Compile-time type checking prevents misuse -- Clear separation between address and endpoint types -- Safe conversion between string and numeric representations -- Consistent behavior across different network operations - -**Section sources** -- [thirdparty/fc/include/fc/network/ip.hpp:12-87](file://thirdparty/fc/include/fc/network/ip.hpp#L12-L87) -- [thirdparty/fc/src/network/ip.cpp:14-87](file://thirdparty/fc/src/network/ip.cpp#L14-L87) - -### Network Integration Points -The enhanced type safety extends throughout the network layer: - -**Peer Connection Integration**: -- Peer connections store fc::ip::endpoint for remote addresses -- Node implementation extracts endpoints for statistics reporting -- Message handling maintains type-safe address information -- Firewall and NAT detection uses proper endpoint types - -**Message Protocol Integration**: -- Hello messages exchange endpoint information -- Address broadcasting maintains type safety -- Connection establishment preserves endpoint integrity -- Peer discovery protocols use consistent address formats - -**Section sources** -- [libraries/network/peer_connection.hpp:109-110](file://libraries/network/peer_connection.hpp#L109-L110) -- [libraries/network/node.cpp:4900-4903](file://libraries/network/node.cpp#L4900-L4903) -- [libraries/network/core_messages.hpp:333-345](file://libraries/network/core_messages.hpp#L333-L345) - -## Dependency Analysis -Plugins declare explicit dependencies using APPBASE_PLUGIN_REQUIRES. The JSON-RPC plugin is a central dependency for most plugins that expose APIs. The Chain plugin is often required by stateful plugins. **Enhanced** with cross-plugin integration patterns. - -```mermaid -graph LR -JSON["json_rpc::plugin"] --> CHAIN["chain::plugin"] -JSON --> WEB["webserver::plugin"] -JSON --> DBAPI["database_api::plugin"] -JSON --> AHIST["account_history::plugin"] -CHAIN --> P2P["p2p::plugin"] -CHAIN --> MONGO["mongo_db::plugin"] -CHAIN --> SNAP["snapshot::plugin"] -P2P --> DLT["dlt_block_log"] -SNAP --> DLT -P2P --> TYPESAFE["Type Safety Enhancements"] -P2P --> CROSSPLUGIN["Cross-Plugin Integration
Snapshot Trust Management"] -SNAP --> TRUSTEDPEERS["Trusted Peer Management
Automatic Endpoint Registration"] -``` - -**Diagram sources** -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:84-118](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L84-L118) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-24](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp:32-38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L38) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:188-191](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:61-65](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L61-L65) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:20-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L20) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:17-19](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L17-L19) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:44-44](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L44-L44) -- [libraries/chain/include/graphene/chain/dlt_block_log.hpp:35-35](file://libraries/chain/include/graphene/chain/dlt_block_log.hpp#L35-L35) -- [plugins/p2p/CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) - -**Section sources** -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:21-24](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L21-L24) -- [plugins/database_api/include/graphene/plugins/database_api/plugin.hpp:188-191](file://plugins/database_api/include/graphene/plugins/database_api/plugin.hpp#L188-L191) -- [plugins/account_history/include/graphene/plugins/account_history/plugin.hpp:61-65](file://plugins/account_history/include/graphene/plugins/account_history/plugin.hpp#L61-L65) -- [plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp:20-20](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L20-L20) -- [plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp:17-19](file://plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_plugin.hpp#L17-L19) -- [plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp:44-44](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L44-L44) -- [plugins/p2p/CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) - -## Plugin Deprecation Status and Maintenance Practices - -### Deprecation Overview -The VIZ blockchain platform maintains strict deprecation policies for operations and plugins that are no longer supported. Understanding deprecation status is crucial for maintaining compatibility and avoiding runtime errors. - -### Deprecated Operations -Several blockchain operations have been deprecated as part of hardfork implementations: - -#### Hardfork 4 Deprecations -- **vote_operation**: Voting operations are deprecated as of Hardfork 4 -- **content_operation**: Content creation/update operations are deprecated as of Hardfork 4 -- **delete_content_operation**: Content deletion operations are deprecated as of Hardfork 4 - -These operations are explicitly marked as deprecated in the operations header and validated in chain evaluators with hardfork checks. - -**Section sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp:14-27](file://libraries/protocol/include/graphene/protocol/operations.hpp#L14-L27) -- [libraries/chain/chain_evaluator.cpp:216-216](file://libraries/chain/chain_evaluator.cpp#L216-L216) -- [libraries/chain/chain_evaluator.cpp:551-551](file://libraries/chain/chain_evaluator.cpp#L551-L551) -- [libraries/chain/chain_evaluator.cpp:1296-1296](file://libraries/chain/chain_evaluator.cpp#L1296-L1296) - -### Plugin Configuration Deprecations -Several plugin configuration options have been deprecated in favor of newer alternatives: - -#### P2P Plugin Deprecations -- **seed-node**: Deprecated in favor of `p2p-seed-node` -- **force-validate**: Deprecated in favor of `p2p-force-validate` - -Both deprecations emit warnings during plugin initialization and support graceful migration by accepting both old and new option formats. - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:499-528](file://plugins/p2p/p2p_plugin.cpp#L499-L528) - -#### Debug Node Plugin Deprecations -- **edit-script**: Deprecated in favor of `debug-node-edit-script` - -The debug node plugin maintains backward compatibility by logging warnings and merging deprecated options with their modern equivalents. - -**Section sources** -- [plugins/debug_node/plugin.cpp:124-128](file://plugins/debug_node/plugin.cpp#L124-L128) - -### Maintenance Best Practices - -#### Migration Strategies -1. **Configuration Migration**: Replace deprecated configuration options with their modern equivalents -2. **Operation Updates**: Update client applications to use supported alternatives -3. **Plugin Updates**: Monitor plugin deprecation notices and migrate to maintained alternatives -4. **DLT Mode Adoption**: Consider migrating to DLT mode for improved performance and reduced storage requirements -5. **Type Safety Updates**: Ensure all IP address handling uses explicit type conversion for reliability -6. **Cross-Plugin Integration**: Leverage enhanced P2P-snapshot integration for improved peer trust management - -#### Monitoring Deprecation Warnings -Plugins emit warnings when deprecated features are detected: -- P2P plugin logs warnings for deprecated seed-node and force-validate options -- Debug node plugin logs warnings for deprecated edit-script option -- Chain evaluators enforce hardfork-based operation deprecations - -#### Testing and Validation -- Test with deprecated operations to identify compatibility issues -- Monitor warning logs for deprecated feature usage -- Validate migration paths before hardfork activation -- Test DLT mode configurations for proper snapshot and block log operation -- Verify type safety improvements in IP address handling -- **Test cross-plugin integration**: Validate P2P-snapshot integration for trusted peer management -- **Verify CMake linking**: Ensure P2P plugin can access snapshot plugin APIs - -### Practical Examples - -#### Handling Deprecated P2P Configuration -```ini -# Deprecated (will show warning) -seed-node = 192.168.0.1:4243 - -# Recommended approach -p2p-seed-node = 192.168.0.1:4243 -``` - -#### Managing Deprecated Operations -When encountering deprecation errors: -1. Check current hardfork status using chain operations -2. Update client applications to use supported alternatives -3. Monitor deprecation warnings in plugin logs - -#### DLT Mode Configuration Example -```ini -# Enable DLT mode with snapshot support -plugin = snapshot -snapshot-every-n-blocks = 100000 -snapshot-dir = /var/lib/vizd/snapshots -dlt-block-log-max-blocks = 100000 - -# Configure P2P for DLT mode -plugin = p2p - -# Configure trusted snapshot peers for enhanced integration -trusted-snapshot-peer = 192.168.1.100:8093 -trusted-snapshot-peer = 192.168.1.101:8093 -``` - -**Section sources** -- [plugins/p2p/p2p_plugin.cpp:499-528](file://plugins/p2p/p2p_plugin.cpp#L499-L528) -- [plugins/debug_node/plugin.cpp:124-128](file://plugins/debug_node/plugin.cpp#L124-L128) -- [libraries/chain/chain_evaluator.cpp:216-216](file://libraries/chain/chain_evaluator.cpp#L216-L216) - -## Performance Considerations -- JSON-RPC Overhead: Each request incurs serialization/deserialization and method dispatch overhead. Batch requests and minimize unnecessary API calls. -- Database Access: Heavy queries against the chain database should be cached or paginated to avoid latency spikes. -- Threading Model: Webserver runs on its own io_service thread to isolate HTTP processing from other plugins. -- Signal Usage: Prefer signals for lightweight coordination; avoid heavy computation inside connected slots. -- Storage Backends: Plugins like mongo_db introduce additional write amplification; tune indexing and batching strategies. -- **DLT Mode Benefits**: Snapshot-based nodes eliminate block log storage requirements and enable instant bootstrap times. -- **DLT Mode Overhead**: Rolling block log requires additional disk I/O for recent block storage and truncation operations. -- **Enhanced Error Handling**: Improved logging reduces error noise while providing better context information for debugging. -- **Maintenance Impact**: Deprecated plugins may have reduced performance due to compatibility layers and should be migrated to supported alternatives. -- **Sync Block Processing**: Enhanced logging provides better visibility into synchronization progress without impacting performance. -- **Type Safety Improvements**: Explicit type conversion adds minimal overhead while preventing runtime errors and improving reliability. -- **Cross-Plugin Integration**: **Enhanced** with minimal overhead for trusted peer management through direct plugin access. -- **CMake Integration**: Linking against snapshot library enables efficient cross-plugin communication without runtime overhead. - -## Troubleshooting Guide -- Plugin Not Found: - - Ensure the plugin directory exists and contains a CMakeLists.txt; the build system enumerates subdirectories. -- API Not Available: - - Verify the plugin is enabled and public-api is configured if exposing public endpoints. - - Confirm the plugin registered its API factory during startup. -- Replay Required: - - Some plugins maintain persistent records; disabling/enabling them may require a replay. -- Authentication: - - Use api-user to protect sensitive APIs. -- **DLT Mode Issues**: - - Verify snapshot files exist and are accessible - - Check DLT block log configuration and permissions - - Monitor rolling window truncation logs - - Validate P2P fallback to DLT block log - - **Enhanced Error Handling**: Check debug logs for DLT mode context information - - **DLT Mode Logging**: Look for specific DLT mode debug messages indicating expected block unavailability - - **Sync Block Processing**: Monitor sync mode logging for better visibility into synchronization progress -- **Deprecation Issues**: - - Check for deprecation warnings in plugin logs - - Review deprecated operations and migrate to supported alternatives - - Update configuration options to use non-deprecated values - - Monitor hardfork compliance for operation usage -- **Type Safety Issues**: - - Verify IP address handling uses explicit type conversion - - Check for compilation errors related to implicit type conversions - - Ensure peer statistics logging displays correct IP addresses - - Validate network connectivity and address resolution -- **Cross-Plugin Integration Issues**: - - **P2P-Snapshot Integration**: Verify snapshot plugin is loaded before P2P plugin - - **Trusted Peer Registration**: Check that `trusted-snapshot-peer` configuration is properly parsed - - **CMake Linking**: Ensure P2P plugin links against snapshot library - - **Plugin Access**: Verify direct plugin access functionality works correctly - - **Soft-Ban Duration**: Confirm trusted peers receive reduced soft-ban duration - -**Section sources** -- [documentation/plugin.md:11-20](file://documentation/plugin.md#L11-L20) -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [plugins/p2p/p2p_plugin.cpp:499-528](file://plugins/p2p/p2p_plugin.cpp#L499-L528) -- [plugins/debug_node/plugin.cpp:124-128](file://plugins/debug_node/plugin.cpp#L124-L128) -- [libraries/chain/database.cpp:292-292](file://libraries/chain/database.cpp#L292-L292) -- [plugins/p2p/CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) - -## Conclusion -The VIZ C++ Node plugin system leverages appbase to deliver a modular, extensible architecture. Plugins integrate seamlessly through JSON-RPC, share the chain database, and coordinate via signals. The template-based development tool accelerates custom plugin creation, while configuration options govern exposure and security. With 40+ built-in plugins spanning core blockchain functionality to specialized integrations, the system supports diverse use cases from public APIs to archival pipelines. - -**Updated** The system now includes comprehensive DLT mode support with snapshot-based state management, rolling block log integration, and P2P layer compatibility. The enhanced P2P plugin integration with DLT mode awareness provides improved error handling and logging capabilities for DLT mode block serving operations, featuring sophisticated debug logging for DLT mode scenarios, enhanced sync block processing visibility, and better synchronization progress monitoring. Recent improvements include enhanced type safety in IP address handling using explicit type conversion to prevent implicit type conversion issues in peer management code, further improving the reliability and maintainability of the plugin system. **Cross-plugin integration** has been significantly enhanced with the P2P plugin's ability to automatically register trusted peer endpoints from the snapshot plugin, improving peer trust management and reducing soft-ban durations for verified snapshot sources through direct plugin access and CMake-based linking. - -## Appendices - -### Appendix A: Plugin Lifecycle Reference -- Discovery: Build system scans plugins directory and sets available plugin list. -- Initialization: plugin_initialize parses options and prepares state. -- Startup: plugin_startup registers APIs and connects to chain/database signals. -- Shutdown: plugin_shutdown tears down connections and cleans up. - -**Section sources** -- [plugins/CMakeLists.txt:1-12](file://plugins/CMakeLists.txt#L1-L12) -- [plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp:103-107](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp#L103-L107) -- [plugins/chain/include/graphene/plugins/chain/plugin.hpp:36-42](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp#L36-L42) - -### Appendix B: Configuration Options -- enable-plugin: Comma-separated list of plugin names to activate. -- public-api: Comma-separated list of public API names to expose. -- api-user: Username/password protection for APIs. -- **DLT Mode Options**: - - `dlt-block-log-max-blocks`: Rolling window size for DLT block log (default: 100,000) - - `snapshot-at-block`: Create snapshot at specific block number - - `snapshot-every-n-blocks`: Create periodic snapshots (0 = disabled) - - `snapshot-dir`: Directory for auto-generated snapshots -- **Deprecated Options**: seed-node (use p2p-seed-node), force-validate (use p2p-force-validate), edit-script (use debug-node-edit-script). -- **Type Safety Options**: Enhanced IP address handling with explicit type conversion for reliable peer management. -- **Cross-Plugin Integration Options**: - - `trusted-snapshot-peer`: IP:port pairs of trusted snapshot sources for P2P integration - - Enables automatic trusted peer endpoint registration and reduced soft-ban duration - -**Section sources** -- [documentation/plugin.md:11-20](file://documentation/plugin.md#L11-L20) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [plugins/p2p/p2p_plugin.cpp:499-528](file://plugins/p2p/p2p_plugin.cpp#L499-L528) -- [plugins/debug_node/plugin.cpp:124-128](file://plugins/debug_node/plugin.cpp#L124-L128) -- [documentation/snapshot-plugin.md:142-164](file://documentation/snapshot-plugin.md#L142-L164) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) - -### Appendix C: Deprecation Timeline -- Hardfork 4: vote_operation, content_operation, delete_content_operation deprecated -- Future Hardforks: Monitor operation deprecation notices and prepare migration plans -- Plugin Deprecations: Regular review and replacement of deprecated plugin functionality -- **DLT Mode Introduction**: Snapshot-based DLT mode with rolling block log support -- **Enhanced P2P Integration**: Improved DLT mode awareness with sophisticated error handling and logging -- **Sync Block Processing**: Enhanced logging for better synchronization progress visibility -- **Type Safety Improvements**: Enhanced IP address handling with explicit type conversion for reliability -- **Cross-Plugin Integration**: **Enhanced** with P2P plugin linking against snapshot library and automatic trusted peer registration - -**Section sources** -- [libraries/protocol/include/graphene/protocol/operations.hpp:14-27](file://libraries/protocol/include/graphene/protocol/operations.hpp#L14-L27) -- [libraries/chain/chain_evaluator.cpp:216-216](file://libraries/chain/chain_evaluator.cpp#L216-L216) -- [libraries/chain/chain_evaluator.cpp:551-551](file://libraries/chain/chain_evaluator.cpp#L551-L551) -- [libraries/chain/chain_evaluator.cpp:1296-1296](file://libraries/chain/chain_evaluator.cpp#L1296-L1296) -- [libraries/chain/database.cpp:292-292](file://libraries/chain/database.cpp#L292-L292) -- [plugins/p2p/CMakeLists.txt:27-34](file://plugins/p2p/CMakeLists.txt#L27-L34) -- [plugins/p2p/p2p_plugin.cpp:688-697](file://plugins/p2p/p2p_plugin.cpp#L688-L697) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Plugin System/Snapshot Plugin System.md b/.qoder/repowiki/en/content/Plugin System/Snapshot Plugin System.md deleted file mode 100644 index 06cad90dd7..0000000000 --- a/.qoder/repowiki/en/content/Plugin System/Snapshot Plugin System.md +++ /dev/null @@ -1,1602 +0,0 @@ -# Snapshot Plugin System - - -**Referenced Files in This Document** -- [plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [plugin.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp) -- [snapshot_types.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp) -- [snapshot_serializer.hpp](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp) -- [CMakeLists.txt](file://plugins/snapshot/CMakeLists.txt) -- [snapshot.json](file://share/vizd/snapshot.json) -- [snapshot-testnet.json](file://share/vizd/snapshot-testnet.json) -- [snapshot-plugin.md](file://documentation/snapshot-plugin.md) -- [plugin.cpp](file://plugins/chain/plugin.cpp) -- [plugin.hpp](file://plugins/chain/include/graphene/plugins/chain/plugin.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [dlt_block_log.cpp](file://libraries/chain/dlt_block_log.cpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [file_mutex.cpp](file://thirdparty/fc/src/interprocess/file_mutex.cpp) -- [config.ini](file://share/vizd/config/config.ini) -- [node.cpp](file://libraries/network/node.cpp) -- [node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [p2p_plugin.cpp](file://plugins/p2p/p2p_plugin.cpp) -- [logger_config.cpp](file://thirdparty/fc/src/log/logger_config.cpp) -- [console_appender.cpp](file://thirdparty/fc/src/log/console_appender.cpp) -- [chainbase.cpp](file://thirdparty/chainbase/src/chainbase.cpp) - - -## Update Summary -**Changes Made** -- Added comprehensive stale snapshot detection feature that automatically identifies snapshots older than DLT block log start -- Implemented urgent fresh snapshot creation to prevent sync gaps when downloading nodes would have missing blocks -- Enhanced DLT mode recovery capabilities with intelligent gap detection and automatic snapshot regeneration -- Improved snapshot validation logic to prevent serving broken snapshots with gaps -- Fixed snapshot path propagation: snapshot path is now passed correctly into the async load callback (was empty before) -- Fixed initialization order in snapshot plugin startup to avoid accessing uninitialized state -- Fixed P2P resume after async snapshot task: `_block_processing_paused` flag and P2P catchup flag are now reliably reset even when the async task completes on a non-P2P thread - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Asynchronous Snapshot Creation](#asynchronous-snapshot-creation) -7. [validator-Aware Deferral Mechanism](#validator-aware-deferral-mechanism) -8. [Enhanced Error Handling](#enhanced-error-handling) -9. [Automatic Snapshot Discovery](#automatic-snapshot-discovery) -10. [Integrated Recovery Workflow](#integrated-recovery-workflow) -11. [DLT Replay Integration](#dlt-replay-integration) -12. [Signal-Based DLT Block Log Reset Handling](#signal-based-dlt-block-log-reset-handling) -13. [Peer-to-Peer Snapshot Synchronization](#peer-to-peer-snapshot-synchronization) -14. [Enhanced P2P Integration with Trusted Peers](#enhanced-p2p-integration-with-trusted-peers) -15. [Watchdog and Stalled Sync Detection](#watchdog-and-stalled-sync-detection) -16. [P2P Stale Sync Detection](#p2p-stale-sync-detection) -17. [Emergency Consensus Handling](#emergency-consensus-handling) -18. [Enhanced Anti-Spam Protection](#enhanced-anti-spam-protection) -19. [Access Control and Security Mechanisms](#access-control-and-security-mechanisms) -20. [Integration with Chain Plugin](#integration-with-chain-plugin) -21. [Dependency Analysis](#dependency-analysis) -22. [Performance Considerations](#performance-considerations) -23. [Troubleshooting Guide](#troubleshooting-guide) -24. [Conclusion](#conclusion) - -## Introduction - -The Snapshot Plugin System is a comprehensive solution for VIZ blockchain nodes that enables efficient state synchronization through distributed ledger technology (DLT). This system provides mechanisms for creating, loading, serving, and downloading blockchain state snapshots, significantly reducing bootstrap times and enabling rapid node initialization. - -**Updated** The system has been enhanced with comprehensive snapshot plugin configuration supporting multiple trusted snapshot peers, snapshot scheduling parameters, serving options, watchdog monitoring, automatic snapshot discovery, integrated recovery workflow, enhanced anti-spam protection, **signal-based DLT block log reset handling**, **enhanced P2P integration with trusted peers**, **enhanced error handling for snapshot download operations**, **improved undo stack management during snapshot loading**, and **stale snapshot detection**. These enhancements provide robust error handling for recovery scenarios, automatic peer-to-peer snapshot synchronization for empty state nodes, **automatic registration of trusted peer endpoints with the P2P layer**, **advanced watchdog mechanisms for DLT mode operation**, **automatic snapshot creation during DLT block log reset scenarios**, and **intelligent gap detection to prevent sync gaps when serving snapshots**. - -The plugin addresses the fundamental challenge of blockchain bootstrapping by allowing nodes to jump directly to a recent state rather than replaying thousands of blocks. This is particularly crucial for VIZ's social media and content platform characteristics, where rapid deployment and scaling are essential. - -## Project Structure - -The snapshot plugin is organized within the VIZ C++ node codebase following a modular architecture: - -```mermaid -graph TB -subgraph "Plugin Structure" -A[snapshot/] --> B[include/] -A --> C[source/] -B --> D[plugin.hpp] -B --> E[snapshot_types.hpp] -B --> F[snapshot_serializer.hpp] -C --> G[plugin.cpp] -end -subgraph "Configuration" -H[share/vizd/] --> I[snapshot.json] -H --> J[snapshot-testnet.json] -H --> K[config.ini] -L[documentation/] --> M[snapshot-plugin.md] -end -subgraph "Build System" -N[CMakeLists.txt] --> O[Target: graphene_snapshot] -O --> P[Dependencies] -P --> Q[graphene_chain] -P --> R[appbase] -P --> S[chainbase] -P --> T[fc] -end -D --> G -E --> G -F --> G -``` - -**Diagram sources** -- [plugin.cpp:1-50](file://plugins/snapshot/plugin.cpp#L1-L50) -- [plugin.hpp:1-88](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L1-L88) -- [snapshot_types.hpp:1-52](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp#L1-L52) -- [CMakeLists.txt:1-52](file://plugins/snapshot/CMakeLists.txt#L1-L52) - -**Section sources** -- [plugin.cpp:1-50](file://plugins/snapshot/plugin.cpp#L1-L50) -- [CMakeLists.txt:1-52](file://plugins/snapshot/CMakeLists.txt#L1-L52) - -## Core Components - -The snapshot plugin consists of several interconnected components that work together to provide comprehensive state synchronization capabilities: - -### Modular Layer Architecture -The plugin has been refactored into distinct functional layers: - -#### Interface Layer -The main plugin class provides the primary interface for external systems to interact with the snapshot functionality. It implements the appbase plugin interface and exposes methods for loading and creating snapshots programmatically. - -#### Serialization Engine Layer -A sophisticated serialization system handles the conversion of blockchain state objects to/from compressed JSON format. This engine manages different object types with varying memory layouts and special data structures. - -#### Network Protocol Layer -The plugin implements a custom TCP protocol for peer-to-peer snapshot distribution, including message framing, authentication, and transfer optimization. Enhanced with comprehensive access control mechanisms and denial reasons. - -#### Database Integration Layer -Deep integration with the VIZ blockchain database ensures seamless state transitions and maintains consistency during snapshot operations. - -#### Recovery Workflow Layer -**New** Comprehensive recovery workflow integration with DLT replay capabilities, automatic snapshot discovery, and enhanced error handling for recovery scenarios. - -#### Asynchronous Execution Layer -**New** Dedicated thread-based asynchronous execution system that prevents snapshot creation from blocking main thread operations and causing read-lock timeouts. - -#### Watchdog Monitoring Layer -**New** Comprehensive watchdog system that monitors server health and automatically restarts dead accept loops to ensure continuous operation. - -#### **Signal-Based DLT Block Log Reset Handling Layer** -**New** Advanced integration with DLT block log reset events that automatically schedules fresh snapshot creation for other DLT nodes to bootstrap from, providing seamless network recovery and state synchronization. - -#### **Enhanced P2P Integration Layer** -**New** Advanced integration with the P2P layer that automatically registers trusted peer endpoints for reduced soft-ban duration, enabling trusted peers to receive 5-minute bans instead of the default 1-hour duration. - -#### **Enhanced Logging Layer** -**New** Comprehensive logging system with ANSI color codes for improved visibility and debugging capabilities across different log levels. - -#### **P2P Stale Sync Detection Layer** -**New** Lightweight recovery mechanism that automatically detects and recovers from network stalls without requiring snapshot downloads, resetting sync from LIB and reconnecting peers. - -#### **Dedicated Threading for Stalled Sync Detection** -**New** Dedicated fc::thread instance for stalled sync detection operations, preventing fc fibers from stalling on main thread blocked in io_serv->run(). - -#### **Automatic Gap Detection for DLT Block Log Initialization** -**New** Intelligent gap detection and automatic reset logic that prevents index position mismatch assertions during DLT block log initialization after snapshot imports, ensuring seamless state synchronization. - -#### **Enhanced Error Handling for Snapshot Download Operations** -**New** Comprehensive error handling around snapshot download operations within the check_stalled_sync_loop method, ensuring stalled sync monitoring continues running even when snapshot loading fails. - -#### **Improved Undo Stack Management** -**New** Enhanced undo stack management in load_snapshot method by adding proper undo stack management with db.undo_all() call before set_revision operations, ensuring proper database state cleanup. - -#### **Enhanced Exception Handling** -**New** Enhanced exception handling for both fc::exception and std::exception types during snapshot download attempts, providing robust error recovery mechanisms. - -#### **Stale Snapshot Detection Layer** -**New** Intelligent stale snapshot detection system that automatically identifies snapshots older than DLT block log start and schedules urgent fresh snapshot creation to prevent sync gaps when downloading nodes would encounter missing blocks. - -**Updated** The modular architecture provides enhanced extensibility and maintainability through clear separation of concerns between interface, serialization, network, database, recovery, asynchronous execution, watchdog, **signal-based DLT block log reset handling**, **enhanced P2P integration**, **enhanced error handling**, **improved undo stack management**, and **stale snapshot detection** components. The recent additions include asynchronous snapshot creation, validator-aware deferral, watchdog mechanisms, automatic snapshot discovery, integrated recovery workflow, comprehensive error handling, **signal-based DLT block log reset handling**, **enhanced P2P integration with trusted peer support**, **dedicated threading for stalled sync detection**, **P2P stale sync detection**, **automatic gap detection for DLT block log initialization**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **enhanced exception handling**, and **stale snapshot detection**. - -**Section sources** -- [plugin.hpp:42-76](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L42-L76) -- [snapshot_types.hpp:16-52](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp#L16-L52) -- [snapshot_serializer.hpp:30-158](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp#L30-L158) - -## Architecture Overview - -The snapshot plugin follows a layered architecture designed for modularity and extensibility: - -```mermaid -graph TB -subgraph "Application Layer" -A[CLI Commands] --> B[Plugin Interface] -C[Configuration] --> B -D[Recovery Mode] --> B -end -subgraph "Plugin Core" -B --> E[Serialization Engine] -B --> F[Network Protocol] -B --> G[Database Manager] -B --> H[Recovery Workflow] -B --> I[Async Execution Engine] -B --> J[Watchdog Monitor] -B --> K[P2P Integration Layer] -B --> L[Enhanced Logging System] -B --> M[P2P Stale Sync Detection] -B --> N[Signal-Based DLT Reset Handler] -B --> O[Dedicated Threading for Stalled Sync] -B --> P[Automatic Gap Detection for DLT] -B --> Q[Enhanced Error Handling] -B --> R[Improved Undo Stack Management] -B --> S[Stale Snapshot Detection] -end -subgraph "Security Layer" -F --> T[Access Control] -T --> U[Trust Enforcement] -T --> V[Anti-Spam Protection] -end -subgraph "Serialization Layer" -E --> W[Object Exporter] -E --> X[Object Importer] -W --> Y[JSON Serializer] -X --> Z[Object Constructor] -end -subgraph "Network Layer" -F --> AA[TCP Server] -F --> AB[TCP Client] -AA --> AC[Connection Management] -AB --> AD[Peer Discovery] -end -subgraph "Database Layer" -G --> AE[Chainbase Integration] -G --> AF[Fork Database] -AE --> AG[Object Indexes] -AF --> AH[Block Validation] -end -subgraph "Storage Layer" -Y --> AI[File System] -Z --> AI -AI --> AJ[Snapshot Files] -end -subgraph "Recovery Layer" -H --> AK[DLT Replay Engine] -H --> AL[Automatic Discovery] -H --> AM[Error Handling] -AK --> AN[Block Log Integration] -AL --> AO[Peer Synchronization] -AM --> AP[Diagnostic Tools] -end -subgraph "Reliability Layer" -AA --> AQ[Watchdog Mechanism] -AQ --> AR[Dedicated Server Thread] -AQ --> AS[Stalled Sync Detection] -I --> AT[Dedicated Snapshot Thread] -I --> AU[Async Snapshot Guard] -O --> AV[Dedicated Stalled Sync Thread] -O --> AW[Stalled Sync Operations] -K --> AX[Trusted Peer Registration] -K --> AY[Soft-Ban Duration Management] -L --> AZ[ANSI Color Codes] -L --> BA[Level-Based Coloring] -M --> BB[LIB Reset Mechanism] -M --> BC[Peer Reconnection] -M --> BD[Seed Node Management] -N --> BE[DLT Reset Signal Handling] -N --> BF[Automatic Snapshot Scheduling] -P --> BG[Index Position Mismatch Prevention] -P --> BH[Gap Detection Logic] -Q --> BI[Enhanced Exception Handling] -R --> BJ[Undo Stack Management] -S --> BK[Gap Detection Logic] -S --> BL[Urgent Fresh Snapshot Creation] -``` - -**Updated** The architecture emphasizes separation of concerns with clear boundaries between serialization, networking, database operations, security controls, recovery workflows, asynchronous execution, watchdog monitoring, **signal-based DLT block log reset handling**, **enhanced P2P integration**, **enhanced error handling**, **improved undo stack management**, and **stale snapshot detection**. The modular design enables independent development and testing of each component while maintaining system coherence. Recent enhancements include integrated recovery workflow, DLT replay integration, automatic snapshot discovery, comprehensive watchdog mechanisms, asynchronous execution system, enhanced error handling, **signal-based DLT block log reset handling**, **enhanced P2P integration with trusted peer support**, **dedicated threading for stalled sync detection**, **P2P stale sync detection**, **automatic gap detection for DLT block log initialization**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **enhanced exception handling**, and **stale snapshot detection**. - -**Diagram sources** -- [plugin.cpp:675-780](file://plugins/snapshot/plugin.cpp#L675-L780) -- [snapshot_serializer.hpp:37-107](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp#L37-L107) - -**Section sources** -- [plugin.cpp:675-780](file://plugins/snapshot/plugin.cpp#L675-L780) -- [snapshot_serializer.hpp:37-107](file://plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp#L37-L107) - -## Detailed Component Analysis - -### Snapshot Creation and Management - -The snapshot creation process involves comprehensive state serialization with careful handling of different object types: - -```mermaid -sequenceDiagram -participant CLI as CLI Interface -participant Plugin as Snapshot Plugin -participant DB as Database -participant Serializer as Serializer -participant FileSys as File System -CLI->>Plugin : create_snapshot(path) -Plugin->>Plugin : schedule_async_snapshot() -Plugin->>Plugin : snapshot_thread->async() -Plugin->>DB : with_strong_read_lock() -DB-->>Plugin : locked_access -Plugin->>Serializer : serialize_state() -loop For each object type -Serializer->>DB : get_index(type) -DB-->>Serializer : object_iterator -Serializer->>Serializer : to_variant() -end -Serializer-->>Plugin : state_json -Plugin->>Plugin : compute_checksum() -Plugin->>FileSys : write_compressed_file() -FileSys-->>Plugin : success -Plugin-->>CLI : completion_status -``` - -**Updated** The creation process handles over 30 different object types, from critical singleton objects to optional metadata. Each object type receives specialized treatment based on its memory layout and data structure complexity, demonstrating the modular architecture's flexibility. The recent enhancements include validator-aware deferral to prevent missed block production slots, improved anti-spam protection, integrated recovery workflow capabilities, asynchronous execution system that prevents read-lock timeouts for API and P2P threads, **signal-based DLT block log reset handling**, **dedicated threading for stalled sync detection**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection**, and **enhanced P2P integration with automatic trusted peer endpoint registration**. - -**Diagram sources** -- [plugin.cpp:885-987](file://plugins/snapshot/plugin.cpp#L885-L987) -- [plugin.cpp:789-883](file://plugins/snapshot/plugin.cpp#L789-L883) -- [plugin.cpp:1400-1484](file://plugins/snapshot/plugin.cpp#L1400-L1484) - -### Snapshot Loading and Validation - -Snapshot loading implements rigorous validation and reconstruction procedures with enhanced memory management and improved error handling: - -```mermaid -flowchart TD -Start([Load Snapshot]) --> ReadFile["Read Compressed File"] -ReadFile --> Decompress["Decompress Zlib"] -Decompress --> ParseJSON["Parse JSON Header"] -ParseJSON --> ValidateVersion["Validate Format Version"] -ValidateVersion --> CheckChainID["Verify Chain ID"] -CheckChainID --> ChecksumVerify["Verify Payload Checksum"] -ChecksumVerify --> ClearGenesis["Clear Genesis Objects"] -ClearGenesis --> ImportSingletons["Import Singleton Objects"] -ImportSingletons --> ImportCritical["Import Critical Objects"] -ImportCritical --> ImportImportant["Import Important Objects"] -ImportImportant --> ImportOptional["Import Optional Objects"] -ImportOptional --> UndoAll["db.undo_all() - Clear Undo Stack"] -UndoAll --> SetRevision["Set Database Revision"] -SetRevision --> SeedForkDB["Seed Fork Database"] -SeedForkDB --> PromoteLIB["Promote LIB to Head Block"] -PromoteLIB --> Complete([Load Complete]) -``` - -**Updated** The loading process includes extensive validation steps to ensure data integrity and compatibility with the current node configuration, showcasing the robustness of the modular design. Recent improvements include enhanced LIB promotion for DLT mode, improved fork database seeding for reliable P2P synchronization, integrated recovery workflow integration, comprehensive error handling for unlinkable_block_exception scenarios, comprehensive object clearing for hot-reload scenarios, **improved undo stack management with proper cleanup before set_revision operations**, **enhanced error handling for snapshot download operations**, **enhanced exception handling for both fc::exception and std::exception types**, **stale snapshot detection with gap prevention**, and **enhanced P2P integration with automatic trusted peer endpoint registration**. - -**Diagram sources** -- [plugin.cpp:1046-1288](file://plugins/snapshot/plugin.cpp#L1046-L1288) - -### Network Protocol Implementation - -The snapshot protocol provides efficient peer-to-peer distribution with robust error handling and comprehensive access control: - -```mermaid -sequenceDiagram -participant Client as Client Node -participant Server as Server Node -participant AntiSpam as Anti-Spam -participant Security as Security Layer -Client->>Server : SNAPSHOT_INFO_REQUEST -Server->>Security : Check Trust Status -Security-->>Server : Trusted/Untrusted -alt Untrusted IP -Server->>Client : SNAPSHOT_ACCESS_DENIED (untrusted) -else Trusted IP -Server->>AntiSpam : Check Rate Limits -AntiSpam-->>Server : Allow/Deny -alt Rate Limit Exceeded -Server->>Client : SNAPSHOT_ACCESS_DENIED (rate_limited) -else Within Limits -Server->>Server : Check Session Limits -alt Too Many Active Sessions -Server->>Client : SNAPSHOT_ACCESS_DENIED (session_limit) -else Within Session Limits -Server->>Server : Check Concurrent Connections -alt Max Connections Reached -Server->>Client : SNAPSHOT_ACCESS_DENIED (max_connections) -else Within Connection Limits -Server->>Server : Find Latest Snapshot -Server-->>Client : SNAPSHOT_INFO_REPLY -Client->>Server : SNAPSHOT_DATA_REQUEST(offset, size) -loop Until Complete -Server->>Server : Read Chunk -Server-->>Client : SNAPSHOT_DATA_REPLY -Client->>Server : Next Request -end -end -end -end -Note over Client,Server : Connection Closed -``` - -**Updated** The protocol includes sophisticated anti-spam protection mechanisms, trust enforcement, and detailed denial reasons. The security layer provides comprehensive access control with specific reason codes for different violation types. Recent enhancements include watchdog mechanisms for server reliability, improved peer selection algorithms, integrated recovery workflow support, enhanced error handling for connection timeouts and failures, **dual-tier soft-ban system with automatic trusted peer endpoint registration**, **signal-based DLT block log reset handling**, **dedicated threading for stalled sync detection**, **P2P stale sync detection**, **enhanced error handling for snapshot download operations**, **stale snapshot detection**, and **enhanced exception handling**. - -**Diagram sources** -- [plugin.cpp:1902-2038](file://plugins/snapshot/plugin.cpp#L1902-L2038) -- [plugin.cpp:1470-1599](file://plugins/snapshot/plugin.cpp#L1470-L1599) - -### Configuration and Options - -The plugin supports extensive configuration through both command-line arguments and configuration files: - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `snapshot-at-block` | uint32 | 0 | Create snapshot at specific block number | -| `snapshot-every-n-blocks` | uint32 | 0 | Create periodic snapshots | -| `snapshot-dir` | string | "" | Directory for auto-generated snapshots | -| `snapshot-max-age-days` | uint32 | 0 | Delete snapshots older than N days (0 = disabled) | -| `snapshot-auto-latest` | bool | false | Auto-discover latest snapshot in snapshot-dir | -| `allow-snapshot-serving` | bool | false | Enable TCP snapshot serving | -| `allow-snapshot-serving-only-trusted` | bool | false | Restrict serving to trusted peers | -| `snapshot-serve-endpoint` | string | "0.0.0.0:8092" | TCP listen endpoint | -| `trusted-snapshot-peer` | string[] | [] | Trusted peer endpoints | -| `sync-snapshot-from-trusted-peer` | bool | false | Download snapshot on empty state | -| `enable-stalled-sync-detection` | bool | false | Auto-detect stalled sync | -| `stalled-sync-timeout-minutes` | uint32 | 5 | Timeout for stalled sync | -| `test-trusted-seeds` | bool | false | Test trusted peers connectivity | -| `dlt-block-log-max-blocks` | uint32 | 100000 | Rolling DLT block log window | -| `disable-snapshot-anti-spam` | bool | false | Disable anti-spam checks | -| `snapshot-serve-allow-ip` | string[] | [] | Allowed client IPs for serving | -| **`dlt-block-log-reset-snapshots`** | **bool** | **true** | Enable automatic snapshots on DLT reset | -| **`p2p-stale-sync-detection`** | **bool** | **false** | **Enable P2P stale sync detection** | -| **`p2p-stale-sync-timeout-seconds`** | **uint32** | **120** | **Timeout for P2P stale sync detection** | -| **`needs_fresh_snapshot`** | **bool** | **false** | **Internal flag for stale snapshot detection** | - -**Updated** The configuration system now includes new options for enhanced anti-spam protection, automatic snapshot discovery, integrated recovery workflow, watchdog monitoring, **signal-based DLT block log reset handling**, **enhanced P2P integration with trusted peer support**, **dedicated threading for stalled sync detection**, **P2P stale sync detection**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection**, and **enhanced exception handling**. The `snapshot-auto-latest` option enables automatic discovery of the latest snapshot in the specified directory, while `replay-from-snapshot` provides comprehensive recovery mode functionality, and `trusted-snapshot-peer` enables **automatic registration of trusted peer endpoints with the P2P layer**. **The new DLT block log reset snapshots option enables automatic snapshot creation when DLT block logs are reset, the new P2P stale sync detection options provide lightweight recovery from network stalls without requiring snapshot downloads, and the needs_fresh_snapshot internal flag enables stale snapshot detection and urgent fresh snapshot creation**. - -**Section sources** -- [plugin.cpp:2473-2510](file://plugins/snapshot/plugin.cpp#L2473-L2510) -- [snapshot-plugin.md:247-273](file://documentation/snapshot-plugin.md#L247-L273) - -## Asynchronous Snapshot Creation - -**New** The snapshot plugin now implements a comprehensive asynchronous non-blocking snapshot creation system that prevents read-lock timeouts for API and P2P threads while maintaining snapshot quality and consistency. - -### Asynchronous Execution Architecture - -The asynchronous snapshot creation system provides non-blocking snapshot generation through dedicated thread management: - -```mermaid -sequenceDiagram -participant MainThread as Main Thread -participant SnapshotThread as Snapshot Thread -participant Database as Database -participant Serializer as Serializer -participant FileSystem as File System -MainThread->>MainThread : schedule_async_snapshot() -MainThread->>MainThread : snapshot_in_progress.exchange(true) -alt First Snapshot -MainThread->>MainThread : snapshot_thread = std : : make_unique() -end -MainThread->>SnapshotThread : snapshot_thread->async([=](){}) -SnapshotThread->>Database : with_strong_read_lock() -Database-->>SnapshotThread : locked_access -SnapshotThread->>Serializer : serialize_state() -Serializer-->>SnapshotThread : state_json -SnapshotThread->>FileSystem : write_compressed_file() -FileSystem-->>SnapshotThread : success -SnapshotThread->>MainThread : snapshot_in_progress = false -``` - -### Key Asynchronous Features - -The asynchronous execution system includes several critical improvements: - -#### Dedicated Snapshot Thread -- **Thread Isolation**: Uses dedicated `fc::thread` for snapshot I/O operations -- **Fiber Scheduler Independence**: Mirrors P2P plugin's approach with dedicated worker thread -- **Background Processing**: Prevents snapshot creation from blocking main thread operations - -#### Atomic Progress Tracking -- **Progress Guard**: Uses `std::atomic` to prevent concurrent snapshot creation -- **RAII Cleanup**: Automatic cleanup through destructor-based guard pattern -- **Exception Safety**: Comprehensive error handling with proper cleanup guarantees - -#### Read Lock Optimization -- **Lock Scope Minimization**: Database read operations occur within tight lock scope -- **Background Processing**: Compression and file I/O occur outside database lock -- **Performance Enhancement**: Reduces main thread blocking time from 3+ seconds - -#### Enhanced Error Handling -- **Comprehensive Exception Catching**: Catches fc::exception, std::exception, and unknown exceptions -- **Graceful Degradation**: Logs errors and continues normal operation -- **Resource Cleanup**: Ensures proper cleanup even on failure - -**Section sources** -- [plugin.cpp:1400-1484](file://plugins/snapshot/plugin.cpp#L1400-L1484) -- [plugin.cpp:1418-1436](file://plugins/snapshot/plugin.cpp#L1418-L1436) -- [plugin.cpp:737-743](file://plugins/snapshot/plugin.cpp#L737-L743) - -## validator-Aware Deferral Mechanism - -**New** The snapshot plugin now includes a sophisticated validator-aware deferral mechanism that prevents snapshot creation from interrupting validator block production, ensuring network stability and consensus participation. - -### validator Detection and Deferral Logic - -The validator-aware deferral system provides intelligent scheduling based on validator production status: - -```mermaid -flowchart TD -Start([Snapshot Creation Request]) --> CheckSync{"is_syncing?"} -CheckSync --> |Yes| Skip["Skip Snapshot Creation"] -CheckSync --> |No| CheckWitness["is_witness_producing_soon()?"] -CheckWitness --> |No| CreateNow["Create Snapshot Immediately"] -CheckWitness --> |Yes| CheckPending{"snapshot_pending?"} -CheckWitness --> |Yes| CheckDeferred["Handle Deferred Snapshot"] -CheckPending --> |No| SetPending["Set snapshot_pending = true"] -SetPending --> StorePath["Store pending_snapshot_path"] -CheckPending --> |Yes| Skip -CreateNow --> ScheduleAsync["schedule_async_snapshot()"] -ScheduleAsync --> Complete([Creation Scheduled]) -StorePath --> Complete -CheckDeferred --> CreateDeferred["Create Deferred Snapshot"] -CreateDeferred --> Complete -Skip --> Complete -``` - -### validator Integration Features - -The validator-aware deferral system includes several key components: - -#### validator Production Detection -- **Plugin Integration**: Queries Validator Plugin for production schedule information -- **State Validation**: Checks if Validator Plugin is properly initialized and started -- **Graceful Degradation**: Falls back to conservative behavior if Validator Plugin unavailable - -#### One-Time Deferral Limit -- **Single Deferral Policy**: Allows only one deferral per validator production cycle -- **Infinite Loop Prevention**: Prevents infinite deferral loops with validator-aware checks -- **Production Priority**: Ensures validator block production takes precedence over snapshot creation - -#### Deferred Snapshot Management -- **State Persistence**: Stores snapshot path for later execution -- **Cleanup Mechanism**: Clears pending state after successful execution -- **Atomic Operations**: Uses atomic flags for thread-safe state management - -#### Integration with Block Processing -- **Applied Block Handler**: Monitors block application for deferral timing -- **Sync Detection**: Skips snapshot creation during P2P synchronization -- **Live Block Priority**: Only creates snapshots for live, synchronized blocks - -**Section sources** -- [plugin.cpp:1390-1484](file://plugins/snapshot/plugin.cpp#L1390-L1484) -- [plugin.cpp:1440-1449](file://plugins/snapshot/plugin.cpp#L1440-L1449) -- [validator.cpp:335-551](file://plugins/validator/validator.cpp#L335-L551) - -## Enhanced Error Handling - -**New** The snapshot plugin now includes comprehensive enhanced error handling capabilities specifically designed for recovery scenarios, providing robust fault tolerance and diagnostic information for unlinkable_block_exception and related errors. - -### Enhanced Error Handling Architecture - -The enhanced error handling system provides comprehensive fault tolerance and recovery mechanisms: - -```mermaid -flowchart TD -Start([Operation Request]) --> TryOperation["Attempt Operation"] -TryOperation --> Success{"Operation Success?"} -Success --> |Yes| LogSuccess["Log Success"] -Success --> |No| CheckErrorType{"Check Error Type"} -CheckErrorType --> IsRecoverable{"Is Recoverable?"} -IsRecoverable --> |Yes| AttemptRecovery["Attempt Recovery"] -IsRecoverable --> |No| LogFatal["Log Fatal Error"] -AttemptRecovery --> RecoverySuccess{"Recovery Success?"} -RecoverySuccess --> |Yes| RetryOperation["Retry Original Operation"] -RecoverySuccess --> |No| LogFatal -RetryOperation --> Success -LogSuccess --> Complete([Operation Complete]) -LogFatal --> Complete -``` - -### Enhanced Error Handling Features - -The enhanced error handling system includes several key improvements: - -#### Unlinkable Block Exception Prevention -- **LIB Promotion Strategy**: Promotes LIB to head block during snapshot import to prevent unlinkable_block_exception -- **Fork Database Seeding**: Seeds fork database with head block to ensure proper linking -- **Safe Transition**: Ensures snapshot import doesn't create orphaned blocks that cause synchronization issues - -#### Comprehensive Exception Coverage -- **Multiple Exception Types**: Catches fc::exception, std::exception, and unknown exceptions -- **Detailed Logging**: Provides comprehensive error context and diagnostic information -- **Graceful Degradation**: Continues normal operation even when snapshot creation fails - -#### Resource Management and Cleanup -- **Atomic State Management**: Uses atomic flags for thread-safe error handling -- **RAII Pattern**: Ensures proper cleanup through destructor-based resource management -- **Memory Safety**: Prevents memory leaks and resource corruption during error scenarios - -#### Integration with Watchdog System -- **Server Health Monitoring**: Integrates with watchdog to detect and recover from server failures -- **Connection Timeout Handling**: Manages connection timeouts and socket errors gracefully -- **Thread Safety**: Ensures error handling doesn't interfere with other system components - -#### **Enhanced Error Handling for Snapshot Download Operations** -- **Stalled Sync Monitoring Continuity**: Ensures stalled sync monitoring continues running even when snapshot loading fails -- **Graceful Recovery**: Restarts stalled sync detection after failed snapshot download attempts -- **Improved Exception Handling**: Enhanced exception handling for both fc::exception and std::exception types during snapshot download attempts - -#### **Improved Undo Stack Management** -- **Proper Cleanup**: Adds proper undo stack management with db.undo_all() call before set_revision operations -- **Database State Consistency**: Ensures proper cleanup of database state during snapshot loading -- **Hot-Reload Safety**: Prevents undo stack corruption during hot-reload scenarios - -**Section sources** -- [plugin.cpp:1326-1376](file://plugins/snapshot/plugin.cpp#L1326-L1376) -- [plugin.cpp:1426-1435](file://plugins/snapshot/plugin.cpp#L1426-L1435) -- [plugin.cpp:745-750](file://plugins/snapshot/plugin.cpp#L745-L750) - -## Automatic Snapshot Discovery - -**New** The snapshot plugin now includes comprehensive automatic snapshot discovery functionality through the --snapshot-auto-latest option, enabling nodes to automatically locate and use the most recent snapshot in a specified directory. - -### Enhanced Path Validation Logic - -**Updated** The automatic snapshot discovery process now includes robust path validation and defensive programming checks: - -```mermaid -flowchart TD -Start([Auto-Discovery Request]) --> CheckOption{"--snapshot-auto-latest Enabled?"} -CheckOption --> |No| End([Skip Discovery]) -CheckOption --> |Yes| CheckPath{"--snapshot Path Provided?"} -CheckPath --> |Yes| LogIgnored["Log: Ignored (manual path provided)"] -CheckPath --> |No| CheckDir{"--snapshot-dir Configured?"} -CheckDir --> |No| LogNoDir["Log: No directory configured"] -CheckDir --> |Yes| ValidateDir["Validate Directory Path"] -ValidateDir --> DirExists{"Directory Exists?"} -DirExists --> |No| LogInvalidDir["Log: Invalid directory path"] -DirExists --> |Yes| ReadDir["Read Directory Contents"] -ReadDir --> FilterFiles["Filter .vizjson/.json Files"] -FilterFiles --> ParseName["Parse 'snapshot-block-N' Names"] -ParseName --> CheckNonEmpty["Check Non-Empty String Representations"] -CheckNonEmpty --> FindBest["Find Highest Block Number"] -FindBest --> Found{"Snapshot Found?"} -Found --> |Yes| SetPath["Set snapshot_path to best snapshot"] -Found --> |No| LogNotFound["Log: No snapshots found"] -SetPath --> Complete([Discovery Complete]) -LogIgnored --> Complete -LogNoDir --> Complete -LogInvalidDir --> Complete -LogNotFound --> Complete -End -``` - -### Enhanced Discovery Features - -The automatic snapshot discovery system includes several key improvements: - -#### Robust Directory Validation -- **Path Existence Verification**: Validates directory existence before processing -- **Directory Type Checking**: Ensures the path points to a valid directory -- **Defensive Path Handling**: Handles edge cases and invalid path representations - -#### Improved File Processing -- **Enhanced String Validation**: Checks for non-empty string representations of file paths -- **Robust Filename Parsing**: Gracefully handles malformed filenames with defensive checks -- **Comprehensive Error Handling**: Provides detailed logging for debugging and monitoring - -#### Integration with Recovery Workflow -- **Recovery Mode Compatibility**: Works seamlessly with --replay-from-snapshot flag -- **Fallback Support**: Provides graceful degradation when no snapshots found -- **Manual Override Priority**: Respects manually specified snapshot paths - -**Section sources** -- [plugin.cpp:697-700](file://plugins/snapshot/plugin.cpp#L697-L700) -- [plugin.cpp:2831-2845](file://plugins/snapshot/plugin.cpp#L2831-L2845) -- [plugin.cpp:1719-1748](file://plugins/snapshot/plugin.cpp#L1719-L1748) -- [plugin.cpp:1706-1748](file://plugins/snapshot/plugin.cpp#L1706-L1748) - -## Integrated Recovery Workflow - -**New** The snapshot plugin now provides comprehensive integrated recovery workflow through the --replay-from-snapshot flag, enabling nodes to recover from corrupted states using snapshot-based restoration and DLT block log replay. - -### Recovery Mode Startup Sequence - -The integrated recovery workflow provides a complete recovery process: - -```mermaid -sequenceDiagram -participant User as User Command -participant Chain as Chain Plugin -participant Snap as Snapshot Plugin -participant DB as Database -User->>Chain : Start with --replay-from-snapshot -Chain->>Chain : Validate Snapshot Path -Chain->>DB : open_from_snapshot() -DB-->>Chain : Database Ready -Chain->>Snap : Execute snapshot_load_callback() -Snap->>DB : load_snapshot_from_snapshot_file() -DB-->>Snap : State Restored -Snap->>DB : initialize_hardforks() -DB-->>Snap : Hardforks Initialized -Snap->>DB : promote LIB to head block -Snap->>DB : seed fork database -DB-->>Snap : Recovery Complete -Snap-->>Chain : Success -Chain->>DB : initialize_hardforks() -Chain->>DB : reindex_from_dlt() -DB-->>Chain : DLT Replay Complete -Chain->>Chain : on_sync() Complete -``` - -### Enhanced Recovery Workflow Features - -The integrated recovery workflow includes several key components: - -#### Snapshot-Based State Restoration -- **Complete State Import**: Full snapshot loading with validation and checksum verification -- **Hardfork Initialization**: Proper hardfork state initialization after snapshot import -- **LIB Promotion**: Automatic promotion of last irreversible block to snapshot head - -#### DLT Block Log Integration -- **Post-Recovery Replay**: Automatic replay of DLT block log blocks after snapshot import -- **Gap Resolution**: Seamless filling of gaps between snapshot and current blockchain state -- **Mode Detection**: Intelligent detection and handling of DLT mode operation - -#### Enhanced Error Recovery and Diagnostics -- **Graceful Degradation**: Fallback to normal operation if recovery fails -- **Comprehensive Logging**: Detailed logging for recovery process monitoring -- **Diagnostic Information**: Extensive error reporting and recovery status information - -**Section sources** -- [plugin.cpp:490-560](file://plugins/chain/plugin.cpp#L490-L560) -- [plugin.cpp:2945-2959](file://plugins/snapshot/plugin.cpp#L2945-L2959) -- [database.cpp:441-5201](file://libraries/chain/database.cpp#L441-L5201) - -## DLT Replay Integration - -**New** The snapshot plugin now includes comprehensive DLT (Distributed Ledger Technology) replay integration, enabling enhanced error handling and recovery scenarios through seamless integration with the DLT block log system. - -### DLT Replay Architecture - -The DLT replay integration provides robust block log management: - -```mermaid -flowchart TD -Start([DLT Replay Request]) --> CheckHead{"DLT Head Available?"} -CheckHead --> |No| LogEmpty["Log: No blocks in dlt_block_log"] -CheckHead --> |Yes| ValidateRange["Validate Replay Range"] -ValidateRange --> AdjustFrom["Adjust from_block if needed"] -AdjustFrom --> ReadBlocks["Read Blocks from DLT Log"] -ReadBlocks --> ProcessBlocks["Process Each Block"] -ProcessBlocks --> ApplyBlock["Apply Block to Database"] -ApplyBlock --> UpdateDatabases["Update DLT and Fork DB"] -UpdateDatabases --> CheckComplete{"More Blocks?"} -CheckComplete --> |Yes| ReadBlocks -CheckComplete --> |No| Complete([Replay Complete]) -LogEmpty --> Complete -``` - -### Enhanced DLT Replay Features - -The DLT replay integration includes several key improvements: - -#### Intelligent Block Log Management -- **Dynamic Range Adjustment**: Automatic adjustment of replay range based on DLT log availability -- **Gap Detection and Handling**: Detection and handling of gaps between snapshot and current state -- **Efficient Memory Usage**: Streaming processing of blocks to minimize memory footprint - -#### Seamless Integration -- **Database State Synchronization**: Automatic synchronization between DLT block log and database state -- **Fork Database Seeding**: Proper seeding of fork database for P2P synchronization -- **Hardfork State Preservation**: Maintenance of hardfork state during replay process - -#### Enhanced Error Resilience -- **Graceful Error Handling**: Comprehensive error handling with fallback to normal operation -- **Progress Reporting**: Real-time progress reporting and status updates -- **Resource Management**: Efficient resource management during long-running replay operations - -**Section sources** -- [database.cpp:441-5201](file://libraries/chain/database.cpp#L441-L5201) -- [plugin.cpp:542-559](file://plugins/chain/plugin.cpp#L542-L559) - -## Signal-Based DLT Block Log Reset Handling - -**New** The snapshot plugin now includes comprehensive signal-based DLT block log reset handling that automatically creates fresh snapshots when DLT block logs are reset, enabling other DLT nodes to bootstrap from the current state. - -### DLT Block Log Reset Architecture - -The signal-based DLT block log reset handling provides automatic snapshot creation: - -```mermaid -sequenceDiagram -participant DLT as DLT Block Log -participant DB as Database -participant Snap as Snapshot Plugin -participant FS as File System -DLT->>DB : reset() or truncate_before() -DB->>DB : emit dlt_block_log_was_reset() -DB->>Snap : dlt_block_log_was_reset signal -Snap->>Snap : Check DLT mode and snapshot_dir -Snap->>Snap : schedule_async_snapshot() -Snap->>Snap : snapshot_thread->async() -Snap->>FS : create_snapshot(output_path) -FS-->>Snap : snapshot_created -Snap->>Snap : cleanup_old_snapshots() -Snap-->>DB : automatic snapshot ready -``` - -### DLT Reset Handling Features - -The signal-based DLT block log reset handling includes several key improvements: - -#### Signal-Based Event Handling -- **Automatic Connection**: The snapshot plugin connects to `dlt_block_log_was_reset` signal during initialization -- **Conditional Activation**: Only activates when DLT mode is enabled and snapshot directory is configured -- **Event-Driven Creation**: Creates snapshots automatically when DLT block logs are reset - -#### Automatic Snapshot Generation -- **Fresh State Creation**: Generates snapshots at the current head block number after reset -- **Directory Management**: Places snapshots in the configured snapshot directory with proper naming -- **Async Execution**: Uses the existing asynchronous snapshot creation system to prevent blocking - -#### Integration with Existing Systems -- **Consistent Naming**: Uses the same naming convention as manual snapshots (`snapshot-block-.vizjson`) -- **Cache Updates**: Automatically updates the snapshot cache after creation -- **Cleanup Integration**: Integrates with existing snapshot cleanup mechanisms - -#### Enhanced Error Handling -- **Exception Safety**: Comprehensive error handling with proper logging -- **Guard Mechanisms**: Uses atomic flags to prevent concurrent snapshot creation -- **Thread Management**: Reuses existing dedicated snapshot thread infrastructure - -**Section sources** -- [plugin.cpp:3252-3290](file://plugins/snapshot/plugin.cpp#L3252-L3290) -- [database.cpp:4945-4947](file://libraries/chain/database.cpp#L4945-L4947) -- [database.cpp:5139-5140](file://libraries/chain/database.cpp#L5139-L5140) -- [database.hpp:337-338](file://libraries/chain/include/graphene/chain/database.hpp#L337-L338) - -## Peer-to-Peer Snapshot Synchronization - -**New** The snapshot plugin now includes comprehensive peer-to-peer snapshot synchronization capabilities for nodes with empty state, enabling automatic discovery and download of snapshots from trusted peers. - -### Enhanced P2P Synchronization Workflow - -The peer-to-peer synchronization provides automated snapshot acquisition with improved robustness: - -```mermaid -sequenceDiagram -participant Node as Empty State Node -participant Peer as Trusted Peer -participant Snap as Snapshot Plugin -Node->>Snap : Trigger P2P Sync -Snap->>Peer : Connect to Trusted Peers -Peer-->>Snap : SNAPSHOT_INFO_REPLY -Snap->>Snap : Validate Snapshot Info -Snap->>Peer : SNAPSHOT_DATA_REQUEST -loop Until Complete -Peer-->>Snap : SNAPSHOT_DATA_REPLY -Snap->>Snap : Append to Temp File -end -Snap->>Snap : Verify Checksum -Snap->>Snap : Rename to Final Path -Snap->>Snap : Load Snapshot -Snap->>Node : Sync Complete -``` - -### Enhanced P2P Synchronization Features - -The peer-to-peer synchronization system includes several key improvements: - -#### Automated Peer Discovery -- **Trusted Peer Configuration**: Support for multiple trusted peer endpoints -- **Connection Management**: Robust connection management with retry logic -- **Peer Selection Algorithms**: Intelligent peer selection and load balancing - -#### Secure and Reliable Transfer -- **Checksum Verification**: Comprehensive checksum verification for data integrity -- **Chunked Transfer**: Efficient chunked transfer with configurable chunk sizes -- **Connection Timeouts**: Proper timeout handling for network reliability - -#### Enhanced Integration with Recovery Workflow -- **Pre-Sync State Management**: Proper state management before snapshot import -- **Post-Sync Validation**: Comprehensive validation after successful transfer -- **Fallback Mechanisms**: Multiple retry attempts with progressive failure handling - -**Section sources** -- [plugin.cpp:2976-3009](file://plugins/snapshot/plugin.cpp#L2976-L3009) -- [plugin.cpp:2468-2570](file://plugins/snapshot/plugin.cpp#L2468-L2570) - -## Enhanced P2P Integration with Trusted Peers - -**New** The snapshot plugin now provides **enhanced P2P integration with trusted peer support** through automatic registration of trusted peer endpoints with the P2P layer, enabling **dual-tier soft-ban system** where trusted peers receive 5-minute bans instead of the default 1-hour duration. - -### Automatic Trusted Peer Endpoint Registration - -The enhanced P2P integration provides seamless trusted peer endpoint registration: - -```mermaid -sequenceDiagram -participant SnapPlug as Snapshot Plugin -participant P2PPlug as P2P Plugin -participant NetNode as Network Node -participant Peer as Trusted Peer -SnapPlug->>P2PPlug : get_trusted_snapshot_peers() -P2PPlug->>SnapPlug : trusted_eps (IP : port list) -P2PPlug->>NetNode : set_trusted_peer_endpoints(trusted_eps) -NetNode->>NetNode : Parse IP addresses from endpoints -NetNode->>NetNode : Store as uint32_t raw IPs -NetNode->>Peer : Soft-ban Duration Check -Peer-->>NetNode : is_trusted_peer(peer)? -alt Trusted Peer -NetNode->>Peer : 5-minute soft-ban (TRUSTED_SOFT_BAN_DURATION_SEC) -else Untrusted Peer -NetNode->>Peer : 1-hour soft-ban (SOFT_BAN_DURATION_SEC) -end -``` - -### Dual-Tier Soft-Ban System - -The enhanced P2P integration implements a comprehensive dual-tier soft-ban system: - -#### Soft-Ban Duration Constants -- **Default Soft-Ban Duration**: 3600 seconds (1 hour) for untrusted peers -- **Trusted Peer Soft-Ban Duration**: 300 seconds (5 minutes) for trusted peers -- **Automatic Application**: Soft-ban duration determined by peer trust status - -#### Trusted Peer Detection -- **Endpoint Parsing**: Extracts IP addresses from "host:port" endpoint strings -- **Raw IP Storage**: Stores trusted peer IPs as 32-bit integers for O(1) lookup -- **Dynamic Updates**: Supports runtime updates to trusted peer lists - -#### Enhanced P2P Integration Features - -#### Automatic Registration Process -- **Plugin Discovery**: P2P plugin automatically discovers snapshot plugin -- **Endpoint Retrieval**: Retrieves trusted snapshot peer endpoints -- **Registration Automation**: Registers endpoints with network node automatically - -#### Reduced Soft-Ban Duration Benefits -- **Faster Recovery**: Trusted peers recover from soft-bans in 5 minutes instead of 1 hour -- **Improved Reliability**: Better handling of legitimate snapshot requests from trusted peers -- **Network Efficiency**: Reduced downtime for trusted peers during snapshot operations - -#### Enhanced Trust Enforcement -- **Consistent Application**: Soft-ban duration applies consistently across all P2P operations -- **Performance Optimization**: O(1) trust lookup using raw IP addresses -- **Scalability**: Efficient handling of large numbers of trusted peers - -**Section sources** -- [p2p_plugin.cpp:689-697](file://plugins/p2p/p2p_plugin.cpp#L689-L697) -- [node.cpp:5241-5274](file://libraries/network/node.cpp#L5241-L5274) -- [node.hpp:284-290](file://libraries/network/include/graphene/network/node.hpp#L284-L290) -- [plugin.hpp:86-88](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L86-L88) - -## Watchdog and Stalled Sync Detection - -**Updated** The snapshot plugin now includes comprehensive watchdog mechanisms and stalled sync detection for DLT mode, ensuring server reliability and continuous operation through automatic monitoring and recovery. - -### Enhanced Watchdog Architecture - -The watchdog system provides continuous monitoring and automatic recovery: - -```mermaid -sequenceDiagram -participant Watchdog as Watchdog Thread -participant Server as Snapshot Server -participant AcceptLoop as Accept Loop -Watchdog->>Watchdog : Start monitoring loop -loop Every 30 Seconds -Watchdog->>Server : Check last_accept_activity -Server-->>Watchdog : Last activity timestamp -Watchdog->>Watchdog : Calculate idle time -alt Accept Loop Dead -Watchdog->>Server : Restart accept loop -Server->>Server : Clean up old state -Server->>Server : Create new server socket -Server->>Server : Reset anti-spam state -Server->>Server : Start fresh accept loop -else Accept Loop Alive -Watchdog->>Watchdog : Continue monitoring -end -end -``` - -### Enhanced Stalled Sync Detection for DLT Mode - -The stalled sync detection provides DLT mode monitoring with improved reliability: - -```mermaid -sequenceDiagram -participant Monitor as Stalled Sync Monitor -participant Chain as Chain Plugin -participant DB as Database -Monitor->>Monitor : Start monitoring loop -loop Every Check Interval -Monitor->>DB : Check last_block_received_time -DB-->>Monitor : Last block reception time -Monitor->>Monitor : Calculate idle duration -alt Sync Stalled -Monitor->>Chain : Trigger Recovery Actions -Chain->>Chain : Initiate P2P Sync -Chain->>Chain : Attempt Snapshot Download -else Sync Active -Monitor->>Monitor : Continue monitoring -end -end -``` - -### Enhanced Watchdog and Stalled Sync Features - -The watchdog and stalled sync detection include several key improvements: - -#### Dead Accept Loop Detection -- **Monitoring Interval**: Checks every 30 seconds -- **Activity Tracking**: Monitors last accept loop activity timestamp -- **Automatic Restart**: Restarts dead accept loops with full cleanup - -#### Enhanced Stalled Sync Detection for DLT Mode -- **Idle Time Monitoring**: Monitors time since last block reception -- **Timeout Configuration**: Configurable timeout periods for stall detection -- **Recovery Action Triggering**: Automatic triggering of recovery actions when stalls detected - -#### Improved Graceful Recovery -- **State Cleanup**: Resets all anti-spam state to prevent corruption -- **Socket Recreation**: Creates fresh server sockets for new connections -- **Thread Safety**: Properly shuts down dedicated server thread before restart - -#### **Dedicated Threading for Stalled Sync Detection** -- **Thread Isolation**: Uses dedicated `fc::thread` for stalled sync operations -- **Fiber Scheduler Independence**: Prevents fc fibers from stalling on main thread blocked in io_serv->run() -- **Background Processing**: Runs stalled sync detection in background without affecting main thread - -#### **Enhanced Error Handling for Snapshot Download Operations** -- **Continuity Guarantee**: Ensures stalled sync monitoring continues running even when snapshot loading fails -- **Graceful Recovery**: Restarts stalled sync detection after failed snapshot download attempts -- **Improved Exception Handling**: Enhanced exception handling for both fc::exception and std::exception types during snapshot download attempts - -**Section sources** -- [plugin.cpp:735-740](file://plugins/snapshot/plugin.cpp#L735-L740) -- [plugin.cpp:1814-1862](file://plugins/snapshot/plugin.cpp#L1814-L1862) -- [plugin.cpp:772-785](file://plugins/snapshot/plugin.cpp#L772-L785) -- [plugin.cpp:1595-1624](file://plugins/snapshot/plugin.cpp#L1595-L1624) - -## P2P Stale Sync Detection - -**New** The P2P plugin provides a lightweight recovery mechanism that automatically detects and recovers from network stalls without requiring snapshot downloads. This complements the snapshot plugin's stalled sync detection by providing immediate recovery for temporary network issues. - -### How It Works - -When enabled, the P2P plugin tracks the last time a block was received via the network. A background task checks every 30 seconds whether the elapsed time exceeds the configured timeout. If a stall is detected, the node performs three recovery actions in sequence: - -1. **Reset sync from LIB** — The P2P layer's sync start point is reset to the last irreversible block (LIB). This ensures the node resumes from a safe, fork-proof position instead of potentially chasing a dead fork. -2. **Resync with connected peers** — The node explicitly restarts synchronization with all currently connected peers by sending fresh `fetch_blockchain_item_ids_message` requests. -3. **Reconnect seed peers** — All seed nodes from `p2p-seed-node` config are re-added to the connection queue and reconnection is attempted for any that were disconnected. - -This is complementary to the snapshot plugin's stalled sync detection (which downloads a new snapshot). The P2P stale recovery is faster and less disruptive — it only adjusts sync state and reconnects peers, without requiring any state reload. - -### Configuration - -```ini -# Enable P2P stale sync detection (default: false) -p2p-stale-sync-detection = true - -# Timeout in seconds before recovery triggers (default: 120 = 2 minutes) -p2p-stale-sync-timeout-seconds = 120 -``` - -### Use Cases - -- **Temporary network partition**: When peers become unreachable for short periods, the node automatically recovers without manual intervention. -- **Peer disconnections**: If connected peers disconnect unexpectedly, the node can quickly reconnect and resume synchronization. -- **Initial sync delays**: During heavy network traffic or node startup, the node can recover from temporary stalls. - -### Comparison with Snapshot Stalled Sync Detection - -| Feature | P2P Stale Sync | Snapshot Stalled Sync | -|---------|---------------|----------------------| -| Plugin | P2P | Snapshot | -| Trigger | No blocks received for timeout | No blocks received for timeout | -| Recovery action | Reset sync + reconnect peers | Download newer snapshot + reload state | -| Timeout default | 120 seconds | 5 minutes | -| Use case | Temporary network partition, peer disconnections | Node far behind, peers lack old blocks | -| DLT mode | Works for all nodes | Designed for DLT mode | - -Both can be enabled independently. For DLT nodes, the snapshot detection provides deeper recovery (fresh state), while P2P detection handles transient connectivity issues without state reload. - -**Section sources** -- [snapshot-plugin.md:339-374](file://documentation/snapshot-plugin.md#L339-L374) -- [p2p_plugin.cpp:585-649](file://plugins/p2p/p2p_plugin.cpp#L585-L649) -- [p2p_plugin.cpp:673-677](file://plugins/p2p/p2p_plugin.cpp#L673-L677) -- [p2p_plugin.cpp:744-755](file://plugins/p2p/p2p_plugin.cpp#L744-L755) - -## Emergency Consensus Handling - -**Updated** The snapshot plugin now includes comprehensive emergency consensus handling with forward-compatible fields for emergency consensus activation. - -### Enhanced Emergency Consensus Fields - -The dynamic global property object now includes enhanced emergency consensus fields for improved network resilience: - -```mermaid -flowchart TD -Start([Dynamic Global Property Import]) --> CheckFields["Check for Emergency Consensus Fields"] -CheckFields --> HasFields{"Emergency Consensus Fields Present?"} -HasFields --> |Yes| ImportFields["Import emergency_consensus_active
and emergency_consensus_start_block"] -HasFields --> |No| SetDefaults["Set defaults:
emergency_consensus_active = false
emergency_consensus_start_block = 0"] -ImportFields --> ContinueProcessing["Continue with Normal Processing"] -SetDefaults --> ContinueProcessing -ContinueProcessing --> Complete([Import Complete]) -``` - -### Enhanced Forward-Compatible Design - -The emergency consensus handling implements a forward-compatible approach: - -- **Backward Compatibility**: Nodes without emergency consensus fields gracefully handle snapshots from newer nodes -- **Default Values**: Missing fields are assigned sensible defaults -- **Runtime Activation**: Emergency consensus can be activated dynamically without requiring snapshot regeneration - -**Section sources** -- [plugin.cpp:165-176](file://plugins/snapshot/plugin.cpp#L165-L176) - -## Enhanced Anti-Spam Protection - -**Updated** The snapshot plugin now includes comprehensive anti-spam protection with new configuration options and improved trust enforcement mechanisms. - -### Enhanced Anti-Spam Architecture - -The anti-spam system provides multiple layers of protection against abuse: - -```mermaid -flowchart TD -Start([Incoming Connection]) --> CheckAntiSpam{"Anti-Spam Enabled?"} -CheckAntiSpam --> |No| CheckTrust{"Allow Only Trusted?"} -CheckAntiSpam --> |Yes| CheckConcurrent{"Concurrent Connections < 5?"} -CheckTrust --> |Yes| ValidateTrust{"IP in Trusted List?"} -CheckTrust --> |No| CheckSession{"Active Sessions < 3/IP?"} -CheckConcurrent --> |No| DenyMaxConnections["Send DENY_MAX_CONNECTIONS"] -CheckConcurrent --> |Yes| CheckSession -CheckSession --> |No| DenySessionLimit["Send DENY_SESSION_LIMIT"] -CheckSession --> |Yes| CheckRate{"Connections < 10/Hour/IP?"} -CheckRate --> |No| DenyRateLimited["Send DENY_RATE_LIMITED"] -CheckRate --> |Yes| Accept["Accept Connection"] -ValidateTrust --> |No| DenyUntrusted["Send DENY_UNTRUSTED"] -ValidateTrust --> |Yes| Accept -Accept --> Process["Process Snapshot Request"] -``` - -**Updated** The anti-spam system has been enhanced with increased limits to improve service accessibility while maintaining security controls. The new configuration values are: - -- **Maximum sessions per IP**: Increased from 2 to 3 sessions per IP -- **Maximum connections per hour**: Increased from 6 to 10 connections per hour per IP - -These changes provide better support for legitimate users while maintaining effective protection against abuse. - -### Enhanced New Configuration Options - -The anti-spam system introduces several new configuration options: - -#### disable-snapshot-anti-spam -- **Purpose**: Disable all anti-spam checks for snapshot serving -- **Use Case**: Trusted networks where anti-spam protection is not needed -- **Security Implications**: Removes all rate limiting and session management - -#### snapshot-serve-allow-ip -- **Purpose**: Specify which client IPs are allowed to connect for snapshot serving -- **Use Case**: Private networks with controlled access -- **Implementation**: Maintains whitelist of approved client IP addresses - -### Enhanced Trust Enforcement - -The trust enforcement system now operates independently of anti-spam protection: - -- **Separate Logic**: Trust validation occurs before anti-spam checks -- **Whitelist Management**: Dynamic updates to trusted IP lists -- **Consistent Enforcement**: Anti-spam rules apply uniformly regardless of trust status - -**Section sources** -- [plugin.cpp:1587-1596](file://plugins/snapshot/plugin.cpp#L1587-L1596) -- [plugin.cpp:1610-1620](file://plugins/snapshot/plugin.cpp#L1610-L1620) -- [plugin.cpp:1812-1877](file://plugins/snapshot/plugin.cpp#L1812-L1877) - -## Access Control and Security Mechanisms - -**Updated** The snapshot plugin now includes comprehensive access control mechanisms with detailed denial reasons for enhanced security and resource management. - -### Enhanced Access Control Architecture - -The access control system provides multiple layers of security enforcement: - -```mermaid -flowchart TD -Start([Incoming Connection]) --> CheckTrust{"Allow Only Trusted?"} -CheckTrust --> |Yes| ValidateTrust{"IP in Trusted List?"} -CheckTrust --> |No| CheckConcurrent{"Concurrent Connections < 5?"} -ValidateTrust --> |No| DenyUntrusted["Send DENY_UNTRUSTED"] -ValidateTrust --> |Yes| CheckConcurrent -CheckConcurrent --> |No| DenyMaxConnections["Send DENY_MAX_CONNECTIONS"] -CheckConcurrent --> |Yes| CheckSession{"Active Sessions < 3/IP?"} -CheckSession --> |No| DenySessionLimit["Send DENY_SESSION_LIMIT"] -CheckSession --> |Yes| CheckRate{"Connections < 10/Hour/IP?"} -CheckRate --> |No| DenyRateLimited["Send DENY_RATE_LIMITED"] -CheckRate --> |Yes| Accept["Accept Connection"] -DenyUntrusted --> Close["Close Connection"] -DenyMaxConnections --> Close -DenySessionLimit --> Close -DenyRateLimited --> Close -Accept --> Process["Process Snapshot Request"] -``` - -**Updated** The access control system now enforces the enhanced anti-spam limits with improved session management and rate limiting: - -- **Maximum concurrent connections**: 5 simultaneous connections -- **Per-IP session limit**: 3 active sessions per IP (increased from 2) -- **Rate limit**: 10 connections per hour per IP (increased from 6) - -These enhanced limits provide better support for legitimate users while maintaining effective protection against abuse. - -### Enhanced Denial Reason Codes - -The system provides specific denial reasons for different violation types: - -| Reason Code | Enum Value | Description | -|-------------|------------|-------------| -| `deny_untrusted` | 1 | IP address not in trusted list | -| `deny_max_connections` | 2 | Server has reached maximum concurrent connections (5) | -| `deny_session_limit` | 3 | Too many active sessions from this IP (3 per IP limit) | -| `deny_rate_limited` | 4 | Too many connections per hour from this IP (10 per hour limit) | - -### Enhanced Anti-Spam Protection Features - -The access control system implements multiple enhanced anti-spam mechanisms: - -#### Connection Throttling -- **Maximum Concurrent Connections**: 5 simultaneous connections -- **Per-IP Session Limit**: 3 active sessions per IP (increased from 2) -- **Rate Limiting**: 10 connections per hour per IP (increased from 6) - -#### Enhanced Session Management -- **Active Session Tracking**: Monitors concurrent sessions per IP -- **Connection History**: Tracks connection timestamps for rate limiting -- **RAII Session Guards**: Ensures proper cleanup of session resources - -#### Enhanced Trust Enforcement -- **Trusted IP Validation**: Maintains whitelist of approved IP addresses -- **Dynamic Trust Updates**: Supports runtime updates to trusted peer lists -- **Consistent Enforcement**: Anti-spam rules apply uniformly to all connections - -**Section sources** -- [plugin.hpp:24-34](file://plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#L24-L34) -- [plugin.cpp:1587-1596](file://plugins/snapshot/plugin.cpp#L1587-L1596) -- [plugin.cpp:1610-1620](file://plugins/snapshot/plugin.cpp#L1610-L1620) -- [plugin.cpp:1812-1877](file://plugins/snapshot/plugin.cpp#L1812-L1877) - -## Integration with Chain Plugin - -**Updated** The snapshot plugin has been integrated with the chain plugin through a sophisticated callback system that enables programmatic state restoration, enhanced P2P synchronization, and comprehensive recovery workflow integration. - -The integration works through three key callback mechanisms registered during plugin initialization: - -### Enhanced Snapshot Loading Callback -```mermaid -sequenceDiagram -participant Chain as Chain Plugin -participant Snapshot as Snapshot Plugin -participant DB as Database -Chain->>Chain : Detect --snapshot option -Chain->>Snapshot : Register snapshot_load_callback() -Snapshot->>Chain : Callback registered -Chain->>Chain : During startup, check for callback -Chain->>Snapshot : Execute snapshot_load_callback() -Snapshot->>DB : load_snapshot_from_snapshot_file() -DB-->>Snapshot : State restored -Snapshot-->>Chain : Success -Chain->>Chain : Continue normal startup -``` - -### Enhanced Snapshot Creation Callback -The snapshot plugin registers a callback that executes during chain plugin startup to create snapshots after full database load, ensuring proper state capture. - -### Enhanced P2P Snapshot Sync Callback -For nodes with empty state, the snapshot plugin registers a callback that downloads and loads snapshots from trusted peers before normal P2P synchronization begins. Enhanced with automatic retry logic, improved peer selection algorithms, comprehensive error handling, **automatic trusted peer endpoint registration with the P2P layer**, **signal-based DLT block log reset handling**, **dedicated threading for stalled sync detection**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, and **stale snapshot detection**. - -### Enhanced Recovery Workflow Integration -**New** The chain plugin now includes comprehensive recovery workflow integration that coordinates with the snapshot plugin for automatic recovery from corrupted states using snapshot-based restoration and DLT block log replay. - -**Section sources** -- [plugin.cpp:2598-2680](file://plugins/snapshot/plugin.cpp#L2598-L2680) -- [plugin.cpp:364-432](file://plugins/chain/plugin.cpp#L364-L432) - -## Dependency Analysis - -The snapshot plugin has carefully managed dependencies to ensure modularity and maintainability: - -```mermaid -graph LR -subgraph "Core Dependencies" -A[graphene_chain] --> D[snapshot_plugin] -B[appbase] --> D -C[chainbase] --> D -E[fc] --> D -end -subgraph "Blockchain Dependencies" -F[graphene_protocol] --> D -G[graphene_witness] --> D -H[graphene_json_rpc] --> D -I[graphene_time] --> D -end -subgraph "External Libraries" -J[Boost.Filesystem] --> D -K[zlib] --> D -L[OpenSSL] --> D -end -subgraph "Build System" -M[CMake] --> N[Target: graphene_snapshot] -N --> O[Static Library] -N --> P[Shared Library] -end -``` - -**Updated** The dependency graph reveals a clean separation between core blockchain functionality and plugin-specific features. The plugin relies on established VIZ infrastructure while maintaining independence from external systems, demonstrating the benefits of the modular architecture. Recent enhancements include watchdog dependencies, **signal-based DLT block log reset handling**, enhanced P2P integration, automatic snapshot discovery, comprehensive recovery workflow integration, asynchronous execution system dependencies, **enhanced P2P integration with trusted peer support**, **dedicated threading for stalled sync detection**, **P2P stale sync detection**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection**, and **enhanced exception handling**. - -**Diagram sources** -- [CMakeLists.txt:27-38](file://plugins/snapshot/CMakeLists.txt#L27-L38) - -**Section sources** -- [CMakeLists.txt:27-38](file://plugins/snapshot/CMakeLists.txt#L27-L38) - -## Performance Considerations - -The snapshot plugin implements several performance optimization strategies through its modular architecture: - -### Asynchronous Execution Performance -- **Thread Isolation**: Dedicated snapshot thread prevents main thread blocking -- **Atomic Operations**: Minimal synchronization overhead with atomic flags -- **Background Processing**: Long-running operations occur outside critical sections - -### Compression and Storage Efficiency -- Uses zlib compression to reduce snapshot file sizes by approximately 70-80% -- Implements streaming compression/decompression to minimize memory usage -- Supports automatic snapshot rotation to manage storage requirements - -### Network Transfer Optimization -- Chunked transfer protocol with configurable chunk sizes (up to 1MB) -- Connection pooling and reuse for efficient peer communication -- Anti-spam measures prevent resource exhaustion during transfers - -### Database Operation Optimization -- Uses strong read locks during snapshot creation to ensure consistency -- Implements validator-aware deferral to prevent missed block production slots -- Optimized object serialization minimizes CPU overhead - -### Enhanced Memory Management -- Streaming JSON parsing prevents loading entire snapshots into memory -- Efficient object copying mechanisms handle complex data structures -- Automatic cleanup of temporary files and resources - -### **Signal-Based DLT Block Log Reset Performance** -- **Event-Driven Creation**: Automatic snapshot creation only when DLT block logs are reset -- **Async Execution**: Reuses existing asynchronous snapshot creation infrastructure -- **Minimal Overhead**: Single atomic flag check during DLT reset events -- **Thread Safety**: Dedicated snapshot thread prevents blocking during reset events - -### **Enhanced P2P Integration Performance** -- **Automatic Registration**: Eliminates manual configuration overhead -- **Efficient Lookup**: O(1) trust validation using raw IP addresses -- **Reduced Soft-Ban Impact**: Faster recovery for trusted peers reduces network downtime -- **Optimized Communication**: Streamlined trusted peer endpoint management - -### **Enhanced Logging Performance** -- **ANSI Color Codes**: Provides visual distinction between log levels without performance overhead -- **Level-Based Coloring**: Green for success, orange for warnings, yellow for informational messages -- **Minimal Processing Overhead**: Color code injection occurs only when terminal supports color output - -### **P2P Stale Sync Detection Performance** -- **Lightweight Monitoring**: Minimal CPU overhead through efficient background task scheduling -- **LIB Reset Optimization**: Fast sync reset using pre-computed block IDs -- **Selective Peer Reconnection**: Only reconnects seed nodes that were previously connected -- **30-second Check Interval**: Balances responsiveness with minimal resource usage - -### **Dedicated Threading for Stalled Sync Detection Performance** -- **Thread Isolation**: Dedicated fc::thread prevents main thread blocking -- **Fiber Scheduler Independence**: Prevents fc fibers from stalling on main thread blocked in io_serv->run() -- **Background Processing**: Runs stalled sync detection in background without affecting main thread -- **Minimal Overhead**: Single dedicated thread for all stalled sync operations - -### **Automatic Gap Detection for DLT Block Log Initialization Performance** -- **Intelligent Gap Detection**: Prevents index position mismatch assertions through early detection -- **Automatic Reset Mechanism**: Seamlessly resets DLT block log when gaps are detected -- **Minimal Performance Impact**: Gap detection adds negligible overhead during snapshot import -- **Thread-Safe Operations**: Uses atomic operations to prevent race conditions during gap detection - -### **Enhanced Error Handling Performance** -- **Exception Safety**: Comprehensive error handling prevents cascading failures -- **Graceful Degradation**: Continues normal operation even when snapshot download fails -- **Minimal Overhead**: Enhanced exception handling adds negligible performance impact -- **Improved Resource Management**: Proper cleanup prevents resource leaks during error scenarios - -### **Improved Undo Stack Management Performance** -- **Efficient Cleanup**: db.undo_all() call prevents undo stack corruption -- **Minimal Performance Impact**: Undo stack management adds negligible overhead -- **Thread Safety**: Atomic operations ensure safe undo stack manipulation -- **Hot-Reload Optimization**: Prevents undo stack issues during state reload scenarios - -### **Enhanced Anti-Spam Configuration Performance** -- **Optimized Limits**: Enhanced limits provide better user experience with minimal overhead -- **Efficient Rate Limiting**: Sliding window algorithm minimizes memory usage -- **Thread-Safe Operations**: Atomic counters prevent race conditions -- **Minimal Processing Overhead**: Anti-spam checks add negligible CPU overhead - -### **Stale Snapshot Detection Performance** -- **Intelligent Gap Detection**: Prevents serving broken snapshots with gaps through early detection -- **Automatic Fresh Snapshot Creation**: Urgent snapshot creation eliminates sync gaps without manual intervention -- **Minimal Performance Impact**: Stale detection adds negligible overhead during plugin initialization -- **Thread-Safe Operations**: Uses atomic flags to prevent race conditions during detection - -**Updated** The modular architecture enhances performance by enabling independent optimization of each layer while maintaining system coherence. The watchdog mechanism, **signal-based DLT block log reset handling**, enhanced anti-spam protections, automatic snapshot discovery, integrated recovery workflow, asynchronous execution system, comprehensive error handling, **enhanced P2P integration with trusted peer support**, **dedicated threading for stalled sync detection**, **P2P stale sync detection**, **automatic gap detection for DLT block log initialization**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **enhanced anti-spam configuration**, **stale snapshot detection**, and **enhanced exception handling** are designed to minimize performance impact while providing comprehensive functionality. Recent improvements include dedicated server thread optimizations, DLT replay efficiency, enhanced error handling performance, validator-aware deferral optimization, **efficient dual-tier soft-ban system implementation**, **signal-based DLT block log reset handling**, **optimized P2P stale sync detection with minimal overhead**, **dedicated threading for stalled sync detection with thread isolation**, **intelligent gap detection preventing index position mismatch assertions**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection with urgent fresh snapshot creation**, and **enhanced exception handling**. - -### Enhanced Security Performance Considerations -- Access control checks are performed efficiently using hash maps for IP lookups -- Session tracking uses atomic counters for thread-safe operations -- Rate limiting maintains minimal memory overhead through sliding window algorithm -- Watchdog mechanism operates with minimal CPU overhead through efficient monitoring -- Recovery workflow includes performance-optimized snapshot validation and checksum verification -- Asynchronous execution system minimizes main thread blocking time -- **Signal-based DLT block log reset handling provides efficient event-driven snapshot creation** -- **Enhanced P2P integration provides efficient trust validation with O(1) lookup performance** -- **Enhanced logging system provides efficient colored output with minimal performance impact** -- **P2P stale sync detection operates with minimal overhead through optimized background tasks** -- **Dedicated threading for stalled sync detection prevents main thread blocking and fiber stalling** -- **Automatic gap detection prevents index position mismatch assertions with minimal performance impact** -- **Enhanced error handling for snapshot download operations ensures continuous monitoring** -- **Improved undo stack management prevents database state corruption** -- **Stale snapshot detection prevents serving broken snapshots with gaps** -- **Enhanced exception handling provides robust error recovery mechanisms** - -## Troubleshooting Guide - -### Enhanced Common Issues and Solutions - -**Snapshot Creation Failures** -- **Symptom**: Snapshot creation fails with database lock errors -- **Cause**: validator production conflicts with snapshot creation -- **Solution**: Configure validator-aware deferral or schedule snapshots during maintenance windows - -**Enhanced Asynchronous Execution Issues** -- **Symptom**: Snapshot creation appears stuck or slow -- **Cause**: Main thread blocked by synchronous operations -- **Solution**: Verify dedicated snapshot thread is running and atomic flags are properly managed - -**Enhanced Network Transfer Problems** -- **Symptom**: Peers fail to respond to snapshot requests -- **Cause**: Firewall restrictions or anti-spam protection -- **Solution**: Verify port accessibility and adjust anti-spam thresholds - -**Memory Issues During Loading** -- **Symptom**: Loading fails due to insufficient memory -- **Cause**: Large snapshot files exceeding available RAM -- **Solution**: Use streaming loading or increase system resources - -**Checksum Validation Errors** -- **Symptom**: Snapshot loading fails with checksum mismatch -- **Cause**: Corrupted snapshot file or tampering -- **Solution**: Recreate snapshot from source or download from trusted peer - -**Enhanced Automatic Snapshot Discovery Failures** -- **Symptom**: --snapshot-auto-latest option fails to find snapshots -- **Cause**: Incorrect snapshot directory configuration or malformed filenames -- **Solution**: Verify snapshot directory path and filename naming conventions - -**Enhanced Recovery Mode Issues** -- **Symptom**: --replay-from-snapshot fails to recover from corrupted state -- **Cause**: Missing snapshot file or incompatible snapshot format -- **Solution**: Verify snapshot file existence and compatibility, check DLT block log availability - -**Enhanced DLT Replay Failures** -- **Symptom**: DLT replay fails during recovery process -- **Cause**: Corrupted DLT block log or insufficient disk space -- **Solution**: Check DLT block log integrity, verify sufficient disk space, review error logs - -**Enhanced Watchdog and Server Issues** -- **Symptom**: Server appears to stop accepting connections -- **Cause**: Accept loop fiber died or became unresponsive -- **Solution**: Watchdog automatically restarts accept loop with full cleanup - -**Enhanced Error Handling Issues** -- **Symptom**: Unlinkable block exceptions during snapshot import -- **Cause**: Improper LIB promotion or fork database state -- **Solution**: Verify LIB promotion to head block and fork database seeding - -**Enhanced P2P Integration Issues** -- **Symptom**: Trusted peers still receiving 1-hour soft-bans instead of 5-minute soft-bans -- **Cause**: P2P plugin not properly registering trusted peer endpoints -- **Solution**: Add client IP to trusted list or disable trust enforcement - -**Enhanced Trusted Peer Registration Issues** -- **Symptom**: P2P plugin fails to register trusted peer endpoints -- **Cause**: Snapshot plugin not providing trusted peer list or P2P plugin startup order issues -- **Solution**: Check snapshot plugin configuration and verify P2P plugin initialization sequence - -**Enhanced Snapshot Directory Creation Issues** -- **Symptom**: Snapshot creation fails with directory not found errors -- **Cause**: Automatic directory creation not working or permission issues -- **Solution**: Verify snapshot directory permissions and manual creation if needed - -**Enhanced Logging Color Issues** -- **Symptom**: Log messages appear without color codes -- **Cause**: Terminal not supporting ANSI color codes or color output disabled -- **Solution**: Check terminal capabilities or disable color output in configuration - -**Enhanced P2P Stale Sync Detection Issues** -- **Symptom**: P2P stale sync detection not triggering recovery actions -- **Cause**: Timeout too low or P2P plugin not properly tracking last block received time -- **Solution**: Increase `p2p-stale-sync-timeout-seconds`, verify P2P plugin initialization - -**Enhanced DLT Block Log Reset Handling Issues** -- **Symptom**: Automatic snapshots not created after DLT block log reset -- **Cause**: DLT mode not enabled or snapshot directory not configured -- **Solution**: Verify DLT mode configuration and snapshot directory settings - -**Enhanced Anti-Spam Configuration Issues** -- **Symptom**: Users experiencing connection denials despite legitimate usage -- **Cause**: Anti-spam limits too restrictive with new values (3 sessions/IP, 10 connections/hour/IP) -- **Solution**: Review anti-spam configuration, consider increasing limits for legitimate use cases, monitor connection patterns - -**Enhanced Stalled Sync Detection Issues** -- **Symptom**: Stalled sync detection not functioning properly -- **Cause**: Main thread blocked in io_serv->run() preventing fc fiber execution -- **Solution**: Verify dedicated stalled sync thread is running and properly configured - -**Enhanced Automatic Gap Detection Issues** -- **Symptom**: Index position mismatch assertions during DLT block log initialization -- **Cause**: Gap between DLT block log head and snapshot head not properly detected -- **Solution**: Verify DLT block log gap detection logic and automatic reset mechanism - -**Enhanced Error Handling for Snapshot Download Operations Issues** -- **Symptom**: Stalled sync monitoring stops when snapshot download fails -- **Cause**: Missing exception handling for snapshot download attempts -- **Solution**: Verify enhanced error handling is properly catching fc::exception and std::exception types - -**Enhanced Undo Stack Management Issues** -- **Symptom**: Database state corruption during snapshot loading -- **Cause**: Missing db.undo_all() call before set_revision operations -- **Solution**: Verify proper undo stack management with db.undo_all() before set_revision - -**Enhanced Exception Handling Issues** -- **Symptom**: Snapshot download failures not properly handled -- **Cause**: Missing comprehensive exception handling for both fc::exception and std::exception types -- **Solution**: Verify enhanced exception handling covers all error scenarios during snapshot download attempts - -**Enhanced Hot-Reload Scenario Issues** -- **Symptom**: Stalled sync detection fails during hot-reload scenarios -- **Cause**: Undo stack not properly cleared before import operations -- **Solution**: Verify db.undo_all() is called before import operations during hot-reload scenarios - -**Enhanced Database State Cleanup Issues** -- **Symptom**: Database state inconsistency after snapshot loading -- **Cause**: Missing proper cleanup of multi-instance objects during hot-reload -- **Solution**: Verify comprehensive object clearing for hot-reload scenarios before import operations - -**Enhanced Stale Snapshot Detection Issues** -- **Symptom**: Stale snapshot detection not working properly -- **Cause**: needs_fresh_snapshot flag not being set or applied_block signal not connecting -- **Solution**: Verify stale snapshot detection logic, check DLT block log start block detection, and ensure urgent fresh snapshot creation is triggered - -**Enhanced Urgent Fresh Snapshot Creation Issues** -- **Symptom**: Urgent fresh snapshots not being created when stale snapshot detected -- **Cause**: needs_fresh_snapshot flag not reset or validator-aware deferral interfering -- **Solution**: Verify needs_fresh_snapshot flag management, check validator-aware deferral logic, and ensure proper snapshot creation scheduling - -**Enhanced Diagnostic Tools** - -The plugin includes comprehensive enhanced diagnostic capabilities: - -- **Trusted Seeds Test**: Validates connectivity and performance of configured peers -- **Stalled Sync Detection**: Automatically recovers from network partitions -- **Watchdog Monitoring**: Real-time server health and accept loop status -- **Access Control Logging**: Detailed logs for denial reasons and security events -- **Emergency Consensus Monitoring**: Tracks emergency consensus activation status -- **Recovery Workflow Diagnostics**: Comprehensive logging for recovery process monitoring -- **DLT Replay Status**: Real-time monitoring of DLT replay progress and status -- **Asynchronous Execution Monitoring**: Tracks snapshot creation progress and thread health -- **Signal-Based DLT Reset Handling**: Monitors DLT block log reset events and automatic snapshot creation -- **Enhanced P2P Integration Diagnostics**: Monitors trusted peer endpoint registration and soft-ban duration application -- **Snapshot Directory Management**: Monitors automatic directory creation and cleanup processes -- **Enhanced Logging Diagnostics**: Monitors ANSI color code application and terminal compatibility -- **P2P Stale Sync Detection Diagnostics**: Monitors LIB reset, peer reconnection, and seed node management -- **Enhanced Anti-Spam Configuration Diagnostics**: Monitors session limits, rate limiting, and connection patterns -- **Dedicated Threading Diagnostics**: Monitors stalled sync thread health and operation status -- **Automatic Gap Detection Diagnostics**: Monitors DLT block log gap detection and automatic reset operations -- **Enhanced Error Handling Diagnostics**: Monitors exception handling for snapshot download operations -- **Undo Stack Management Diagnostics**: Monitors database state cleanup and undo stack operations -- **Enhanced Exception Handling Diagnostics**: Monitors comprehensive exception handling coverage -- **Stale Snapshot Detection Diagnostics**: Monitors gap detection logic and urgent fresh snapshot creation -- **Urgent Fresh Snapshot Creation Diagnostics**: Monitors needs_fresh_snapshot flag management and snapshot creation scheduling - -**Updated** The modular architecture provides enhanced diagnostic capabilities through separate layers for serialization, networking, database operations, security controls, recovery workflows, asynchronous execution, watchdog monitoring, **signal-based DLT block log reset handling**, **enhanced P2P integration**, **enhanced error handling**, **improved undo stack management**, **stale snapshot detection**, and **enhanced exception handling**. Recent improvements include watchdog monitoring, **signal-based DLT block log reset handling diagnostics**, enhanced P2P fallback diagnostics, emergency consensus status tracking, comprehensive recovery workflow diagnostics, DLT replay status monitoring, asynchronous execution health monitoring, **P2P stale sync detection diagnostics**, **dedicated threading diagnostics**, **automatic gap detection diagnostics**, **enhanced error handling diagnostics**, **undo stack management diagnostics**, **stale snapshot detection diagnostics**, **urgent fresh snapshot creation diagnostics**, and **enhanced exception handling diagnostics**. - -**Section sources** -- [plugin.cpp:2294-2464](file://plugins/snapshot/plugin.cpp#L2294-L2464) -- [plugin.cpp:1378-1464](file://plugins/snapshot/plugin.cpp#L1378-L1464) - -## Conclusion - -The Snapshot Plugin System represents a sophisticated solution for blockchain state synchronization that significantly improves the VIZ node bootstrapping experience. Through careful architectural design, comprehensive feature coverage, and robust error handling, it enables efficient deployment and scaling of VIZ-based applications. - -**Updated** The recent enhancements with comprehensive snapshot plugin configuration supporting multiple trusted snapshot peers, snapshot scheduling parameters, serving options, watchdog monitoring, automatic snapshot discovery, integrated recovery workflow, enhanced anti-spam protection, **signal-based DLT block log reset handling**, **enhanced P2P integration with trusted peer support**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection**, and **enhanced exception handling** have significantly strengthened the security, reliability, and resource management capabilities of the snapshot distribution services. - -Key strengths of the system include its modular architecture, extensive configuration options, built-in performance optimizations, comprehensive security features, automatic snapshot discovery, integrated recovery workflow, DLT replay integration, watchdog monitoring, asynchronous execution system, comprehensive diagnostic capabilities, **signal-based DLT block log reset handling**, **P2P stale sync detection**, **dedicated threading for stalled sync detection**, **automatic gap detection for DLT block log initialization**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection**, **automatic P2P integration with trusted peer support**, and **enhanced exception handling**. The plugin seamlessly integrates with existing VIZ infrastructure while providing powerful new capabilities for state management, peer-to-peer synchronization, automatic recovery from corrupted states, intelligent validator-aware scheduling, **efficient P2P integration with trusted peer support**, **lightweight P2P stale sync detection**, **intelligent gap detection preventing index position mismatch assertions**, **robust error handling for snapshot download operations**, **proper undo stack management during snapshot loading**, **stale snapshot detection with urgent fresh snapshot creation**, and **comprehensive exception handling mechanisms**. - -The implementation demonstrates best practices in blockchain plugin development, including proper resource management, error handling, user experience considerations, security through layered access control, comprehensive monitoring and recovery capabilities, asynchronous execution for improved performance, **signal-based DLT block log reset handling**, **dedicated threading for stalled sync detection**, **automatic gap detection for DLT block log initialization**, **enhanced error handling for snapshot download operations**, **improved undo stack management**, **stale snapshot detection**, and **enhanced exception handling**. The modular design enables independent development and testing of each component while maintaining system coherence, representing a significant advancement in extensibility and maintainability. - -Future enhancements could focus on additional compression algorithms, enhanced security features, expanded monitoring capabilities, more sophisticated access control policies, improved recovery workflow automation, enhanced DLT replay performance optimization, advanced validator-aware scheduling algorithms, **optimized signal-based DLT block log reset handling**, **further optimization of the dual-tier soft-ban system**, **enhanced P2P stale sync detection**, **improved dedicated threading for stalled sync detection**, **intelligent gap detection preventing index position mismatch assertions**, **comprehensive error handling for all snapshot operations**, **advanced undo stack management techniques**, **stale snapshot detection optimization**, **enhanced exception handling mechanisms**, and **stale snapshot detection with urgent fresh snapshot creation**. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Plugin System/Witness Guard Plugin.md b/.qoder/repowiki/en/content/Plugin System/Witness Guard Plugin.md deleted file mode 100644 index d7fbbe97c7..0000000000 --- a/.qoder/repowiki/en/content/Plugin System/Witness Guard Plugin.md +++ /dev/null @@ -1,416 +0,0 @@ -# validator Guard Plugin - - -**Referenced Files in This Document** -- [witness_guard.hpp](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp) -- [witness_guard.cpp](file://plugins/witness_guard/witness_guard.cpp) -- [CMakeLists.txt](file://plugins/witness_guard/CMakeLists.txt) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) -- [account_object.hpp](file://libraries/chain/include/graphene/chain/account_object.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [config.ini](file://share/vizd/config/config.ini) -- [plugin.md](file://documentation/plugin.md) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) - -## Introduction - -The validator Guard Plugin is a specialized plugin for the VIZ blockchain node that automatically monitors and maintains validator signing keys to prevent downtime in block production. This plugin serves as a critical safety mechanism for validator operators who want to ensure their validators remain productive even when encountering issues with their signing keys. - -The plugin operates by continuously monitoring configured validators and automatically restoring their on-chain signing keys when they become null or invalid. It also includes intelligent auto-disable functionality to prevent excessive block production by a single validator, protecting the network from potential centralization risks. - -## Project Structure - -The validator Guard Plugin follows the standard VIZ plugin architecture pattern with a clear separation between interface and implementation: - -```mermaid -graph TB -subgraph "Plugin Structure" -A[witness_guard.hpp
Header Definition] --> B[witness_guard.cpp
Implementation] -C[CMakeLists.txt
Build Configuration] --> B -end -subgraph "Dependencies" -D[chain_plugin] --> B -E[p2p_plugin] --> B -F[protocol] --> B -G[utilities] --> B -H[time] --> B -I[appbase] --> B -end -subgraph "Chain Objects" -J[witness_objects.hpp] --> B -K[account_object.hpp] --> B -L[database.hpp] --> B -end -``` - -**Diagram sources** -- [witness_guard.hpp:1-48](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L1-L48) -- [witness_guard.cpp:1-559](file://plugins/witness_guard/witness_guard.cpp#L1-L559) -- [CMakeLists.txt:1-44](file://plugins/witness_guard/CMakeLists.txt#L1-L44) - -**Section sources** -- [witness_guard.hpp:1-48](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L1-L48) -- [witness_guard.cpp:1-559](file://plugins/witness_guard/witness_guard.cpp#L1-L559) -- [CMakeLists.txt:1-44](file://plugins/witness_guard/CMakeLists.txt#L1-L44) - -## Core Components - -The validator Guard Plugin consists of several key components that work together to provide comprehensive validator monitoring and protection: - -### Main Plugin Class -The primary plugin class implements the appbase plugin interface and manages the plugin lifecycle. It requires both the chain plugin and p2p plugin to function properly. - -### Internal Implementation (impl) -The internal implementation class contains all the core logic for: -- Configuration management and validation -- Periodic monitoring and restoration processes -- Auto-disable functionality for excessive block production -- Transaction broadcasting and confirmation tracking - -### Data Structures -The plugin maintains several critical data structures: -- **validator Configuration Map**: Stores validator names with their associated key pairs -- **Consecutive Block Counters**: Tracks blocks produced by each validator -- **Pending Restoration Tracking**: Manages in-flight transactions -- **Auto-Disabled validators**: Prevents automatic restoration of problematic validators - -**Section sources** -- [witness_guard.hpp:11-44](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L11-L44) -- [witness_guard.cpp:27-78](file://plugins/witness_guard/witness_guard.cpp#L27-L78) - -## Architecture Overview - -The validator Guard Plugin integrates deeply with the VIZ blockchain's core infrastructure through a sophisticated event-driven architecture: - -```mermaid -sequenceDiagram -participant Node as "VIZ Node" -participant Chain as "Chain Plugin" -participant Guard as "validator Guard Plugin" -participant DB as "Database" -participant P2P as "P2P Network" -participant validator as "validator Node" -Note over Node,validator : Startup Phase -Node->>Guard : plugin_initialize() -Guard->>Guard : Parse Configuration -Guard->>DB : Verify Authority Keys -Guard->>Guard : Setup Monitoring -Note over Node,validator : Runtime Monitoring -Chain->>Guard : applied_block Signal -Guard->>Guard : Check Consecutive Blocks -Guard->>DB : Query validator Status -Guard->>Guard : Auto-Disable Check -alt Null Signing Key Detected -Guard->>DB : Fetch validator Object -Guard->>Guard : Build Restore Transaction -Guard->>P2P : Broadcast Transaction -Guard->>Guard : Track Confirmation -end -Note over Node,validator : Periodic Checks -Chain->>Guard : Block Applied -Guard->>Guard : Check Restoration Status -Guard->>DB : Confirm Transaction Inclusion -``` - -**Diagram sources** -- [witness_guard.cpp:410-548](file://plugins/witness_guard/witness_guard.cpp#L410-L548) -- [witness_guard.cpp:83-191](file://plugins/witness_guard/witness_guard.cpp#L83-L191) - -The architecture follows a reactive pattern where the plugin listens for blockchain events and responds appropriately. The plugin subscribes to the `applied_block` signal from the chain database, enabling it to monitor block production in real-time. - -**Section sources** -- [witness_guard.cpp:455-544](file://plugins/witness_guard/witness_guard.cpp#L455-L544) -- [database.hpp:1-200](file://libraries/chain/include/graphene/chain/database.hpp#L1-L200) - -## Detailed Component Analysis - -### Configuration Management - -The plugin supports extensive configuration options that allow fine-tuned control over its behavior: - -#### Core Configuration Options -- **validator-guard-enabled**: Enables or disables the entire plugin functionality -- **validator-guard-validator**: Configures individual validators with their key pairs -- **validator-guard-interval**: Sets the frequency of periodic checks in blocks -- **validator-guard-disable**: Controls auto-disable threshold for excessive block production - -#### validator Configuration Format -Each validator configuration requires three components: -1. **validator Name**: The account name of the validator -2. **Signing WIF**: Private key for signing blocks -3. **Active WIF**: Private key for transaction authorization - -The plugin validates all configurations during initialization and performs authority verification against the blockchain state. - -**Section sources** -- [witness_guard.cpp:301-408](file://plugins/witness_guard/witness_guard.cpp#L301-L408) - -### Monitoring and Restoration Logic - -The core monitoring functionality operates through a sophisticated state machine that tracks validator health and automatically restores compromised keys: - -```mermaid -flowchart TD -Start([Block Applied]) --> CheckStale["Check Stale Production Mode"] -CheckStale --> StaleEnabled{"Stale Production Enabled?"} -StaleEnabled --> |Yes| CheckHealth["Check Network Health (≥33%)"] -CheckHealth --> Healthy{"Network Healthy?"} -Healthy --> |No| SkipRestore["Skip Auto-Restore"] -Healthy --> |Yes| Proceed["Proceed with Check"] -StaleEnabled --> |No| Proceed -Proceed --> CheckSync["Check Node Sync Status"] -CheckSync --> SyncOK{"Node in Sync?"} -SyncOK --> |No| SkipRestore -SyncOK --> |Yes| CheckLIB["Check LIB Age"] -CheckLIB --> LIBOK{"LIB Recent?"} -LIBOK --> |No| SkipRestore -LIBOK --> |Yes| CheckWitnesses["Iterate Configured validators"] -CheckWitnesses --> NullKey{"Null Signing Key?"} -NullKey --> |No| ClearState["Clear Pending State"] -NullKey --> |Yes| CheckAutoDisabled{"Auto-Disabled?"} -CheckAutoDisabled --> |Yes| SkipRestore -CheckAutoDisabled --> |No| CheckPending{"Restore Pending?"} -CheckPending --> |Yes| CheckExpire{"Expired?"} -CheckExpire --> |Yes| RetryRestore["Retry Restore"] -CheckExpire --> |No| SkipRestore -CheckPending --> |No| InitiateRestore["Initiate Restore"] -RetryRestore --> InitiateRestore -InitiateRestore --> BroadcastTx["Broadcast Restore Transaction"] -BroadcastTx --> TrackConfirm["Track Confirmation"] -ClearState --> End([Complete]) -TrackConfirm --> End -SkipRestore --> End -``` - -**Diagram sources** -- [witness_guard.cpp:83-191](file://plugins/witness_guard/witness_guard.cpp#L83-L191) - -The restoration process includes comprehensive error handling and retry mechanisms to ensure reliable key restoration even in challenging network conditions. - -**Section sources** -- [witness_guard.cpp:197-246](file://plugins/witness_guard/witness_guard.cpp#L197-L246) -- [witness_guard.cpp:252-294](file://plugins/witness_guard/witness_guard.cpp#L252-L294) - -### Auto-Disable Mechanism - -The plugin includes an intelligent auto-disable feature designed to prevent excessive block production by a single validator: - -#### Consecutive Block Detection -The system tracks blocks produced by each validator and increments counters when the same validator produces consecutive blocks. When the counter reaches the configured threshold, the system automatically disables the validator by broadcasting a transaction that sets the signing key to null. - -#### Prevention of Excessive Centralization -This mechanism serves as a safeguard against: -- Single-validator dominance in block production -- Potential malicious behavior by a single validator -- Network instability caused by excessive block production - -#### Operator Intervention Required -When a validator is auto-disabled, the plugin prevents automatic restoration to ensure operators investigate and address underlying issues. Manual intervention is required to re-enable the validator. - -**Section sources** -- [witness_guard.cpp:459-495](file://plugins/witness_guard/witness_guard.cpp#L459-L495) -- [witness_guard.cpp:467-484](file://plugins/witness_guard/witness_guard.cpp#L467-L484) - -### Transaction Broadcasting and Confirmation - -The plugin implements robust transaction management for both restoration and disabling operations: - -#### Transaction Construction -Each operation constructs a properly formatted `witness_update` transaction with: -- Correct validator owner identification -- Appropriate URL preservation -- Proper key updates (restore or disable) -- Transaction expiration handling - -#### Broadcasting Strategy -Transactions are broadcast through the P2P network with careful consideration of: -- Transaction fee optimization -- Network congestion handling -- Confirmation tracking mechanisms - -#### Confirmation Tracking -The plugin maintains detailed tracking of all broadcast transactions: -- Transaction ID correlation -- Expiration time management -- Confirmation verification in subsequent blocks -- Automatic retry for failed transactions - -**Section sources** -- [witness_guard.cpp:197-246](file://plugins/witness_guard/witness_guard.cpp#L197-L246) -- [witness_guard.cpp:252-294](file://plugins/witness_guard/witness_guard.cpp#L252-L294) - -## Dependency Analysis - -The validator Guard Plugin has carefully managed dependencies that enable it to function effectively within the VIZ ecosystem: - -```mermaid -graph LR -subgraph "Plugin Dependencies" -A[witness_guard_plugin] --> B[chain_plugin] -A --> C[p2p_plugin] -A --> D[protocol] -A --> E[utilities] -A --> F[time] -A --> G[appbase] -end -subgraph "Chain Dependencies" -B --> H[database] -H --> I[witness_objects] -H --> J[account_authority_object] -H --> K[global_property_object] -end -subgraph "External Dependencies" -L[fc::signals] --> M[Boost Signals] -N[fc::variant] --> O[JSON Processing] -P[fc::ecc] --> Q[Crypto Operations] -end -A --> L -A --> N -A --> P -``` - -**Diagram sources** -- [CMakeLists.txt:26-34](file://plugins/witness_guard/CMakeLists.txt#L26-L34) -- [witness_guard.cpp:3-18](file://plugins/witness_guard/witness_guard.cpp#L3-L18) - -### Core Dependencies - -#### Chain Plugin Integration -The plugin requires the chain plugin for: -- Database access and manipulation -- Block production scheduling -- validator object management -- Authority verification - -#### P2P Plugin Integration -The plugin requires the p2p plugin for: -- Transaction broadcasting -- Network connectivity -- Peer communication -- Transaction propagation - -#### Protocol Dependencies -The plugin relies on protocol definitions for: -- Operation structures -- Authority formats -- Transaction construction -- Cryptographic operations - -**Section sources** -- [CMakeLists.txt:26-34](file://plugins/witness_guard/CMakeLists.txt#L26-L34) -- [witness_guard.hpp:3-6](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L3-L6) - -## Performance Considerations - -The validator Guard Plugin is designed with performance optimization in mind to minimize impact on node operations: - -### Efficient Monitoring Strategy -- **Event-Driven Architecture**: Uses blockchain event signals rather than polling -- **Intelligent Scheduling**: Adjusts check frequency based on network conditions -- **Selective Processing**: Only processes blocks that affect monitored validators -- **Memory Management**: Implements efficient data structures for tracking state - -### Resource Optimization -- **Minimal Memory Footprint**: Uses compact data structures for tracking -- **Efficient Key Storage**: Optimizes storage of validator configurations -- **Connection Management**: Properly manages database connections -- **Signal Handling**: Efficient signal connection and disconnection - -### Network Efficiency -- **Transaction Batching**: Minimizes unnecessary transaction broadcasts -- **Confirmation Optimization**: Reduces redundant processing of confirmed transactions -- **Network Awareness**: Adapts behavior based on network conditions -- **Timeout Management**: Implements appropriate timeouts for various operations - -## Troubleshooting Guide - -### Common Issues and Solutions - -#### Plugin Not Starting -**Symptoms**: Plugin fails to initialize or appears disabled -**Causes**: -- Missing configuration options -- Invalid validator configurations -- Missing required plugins (chain, p2p) -- Authority verification failures - -**Solutions**: -- Verify all configuration options are properly set -- Check validator configuration format and validity -- Ensure required plugins are enabled in config.ini -- Validate validator authority keys against blockchain state - -#### validator Restoration Failures -**Symptoms**: validator keys not being restored despite null signing keys -**Causes**: -- Active authority key mismatch -- Insufficient network synchronization -- Stale production mode interference -- Transaction broadcast failures - -**Solutions**: -- Verify active authority key matches on-chain authority -- Ensure node is fully synchronized with network -- Check stale production mode configuration -- Monitor P2P network connectivity and transaction propagation - -#### Auto-Disable Issues -**Symptoms**: validators being auto-disabled unexpectedly or not being disabled -**Causes**: -- Incorrect disable threshold configuration -- Network timing issues -- validator scheduling conflicts -- Database access problems - -**Solutions**: -- Review and adjust disable threshold settings -- Monitor validator production patterns -- Check network stability and block times -- Verify database connectivity and performance - -### Configuration Validation - -The plugin performs extensive validation during initialization: - -#### Configuration Validation Steps -1. **Option Parsing**: Validates all command-line and config file options -2. **validator Entry Validation**: Verifies each validator configuration triplet -3. **Authority Verification**: Confirms active keys have proper authority -4. **Network Health Assessment**: Evaluates current network conditions -5. **Stale Production Detection**: Identifies stale production mode activation - -#### Error Handling and Logging -The plugin implements comprehensive logging for troubleshooting: -- **Debug Information**: Detailed operational information -- **Warning Messages**: Potential issues and recommendations -- **Error Reporting**: Critical failures and resolution steps -- **Success Confirmations**: Successful operations and outcomes - -**Section sources** -- [witness_guard.cpp:330-408](file://plugins/witness_guard/witness_guard.cpp#L330-L408) -- [witness_guard.cpp:410-548](file://plugins/witness_guard/witness_guard.cpp#L410-L548) - -## Conclusion - -The validator Guard Plugin represents a sophisticated solution for maintaining validator reliability in the VIZ blockchain ecosystem. Its comprehensive monitoring capabilities, intelligent auto-disable mechanisms, and robust restoration processes provide essential protection against validator downtime while preventing excessive centralization risks. - -The plugin's architecture demonstrates best practices in blockchain plugin development, including proper separation of concerns, efficient resource management, and comprehensive error handling. Its integration with the VIZ blockchain's event-driven architecture enables real-time monitoring and response to network conditions. - -Key benefits of the validator Guard Plugin include: -- **Automated Reliability**: Continuous monitoring reduces manual intervention requirements -- **Network Protection**: Prevents excessive validator dominance and centralization -- **Operational Efficiency**: Intelligent scheduling minimizes performance impact -- **Security Enhancement**: Comprehensive validation protects against unauthorized operations - -For optimal deployment, operators should carefully configure the plugin according to their specific needs, monitor its performance regularly, and maintain awareness of network conditions that may affect its operation. The plugin's comprehensive logging and error reporting capabilities provide excellent visibility into its operations and help ensure reliable validator protection. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Project Overview.md b/.qoder/repowiki/en/content/Project Overview.md deleted file mode 100644 index 2435b923a3..0000000000 --- a/.qoder/repowiki/en/content/Project Overview.md +++ /dev/null @@ -1,388 +0,0 @@ -# Project Overview - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [building.md](file://documentation/building.md) -- [plugin.md](file://documentation/plugin.md) -- [main.cpp](file://programs/vizd/main.cpp) -- [config.ini](file://share/vizd/config/config.ini) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini) -- [vizd.sh](file://share/vizd/vizd.sh) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp) -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [social_network.hpp](file://plugins/social_network/include/graphene/plugins/social_network/social_network.hpp) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -VIZ is a Graphene-based blockchain implementing Fair-DPOS consensus, designed as a full consensus node for the VIZ World platform. It provides a robust, extensible foundation for decentralized applications, social networks, and financial systems with a focus on fairness, transparency, and efficient governance. The project emphasizes: -- Fair-DPOS consensus ensuring equitable validator participation and penalties for missed blocks -- Rich social and content features integrated into the blockchain state -- A modular plugin architecture enabling flexible node configurations for different roles (full node, validator, indexer, etc.) -- Strong developer tooling and operational scripts for building, running, and maintaining nodes - -Target audiences: -- Node operators: Run full nodes, seed nodes, and validator nodes with configurable plugins and performance tuning -- Application developers: Build decentralized apps leveraging JSON-RPC APIs and plugin-specific endpoints -- Wallet developers: Integrate with database and chain APIs for account, transaction, and content queries - -Key differentiators: -- Fair-DPOS with explicit participation checks and penalties for inactive validators -- Integrated social features (content, voting, rewards, invites, subscriptions) in the core chain -- Extensive plugin ecosystem for APIs, indexing, and specialized node roles -- Operational readiness with Docker images, seed nodes, and shell scripts - -**Section sources** -- [README.md](file://README.md#L5-L10) -- [building.md](file://documentation/building.md#L1-L20) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) - -## Project Structure -At a high level, the repository is organized into: -- Core libraries: Protocol definitions, chain logic, network messaging, utilities, and wallet support -- Plugins: Modular extensions exposing APIs and specialized functionality (e.g., validator, social_network, database_api) -- Programs: Executables (vizd, cli_wallet) and utilities -- Documentation: Build instructions, plugin usage, testnet setup, and API notes -- Share assets: Configurations, Dockerfiles, seed nodes, and shell scripts for deployment - -```mermaid -graph TB -subgraph "Programs" -VIZD["vizd (full node)"] -CLI["cli_wallet"] -end -subgraph "Libraries" -LChain["libraries/chain"] -LProto["libraries/protocol"] -LNet["libraries/network"] -LUtil["libraries/utilities"] -LWallet["libraries/wallet"] -end -subgraph "Plugins" -PChain["plugins/chain"] -PWitness["plugins/validator"] -PDB["plugins/database_api"] -PSocial["plugins/social_network"] -PJSON["plugins/json_rpc"] -PWeb["plugins/webserver"] -PComm["plugins/committee_api"] -PInv["plugins/invite_api"] -PPaid["plugins/paid_subscription_api"] -end -VIZD --> PChain -VIZD --> PWitness -VIZD --> PDB -VIZD --> PSocial -VIZD --> PJSON -VIZD --> PWeb -VIZD --> PComm -VIZD --> PInv -VIZD --> PPaid -PChain --> LChain -PDB --> LChain -PSocial --> LChain -PWitness --> LChain -PJSON --> LNet -PWeb --> LNet -LChain --> LProto -LChain --> LUtil -LWallet --> LProto -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) - -## Core Components -- Chain database and consensus engine: Manages blockchain state, fork resolution, block validation, validator scheduling, and reward/cashout mechanics -- Protocol definitions: Enumerates operations (transfers, content, governance, social features) and virtual operations for rewards and payouts -- Plugin architecture: Enables modular APIs (database, social_network, committee, invite, paid_subscription, witness_api) and transport (JSON-RPC, WebServer) -- Validator Plugin: Produces blocks according to Fair-DPOS participation thresholds and schedule -- Social network plugin: Exposes content, votes, replies, and governance APIs tailored for VIZ World’s social features -- Configuration and deployment: Comprehensive config files, Docker images, and shell scripts for production and testnet - -Practical examples: -- Run a full node: Use the provided Docker image or build from source, configure endpoints and plugins, and start the node -- Develop applications: Consume JSON-RPC endpoints exposed by database_api and social_network plugins -- Operate a validator: Enable the Validator Plugin, set validator name and private key, and monitor participation metrics - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L194-L227) -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L65) -- [social_network.hpp](file://plugins/social_network/include/graphene/plugins/social_network/social_network.hpp#L36-L76) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L111) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Architecture Overview -The node initializes plugins, opens the chain database, and starts P2P networking and webserver services. The chain plugin coordinates block production and validation, while Validator Plugin enforces Fair-DPOS participation. Social and governance plugins expose APIs for content, voting, and committee operations. - -```mermaid -graph TB -Main["vizd main()
initializes plugins"] --> Chain["chain plugin
blockchain state"] -Main --> P2P["p2p plugin
peer connections"] -Main --> Web["webserver plugin
HTTP/WebSocket"] -Main --> JSON["json_rpc plugin
RPC transport"] -Main --> DBAPI["database_api plugin
chain queries"] -Main --> validator["Validator Plugin
block production"] -Main --> Social["social_network plugin
content & votes"] -Main --> Others["other plugins
committee, invite, paid_subscription"] -Chain --> DB["database
blocks, txns, objects"] -DBAPI --> DB -Social --> DB -validator --> DB -P2P --> Net["network core"] -Web --> JSON -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L140) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L106-L140) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) - -## Detailed Component Analysis - -### Fair-DPOS Consensus and validator Production -Fair-DPOS ensures that only participating validators produce blocks, with penalties for missed slots. The chain tracks participation and schedules validators accordingly. Operators can configure participation thresholds and enable stale production for resilience. - -```mermaid -flowchart TD -Start(["Block production cycle"]) --> CheckSync["Check sync with network"] -CheckSync --> SyncOK{"Synced?"} -SyncOK --> |No| WaitSync["Wait for peers"] -SyncOK --> |Yes| CheckTurn["Is it my turn?"] -CheckTurn --> MyTurn{"validator slot active?"} -MyTurn --> |No| SleepSlot["Sleep until next slot"] -MyTurn --> |Yes| CheckParticipation["Check participation threshold"] -CheckParticipation --> PartOK{"Participation >= required?"} -PartOK --> |No| SkipBlock["Skip block production"] -PartOK --> |Yes| Produce["Generate and sign block"] -Produce --> Broadcast["Broadcast block to peers"] -SleepSlot --> CheckSync -WaitSync --> CheckSync -SkipBlock --> CheckSync -Broadcast --> CheckSync -``` - -**Diagram sources** -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L99-L103) - -**Section sources** -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L20-L32) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L99-L103) - -### Database and State Management -The database manages blocks, transactions, and domain objects (accounts, content, proposals, validators). It supports validation, push operations, fork resolution, and periodic processing (cashouts, inflation, committee actions). - -```mermaid -classDiagram -class Database { -+open(data_dir, shared_mem_dir, ...) -+reindex(data_dir, shared_mem_dir, from_block_num, ...) -+push_block(signed_block, skip_flags) -+push_transaction(signed_transaction, skip_flags) -+generate_block(when, witness_owner, priv_key, skip) -+get_account(name) -+get_content(author, permlink) -+get_witness_schedule_object() -+applied_block signal -+on_applied_transaction signal -} -class DynamicGlobalPropertyObject { -+head_block_number -+current_witness -+current_supply -+total_vesting_fund -+total_reward_fund -+average_block_size -+maximum_block_size -+current_aslot -+recent_slots_filled -+participation_count -+last_irreversible_block_num -+max_virtual_bandwidth -+current_reserve_ratio -} -Database --> DynamicGlobalPropertyObject : "manages" -``` - -**Diagram sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L287) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L133) - -**Section sources** -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L287) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L24-L133) - -### Protocol Operations and Social Features -The protocol defines operations covering transfers, vesting, governance proposals, and social features such as content creation, voting, rewards, invites, and paid subscriptions. These operations drive the state managed by the chain database. - -```mermaid -classDiagram -class Operation { -<> -+transfer_operation -+transfer_to_vesting_operation -+withdraw_vesting_operation -+account_update_operation -+witness_update_operation -+account_witness_vote_operation -+account_witness_proxy_operation -+proposal_create/update/delete_operation -+chain_properties_update_operation -+content_operation -+delete_content_operation -+vote_operation -+custom_operation -+author_reward_operation -+curation_reward_operation -+content_reward_operation -+witness_reward_operation -+create_invite_operation -+claim_invite_balance_operation -+invite_registration_operation -+set_paid_subscription_operation -+paid_subscribe_operation -+award_operation -+receive_award_operation -+benefactor_award_operation -} -``` - -**Diagram sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -**Section sources** -- [operations.hpp](file://libraries/protocol/include/graphene/protocol/operations.hpp#L13-L102) - -### Plugin APIs and Deployment -The node enables a wide array of plugins for APIs and transport. Configuration files define endpoints, plugin lists, and validator credentials. Shell scripts and Docker images streamline deployment and seeding. - -```mermaid -sequenceDiagram -participant Operator as "Operator" -participant Script as "vizd.sh" -participant VIZD as "vizd" -participant P2P as "p2p plugin" -participant Web as "webserver plugin" -participant JSON as "json_rpc plugin" -Operator->>Script : "Run with env vars" -Script->>VIZD : "Start with args (RPC/P2P/data)" -VIZD->>P2P : "Initialize P2P" -VIZD->>Web : "Start HTTP/WS" -VIZD->>JSON : "Expose RPC endpoints" -VIZD-->>Operator : "Logs and metrics" -``` - -**Diagram sources** -- [vizd.sh](file://share/vizd/vizd.sh#L74-L81) -- [config.ini](file://share/vizd/config/config.ini#L1-L130) - -**Section sources** -- [plugin.md](file://documentation/plugin.md#L11-L28) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) - -## Dependency Analysis -The node composes multiple subsystems: -- Program entry point registers and starts plugins -- Plugins depend on chain library for state and protocol definitions -- Network and transport plugins depend on network core -- Social and governance plugins depend on chain objects and protocol operations - -```mermaid -graph LR -Entry["programs/vizd/main.cpp"] --> Reg["register_plugins()"] -Reg --> PChain["plugins/chain"] -Reg --> PWitness["plugins/validator"] -Reg --> PDB["plugins/database_api"] -Reg --> PSocial["plugins/social_network"] -Reg --> PJSON["plugins/json_rpc"] -Reg --> PWeb["plugins/webserver"] -PChain --> LChain["libraries/chain"] -PDB --> LChain -PSocial --> LChain -PWitness --> LChain -PJSON --> LNet["libraries/network"] -PWeb --> LNet -LChain --> LProto["libraries/protocol"] -``` - -**Diagram sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) - -**Section sources** -- [main.cpp](file://programs/vizd/main.cpp#L62-L91) - -## Performance Considerations -- Single write thread: Dedicates all write operations to a single thread to reduce lock contention and improve stability under load -- Lock wait tuning: Configurable read/write wait timeouts and retries to balance responsiveness and throughput -- Shared memory sizing: Adjustable initial size, minimum free space, and increment steps to manage storage growth efficiently -- Plugin notifications: Option to disable plugin notifications on push_transaction to reduce overhead -- Participation thresholds: Tuning participation requirements impacts block production frequency and network liveness - -Operational tips: -- Use LOW_MEMORY_NODE build option for consensus-only nodes (validators/seed nodes) -- Monitor shared memory free space and tune increments based on workload -- Adjust thread pool size for RPC clients to match CPU cores - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L36-L47) -- [config.ini](file://share/vizd/config/config.ini#L49-L67) -- [building.md](file://documentation/building.md#L11-L15) - -## Troubleshooting Guide -Common operational scenarios: -- Node not syncing: Verify P2P endpoints and seed nodes; check logs for connection errors -- RPC lock errors: Increase read/write wait retries or tune single-write-thread behavior -- Memory pressure: Reduce plugin overhead, enable virtual ops skipping, and adjust shared memory parameters -- validator production issues: Confirm participation thresholds, validator name, and private key configuration - -Useful references: -- Logging configuration via config sections for console and file appenders -- Testnet configuration enabling stale production and default validator credentials for development -- Shell script arguments for RPC/P2P endpoints and validator overrides - -**Section sources** -- [config.ini](file://share/vizd/config/config.ini#L112-L130) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L99-L111) -- [vizd.sh](file://share/vizd/vizd.sh#L62-L73) - -## Conclusion -VIZ delivers a Graphene-based blockchain optimized for fairness and social features, with a modular plugin architecture and strong operational tooling. Its Fair-DPOS consensus, integrated content and governance primitives, and extensive APIs make it suitable for diverse use cases—from full node operations and validator roles to application and wallet development. The combination of comprehensive documentation, Docker images, and shell scripts lowers the barrier to entry while offering deep customization for advanced users. - -## Appendices -- Practical examples: - - Full node: Use Docker image or build from source; configure endpoints and plugins; start with default or testnet config - - Application development: Consume database_api and social_network endpoints over HTTP/WebSocket - - validator operation: Enable Validator Plugin, set validator name and private key, monitor participation and block production - -**Section sources** -- [README.md](file://README.md#L12-L29) -- [building.md](file://documentation/building.md#L1-L20) -- [config.ini](file://share/vizd/config/config.ini#L69-L74) -- [config_testnet.ini](file://share/vizd/config/config_testnet.ini#L69-L111) -- [vizd.sh](file://share/vizd/vizd.sh#L1-L82) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Troubleshooting and FAQ.md b/.qoder/repowiki/en/content/Troubleshooting and FAQ.md deleted file mode 100644 index 1b69f800ff..0000000000 --- a/.qoder/repowiki/en/content/Troubleshooting and FAQ.md +++ /dev/null @@ -1,434 +0,0 @@ -# Troubleshooting and FAQ - - -**Referenced Files in This Document** -- [README.md](file://README.md) -- [documentation/building.md](file://documentation/building.md) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md) -- [documentation/api_notes.md](file://documentation/api_notes.md) -- [documentation/testnet.md](file://documentation/testnet.md) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini) -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp) -- [libraries/network/node.cpp](file://libraries/network/node.cpp) -- [libraries/network/include/graphene/network/exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [libraries/chain/include/graphene/chain/database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp) -- [plugins/debug_node/plugin.cpp](file://plugins/debug_node/plugin.cpp) - - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Troubleshooting Guide](#troubleshooting-guide) -9. [Conclusion](#conclusion) -10. [Appendices](#appendices) - -## Introduction -This document provides comprehensive troubleshooting and FAQ guidance for the VIZ CPP Node. It focuses on: -- Build issues and dependency problems -- Network connectivity failures and sync issues -- Performance tuning for memory, CPU, and disk I/O -- Interpretation of error messages and diagnostic procedures -- Frequently asked questions about node operation, API usage, plugin development, and integration -- Systematic debugging with the debug_node plugin, log analysis, and diagnostic tools -- Recovery procedures for common failure scenarios - -## Project Structure -The VIZ node is organized into libraries (chain, protocol, network, utilities, wallet), plugins (core and optional), and the main application entry point. Configuration is primarily managed via INI files, with logging configured through dedicated sections. - -```mermaid -graph TB -subgraph "Application" -MAIN["programs/vizd/main.cpp"] -end -subgraph "Libraries" -NET["libraries/network"] -CHAIN["libraries/chain"] -UTIL["libraries/utilities"] -WALLET["libraries/wallet"] -API["libraries/api"] -end -subgraph "Plugins" -P2P["plugins/p2p"] -CHAINPLUG["plugins/chain"] -WEB["plugins/webserver"] -DEBUG["plugins/debug_node"] -others["..."] -end -subgraph "Config" -CFG["share/vizd/config/config.ini"] -CFGD["share/vizd/config/config_debug.ini"] -end -MAIN --> NET -MAIN --> CHAIN -MAIN --> P2P -MAIN --> WEB -MAIN --> DEBUG -MAIN --> others -CFG -. loads .-> MAIN -CFGD -. loads .-> MAIN -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L69-L73) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini#L69) - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L69-L73) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini#L69) - -## Core Components -- Application entry and plugin registration: The main executable registers and initializes core plugins including chain, p2p, webserver, and optional plugins. -- Network layer: Provides peer-to-peer connectivity, message propagation, sync orchestration, and bandwidth monitoring. -- Chain database: Manages blockchain state, block and transaction validation, fork handling, and shared memory sizing. -- Configuration: Centralized via INI files controlling endpoints, RPC/TLS, plugin activation, and performance knobs. -- Logging: Configurable console and file appenders with logger sections. - -Key configuration areas: -- P2P endpoints, seed nodes, and connection limits -- RPC endpoints, thread pools, and lock timeouts -- Shared memory sizing and growth thresholds -- Plugin selection and API exposure -- Loggers and appenders - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L83-L107) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) -- [share/vizd/config/config_debug.ini](file://share/vizd/config/config_debug.ini#L1-L126) - -## Architecture Overview -High-level runtime flow: -- The application initializes plugins, loads configuration, and starts the P2P and webserver components. -- The P2P node connects to peers, discovers candidates, and synchronizes blocks and transactions. -- The chain database validates and applies blocks and transactions, emitting signals for plugins. -- The webserver exposes JSON-RPC APIs and WebSocket endpoints for clients. - -```mermaid -sequenceDiagram -participant App as "vizd main" -participant P2P as "P2P Node" -participant Net as "Network Layer" -participant DB as "Chain Database" -participant WS as "Webserver" -App->>P2P : Initialize and connect -P2P->>Net : Listen/connect to peers -Net-->>P2P : Peer inventory and messages -P2P->>DB : Push blocks/transactions -DB-->>P2P : Validation results and signals -App->>WS : Expose RPC/WebSocket APIs -WS-->>App : Requests processed via plugins -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L106-L142) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L776-L788) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L194-L206) - -## Detailed Component Analysis - -### Build and Dependency Troubleshooting -Common issues and resolutions: -- Missing system dependencies (Linux): Ensure required packages are installed as documented for your distribution. -- Boost version conflicts (especially on older distributions): Use the documented Boost versions and installation steps. -- Compiler compatibility: GCC and Clang are supported; avoid compilers not used by maintainers unless you can resolve warnings. -- Docker builds: Use provided Dockerfiles for reproducible environments. - -Recommended steps: -- Verify all documented dependencies are present. -- Clean build tree and rerun CMake with Release configuration. -- For Docker, use the provided Dockerfiles and tags. - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [documentation/building.md](file://documentation/building.md#L76-L137) -- [documentation/building.md](file://documentation/building.md#L138-L189) -- [documentation/building.md](file://documentation/building.md#L202-L212) - -### Network Connectivity and Sync Issues -Symptoms and diagnostics: -- No peers or slow peer discovery: - - Confirm P2P endpoint and seed nodes are configured. - - Clear peer database if stuck due to stale entries. -- Sync stalls or fork errors: - - Review network exceptions for unlinkable blocks or peers on unreachable forks. - - Consider checkpoints and shared memory sizing. -- Firewall/NAT: - - The node can detect firewall status and may require explicit public endpoint configuration. - -Actions: -- Adjust p2p-endpoint, p2p-max-connections, and p2p-seed-node. -- Increase read/write lock wait retries and microsecond timeouts if RPC contention is observed. -- Use the debug_node plugin to simulate and validate behavior in isolation. - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L27) -- [libraries/network/include/graphene/network/exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L46) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L788-L796) - -### API Access Control and Security -- Bind RPC to localhost for trusted environments. -- Use TLS endpoints for remote access with proper certificates. -- Username/password authentication for specific API sets. - -Best practices: -- Keep public-api minimal and ordered. -- Use login_api as the second API for string identifier compatibility. -- Protect sensitive APIs with credentials and TLS. - -**Section sources** -- [documentation/api_notes.md](file://documentation/api_notes.md#L56-L121) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L16-L20) - -### Performance Tuning -Key levers: -- Single write thread: Reduces lock contention for write-heavy workloads. -- Plugin notifications on push_transaction: Disable to reduce overhead. -- Shared memory sizing: Tune initial size, minimum free space, and increment step. -- RPC thread pool size: Match to CPU cores minus one. -- Skip virtual operations: Reduce memory footprint for non-consensus nodes. - -Operational tips: -- Monitor average network read/write speeds exposed by the network layer. -- Adjust block-number-based free-space checks to balance safety and performance. - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L36-L47) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L13-L14) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L632-L634) - -### Error Message Interpretation and Diagnostics -- Network exceptions: - - send_queue_overflow, insufficient_relay_fee, already_connected_to_requested_peer, block_older_than_undo_history, peer_is_on_an_unreachable_fork, unlinkable_block_exception. -- Chain exceptions: - - database_query_exception, block_validate_exception, transaction_exception, operation_validate_exception, operation_evaluate_exception, unlinkable_block_exception, unknown_hardfork_exception, plugin_exception, block_log_exception. - -Diagnostic procedure: -- Capture full exception backtraces from logs. -- Correlate timestamps with network and chain events. -- Use debug_node to reproduce and narrow issues in isolation. - -**Section sources** -- [libraries/network/include/graphene/network/exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L46) -- [libraries/chain/include/graphene/chain/database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L67-L117) - -### Debugging with the debug_node Plugin -Capabilities: -- Load blocks from a block log or JSON array. -- Generate blocks deterministically for testing. -- Edit chain state for “what-if” experiments. -- Replay and inspect applied operations. - -Usage highlights: -- Configure RPC to localhost for security. -- Enable debug_node and required APIs. -- Use curl or WebSocket clients to call debug APIs. - -Recovery and isolation: -- Use debug_node to validate assumptions and isolate bugs without affecting live networks. - -**Section sources** -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) -- [plugins/debug_node/plugin.cpp](file://plugins/debug_node/plugin.cpp#L475-L556) - -### Recovery Procedures -- Database corruption or replay issues: - - Use database reindex/replay capabilities with appropriate skip flags. - - Adjust shared memory sizing and free-space thresholds. -- Sync desynchronization: - - Clear peer database and restart sync. - - Verify checkpoints and hardfork handling. -- Configuration errors: - - Validate INI sections and logger configurations. - - Use minimal config to confirm correctness, then re-add options incrementally. - -**Section sources** -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L91-L92) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L94-L97) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L288) - -### Frequently Asked Questions -- How do I run a testnet? - - Use the provided Docker images or build locally with the testnet Dockerfile. -- How do I expose APIs securely? - - Bind RPC to localhost, or use TLS endpoints with certificates. -- How do I limit API access? - - Use username/password authentication and restrict allowed_apis. -- How do I troubleshoot missing peers? - - Check p2p-endpoint, p2p-seed-node, and firewall/NAT status. -- How do I improve performance? - - Tune shared memory sizing, single write thread, plugin notifications, and RPC thread pool. - -**Section sources** -- [documentation/testnet.md](file://documentation/testnet.md#L21-L37) -- [documentation/api_notes.md](file://documentation/api_notes.md#L56-L121) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L27) - -## Dependency Analysis -The application depends on a layered architecture: -- Application layer registers and orchestrates plugins. -- Network layer handles peer management and synchronization. -- Chain layer validates and applies state transitions. -- Utilities and wallet provide supporting functionality. - -```mermaid -graph LR -APP["vizd main"] --> P2P["p2p_plugin"] -APP --> CHAIN["chain_plugin"] -APP --> WEB["webserver_plugin"] -APP --> DEBUG["debug_node_plugin"] -P2P --> NETLIB["network library"] -CHAIN --> CHAINLIB["chain library"] -WEB --> APILIB["api library"] -``` - -**Diagram sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) -- [libraries/network/include/graphene/network/node.hpp](file://libraries/network/include/graphene/network/node.hpp#L190-L304) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L36-L561) - -**Section sources** -- [programs/vizd/main.cpp](file://programs/vizd/main.cpp#L62-L91) - -## Performance Considerations -- Memory: - - Tune shared memory initial size, minimum free space, and increment step. - - Consider skip-virtual-ops for reduced memory usage. -- CPU: - - Use single-write-thread to reduce lock contention. - - Disable plugin notifications on push_transaction for lower overhead. -- Disk I/O: - - Monitor average network read/write speeds. - - Adjust block-number-based free-space checks to balance safety and throughput. - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L632-L634) - -## Troubleshooting Guide - -### Build Issues -- Symptoms: CMake fails or compiler errors. -- Checks: - - Dependencies installed per platform-specific instructions. - - Boost version matches documented requirements. - - Compiler is GCC or Clang. -- Actions: - - Clean build, rerun CMake with Release. - - Use Docker images for reproducibility. - -**Section sources** -- [documentation/building.md](file://documentation/building.md#L25-L75) -- [documentation/building.md](file://documentation/building.md#L76-L137) -- [documentation/building.md](file://documentation/building.md#L138-L189) - -### Network Connectivity Problems -- Symptoms: No peers, slow sync, or fork errors. -- Checks: - - P2P endpoint and seed nodes configured. - - Firewall/NAT status and public endpoint visibility. -- Actions: - - Clear peer database if stuck. - - Increase connection and timeout parameters. - - Use debug_node to validate behavior. - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L27) -- [libraries/network/include/graphene/network/exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L46) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L788-L796) - -### API Access and Security -- Symptoms: Unauthorized access or TLS handshake failures. -- Checks: - - RPC endpoint binding and TLS configuration. - - Username/password policies and allowed_apis. -- Actions: - - Bind RPC to localhost for trusted environments. - - Configure TLS with matching certificate and client trust. - -**Section sources** -- [documentation/api_notes.md](file://documentation/api_notes.md#L56-L121) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L16-L20) - -### Performance Bottlenecks -- Symptoms: High lock wait errors, slow RPC, or memory pressure. -- Checks: - - Read/write lock wait retries and microsecond timeouts. - - Shared memory sizing and free-space thresholds. -- Actions: - - Enable single-write-thread and disable plugin notifications on push_transaction. - - Increase shared memory increments and adjust free-space checks. - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L22-L47) -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L49-L67) - -### Error Interpretation and Logs -- Use network and chain exception categories to classify issues. -- Capture full backtraces and correlate with timestamps. -- Employ debug_node to reproduce and validate fixes. - -**Section sources** -- [libraries/network/include/graphene/network/exceptions.hpp](file://libraries/network/include/graphene/network/exceptions.hpp#L33-L46) -- [libraries/chain/include/graphene/chain/database_exceptions.hpp](file://libraries/chain/include/graphene/chain/database_exceptions.hpp#L67-L117) -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) - -### Recovery Procedures -- Database issues: - - Reindex with appropriate skip flags. - - Resize shared memory and adjust free-space thresholds. -- Sync issues: - - Clear peer database and restart sync. - - Verify checkpoints and hardfork handling. -- Configuration errors: - - Validate INI sections and logger configurations. - - Gradually reintroduce options. - -**Section sources** -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L91-L92) -- [libraries/chain/include/graphene/chain/database.hpp](file://libraries/chain/include/graphene/chain/database.hpp#L94-L97) -- [libraries/network/node.cpp](file://libraries/network/node.cpp#L288) - -### Systematic Debugging with debug_node -- Isolate issues by loading historical blocks and generating deterministic blocks. -- Edit chain state for “what-if” experiments. -- Use localhost RPC and minimal API exposure for security. - -**Section sources** -- [documentation/debug_node_plugin.md](file://documentation/debug_node_plugin.md#L50-L134) -- [plugins/debug_node/plugin.cpp](file://plugins/debug_node/plugin.cpp#L475-L556) - -## Conclusion -This guide consolidates practical troubleshooting steps, performance tuning, and debugging techniques for the VIZ CPP Node. By aligning configuration with operational needs, leveraging the debug_node plugin, and interpreting structured error messages, most issues can be diagnosed and resolved efficiently. - -## Appendices - -### Quick Reference: Key Configuration Areas -- P2P: p2p-endpoint, p2p-max-connections, p2p-seed-node, checkpoint -- RPC: webserver-thread-pool-size, webserver-http-endpoint, webserver-ws-endpoint -- Locks: read-wait-micro, max-read-wait-retries, write-wait-micro, max-write-wait-retries -- Write path: single-write-thread -- Plugins: plugin list and public-api ordering -- Shared memory: shared-file-size, min-free-shared-file-size, inc-shared-file-size, block-num-check-free-size -- Logging: log.console_appender, log.file_appender, logger sections - -**Section sources** -- [share/vizd/config/config.ini](file://share/vizd/config/config.ini#L1-L130) - -### Testnet Launch -- Use Docker images or build locally with the testnet Dockerfile. -- Inspect snapshot behavior and initial users. - -**Section sources** -- [documentation/testnet.md](file://documentation/testnet.md#L21-L37) \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Validator.md b/.qoder/repowiki/en/content/Validator.md deleted file mode 100644 index fc79bf5889..0000000000 --- a/.qoder/repowiki/en/content/Validator.md +++ /dev/null @@ -1,1435 +0,0 @@ -# validator - - -**Referenced Files in This Document** -- [validator.hpp](file://plugins/validator/include/graphene/plugins/validator/validator.hpp) -- [validator.cpp](file://plugins/validator/validator.cpp) -- [witness_api_plugin.hpp](file://plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp) -- [witness_api_plugin.cpp](file://plugins/witness_api/plugin.cpp) -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) -- [chain_objects.hpp](file://libraries/chain/include/graphene/chain/chain_objects.hpp) -- [database.hpp](file://libraries/chain/include/graphene/chain/database.hpp) -- [database.cpp](file://libraries/chain/database.cpp) -- [fork_database.hpp](file://libraries/chain/include/graphene/chain/fork_database.hpp) -- [fork_database.cpp](file://libraries/chain/fork_database.cpp) -- [time.hpp](file://libraries/time/time.hpp) -- [time.cpp](file://libraries/time/time.cpp) -- [ntp.cpp](file://thirdparty/fc/src/network/ntp.cpp) -- [main.cpp](file://programs/vizd/main.cpp) -- [snapshot_plugin.cpp](file://plugins/snapshot/plugin.cpp) -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) -- [config.ini](file://share/vizd/config/config.ini) -- [config_witness.ini](file://share/vizd/config/config_witness.ini) -- [p2p_plugin.hpp](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp) -- [witness_guard.hpp](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp) -- [witness_guard.cpp](file://plugins/witness_guard/witness_guard.cpp) -- [global_property_object.hpp](file://libraries/chain/include/graphene/chain/global_property_object.hpp) - - -## Update Summary -**Changes Made** -- HF13: Implemented validator reward sharing — stakeholder reward distribution, `set_reward_sharing_operation`, `distribution_epoch_length` consensus parameter, `process_validator_epoch_distribution()`, new virtual op `stakeholder_reward_operation`; rewards accumulate as TOKEN and convert to SHARES at epoch end -- Added comprehensive validator protection and monitoring capabilities with new witness_guard plugin -- Enhanced emergency recovery mechanisms with auto-disable thresholds and improved network connectivity features -- Integrated validator key auto-restore functionality with emergency consensus mode support -- Implemented consecutive block auto-disable protection to prevent validator abuse -- Added enhanced minority fork detection with automatic recovery mechanisms -- Integrated emergency consensus detection and recovery procedures -- Enhanced network connectivity features with improved peer synchronization -- Isolated production timer on dedicated io_service/thread, preventing P2P I/O from delaying slot callbacks -- Fixed lag tight loop: tracks missed slot time and skips ahead to avoid rechecking the same slot every 250ms -- Added production watchdog: fires at 60s (emergency master) or 180s (regular validator) of no block produced -- Added slot hijack detection: counts consecutive slots where emergency master fills slots assigned to our validator -- Fixed false-positive hijack: own-validator blocks no longer trigger hijack counter -- Fixed slot index calculation (current_aslot % num_scheduled_witnesses) for correct schedule position -- Added not_my_turn streak detection: warns at 500 consecutive iterations (~125s) of schedule misalignment -- Added on_block_applied missed-slot diagnostic: dumps full plugin state when incoming block reveals our missed slots -- Refined slot=0 stall detection: only counts real NTP stalls (now ≤ head_block_time), not normal between-slot waits - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Configuration Parameters](#configuration-parameters) -6. [Detailed Component Analysis](#detailed-component-analysis) -7. [Dependency Analysis](#dependency-analysis) -8. [Performance Considerations](#performance-considerations) -9. [Troubleshooting Guide](#troubleshooting-guide) -10. [Conclusion](#conclusion) - -## Introduction -This document explains the validator subsystem of the VIZ node implementation. It covers how validators are scheduled, how blocks are produced, how validator participation is monitored, and how the validator-related APIs expose information to clients. The focus is on the Validator Plugin (block production), the validator API plugin (read-only queries), the validator guard plugin (protection and monitoring), and the underlying chain database that maintains validator state and schedules. - -**Updated** Enhanced with comprehensive validator protection and monitoring capabilities, emergency recovery mechanisms, auto-disable thresholds, improved network connectivity features, validator key auto-restore functionality, consecutive block protection, enhanced minority fork detection, emergency consensus integration, and automatic recovery procedures. - -## Project Structure -The validator functionality spans four primary areas: -- Validator Plugin: Produces blocks and validates blocks posted by other validators with optimized timing. -- validator Guard plugin: Provides protection and monitoring for validator keys with auto-restore and auto-disable capabilities. -- validator API plugin: Exposes validator-related read-only queries via JSON-RPC. -- Chain database: Maintains validator objects, voting, scheduling, and participation metrics. - -```mermaid -graph TB -subgraph "Node Binary" -VIZD["vizd main
registers plugins"] -end -subgraph "Plugins" -validator["Validator Plugin
optimized block production
fork collision timeout: 21 blocks
minority fork detection
DEBUG logging: enabled"] -WGUARD["validator Guard Plugin
key auto-restore
auto-disable protection
emergency consensus support"] -WAPI["validator API Plugin
JSON-RPC queries"] -SNAPSHOT["Snapshot Plugin
coordinated operations"] -P2P["P2P Plugin
broadcast
resync_from_lib()"] -CHAIN["Chain Plugin
database access"] -end -subgraph "Chain Database" -DB["database.hpp/.cpp
compare_fork_branches()
DEBUG logging: enabled
emergency_consensus_active"] -WITNESS_OBJ["witness_objects.hpp"] -BPV_OBJ["chain_objects.hpp
block_post_validation_object"] -FORK_DB["fork_database.hpp/.cpp
enhanced fork collision detection
automatic stale pruning"] -end -subgraph "Time Synchronization" -TIME["Time Service
NTP synchronization with 250ms ticks"] -END -VIZD --> validator -VIZD --> WGUARD -VIZD --> WAPI -VIZD --> SNAPSHOT -VIZD --> P2P -VIZD --> CHAIN -validator --> P2P -validator --> CHAIN -validator --> TIME -WGUARD --> CHAIN -WGUARD --> P2P -CHAIN --> DB -DB --> WITNESS_OBJ -DB --> BPV_OBJ -DB --> FORK_DB -WAPI --> CHAIN -CHAIN --> DB -SNAPSHOT --> validator -``` - -**Diagram sources** -- [main.cpp:63-92](file://programs/vizd/main.cpp#L63-L92) -- [validator.hpp:34-68](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L68) -- [validator.cpp:59-118](file://plugins/validator/validator.cpp#L59-L118) -- [witness_guard.hpp:11-48](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L11-L48) -- [witness_guard.cpp:27-78](file://plugins/witness_guard/witness_guard.cpp#L27-L78) -- [witness_api_plugin.hpp:56-98](file://plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp#L56-L98) -- [witness_api_plugin.cpp:13-28](file://plugins/witness_api/plugin.cpp#L13-L28) -- [database.hpp:37-83](file://libraries/chain/include/graphene/chain/database.hpp#L37-L83) -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [chain_objects.hpp:174-201](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L174-L201) -- [fork_database.hpp:53-81](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L81) -- [time.cpp:13-53](file://libraries/time/time.cpp#L13-L53) -- [snapshot_plugin.cpp:1267-1276](file://plugins/snapshot/plugin.cpp#L1267-1276) -- [p2p_plugin.hpp:50-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L50-L55) - -**Section sources** -- [main.cpp:63-92](file://programs/vizd/main.cpp#L63-L92) - -## Core Components -- Validator Plugin - - Provides optimized block production loop synchronized to 250ms intervals for deterministic slot time alignment. - - Validates whether it is time to produce a block, checks participation thresholds, and signs blocks with configured private keys. - - Broadcasts blocks and block post validations via the P2P plugin. - - **Enhanced**: Implements forced NTP synchronization when timing issues are detected during block production attempts. - - **Enhanced**: Implements comprehensive fork collision detection to prevent competing blocks at the same height. - - **Enhanced**: **NEW**: Implements comprehensive minority fork detection system to identify when all recent blocks were produced by local validators only. - - **Enhanced**: **NEW**: Provides automatic recovery through resync_from_lib() when minority fork is detected. - - **Enhanced**: **NEW**: Integrates with skip_undo_history_check flag to control production during recovery scenarios. - - **Enhanced**: **NEW**: Implements comprehensive debug logging with verbose traces for block production and chain internals. - - **New**: Provides `is_witness_scheduled_soon()` method to check if any locally-controlled validators are scheduled to produce blocks in the upcoming 4 slots. - - **New**: Implements two-level fork collision resolution system with configurable timeout blocks parameter (--fork-collision-timeout-blocks). - - **New**: Integrates with enhanced fork database for automatic stale fork pruning after successful block application. - - **New**: Runs production timer on a dedicated `boost::asio::io_service` and thread, isolated from P2P network I/O. - - **New**: Prevents lag tight loop: records missed slot time and skips ahead to avoid rechecking the same slot every 250ms. - - **New**: Production watchdog alerts when no block is produced for 60s (emergency master) or 180s (regular validator); auto-enables verbose debug logging on first fire. - - **New**: Slot hijack detection counts consecutive emergency-master-filled slots assigned to our validator in the shuffled schedule. - - **New**: not_my_turn streak detection warns after 500 consecutive iterations (~125s) of another validator holding all slots. - - **New**: Missed-slot diagnostics via `on_block_applied` signal: detects gaps and dumps full plugin state when our validator missed a slot. -- validator Guard Plugin - - **NEW**: Provides comprehensive validator protection and monitoring capabilities. - - **NEW**: Implements validator key auto-restore functionality to automatically restore null signing keys. - - **NEW**: Provides consecutive block auto-disable protection to prevent validator abuse by disabling validators after N consecutive blocks. - - **NEW**: Supports emergency consensus mode with automatic key restoration and recovery procedures. - - **NEW**: Monitors validator signing keys and automatically restores them when detected as null on-chain. - - **NEW**: Integrates with P2P plugin to broadcast witness_update transactions for key restoration and disabling. - - **NEW**: Implements safety checks including network health monitoring and long fork detection. - - **NEW**: Provides configurable check intervals and disable thresholds for flexible protection strategies. -- validator API Plugin - - Exposes read-only queries for active validators, schedule, individual validators, and counts. - - Returns API-friendly objects derived from chain validator data. -- Chain Database - - Stores validator objects, schedules, participation metrics, and supports validator scheduling and participation computations. - - Manages block post validation objects and updates last irreversible block computation based on validator confirmations. - - **Enhanced**: Provides enhanced fork database access with comprehensive querying capabilities for fork collision detection. - - **Enhanced**: Implements comprehensive validator reward creation with find_account() validation to prevent crashes from missing account objects. - - **Enhanced**: **NEW**: Integrates with skip_undo_history_check flag for controlled production during recovery scenarios. - - **Enhanced**: **NEW**: Implements comprehensive debug logging system enabling verbose traces for chain internals and block processing. - - **New**: Implements compare_fork_branches() function for intelligent fork weight comparison with +10% longer-chain bonus. - - **New**: Provides automatic stale fork pruning mechanism to remove competing blocks from dead forks. - - **New**: **Enhanced**: Supports emergency_consensus_active field for emergency consensus mode detection. - -**Updated** Added comprehensive error handling and validation for validator reward creation, including find_account() checks before creating vesting rewards, crash prevention mechanisms, clear recovery procedures for database corruption scenarios, new fork collision timeout configuration, two-level fork resolution system with vote-weighted comparison and stuck-head timeout, enhanced fork database querying capabilities, automatic stale fork pruning after successful block application, **NEW**: comprehensive minority fork detection system with automatic recovery mechanisms, **NEW**: enhanced emergency consensus mode integration, **NEW**: skip_undo_history_check flag for controlled production during recovery scenarios, **NEW**: comprehensive debug logging system enabling verbose traces for block production and chain internals, **NEW**: validator protection and monitoring capabilities with auto-restore and auto-disable features, **NEW**: emergency recovery mechanisms with automatic key restoration, **NEW**: consecutive block protection to prevent validator abuse, **NEW**: enhanced network connectivity with improved peer synchronization. - -**Section sources** -- [validator.hpp:34-68](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L68) -- [validator.cpp:59-118](file://plugins/validator/validator.cpp#L59-L118) -- [validator.cpp:206-249](file://plugins/validator/validator.cpp#L206-L249) -- [witness_guard.hpp:11-48](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L11-L48) -- [witness_guard.cpp:27-78](file://plugins/witness_guard/witness_guard.cpp#L27-L78) -- [witness_guard.cpp:83-191](file://plugins/witness_guard/witness_guard.cpp#L83-L191) -- [witness_guard.cpp:360-369](file://plugins/witness_guard/witness_guard.cpp#L360-L369) -- [witness_api_plugin.hpp:56-98](file://plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp#L56-L98) -- [witness_api_plugin.cpp:13-28](file://plugins/witness_api/plugin.cpp#L13-L28) -- [database.hpp:37-83](file://libraries/chain/include/graphene/chain/database.hpp#L37-L83) - -## Architecture Overview -The validator subsystem integrates tightly with the chain database and P2P layer. The Validator Plugin periodically evaluates conditions to produce a block using optimized 250ms interval scheduling, consults the database for validator scheduling and participation, and broadcasts the resulting block. The validator guard plugin provides continuous monitoring and protection for validator keys, automatically restoring null signing keys and preventing validator abuse through auto-disable mechanisms. The validator API plugin reads from the database to serve JSON-RPC queries. **New**: Other plugins can now coordinate with validator scheduling using the `is_witness_scheduled_soon()` method to avoid conflicts during critical operations. - -**Enhanced** The architecture now includes robust NTP time synchronization with automatic fallback mechanisms, crash-safe shutdown procedures, plugin coordination capabilities through the new scheduling method, comprehensive fork collision detection system with two-level resolution, enhanced fork database querying capabilities, automatic stale fork pruning, enhanced validator reward creation with comprehensive validation and error handling, **NEW**: comprehensive minority fork detection system with automatic recovery mechanisms, **NEW**: enhanced emergency consensus mode integration, **NEW**: skip_undo_history_check flag for controlled production during recovery scenarios, **NEW**: comprehensive debug logging system enabling verbose traces for block production and chain internals, **NEW**: validator protection and monitoring capabilities with auto-restore and auto-disable features, **NEW**: emergency recovery mechanisms with automatic key restoration, **NEW**: consecutive block protection to prevent validator abuse, **NEW**: enhanced network connectivity with improved peer synchronization. - -```mermaid -sequenceDiagram -participant Timer as "validator Impl
schedule_production_loop (250ms ticks)" -participant Guard as "validator Guard
auto-restore & protection" -participant NTP as "NTP Service
time synchronization" -participant DB as "Chain Database
DEBUG logging : enabled
emergency_consensus_active" -participant ForkDB as "Fork Database
collision detection
stale pruning" -participant P2P as "P2P Plugin" -participant Net as "Network" -participant Snapshot as "Snapshot Plugin
coordination" -Guard->>DB : check_and_restore_internal() -DB-->>Guard : network health & key status -alt network healthy & key null -Guard->>P2P : broadcast witness_update -P2P-->>Guard : transaction broadcast -Guard->>DB : track pending confirmation -else network unhealthy -Guard->>Guard : skip auto-restore -end -Timer->>NTP : check_time_sync() -NTP-->>Timer : synchronized status -alt timing issues detected -Timer->>NTP : force_sync() -NTP-->>Timer : updated time -end -Timer->>Timer : compute next 250ms boundary -Timer->>DB : get_slot_at_time(now + 250ms) -DB-->>Timer : slot -alt slot available -Timer->>DB : get_scheduled_witness(slot) -Timer->>DB : get_slot_time(slot) -Timer->>DB : witness_participation_rate() -alt conditions met -alt fork collision check -Timer->>ForkDB : fetch_block_by_number(head_block_num + 1) -ForkDB-->>Timer : existing blocks at height -alt competing blocks exist -alt LEVEL 1 : Vote-weighted comparison -Timer->>DB : compare_fork_branches(competing_id, head_id) -DB-->>Timer : weight comparison result -alt comparison possible -alt competing fork heavier -Timer->>NTP : force_sync() on fork collision -Timer->>Timer : log fork collision and defer -else our fork heavier -Timer->>ForkDB : remove(competing_id) -Timer->>Timer : reset defer count -else tied/comparison impossible -alt LEVEL 2 : Stuck-head timeout -Timer->>Timer : fork_collision_defer_count++ -alt timeout exceeded (21 blocks) -Timer->>ForkDB : remove_blocks_by_number(head_block_num + 1) -Timer->>Timer : reset defer count -Timer->>Timer : fall through to produce -else timeout not exceeded -Timer->>NTP : force_sync() on fork collision -Timer->>Timer : log fork collision and defer -end -else no competing blocks -alt minority fork detection -Timer->>ForkDB : check last CHAIN_MAX_WITNESSES blocks -alt all from our validators -alt emergency consensus active -Timer->>Timer : skip minority fork detection -else enable-stale-production enabled -Timer->>Timer : continue production -else enable-stale-production disabled -Timer->>P2P : resync_from_lib() -Timer->>Timer : production disabled -Timer->>Timer : return minority_fork -else not a minority fork -alt validator reward creation -Timer->>DB : get_witness(current_witness) -Timer->>DB : find_account(validator.owner) -alt account exists -Timer->>DB : create_vesting(account, reward) -Timer->>DB : push_virtual_operation(witness_reward) -else account missing -Timer->>DB : log critical error -Timer->>DB : FC_ASSERT restart required -end -Timer->>P2P : broadcast_block(block) -P2P->>Net : transmit block -end -else conditions not met -Timer->>NTP : update_ntp_time() on lag -Timer->>Timer : log reason (sync, participation, key, lag) -end -else no slot -Timer->>Timer : wait until next 250ms tick -end -Timer->>Timer : reschedule for next 250ms tick -Note over Snapshot : Check if validator scheduled soon
to coordinate operations -Snapshot->>Timer : is_witness_scheduled_soon() -Timer-->>Snapshot : true/false -``` - -**Diagram sources** -- [validator.cpp:206-276](file://plugins/validator/validator.cpp#L206-L276) -- [validator.cpp:278-423](file://plugins/validator/validator.cpp#L278-L423) -- [validator.cpp:447-471](file://plugins/validator/validator.cpp#L447-L471) -- [validator.cpp:590-695](file://plugins/validator/validator.cpp#L590-L695) -- [validator.cpp:263-266](file://plugins/validator/validator.cpp#L263-L266) -- [validator.cpp:206-249](file://plugins/validator/validator.cpp#L206-L249) -- [witness_guard.cpp:83-191](file://plugins/witness_guard/witness_guard.cpp#L83-L191) -- [witness_guard.cpp:455-544](file://plugins/witness_guard/witness_guard.cpp#L455-L544) -- [database.cpp:4317-4332](file://libraries/chain/database.cpp#L4317-L4332) -- [time.cpp:74-76](file://libraries/time/time.cpp#L74-L76) -- [snapshot_plugin.cpp:1267-1276](file://plugins/snapshot/plugin.cpp#L1267-1276) -- [database.cpp:2824-2839](file://libraries/chain/database.cpp#L2824-L2839) -- [database.cpp:2871-2886](file://libraries/chain/database.cpp#L2871-2886) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [p2p_plugin.hpp:50-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L50-L55) - -## Configuration Parameters - -### Parameter Types and Scaling - -The Validator Plugin configuration parameters have been updated with improved type safety and scaling: - -- **enable-stale-production**: Boolean parameter controlling whether block production continues when the chain is stale - - Type: `bool` (previously `int`) - - Default: `false` (changed from `true`) - - Purpose: Allows production even when the node is behind the chain head - - Command line: `--enable-stale-production` - - Config file: `enable-stale-production` - -- **required-participation**: Integer parameter specifying minimum validator participation percentage - - Type: `uint32_t` (changed from `int`) - - Scale: Multiplied by `CHAIN_1_PERCENT` (100 units = 1%) - - Range: 0-99% (0-9900 units) - - Default: 33% (3300 units) - - Command line: `--required-participation` - - Config file: `required-participation` - -- **fork-collision-timeout-blocks**: New parameter controlling fork collision timeout behavior - - Type: `uint32_t` - - Default: 21 blocks (one full validator round, 63 seconds) - - Purpose: Number of consecutive fork-collision deferrals before forcing production - - Command line: `--fork-collision-timeout-blocks` - - Config file: `fork-collision-timeout-blocks` - -- **debug-block-production**: **NEW** Boolean parameter enabling verbose debug logging for block production and chain internals - - Type: `bool` - - Default: `false` - - Purpose: Enables comprehensive logging with detailed traces of validator participation checks, emergency mode enforcement, block post-validation processes, and minority fork detection - - Command line: `--debug-block-production` - - Config file: `debug-block-production` - -### validator Guard Plugin Configuration - -**NEW** The validator guard plugin introduces several new configuration parameters: - -- **validator-guard-enabled**: Boolean parameter enabling/disabling the validator protection and monitoring system - - Type: `bool` - - Default: `true` - - Purpose: Controls whether the validator guard plugin is active - - Command line: `--validator-guard-enabled` - - Config file: `validator-guard-enabled` - -- **validator-guard-disable**: Integer parameter controlling consecutive block auto-disable threshold - - Type: `uint32_t` - - Default: 5 blocks - - Purpose: Number of consecutive blocks produced by the same validator before automatic disabling - - Command line: `--validator-guard-disable` - - Config file: `validator-guard-disable` - -- **validator-guard-interval**: Integer parameter controlling check frequency - - Type: `uint32_t` - - Default: 20 blocks (approximately 60 seconds) - - Purpose: How often to check validator signing keys in block intervals - - Command line: `--validator-guard-interval` - - Config file: `validator-guard-interval` - -- **validator-guard-validator**: Array parameter defining validators to monitor - - Type: `std::vector` - - Format: JSON triplet `["name", "signing_wif", "active_wif"]` - - Purpose: Specifies which validators to monitor and their key pairs - - Command line: `--validator-guard-validator` - - Config file: `validator-guard-validator` - -### Configuration Defaults - -**Updated** The default values have been corrected for production stability: - -- **enable-stale-production**: Now defaults to `false` to improve network stability and prevent minority fork propagation -- **required-participation**: Defaults to 33% participation threshold for balanced security/performance -- **fork-collision-timeout-blocks**: Defaults to 21 blocks to match one full validator schedule round -- **debug-block-production**: Defaults to `false` to maintain production performance while providing debugging capability when needed -- **validator-guard-enabled**: Defaults to `true` to provide comprehensive validator protection by default -- **validator-guard-disable**: Defaults to 5 consecutive blocks to prevent validator abuse while allowing normal operation -- **validator-guard-interval**: Defaults to 20 blocks for balanced monitoring frequency -- **validator-guard-validator**: Defaults to empty (no validators monitored) requiring explicit configuration - -### Configuration Processing - -The configuration parameters are processed during plugin initialization: - -```mermaid -flowchart TD -Config["Configuration File"] --> Parser["Parameter Parser"] -Parser --> TypeCheck{"Type Validation"} -TypeCheck --> |enable-stale-production| BoolConvert["Convert to bool
Default: false"] -TypeCheck --> |required-participation| IntConvert["Convert to uint32_t
Scale by CHAIN_1_PERCENT"] -TypeCheck --> |fork-collision-timeout-blocks| TimeoutConvert["Convert to uint32_t
Default: 21 blocks"] -TypeCheck --> |debug-block-production| DebugConvert["Convert to bool
Default: false"] -TypeCheck --> |validator-guard-enabled| GuardEnabled["Convert to bool
Default: true"] -TypeCheck --> |validator-guard-disable| DisableConvert["Convert to uint32_t
Default: 5"] -TypeCheck --> |validator-guard-interval| IntervalConvert["Convert to uint32_t
Default: 20"] -TypeCheck --> |validator-guard-validator| WitnessArray["Parse JSON triplets
Format: [name, signing_wif, active_wif]"] -BoolConvert --> Storage["Store in plugin state"] -IntConvert --> Storage -TimeoutConvert --> Storage -DebugConvert --> Storage -GuardEnabled --> Storage -DisableConvert --> Storage -IntervalConvert --> Storage -WitnessArray --> Storage -Storage --> Runtime["Runtime Usage"] -``` - -**Diagram sources** -- [validator.cpp:125-133](file://plugins/validator/validator.cpp#L125-L133) -- [validator.cpp:149-155](file://plugins/validator/validator.cpp#L149-L155) -- [validator.cpp:222-224](file://plugins/validator/validator.cpp#L222-L224) -- [validator.cpp:228-233](file://plugins/validator/validator.cpp#L228-L233) -- [witness_guard.cpp:301-328](file://plugins/witness_guard/witness_guard.cpp#L301-L328) -- [witness_guard.cpp:330-408](file://plugins/witness_guard/witness_guard.cpp#L330-L408) -- [config.hpp:57-58](file://libraries/protocol/include/graphene/protocol/config.hpp#L57-L58) - -**Section sources** -- [validator.cpp:125-133](file://plugins/validator/validator.cpp#L125-L133) -- [validator.cpp:149-155](file://plugins/validator/validator.cpp#L149-L155) -- [validator.cpp:222-224](file://plugins/validator/validator.cpp#L222-L224) -- [validator.cpp:228-233](file://plugins/validator/validator.cpp#L228-L233) -- [witness_guard.cpp:301-328](file://plugins/witness_guard/witness_guard.cpp#L301-L328) -- [witness_guard.cpp:330-408](file://plugins/witness_guard/witness_guard.cpp#L330-L408) -- [config.hpp:57-58](file://libraries/protocol/include/graphene/protocol/config.hpp#L57-L58) -- [config.ini:99-103](file://share/vizd/config/config.ini#L99-L103) -- [config_witness.ini:76-80](file://share/vizd/config/config_witness.ini#L76-L80) -- [config_witness.ini:128-141](file://share/vizd/config/config_witness.ini#L128-L141) - -## Detailed Component Analysis - -### Validator Plugin -Responsibilities: -- Parse configuration for validator names and private keys. -- Initialize NTP time synchronization with 250ms interval optimization. -- Run a production loop that: - - Waits until synchronized to the next 250ms boundary for deterministic slot alignment. - - Checks participation thresholds and scheduling eligibility. - - **Enhanced**: Performs comprehensive fork collision detection before block generation. - - **Enhanced**: **NEW**: Implements comprehensive minority fork detection to identify when all recent blocks were produced by local validators only. - - **Enhanced**: **NEW**: Provides automatic recovery through resync_from_lib() when minority fork is detected. - - **Enhanced**: **NEW**: Integrates with skip_undo_history_check flag for controlled production during recovery scenarios. - - **Enhanced**: **NEW**: Implements comprehensive debug logging with verbose traces for block production and chain internals. - - **New**: Implements two-level fork collision resolution system with configurable timeout. - - Generates and broadcasts blocks when eligible. - - Signs and broadcasts block post validations when available. - - **Enhanced**: Forces NTP synchronization when timing issues or fork collisions are detected during production attempts. - - **New**: Provides `is_witness_scheduled_soon()` method for external coordination. - -Key behaviors: -- Participation threshold enforcement via validator participation rate. -- Graceful handling of missing private keys, low participation, and timing lags. -- Optional allowance for stale production during initial sync. -- **Enhanced**: Automatic NTP synchronization on lag detection and fork collision to prevent timing-related production failures. -- **Enhanced**: Comprehensive fork collision detection prevents competing blocks at the same height. -- **Enhanced**: **NEW**: Minority fork detection identifies when all recent blocks were produced by local validators only. -- **Enhanced**: **NEW**: Automatic recovery through P2P resynchronization when minority fork is detected. -- **Enhanced**: **NEW**: Controlled production during recovery scenarios using skip_undo_history_check flag. -- **Enhanced**: **NEW**: Comprehensive debug logging system with verbose traces for detailed visibility into block production pipeline. -- **New**: Efficient slot checking across 4 upcoming slots to detect validator scheduling conflicts. -- **New**: Two-level fork collision resolution with vote-weighted comparison and stuck-head timeout mechanism. -- **New**: Configurable fork collision timeout blocks parameter for fine-tuning fork resolution behavior. - -```mermaid -flowchart TD -Start(["Startup"]) --> InitKeys["Load validator names and private keys"] -InitKeys --> InitNTP["Initialize NTP time service with 250ms ticks"] -InitNTP --> InitTimeout["Initialize fork-collision-timeout-blocks (21)"] -InitTimeout --> InitSkipFlag["Initialize skip_undo_history_check (false)"] -InitSkipFlag --> InitDebug["Initialize debug-block-production (false)"] -InitDebug --> SyncCheck["Wait until synchronized to 250ms boundary"] -SyncCheck --> SlotCheck{"Slot available?"} -SlotCheck --> |No| WaitNext["Sleep until next 250ms tick"] --> SyncCheck -SlotCheck --> |Yes| Scheduled["Get scheduled validator and slot time"] -Scheduled --> Participation{"Participation >= threshold?"} -Participation --> |No| LogLowParticipation["Log low participation"] --> Resched["Reschedule"] --> SyncCheck -Participation --> |Yes| TimeCheck{"Within 500ms window?"} -TimeCheck --> |No| LogLag["Log lag"] --> ForceSync["Force NTP sync"] --> Resched -TimeCheck --> |Yes| ForkCollision{"Fork collision check"} -ForkCollision --> |Competing blocks| Level1["LEVEL 1: Vote-weighted comparison"] -Level1 --> |Comparison possible| WeightCheck{"Weight comparison"} -WeightCheck --> |Our fork heavier| RemoveComp["Remove competing block"] --> ResetCount["Reset defer count"] --> SignCheck -WeightCheck --> |Competing fork heavier| LogCollision["Log fork collision"] --> ForceSync --> Resched -WeightCheck --> |Tied/Impossible| Level2["LEVEL 2: Stuck-head timeout"] -Level2 --> TimeoutCheck{"Defer count > timeout (21)?"} -TimeoutCheck --> |Yes| PruneFork["Prune stale competing blocks"] --> ResetCount -TimeoutCheck --> |No| LogCollision2["Log fork collision"] --> ForceSync --> Resched -ForkCollision --> |No competing blocks| MinorityFork{"Minority fork detection"} -MinorityFork --> |All recent blocks from us| EmergencyCheck{"Emergency consensus active?"} -EmergencyCheck --> |Yes| SkipMinority["Skip minority fork detection"] --> SignCheck -EmergencyCheck --> |No| StaleProdCheck{"enable-stale-production enabled?"} -StaleProdCheck --> |Yes| ContinueProd["Continue production"] --> SignCheck -StaleProdCheck --> |No| Resync["P2P resync_from_lib()"] --> DisableProd["Disable production"] --> ReturnMinority["Return minority_fork"] -MinorityFork --> |Not a minority fork| SignCheck{"Private key available?"} -SignCheck --> |No| LogNoKey["Log missing key"] --> Resched -SignCheck --> |Yes| RewardValidation{"Validate validator account"} -RewardValidation --> |Account exists| Produce["Generate block and broadcast"] -RewardValidation --> |Account missing| CriticalError["Log critical error
Request node restart"] -Produce --> Resched -``` - -**Diagram sources** -- [validator.cpp:206-276](file://plugins/validator/validator.cpp#L206-L276) -- [validator.cpp:278-423](file://plugins/validator/validator.cpp#L278-L423) -- [validator.cpp:447-471](file://plugins/validator/validator.cpp#L447-L471) -- [validator.cpp:590-695](file://plugins/validator/validator.cpp#L590-L695) -- [validator.cpp:263-266](file://plugins/validator/validator.cpp#L263-L266) -- [validator.cpp:509-555](file://plugins/validator/validator.cpp#L509-L555) -- [p2p_plugin.hpp:50-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L50-L55) - -**Section sources** -- [validator.hpp:34-68](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L68) -- [validator.cpp:120-169](file://plugins/validator/validator.cpp#L120-L169) -- [validator.cpp:171-192](file://plugins/validator/validator.cpp#L171-L192) -- [validator.cpp:206-276](file://plugins/validator/validator.cpp#L206-L276) -- [validator.cpp:278-423](file://plugins/validator/validator.cpp#L278-L423) -- [validator.cpp:447-471](file://plugins/validator/validator.cpp#L447-L471) -- [validator.cpp:590-695](file://plugins/validator/validator.cpp#L590-L695) -- [validator.cpp:206-249](file://plugins/validator/validator.cpp#L206-L249) -- [validator.cpp:509-555](file://plugins/validator/validator.cpp#L509-L555) - -### New: Enhanced Minoriy Fork Detection System -The Validator Plugin now implements a comprehensive minority fork detection system to identify when all recent blocks were produced by local validators only, indicating a potential minority fork scenario. - -**Detection Logic**: -- Checks the last CHAIN_MAX_WITNESSES (21) blocks in the fork database -- Verifies that all blocks were produced by configured local validators -- Skips detection during emergency consensus mode to prevent false positives -- Integrates with skip_undo_history_check flag for controlled production during recovery - -**Recovery Mechanisms**: -- **Automatic Recovery**: Calls P2P resync_from_lib() to reset sync from last irreversible block -- **Production Control**: Disables production temporarily during recovery -- **Flag Management**: Uses skip_undo_history_check to control production flags during recovery -- **Emergency Mode Protection**: Skips detection when emergency consensus is active - -**Implementation Details**: -- Uses `db.get_fork_db().head()` to access the fork database head -- Iterates backwards through CHAIN_MAX_WITNESSES blocks to verify validator ownership -- Checks `_witnesses.find(current->data.validator) == _witnesses.end()` to detect foreign validators -- Calls `p2p().resync_from_lib()` for automatic recovery -- Returns `block_production_condition::minority_fork` to signal recovery state -- Integrates with emergency consensus detection via `dgp.emergency_consensus_active` - -**Section sources** -- [validator.cpp:509-555](file://plugins/validator/validator.cpp#L509-L555) -- [validator.hpp:31](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L31) -- [database.hpp:88](file://libraries/chain/include/graphene/chain/database.hpp#L88) - -### New: Enhanced Fork Collision Detection System -The Validator Plugin now implements a comprehensive fork collision detection system to prevent competing blocks at the same height. - -**Detection Logic**: -- Queries the fork database for all blocks at the next height level (head_block_num + 1) -- Identifies competing blocks produced by different validators with different parent blocks -- Prevents block production when fork collision is detected -- Automatically triggers NTP synchronization to resolve timing issues - -**Two-Level Resolution System**: -- **LEVEL 1: Vote-weighted comparison** (HF12+) - - Uses compare_fork_branches() function to calculate vote weights for both forks - - Applies +10% bonus to longer chain for fairness - - Removes losing fork or defers production based on weight comparison -- **LEVEL 2: Stuck-head timeout** (All versions) - - Tracks consecutive deferral count for fork collisions - - Forces production after timeout exceeds configured blocks (default: 21) - - Removes all competing blocks at the target height after timeout - -**Implementation Details**: -- Uses `db.get_fork_db().fetch_block_by_number(db.head_block_num() + 1)` to query all blocks at the target height -- Analyzes each existing block to determine if it was produced by a different validator with a different parent -- Captures detailed information including height and scheduled validator for logging -- Returns `block_production_condition::fork_collision` to defer production -- Integrates with compare_fork_branches() function for intelligent fork switching decisions - -**Section sources** -- [validator.cpp:447-471](file://plugins/validator/validator.cpp#L447-L471) -- [validator.cpp:590-695](file://plugins/validator/validator.cpp#L590-L695) -- [fork_database.hpp:73](file://libraries/chain/include/graphene/chain/fork_database.hpp#L73) -- [fork_database.cpp:151-166](file://libraries/chain/fork_database.cpp#L151-166) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) - -### New: Enhanced Fork Database Integration with Automatic Stale Pruning -The fork database now includes automatic stale fork pruning capabilities to maintain database efficiency and prevent memory bloat. - -**Automatic Stale Pruning Mechanism**: -- Executes after successful block application in _push_block() method -- Identifies competing blocks at the same height that are no longer part of the active chain -- Removes blocks whose parent is no longer in fork_db (indicating dead fork) -- Prevents accumulation of stale competing blocks in the fork database - -**Pruning Logic**: -- Called after new block is successfully applied -- Fetches all competing blocks at new_block.block_num() -- Checks if competing block has unknown parent (not in fork_db) -- Removes stale competing blocks with log messages for debugging -- Preserves legitimate fork switching scenarios by only removing truly dead forks - -**Integration Points**: -- Triggered in database::_push_block() after successful block application -- Works in conjunction with fork collision detection system -- Supports both HF12+ vote-weighted comparisons and pre-HF12 longest-chain rules -- Maintains fork database integrity and performance - -**Section sources** -- [database.cpp:1456-1471](file://libraries/chain/database.cpp#L1456-L1471) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) - -### New: Enhanced compare_fork_branches() Function -The database now includes a sophisticated compare_fork_branches() function that intelligently evaluates fork weight and chain length for decision-making. - -**Function Capabilities**: -- Calculates total vote weight for each fork branch using validator objects -- Excludes emergency validator account from weight calculations -- Applies +10% bonus to longer chain to encourage network support -- Handles tied scenarios and comparison impossibility gracefully -- Returns 1 (branch_a heavier), -1 (branch_b heavier), or 0 (tied/impossible) - -**Weight Calculation Algorithm**: -- Iterates through branch items to accumulate validator vote weights -- Uses flat_set to avoid counting the same validator multiple times -- Skips emergency validator account for fairness -- Handles exceptions during validator lookup gracefully -- Returns 0 when comparison cannot be performed - -**Integration with Fork Resolution**: -- Used by Validator Plugin for vote-weighted fork comparisons -- Supports HF12+ consensus rules with longer-chain bonus -- Provides fallback for pre-HF12 longest-chain scenarios -- Enables intelligent fork switching decisions - -**Section sources** -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) - -### New: is_witness_scheduled_soon() Method -The `is_witness_scheduled_soon()` method provides a crucial coordination mechanism for other plugins to avoid conflicts during critical operations. - -**Method Signature**: `bool is_witness_scheduled_soon() const` - -**Purpose**: Checks if any locally-controlled validators are scheduled to produce blocks in the upcoming 4 slots, enabling other plugins to coordinate and avoid conflicts during critical operations like snapshot creation. - -**Implementation Details**: -- Validates that the Validator Plugin has been initialized with validators and private keys -- Calculates the current slot based on synchronized time plus 250ms buffer for deterministic alignment -- Iterates through slots 0-3 positions ahead to check for scheduled validators -- Verifies that the scheduled validator belongs to the locally-controlled set -- Confirms the validator has a valid signing key (not disabled) -- Ensures the plugin has the corresponding private key for block signing - -**Usage Pattern**: Other plugins can use this method to defer operations when validator production is imminent, particularly useful for snapshot creation which requires exclusive access to the blockchain state. - -**Section sources** -- [validator.cpp:206-249](file://plugins/validator/validator.cpp#L206-L249) - -### Enhanced: validator Reward Creation Process -The validator reward creation process has been significantly enhanced with comprehensive error handling and validation to prevent crashes when validator account objects are missing from the database. - -**Enhanced Reward Creation Logic**: -- **Pre-validation**: Uses `find_account()` to check if the validator account exists before attempting reward creation -- **Crash Prevention**: Implements comprehensive validation to prevent crashes from shared memory corruption -- **Clear Recovery Guidance**: Provides explicit instructions for recovery procedures when accounts are missing -- **Multi-hardfork Support**: Applies validation across all hardfork versions (HF4, HF11, and legacy models) - -**Implementation Details**: -- **HF11 Model**: Validates validator account before creating vesting rewards for new emission model -- **HF4 Model**: Comprehensive validation for consensus inflation model with detailed error logging -- **Legacy Model**: Falls back to `get_account()` with clear error messaging for older models -- **Critical Error Handling**: Logs detailed validator information (signing key, missed blocks, penalties) for debugging - -**Error Handling Features**: -- Detailed logging with validator metadata (signing key, missed blocks, penalties, last confirmed block) -- Clear FC_ASSERT messages directing users to restart with replay -- Account index size reporting for diagnostic purposes -- Prevention of crashes during validator reward distribution - -```mermaid -flowchart TD -ProcessFunds["process_funds()"] --> HardforkCheck{"Hardfork Version?"} -HardforkCheck --> |HF11| HF11Path["New Emission Model"] -HF11Check --> |HF4| HF4Path["Consensus Inflation Model"] -HF11Check --> |Legacy| LegacyPath["Legacy Model"] -HF11Path --> HF11Witness["get_witness(current_witness)"] -HF11Witness --> HF11FindAccount["find_account(owner)"] -HF11FindAccount --> |Exists| HF11CreateVesting["create_vesting(account, reward)"] -HF11FindAccount --> |Missing| HF11CriticalError["elog critical error
FC_ASSERT restart required"] -HF4Path --> HF4Witness["get_witness(current_witness)"] -HF4Witness --> HF4FindAccount["find_account(owner)"] -HF4FindAccount --> |Exists| HF4CreateVesting["create_vesting(account, reward)"] -HF4FindAccount --> |Missing| HF4CriticalError["elog critical error
FC_ASSERT restart required"] -LegacyPath --> LegacyWitness["get_witness(current_witness)"] -LegacyWitness --> LegacyGetAccount["get_account(owner)"] -LegacyGetAccount --> LegacyCreateVesting["create_vesting(account, reward)"] -``` - -**Diagram sources** -- [database.cpp:2807-2839](file://libraries/chain/database.cpp#L2807-L2839) -- [database.cpp:2871-2886](file://libraries/chain/database.cpp#L2871-L2886) -- [database.cpp:2897-2914](file://libraries/chain/database.cpp#L2897-L2914) - -**Section sources** -- [database.cpp:2807-2839](file://libraries/chain/database.cpp#L2807-L2839) -- [database.cpp:2871-2886](file://libraries/chain/database.cpp#L2871-L2886) -- [database.cpp:2897-2914](file://libraries/chain/database.cpp#L2897-L2914) -- [database.cpp:1294-1311](file://libraries/chain/database.cpp#L1294-L1311) - -### New: Comprehensive Debug Logging System -The Validator Plugin now implements a comprehensive debug logging system that provides verbose traces for block production and chain internals with detailed visibility into the entire block production pipeline. - -**Debug Logging Features**: -- **Block Production Loop**: Comprehensive logging of block_production_loop() entry and exit points -- **Maybe Produce Block**: Detailed tracing of maybe_produce_block() execution flow -- **Condition Tracking**: Timestamped logging of block production condition results -- **Emergency Mode**: Enhanced logging for emergency consensus mode enforcement -- **Minority Fork Detection**: Granular visibility into minority fork detection process -- **Fork Collision Resolution**: Detailed traces of fork collision detection and resolution -- **Contextual Information**: Rich contextual data including timestamps, block numbers, and validator information - -**Logging Implementation**: -- Uses `database()._debug_block_production` flag to control debug logging -- Implements comprehensive `ilog()` statements throughout the block production pipeline -- Provides detailed traces for validator participation checks, emergency mode enforcement, block post-validation processes, and minority fork detection -- Includes timestamps and contextual information for better debugging and troubleshooting -- Supports granular visibility into all aspects of block production with minimal performance impact - -**Section sources** -- [validator.cpp:228-233](file://plugins/validator/validator.cpp#L228-L233) -- [validator.cpp:338-407](file://plugins/validator/validator.cpp#L338-L407) -- [validator.cpp:411-419](file://plugins/validator/validator.cpp#L411-L419) -- [database.hpp:60](file://libraries/chain/include/graphene/chain/database.hpp#L60) -- [database.cpp:1890-1892](file://libraries/chain/database.cpp#L1890-L1892) -- [database.cpp:4536-4573](file://libraries/chain/database.cpp#L4536-L4573) -- [database.cpp:5530-5655](file://libraries/chain/database.cpp#L5530-L5655) - -### New: Dedicated Production Timer Thread -The production timer runs on its own `production_io_service_` with a dedicated OS thread, fully isolated from the appbase/P2P shared io_service. This prevents P2P network activity — peer disconnects, TLS handshakes, send-queue drains — from delaying the `async_wait` callback and causing missed-slot lag. - -**Implementation**: -- `production_io_service_` is a private `boost::asio::io_service` declared in `impl`, initialized before `production_timer_`. -- `production_io_work_` keeps the io_service alive while the thread runs. -- `production_io_thread_` (`std::thread`) calls `production_io_service_.run()`. -- Destructor: `production_io_service_.stop()` then `join()` for clean shutdown. -- All timer operations (`expires_from_now`, `async_wait`) use this dedicated service. - -**Benefit**: The 250ms tick fires on its own OS thread with no contention from P2P fiber scheduling. Even under heavy peer churn the production callback is called at the correct wall-clock time. - -**Section sources** -- [validator.cpp:64-83](file://plugins/validator/validator.cpp#L64-L83) -- [validator.cpp:139-144](file://plugins/validator/validator.cpp#L139-L144) -- [validator.cpp:374-378](file://plugins/validator/validator.cpp#L374-L378) -- [validator.cpp:663-685](file://plugins/validator/validator.cpp#L663-L685) - -### New: Lag Tight Loop Prevention -After a `lag` production condition the current slot is already missed. Without a guard the next 250ms tick re-evaluates the same slot and returns `lag` again, spinning in a tight loop until the full 3s slot interval passes. - -**Fix**: `_last_lag_slot_time` records the `scheduled_time` of the missed slot. At the top of `schedule_production_loop()` the elapsed time since the lag is computed; if less than one full slot interval (3 s = `CHAIN_BLOCK_INTERVAL * 1000 ms`) has passed, the timer is set to skip the remainder of that slot before resuming normal 250ms scheduling. - -**State variable**: `fc::time_point_sec _last_lag_slot_time` (zero when no lag is active). - -**Section sources** -- [validator.cpp:182-186](file://plugins/validator/validator.cpp#L182-L186) -- [validator.cpp:911-920](file://plugins/validator/validator.cpp#L911-L920) -- [validator.cpp:945-963](file://plugins/validator/validator.cpp#L945-L963) - -### New: Production Watchdog -The watchdog fires when the node has produced at least one block (`_ever_produced = true`) but has gone silent while production conditions are met. This catches cases where an external factor (e.g., the emergency master blanking our key) silently stops production without returning an explicit error. - -**Thresholds**: -- Emergency master (has `CHAIN_EMERGENCY_WITNESS_ACCOUNT` in `_witnesses`): **60 seconds**. -- Regular validator: **180 seconds**. -- Re-fires every **30 seconds** after the first alert. - -**Actions on first fire**: -1. Sets `_watchdog_debug_enabled = true` and enables `database()._debug_block_production` to capture verbose production loop traces automatically. -2. Logs full diagnostic state: NTP drift, head block, DLT sync status, P2P catchup flag, scheduled validator, how many of our validators appear in the shuffled schedule, and which validators have blanked on-chain keys. - -**Relevant state**: -- `bool _ever_produced` — set to true on first successful production. -- `fc::time_point _last_production_time` — updated on every produced block. -- `int _last_slot_result` — last non-`not_time_yet` result (meaningful failure code for diagnostics). -- `bool _watchdog_debug_enabled` — latching flag; never reset once set. - -**Section sources** -- [validator.cpp:174-191](file://plugins/validator/validator.cpp#L174-L191) -- [validator.cpp:965-1065](file://plugins/validator/validator.cpp#L965-L1065) - -### New: Slot Hijack Detection -In DLT emergency consensus mode the emergency master may blank a regular validator's signing key and produce `committee` blocks in that validator's scheduled slots. The hijack counter makes this pattern visible in watchdog diagnostics. - -**Detection logic** (runs inside `on_block_applied`): -1. Skip if emergency consensus is not active. -2. Compute `slot_idx = dgp.current_aslot % num_scheduled_witnesses`. -3. Look up the expected validator at `slot_idx` in the shuffled schedule. -4. If the actual block producer (`block.validator`) is `committee` (or any non-local validator) AND `slot_idx` maps to one of our validators → hijack detected. -5. If the actual producer IS one of our validators (any of them) → reset counter (false-positive guard). - -**State variables**: -- `uint32_t _slot_hijack_count` — consecutive hijacked slots since last own production. -- `uint32_t _slot_hijack_height` — block number of last detected hijack. - -**Logging**: First 3 hijacks are always logged; thereafter once per minute to avoid log spam. - -**Section sources** -- [validator.cpp:192-207](file://plugins/validator/validator.cpp#L192-L207) -- [validator.cpp:486-562](file://plugins/validator/validator.cpp#L486-L562) - -### New: Missed Block Diagnostic via on_block_applied Signal -The Validator Plugin subscribes to the chain's `applied_block` signal via `on_block_applied()`. When an incoming block reveals that one or more slots were skipped (block number jumped by more than 1 since the last applied block), and any of the missed slots were assigned to one of our validators, the handler dumps the full plugin state for diagnosis. - -**Triggered by**: gaps between `_last_applied_block_num` and the new block number. - -**State variable**: `uint64_t _last_applied_block_num` — updated on every applied block. - -**Section sources** -- [validator.cpp:200-207](file://plugins/validator/validator.cpp#L200-L207) -- [validator.cpp:470-562](file://plugins/validator/validator.cpp#L470-L562) - -### New: not_my_turn Streak Detection -When the production loop repeatedly returns `not_my_turn` (another validator is scheduled) for an extended period while our validators are supposed to be in the schedule, it may indicate schedule misalignment, a forked-off chain, or a configuration error. - -**Threshold**: **500 consecutive** `not_my_turn` results ≈ 125 seconds of other validators producing uninterrupted. - -**On threshold**: Logs a warning with streak count, elapsed time, last scheduled validator name, and our configured validator set. - -**Reset**: On any `produced`, `not_synced`, or other non-`not_my_turn` result. - -**State variables**: -- `uint32_t _not_my_turn_streak` -- `fc::time_point _not_my_turn_streak_start` -- `std::string _last_scheduled_witness` - -**Section sources** -- [validator.cpp:170-173](file://plugins/validator/validator.cpp#L170-L173) -- [validator.cpp:754-780](file://plugins/validator/validator.cpp#L754-L780) - -### New: validator Guard Plugin - Comprehensive Protection and Monitoring -The validator guard plugin provides comprehensive protection and monitoring capabilities for validator keys and operations. - -**Core Responsibilities**: -- **Auto-Key Restoration**: Automatically detects and restores null signing keys on-chain -- **Consecutive Block Protection**: Monitors validator block production and auto-disables validators after N consecutive blocks -- **Emergency Consensus Support**: Continues monitoring and protection during emergency consensus mode -- **Network Health Monitoring**: Ensures node synchronization and network health before performing actions -- **Safety Checks**: Implements comprehensive safety checks to prevent malicious actions - -**Auto-Restore Mechanism**: -- Periodically checks configured validators for null signing keys -- Broadcasts witness_update transactions to restore keys using stored key pairs -- Tracks pending transactions and confirms successful restoration -- Implements retry logic for failed restoration attempts -- Prevents unbounded growth of pending confirmation tracking - -**Auto-Disable Protection**: -- Monitors consecutive block production by configured validators -- Auto-disables validators that exceed the configured threshold -- Prevents validator abuse and ensures fair network participation -- Maintains records of auto-disabled validators to prevent auto-restore -- Broadcasts witness_update transactions with null signing key to disable production - -**Emergency Consensus Integration**: -- Continues monitoring during emergency consensus mode -- Adapts behavior based on emergency_consensus_active flag -- Supports key restoration even when network is unstable -- Coordinates with Validator Plugin for seamless operation - -**Safety and Validation**: -- Verifies on-chain authority for configured active keys -- Implements network health checks before performing actions -- Detects and warns about long fork scenarios -- Prevents auto-restore during stale production override periods -- Provides comprehensive logging for all operations - -**Configuration Options**: -- **validator-guard-enabled**: Enable/disable the protection system -- **validator-guard-disable**: Set consecutive block threshold for auto-disable -- **validator-guard-interval**: Configure check frequency in blocks -- **validator-guard-validator**: Define validators to monitor with key pairs - -**Section sources** -- [witness_guard.hpp:11-48](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L11-L48) -- [witness_guard.cpp:27-78](file://plugins/witness_guard/witness_guard.cpp#L27-L78) -- [witness_guard.cpp:83-191](file://plugins/witness_guard/witness_guard.cpp#L83-L191) -- [witness_guard.cpp:197-246](file://plugins/witness_guard/witness_guard.cpp#L197-L246) -- [witness_guard.cpp:252-294](file://plugins/witness_guard/witness_guard.cpp#L252-L294) -- [witness_guard.cpp:301-408](file://plugins/witness_guard/witness_guard.cpp#L301-L408) -- [witness_guard.cpp:410-555](file://plugins/witness_guard/witness_guard.cpp#L410-L555) - -### validator API Plugin -Responsibilities: -- Expose JSON-RPC endpoints for: - - Active validators in the current schedule. - - Full validator schedule object. - - validators by ID, by account, by votes, by counted votes. - - Count of validators. - - Lookup of validator accounts by name range. - -Implementation highlights: -- Uses weak read locks around database queries. -- Enforces limits on returned sets (e.g., max 100 for vote-based lists). -- Converts chain validator objects to API-friendly structures. - -```mermaid -sequenceDiagram -participant Client as "Client" -participant RPC as "JSON-RPC" -participant WAPI as "validator API Plugin" -participant DB as "Chain Database" -Client->>RPC : get_active_witnesses() -RPC->>WAPI : dispatch -WAPI->>DB : get_witness_schedule_object() -DB-->>WAPI : witness_schedule_object -WAPI-->>RPC : active validator names[] -RPC-->>Client : response -Client->>RPC : get_witness_by_account(account) -RPC->>WAPI : dispatch -WAPI->>DB : find validator by name -DB-->>WAPI : witness_object -WAPI-->>RPC : witness_api_object or null -RPC-->>Client : response -``` - -**Diagram sources** -- [witness_api_plugin.cpp:30-49](file://plugins/witness_api/plugin.cpp#L30-L49) -- [witness_api_plugin.cpp:75-91](file://plugins/witness_api/plugin.cpp#L75-L91) -- [witness_api_plugin.cpp:102-125](file://plugins/witness_api/plugin.cpp#L102-L125) -- [witness_api_plugin.cpp:127-159](file://plugins/witness_api/plugin.cpp#L127-L159) -- [witness_api_plugin.cpp:161-169](file://plugins/witness_api/plugin.cpp#L161-L169) -- [witness_api_plugin.cpp:171-203](file://plugins/witness_api/plugin.cpp#L171-L203) - -**Section sources** -- [witness_api_plugin.hpp:56-98](file://plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp#L56-L98) -- [witness_api_plugin.cpp:13-28](file://plugins/witness_api/plugin.cpp#L13-L28) -- [witness_api_plugin.cpp:30-49](file://plugins/witness_api/plugin.cpp#L30-L49) -- [witness_api_plugin.cpp:75-91](file://plugins/witness_api/plugin.cpp#L75-L91) -- [witness_api_plugin.cpp:102-159](file://plugins/witness_api/plugin.cpp#L102-L159) -- [witness_api_plugin.cpp:161-203](file://plugins/witness_api/plugin.cpp#L161-L203) - -### Chain Database: Enhanced Fork Database Integration -The database maintains: -- validator objects with voting, signing keys, virtual scheduling fields, and participation counters. -- validator schedule object with shuffled validators, current virtual time, and majority version. -- Block post validation objects used to coordinate cross-validator validation. -- **Enhanced**: Direct fork database access through `get_fork_db()` method for comprehensive fork collision detection. -- **Enhanced**: Comprehensive validator reward creation with find_account() validation to prevent crashes from missing account objects. -- **Enhanced**: **NEW**: Integration with skip_undo_history_check flag for controlled production during recovery scenarios. -- **Enhanced**: **NEW**: Comprehensive debug logging system enabling verbose traces for chain internals and block processing. -- **New**: Enhanced fork database with automatic stale fork pruning after successful block application. -- **New**: Sophisticated compare_fork_branches() function for intelligent fork weight comparison. -- **New**: **Enhanced**: Supports emergency_consensus_active field for emergency consensus mode detection. - -Behavior highlights: -- Computes validator participation rate and enforces minimum participation thresholds. -- Updates last irreversible block (LIB) based on validator confirmations and thresholds. -- Recomputes validator schedule and shuffles according to virtual time and votes. -- **Enhanced**: Provides comprehensive fork database querying capabilities for fork collision detection. -- **Enhanced**: Implements comprehensive validation for validator reward creation across all hardfork versions. -- **New**: Automatic stale fork pruning removes competing blocks from dead forks to maintain database efficiency. -- **New**: Intelligent fork comparison with vote-weighted calculations and longer-chain bonuses. -- **New**: **Enhanced**: Supports emergency consensus detection through emergency_consensus_active flag. - -**Enhanced Fork Database Capabilities**: -- `fetch_block_by_number()`: Retrieves all blocks at a specific height (handles multiple forks) -- `fetch_block_on_main_branch_by_number()`: Resolves ambiguity between competing blocks -- `fetch_branch_from()`: Provides branch comparison for fork resolution -- **New**: `remove_blocks_by_number()`: Removes all blocks at a specific height for stale fork pruning -- **New**: `compare_fork_branches()`: Intelligent fork weight comparison with +10% longer-chain bonus - -```mermaid -classDiagram -class witness_object { -+id -+owner -+created -+url -+votes -+penalty_percent -+counted_votes -+virtual_last_update -+virtual_position -+virtual_scheduled_time -+total_missed -+last_aslot -+last_confirmed_block_num -+last_supported_block_num -+signing_key -+props -+last_work -+running_version -+hardfork_version_vote -+hardfork_time_vote -+sharing_rate : uint16_t -+pending_stakeholder_reward : share_type -} -class witness_schedule_object { -+id -+current_virtual_time -+next_shuffle_block_num -+current_shuffled_witnesses[] -+num_scheduled_witnesses -+median_props -+majority_version -} -class block_post_validation_object { -+id -+block_num -+block_id -+current_shuffled_witnesses[] -+current_shuffled_witnesses_validations[] -} -class fork_database { -+MAX_BLOCK_REORDERING : 1024 -+push_block() -+fetch_block_by_number() -+fetch_block_on_main_branch_by_number() -+fetch_branch_from() -+remove_blocks_by_number() -+compare_fork_branches() -} -class dynamic_global_property_object { -+emergency_consensus_active : bool -+emergency_consensus_start_block : uint32_t -} -witness_object --> witness_schedule_object : "referenced by schedule" -block_post_validation_object --> witness_schedule_object : "mentions scheduled validators" -fork_database --> witness_schedule_object : "tracks competing blocks" -dynamic_global_property_object --> fork_database : "emergency consensus state" -``` - -**Diagram sources** -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [witness_objects.hpp:104-171](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L104-L171) -- [chain_objects.hpp:174-201](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L174-L201) -- [fork_database.hpp:53-81](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L81) -- [fork_database.hpp:90-95](file://libraries/chain/include/graphene/chain/fork_database.hpp#L90-L95) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [global_property_object.hpp:139](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L139) - -**Section sources** -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [witness_objects.hpp:104-171](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L104-L171) -- [chain_objects.hpp:174-201](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L174-L201) -- [database.cpp:1626-1805](file://libraries/chain/database.cpp#L1626-L1805) -- [database.cpp:4317-4332](file://libraries/chain/database.cpp#L4317-L4332) -- [database.cpp:4334-4463](file://libraries/chain/database.cpp#L4334-L4463) -- [database.hpp:492-499](file://libraries/chain/include/graphene/chain/database.hpp#L492-L499) -- [fork_database.hpp:73](file://libraries/chain/include/graphene/chain/fork_database.hpp#L73) -- [fork_database.cpp:151-166](file://libraries/chain/fork_database.cpp#L151-166) -- [fork_database.cpp:269-274](file://libraries/chain/fork_database.cpp#L269-L274) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [global_property_object.hpp:139](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L139) - -### Enhanced Time Synchronization Service -**New Section** The validator system now includes robust time synchronization capabilities managed through the time service layer with enhanced logging for fork collision detection and comprehensive error handling for validator reward creation. - -Responsibilities: -- Provide precise wall-clock time synchronization using NTP with 250ms interval optimization. -- Handle crash-safe shutdown procedures for NTP services. -- Monitor and report significant time synchronization changes. -- Enable forced synchronization on timing issues and fork collisions. -- **Enhanced**: Comprehensive delta change monitoring with 100ms threshold detection. -- **Enhanced**: Comprehensive error handling for validator reward creation with find_account() validation. - -Key behaviors: -- Thread-safe NTP service initialization and management with 250ms tick scheduling. -- Automatic fallback mechanisms for NTP server failures. -- Significant delta change detection (100ms threshold) for monitoring. -- Graceful shutdown with proper resource cleanup. -- **Enhanced**: Automatic NTP synchronization triggered by fork collision detection. -- **Enhanced**: Comprehensive validation and error handling for validator reward distribution. - -**Section sources** -- [time.cpp:13-53](file://libraries/time/time.cpp#L13-L53) -- [time.cpp:36-39](file://libraries/time/time.cpp#L36-L39) -- [time.cpp:74-76](file://libraries/time/time.cpp#L74-L76) -- [ntp.cpp:184-201](file://thirdparty/fc/src/network/ntp.cpp#L184-L201) -- [ntp.cpp:236-266](file://thirdparty/fc/src/network/ntp.cpp#L236-L266) - -## HF13: Validator Reward Sharing - -Introduced in Hardfork 13. Validators can redirect a fraction of their block reward to the -accounts that voted for them. Rewards accumulate in **TOKEN (VIZ)** inside the `witness_object` -and are converted to SHARES only at epoch end via `create_vesting()`. - -### New chain parameter: `distribution_epoch_length` - -Added to `chain_properties_hf13` (which becomes the `chain_properties` alias used throughout the -chain library). Validators vote on this parameter via `versioned_chain_properties_update_operation`. - -| Field | Default | Range | Description | -|---|---|---|---| -| `distribution_epoch_length` | 28800 (1 day) | [21, ~10.5M] | Blocks between consecutive distribution epochs | - -The median across all scheduled validators becomes the consensus value. - -### New operation: `set_reward_sharing_operation` - -Active authority of `owner` required. Rejected before HF13. - -| Field | Type | Description | -|---|---|---| -| `owner` | `account_name_type` | Validator account name | -| `sharing_rate` | `uint16_t` | Basis points; 0 = 0%, 10000 = 100% | - -### Block reward split (`process_funds`) - -When `sharing_rate > 0` and HF13 is active: - -``` -stakeholder_token = witness_reward * sharing_rate / CHAIN_100_PERCENT -validator_token = witness_reward - stakeholder_token - -create_vesting(validator_account, validator_token) -→ witness_reward_operation(validator, validator_shares) # only validator's share - -witness_object.pending_stakeholder_reward += stakeholder_token # TOKEN, accumulated -``` - -If `sharing_rate == 0`, the full reward goes to the validator as before. - -### Mid-epoch sharing rate change - -The new rate takes effect **immediately** on the next block. The accumulated -`pending_stakeholder_reward` is not recalculated retroactively. Stakeholders receive a proportional -mix of the old and new rates at epoch end. - -### Epoch distribution (`process_validator_epoch_distribution`) - -Called at the end of `_apply_block` when `head_block_num % distribution_epoch_length == 0`. - -Uses **time-weighted** stakeholder shares. Each `witness_vote_object` records `vote_created_block` — -the block at which the vote was cast. At epoch end: - -``` -epoch_start_block = head_block_num - epoch_length + 1 - -for each stakeholder: - first_block = max(vote_created_block, epoch_start_block) - blocks_in_epoch = head_block_num - first_block + 1 - weighted = stakeholder.witness_vote_weight() * blocks_in_epoch - -stakeholder_token = total_token * weighted[stakeholder] / Σ weighted -if stakeholder_token < CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT (1 atomic = 0.001 VIZ): - skip (dust) -else: - create_vesting(stakeholder_account, stakeholder_token) - → stakeholder_reward_operation(validator, stakeholder, stakeholder_shares) - -dust → create_vesting(validator_account, dust) + witness_reward_operation -witness_object.pending_stakeholder_reward = 0 -``` - -A stakeholder who joined mid-epoch receives a proportionally smaller reward. Pre-HF13 votes -(`vote_created_block == 0`) receive full-epoch weight. - -**Dust handling**: sharing rewards is entirely the validator's voluntary decision. A stakeholder -whose computed share falls below `CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT` has not earned a viable -payout — the responsibility is theirs. Unclaimed dust (rounding remainder + skipped sub-threshold -shares) is therefore **not burned** but returned to the validator via `witness_reward_operation`. - -#### Flash-voter protection - -Time-weighting prevents the flash-vote attack (vote just before epoch end, capture full pool). -A stakeholder who votes in the last block of a 1-day epoch (28800 blocks) receives only `1/28800` -of their stake-proportional share — economically insignificant. - -### New virtual operation: `stakeholder_reward_operation` - -| Field | Type | Description | -|---|---|---| -| `validator` | `account_name_type` | Validator that produced the accumulated rewards | -| `stakeholder` | `account_name_type` | Stakeholder account receiving the reward | -| `shares` | `asset` | SHARES credited to the stakeholder | - -**Section sources** -- [config.hpp](file://libraries/protocol/include/graphene/protocol/config.hpp) — `CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT`, `CHAIN_DEFAULT_DISTRIBUTION_EPOCH_LENGTH` -- [chain_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_operations.hpp) — `chain_properties_hf13`, `set_reward_sharing_operation` -- [chain_virtual_operations.hpp](file://libraries/protocol/include/graphene/protocol/chain_virtual_operations.hpp) — `stakeholder_reward_operation` -- [witness_objects.hpp](file://libraries/chain/include/graphene/chain/witness_objects.hpp) — `sharing_rate`, `pending_stakeholder_reward` fields -- [database.cpp](file://libraries/chain/database.cpp) — `process_funds` split, `process_validator_epoch_distribution` - ---- - -## Dependency Analysis -- The Validator Plugin depends on: - - Chain plugin for database access and block generation. - - P2P plugin for broadcasting blocks and block post validations. - - **Enhanced**: NTP time service for precise 250ms slot alignment and timing validation. - - **Enhanced**: Fork database for comprehensive fork collision detection and stale pruning. - - **Enhanced**: **NEW**: P2P resync_from_lib() method for automatic recovery from minority forks. - - **Enhanced**: **NEW**: Comprehensive debug logging system for verbose traces of block production and chain internals. - - **New**: External plugins can depend on the `is_witness_scheduled_soon()` method for coordination. - - **New**: Enhanced fork database with compare_fork_branches() function for intelligent fork switching. -- The validator guard plugin depends on: - - Chain plugin for database access and validator monitoring. - - P2P plugin for broadcasting witness_update transactions. - - **New**: Emergency consensus detection through dynamic_global_property_object. - - **New**: Configurable check intervals and auto-disable thresholds. - - **New**: Key pair management for validator protection. -- The validator API plugin depends on: - - Chain plugin for read-only queries. - - JSON-RPC plugin for transport. -- The chain database depends on: - - validator objects and schedule indices. - - Block post validation objects for cross-validator coordination. - - **Enhanced**: Fork database for tracking competing blocks and fork resolution. - - **Enhanced**: Comprehensive validation for validator reward creation with find_account() checks. - - **Enhanced**: **NEW**: skip_undo_history_check flag for controlled production during recovery scenarios. - - **Enhanced**: **NEW**: Comprehensive debug logging system enabling verbose traces for chain internals. - - **New**: Automatic stale fork pruning mechanism for database efficiency. - - **New**: Enhanced fork comparison functions for intelligent chain selection. - - **New**: **Enhanced**: emergency_consensus_active field for emergency consensus detection. - -```mermaid -graph LR -validator["Validator Plugin"] --> CHAIN["Chain Plugin"] -validator --> P2P["P2P Plugin
resync_from_lib()"] -validator --> TIME["Time Service"] -validator --> FORK_DB["Fork Database
enhanced with stale pruning"] -validator --> DEBUG_LOG["Debug Logging
verbose traces"] -WGUARD["validator Guard Plugin"] --> CHAIN -WGUARD --> P2P -WGUARD --> EMERGENCY["Emergency Consensus
emergency_consensus_active"] -WAPI["validator API Plugin"] --> CHAIN -SNAPSHOT["Snapshot Plugin"] --> validator -CHAIN --> DB["database.hpp/.cpp
compare_fork_branches()"] -DB --> WITNESS_OBJ["witness_objects.hpp"] -DB --> BPV_OBJ["chain_objects.hpp"] -DB --> FORK_DB["fork_database.hpp/.cpp"] -DB --> FIND_ACCOUNT["find_account() validation"] -DB --> COMPARE_FORK["compare_fork_branches()"] -DB --> SKIP_UNDO["skip_undo_history_check flag"] -DB --> EMERGENCY["emergency_consensus_active"] -TIME --> NTP["NTP Service"] -``` - -**Diagram sources** -- [validator.hpp:34-68](file://plugins/validator/include/graphene/plugins/validator/validator.hpp#L34-L68) -- [validator.cpp:59-118](file://plugins/validator/validator.cpp#L59-L118) -- [witness_guard.hpp:11-48](file://plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#L11-L48) -- [witness_guard.cpp:27-78](file://plugins/witness_guard/witness_guard.cpp#L27-L78) -- [witness_api_plugin.hpp:56-98](file://plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp#L56-L98) -- [database.hpp:37-83](file://libraries/chain/include/graphene/chain/database.hpp#L37-L83) -- [witness_objects.hpp:27-132](file://libraries/chain/include/graphene/chain/witness_objects.hpp#L27-L132) -- [chain_objects.hpp:174-201](file://libraries/chain/include/graphene/chain/chain_objects.hpp#L174-L201) -- [fork_database.hpp:53-81](file://libraries/chain/include/graphene/chain/fork_database.hpp#L53-L81) -- [time.cpp:13-53](file://libraries/time/time.cpp#L13-L53) -- [snapshot_plugin.cpp:1267-1276](file://plugins/snapshot/plugin.cpp#L1267-1276) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [global_property_object.hpp:139](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L139) -- [p2p_plugin.hpp:50-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L50-L55) - -**Section sources** -- [validator.cpp:59-118](file://plugins/validator/validator.cpp#L59-L118) -- [witness_guard.cpp:27-78](file://plugins/witness_guard/witness_guard.cpp#L27-L78) -- [witness_api_plugin.cpp:13-28](file://plugins/witness_api/plugin.cpp#L13-L28) -- [database.hpp:37-83](file://libraries/chain/include/graphene/chain/database.hpp#L37-L83) - -## Performance Considerations -- Production loop alignment: The loop waits until the next 250ms boundary and sleeps for at least 50ms to avoid excessive polling, reducing CPU overhead and providing deterministic slot time alignment. -- Retry on block generation failures: On exceptions during block generation, pending transactions are cleared and the generation is retried once to mitigate transient issues. -- Participation threshold: Ensures sufficient validator participation before producing blocks, preventing premature production on minority forks. -- Virtual scheduling: Uses virtual time and votes to fairly distribute block production slots among validators, avoiding hot-spotting and ensuring proportional representation. -- **Enhanced**: Forced NTP synchronization reduces timing-related production failures and improves system reliability during clock drift scenarios. -- **Enhanced**: Comprehensive fork collision detection adds minimal overhead while preventing costly fork resolution failures. -- **Enhanced**: **NEW**: Minority fork detection adds minimal overhead while preventing network fragmentation and minority fork propagation. -- **Enhanced**: **NEW**: Automatic recovery through P2P resynchronization is efficient and prevents prolonged network instability. -- **Enhanced**: **NEW**: Comprehensive debug logging system provides detailed visibility with minimal performance impact through selective logging. -- **New**: Efficient slot checking in `is_witness_scheduled_soon()` method performs minimal database operations across 4 slots to detect scheduling conflicts quickly. -- **Updated**: Improved configuration parameter processing with type safety and proper scaling for better performance and reliability. -- **Enhanced**: Fork database querying uses efficient multi-index containers for fast block lookup and competition detection. -- **Enhanced**: find_account() validation adds minimal overhead while providing comprehensive protection against database corruption scenarios. -- **Enhanced**: Comprehensive error handling in validator reward creation prevents crashes and ensures graceful degradation during critical failures. -- **New**: 250ms interval optimization provides precise timing alignment for deterministic consensus maintenance. -- **New**: Deterministic slot time calculation ensures consistent block production timing across all validator nodes. -- **New**: Two-level fork collision resolution system provides intelligent fork switching decisions with configurable timeout behavior. -- **New**: Automatic stale fork pruning prevents database bloat and maintains fork database efficiency. -- **New**: Enhanced compare_fork_branches() function provides intelligent fork weight comparison with +10% longer-chain bonus. -- **New**: Configurable fork collision timeout blocks parameter allows fine-tuning of fork resolution behavior for different network conditions. -- **New**: skip_undo_history_check flag provides controlled production during recovery scenarios without disrupting normal operations. -- **Enhanced**: **NEW**: debug-block-production configuration option enables verbose logging for block production and chain internals with detailed traces and granular visibility. -- **Enhanced**: **NEW**: validator guard plugin provides comprehensive protection with minimal performance impact through efficient monitoring and safety checks. -- **Enhanced**: **NEW**: Auto-disable threshold prevents validator abuse while maintaining network stability and fair participation. -- **Enhanced**: **NEW**: Emergency consensus integration ensures continuous protection and recovery during network distress scenarios. -- **Enhanced**: **NEW**: Network health monitoring prevents unsafe operations during stale production override periods. -- **Enhanced**: **NEW**: Pending transaction tracking prevents unbounded memory growth while maintaining reliable restoration mechanisms. - -**Updated** Added performance considerations for the corrected configuration parameter types, fork collision detection system, enhanced fork database querying capabilities, comprehensive validator reward creation validation, 250ms interval optimization, deterministic slot time alignment, new fork collision timeout configuration, two-level fork resolution system with intelligent decision-making, automatic stale fork pruning, enhanced fork comparison functions, configurable timeout parameters for optimal network performance, **NEW**: minority fork detection system with minimal overhead, **NEW**: automatic recovery mechanisms through P2P resynchronization, **NEW**: skip_undo_history_check flag for controlled production during recovery scenarios, **NEW**: comprehensive debug logging system enabling verbose traces for block production and chain internals with detailed visibility, **NEW**: validator protection and monitoring capabilities with auto-restore and auto-disable features, **NEW**: emergency recovery mechanisms with automatic key restoration, **NEW**: consecutive block protection to prevent validator abuse, **NEW**: enhanced network connectivity with improved peer synchronization, **NEW**: validator guard plugin with efficient monitoring and safety checks, **NEW**: auto-disable threshold for preventing validator abuse, **NEW**: emergency consensus integration for continuous protection, **NEW**: network health monitoring for safe operations, **NEW**: pending transaction tracking for reliable restoration. - -## Troubleshooting Guide -Common issues and resolutions: -- No validators configured - - Symptom: Startup logs indicate no validators configured. - - Resolution: Add validator names and private keys to configuration. -- Low participation - - Symptom: Blocks not produced due to insufficient validator participation. - - Resolution: Ensure enough validators are online and participating per configured threshold. -- Missing private key - - Symptom: Logs indicate inability to sign block due to missing private key. - - Resolution: Verify private key is provided in the correct WIF format and matches the validator signing key. -- Timing lag - - Symptom: Blocks not produced due to waking up outside the 500ms window. - - Resolution: Improve system clock accuracy and reduce latency; consider enabling stale production only as a temporary workaround. - - **Enhanced**: System automatically forces NTP synchronization when timing issues are detected. -- Consecutive block production disabled - - Symptom: Blocks not produced because the last block was generated by the same validator. - - Resolution: Investigate connectivity issues; disable consecutive production only as a temporary workaround. -- **New**: Fork collision detection issues - - Symptom: Blocks not produced despite good participation and timing, frequent "deferred block production due to fork collision" messages. - - Resolution: Check network connectivity and validator coordination; verify fork database integrity; monitor NTP synchronization quality. - - **New**: Check fork-collision-timeout-blocks configuration (default: 21 blocks) for appropriate timeout settings. - - **New**: Monitor fork collision deferral count and timeout behavior for proper fork resolution. -- **New**: Two-level fork collision resolution problems - - Symptom: Fork collision handling not working as expected with vote-weighted comparisons. - - Resolution: Verify HF12+ compatibility for vote-weighted comparisons; check compare_fork_branches() function behavior; ensure fork database has both forks for comparison. -- **New**: Stale fork pruning issues - - Symptom: Memory usage growing or fork database becoming bloated with competing blocks. - - Resolution: Verify automatic stale fork pruning is working; check fork database remove_blocks_by_number() function; ensure proper parent-child relationships in fork database. -- **New**: validator scheduling conflicts - - Symptom: Other plugins experience conflicts with validator operations. - - Resolution: Use `is_witness_scheduled_soon()` method to coordinate operations and defer critical tasks until validator production is complete. -- **New**: NTP synchronization issues - - Symptom: Frequent timing-related warnings or blocks not produced despite good participation. - - Resolution: Check NTP server connectivity and system clock accuracy; verify NTP service is running properly; monitor delta change logs. -- **New**: Crash race conditions - - Symptom: Validator Plugin fails to shut down cleanly or leaves NTP service in inconsistent state. - - Resolution: Ensure proper shutdown sequence; the system now handles crash-safe NTP service cleanup. -- **New**: Configuration parameter type errors - - Symptom: Errors indicating incorrect parameter types or values. - - Resolution: Verify configuration parameters use correct types: - - `enable-stale-production`: boolean value (`true`/`false`) - - `required-participation`: integer value scaled by `CHAIN_1_PERCENT` (e.g., 33 for 33%) - - `fork-collision-timeout-blocks`: integer value (default: 21 blocks) - - `debug-block-production`: boolean value (`true`/`false`) - **NEW** - - `validator-guard-enabled`: boolean value (`true`/`false`) - **NEW** - - `validator-guard-disable`: integer value (default: 5) - **NEW** - - `validator-guard-interval`: integer value (default: 20) - **NEW** - - `validator-guard-validator`: JSON triplet format - **NEW** - - Check configuration files for proper syntax and values -- **Enhanced**: Fork collision detection logging - - Symptom: Frequent fork collision warnings with "Collision parents at block" messages. - - Resolution: Monitor fork database for competing blocks; check validator coordination; verify network stability; ensure proper NTP synchronization. - - **New**: Check fork collision deferral count and timeout behavior for proper resolution. -- **New**: Enhanced fork comparison failures - - Symptom: compare_fork_branches() function returning 0 (cannot compare) frequently. - - Resolution: Verify both fork tips are in fork database; check validator objects availability; ensure proper fork database state. -- **New**: Automatic stale pruning not working - - Symptom: Fork database grows with stale competing blocks. - - Resolution: Verify _push_block() method is calling stale pruning; check fork database state; ensure proper parent-child relationships. -- **New**: Database corruption detection - - Symptom: Multiple critical error messages during validator reward processing with account validation failures. - - Resolution: - - Immediate restart with replay to rebuild database from genesis - - Check disk space and file system integrity - - Verify database backup and recovery procedures - - Monitor for hardware issues affecting shared memory - - Review system logs for memory corruption indicators -- **New**: 250ms interval timing issues - - Symptom: Blocks not produced at expected 250ms boundaries or inconsistent timing. - - Resolution: Verify system clock precision; check for high system load causing timing delays; ensure NTP service is functioning properly; review system resources for adequate performance. -- **New**: Configurable timeout parameter issues - - Symptom: Fork collision timeout not triggering as expected or triggering too frequently. - - Resolution: Adjust fork-collision-timeout-blocks parameter based on network conditions; monitor fork collision deferral count; verify timeout logic is working correctly. -- **New**: Minority fork detection false positives - - Symptom: Frequent minority fork detection warnings or unexpected recovery behavior. - - Resolution: Verify emergency consensus mode is not active; check skip_undo_history_check flag state; ensure proper network connectivity; verify validator configuration is correct. -- **New**: Recovery mechanism issues - - Symptom: Automatic recovery not working or taking too long to complete. - - Resolution: Check P2P plugin connectivity; verify resync_from_lib() method is functioning; monitor network synchronization progress; ensure sufficient peer connections. -- **New**: skip_undo_history_check flag problems - - Symptom: Production not behaving as expected during recovery scenarios. - - Resolution: Verify flag state during recovery; check enable-stale-production configuration; ensure proper flag management during minority fork detection and recovery. -- **New**: Debug logging configuration issues - - Symptom: Debug logging not providing expected verbose traces or performance impact concerns. - - Resolution: Verify debug-block-production configuration is set to `true`; check log level settings; ensure proper log file configuration; monitor performance impact; adjust debug logging scope as needed. -- **New**: Comprehensive debug trace analysis - - Symptom: Difficulty interpreting debug log output or missing expected trace information. - - Resolution: Review debug log entries for timestamped traces; verify debug-block-production is enabled; check for granular visibility into validator participation checks, emergency mode enforcement, block post-validation processes, and minority fork detection; ensure proper log rotation and retention policies. -- **New**: validator guard plugin issues - - Symptom: validator key not being restored or auto-disabled unexpectedly. - - Resolution: Check validator-guard-enabled configuration; verify validator-guard-validator entries are properly formatted JSON triplets; ensure active keys have proper authority on-chain; monitor validator guard logs for specific error messages. -- **New**: Auto-disable threshold problems - - Symptom: validators being auto-disabled too frequently or not at all. - - Resolution: Adjust validator-guard-disable parameter based on network conditions; verify validator block production patterns; ensure proper key management and network connectivity. -- **New**: Emergency consensus protection issues - - Symptom: validator protection not working during emergency consensus mode. - - Resolution: Verify emergency_consensus_active flag is detected correctly; check validator guard plugin behavior during emergency mode; ensure proper key restoration procedures. -- **New**: Network health monitoring failures - - Symptom: validator guard performing operations during unhealthy network conditions. - - Resolution: Verify network health checks are working; check LIB age monitoring; ensure proper safety checks are in place; review validator guard logs for health check results. -- **New**: Production watchdog fires unexpectedly - - Symptom: `WATCHDOG:` elog appears even though production seems healthy; verbose `DEBUG_CRASH` logging auto-enabled. - - Resolution: The watchdog fires when no block has been produced for 60s (emergency master) or 180s (regular validator) while production conditions appear met. Check: is our validator in the shuffled schedule (`our_slots_in_schedule > 0`)? Is the on-chain signing key blanked (`blanked_keys` field in watchdog log)? Is P2P still syncing (`dlt_syncing`)? The watchdog log contains all these fields. -- **New**: Lag tight loop / high CPU after missed slot - - Symptom: After a `lag` condition, node CPU spikes and production loop fires many times per second on the same slot. - - Resolution: This was a bug fixed in commit 8fce5f1e. The `_last_lag_slot_time` guard now skips ahead to avoid rechecking the same missed slot. Upgrade to a build that includes this fix. -- **New**: Slot hijack counter increments for own blocks - - Symptom: `hijack #N` messages appear in watchdog diagnostics even when one of our own validators produced the block. - - Resolution: Fixed in commit 7b589b71. The hijack counter now resets when any of our validators (not just the slot-assigned one) produced the block. Upgrade to a build containing this fix. -- **New**: not_my_turn streak warning - - Symptom: `NOT_MY_TURN STREAK: N consecutive slots` warning in logs. - - Resolution: Another validator has held all slots for ~125s. Check: (1) Is our validator still in the shuffled schedule? (2) Is there a chain fork where the other side has a different schedule? (3) Has our validator been replaced/disabled on-chain? The log includes `our validators` and `last scheduled` fields for quick diagnosis. - -**Updated** Added troubleshooting information for fork collision detection, validator scheduling conflicts, the new coordination mechanisms, configuration parameter type issues, comprehensive validator reward creation validation, database corruption scenarios with clear recovery procedures, 250ms interval timing optimization issues, new fork collision timeout configuration, two-level fork resolution system, automatic stale fork pruning, enhanced fork comparison functions, configurable timeout parameters for optimal network behavior, **NEW**: comprehensive minority fork detection system with automatic recovery mechanisms, **NEW**: recovery mechanism issues through P2P resynchronization, **NEW**: skip_undo_history_check flag management during recovery scenarios, **NEW**: comprehensive debug logging system enabling verbose traces for block production and chain internals with detailed visibility, **NEW**: validator protection and monitoring capabilities with auto-restore and auto-disable features, **NEW**: emergency recovery mechanisms through P2P resynchronization, **NEW**: auto-disable threshold management for preventing validator abuse, **NEW**: emergency consensus integration for continuous protection, **NEW**: network health monitoring for safe operations, **NEW**: pending transaction tracking for reliable restoration, **NEW**: validator guard plugin configuration issues, **NEW**: auto-disable threshold problems, **NEW**: emergency consensus protection failures, **NEW**: network health monitoring failures. - -**Section sources** -- [validator.cpp:171-192](file://plugins/validator/validator.cpp#L171-L192) -- [validator.cpp:255-271](file://plugins/validator/validator.cpp#L255-L271) -- [validator.cpp:387-396](file://plugins/validator/validator.cpp#L387-L396) -- [validator.cpp:263-266](file://plugins/validator/validator.cpp#L263-L266) -- [validator.cpp:206-249](file://plugins/validator/validator.cpp#L206-L249) -- [validator.cpp:447-471](file://plugins/validator/validator.cpp#L447-L471) -- [validator.cpp:590-695](file://plugins/validator/validator.cpp#L590-L695) -- [time.cpp:36-39](file://libraries/time/time.cpp#L36-L39) -- [database.cpp:2826-2836](file://libraries/chain/database.cpp#L2826-L2836) -- [database.cpp:2873-2883](file://libraries/chain/database.cpp#L2873-L2883) -- [database.cpp:1456-1471](file://libraries/chain/database.cpp#L1456-L1471) -- [database.cpp:1223-1267](file://libraries/chain/database.cpp#L1223-L1267) -- [validator.cpp:509-555](file://plugins/validator/validator.cpp#L509-L555) -- [witness_guard.cpp:83-191](file://plugins/witness_guard/witness_guard.cpp#L83-L191) -- [witness_guard.cpp:455-544](file://plugins/witness_guard/witness_guard.cpp#L455-L544) -- [global_property_object.hpp:139](file://libraries/chain/include/graphene/chain/global_property_object.hpp#L139) -- [p2p_plugin.hpp:50-55](file://plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#L50-L55) - -## Conclusion -The validator subsystem integrates tightly with the chain database and P2P layer to ensure timely, secure, and fair block production. The Validator Plugin manages production loops, participation thresholds, and broadcasting, while the validator guard plugin provides comprehensive protection and monitoring capabilities. The validator API plugin exposes essential read-only data to clients. - -**Enhanced** The system now includes robust NTP time synchronization with automatic fallback mechanisms, crash-safe shutdown procedures, strengthened timing-related production failure prevention, comprehensive fork collision detection system with two-level resolution, and enhanced fork database querying capabilities. **New** The addition of the `is_witness_scheduled_soon()` method enables sophisticated plugin coordination, allowing other plugins to avoid conflicts during critical operations like snapshot creation. **Updated** The configuration parameter system has been improved with corrected defaults and proper type handling for better reliability and performance. **Enhanced** The validator reward creation process has been significantly strengthened with comprehensive error handling, find_account() validation, crash prevention mechanisms, and clear recovery procedures for database corruption scenarios. **New** The 250ms interval optimization provides precise timing alignment for deterministic consensus maintenance, while the enhanced performance characteristics ensure better system responsiveness and consensus stability. **New** The two-level fork collision resolution system with configurable timeout provides intelligent fork switching decisions, automatic stale fork pruning maintains database efficiency, enhanced fork comparison functions enable sophisticated chain selection, and configurable timeout parameters allow fine-tuning for different network conditions. **NEW** The comprehensive minority fork detection system with automatic recovery mechanisms prevents network fragmentation and ensures proper consensus maintenance, while the enhanced emergency consensus mode integration provides seamless operation during network distress scenarios. **NEW** The skip_undo_history_check flag provides controlled production during recovery scenarios, and the P2P resynchronization mechanism ensures efficient network recovery without disrupting normal operations. **NEW** The comprehensive debug logging system enables verbose traces for block production and chain internals with detailed visibility into validator participation checks, emergency mode enforcement, block post-validation processes, and minority fork detection. **NEW** The validator guard plugin provides comprehensive protection and monitoring capabilities with auto-restore functionality, consecutive block auto-disable protection, emergency consensus support, and safety checks. **NEW** The enhanced network connectivity features include improved peer synchronization and emergency recovery mechanisms. Together, they form a robust foundation for validator operations in the VIZ node, with improved time synchronization, crash handling capabilities, enhanced plugin coordination features, comprehensive fork collision detection, reliable configuration parameter processing, strengthened fork database querying for detecting competing blocks at the same height, comprehensive validator reward creation validation with crash prevention and recovery procedures, 250ms interval optimization for deterministic slot time alignment, enhanced performance characteristics for better consensus maintenance, intelligent fork resolution system, automatic stale fork pruning, configurable timeout parameters for optimal network behavior, **NEW**: comprehensive minority fork detection system with automatic recovery mechanisms, **NEW**: enhanced emergency consensus mode integration, **NEW**: controlled production during recovery scenarios, **NEW**: efficient automatic recovery through P2P resynchronization for network stability, **NEW**: comprehensive debug logging system enabling verbose traces for block production and chain internals with detailed visibility, **NEW**: validator protection and monitoring capabilities with auto-restore and auto-disable features, **NEW**: emergency recovery mechanisms with automatic key restoration, **NEW**: consecutive block protection to prevent validator abuse, **NEW**: enhanced network connectivity with improved peer synchronization, **NEW**: validator guard plugin with comprehensive protection and monitoring, **NEW**: emergency consensus integration for continuous protection, **NEW**: network health monitoring for safe operations, and **NEW**: pending transaction tracking for reliable restoration. These enhancements make the validator system more resilient to various operational challenges while providing better integration points for the broader VIZ ecosystem, comprehensive protection against shared memory corruption, robust validation mechanisms for validator reward distribution across all hardfork versions, optimized timing for improved consensus maintenance, intelligent fork resolution with configurable behavior, automatic database maintenance for optimal performance, **NEW**: comprehensive minority fork detection and recovery mechanisms for network stability, **NEW**: enhanced emergency consensus mode integration for seamless operation during network distress, **NEW**: controlled production during recovery scenarios through skip_undo_history_check flag management, **NEW**: efficient automatic recovery through P2P resynchronization for rapid network stabilization, **NEW**: comprehensive debug logging system enabling verbose traces for block production and chain internals with detailed visibility, **NEW**: validator protection and monitoring capabilities with auto-restore and auto-disable features, **NEW**: emergency recovery mechanisms with automatic key restoration, **NEW**: consecutive block protection to prevent validator abuse, **NEW**: enhanced network connectivity with improved peer synchronization, and **NEW**: validator guard plugin with comprehensive protection and monitoring for enhanced network stability and security. \ No newline at end of file diff --git a/.qoder/repowiki/en/content/Webserver Plugin.md b/.qoder/repowiki/en/content/Webserver Plugin.md deleted file mode 100644 index 09475d7c6a..0000000000 --- a/.qoder/repowiki/en/content/Webserver Plugin.md +++ /dev/null @@ -1,823 +0,0 @@ -# Webserver Plugin - - -**Referenced Files in This Document** -- [webserver_plugin.hpp](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp) -- [webserver_plugin.cpp](file://plugins/webserver/webserver_plugin.cpp) -- [plugin.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/plugin.hpp) -- [plugin.cpp](file://plugins/json_rpc/plugin.cpp) -- [utility.hpp](file://plugins/json_rpc/include/graphene/plugins/json_rpc/utility.hpp) -- [webserver-plugin.md](file://documentation/webserver-plugin.md) -- [config.ini](file://share/vizd/config/config.ini) - - -## Update Summary -**Changes Made** -- Enhanced JSON RPC logging with gray color support for data dumps, improving log readability and debugging capabilities for RPC operations -- Updated diagnostic logging system to use ANSI escape sequences for visual distinction between normal operation and diagnostic information -- Improved developer experience with colored log output for request/response data visualization -- Enhanced timing measurements with precise elapsed time tracking for request processing -- Added comprehensive error tracking with timing context for better debugging - -## Table of Contents -1. [Introduction](#introduction) -2. [Project Structure](#project-structure) -3. [Core Components](#core-components) -4. [Architecture Overview](#architecture-overview) -5. [Detailed Component Analysis](#detailed-component-analysis) -6. [Dependency Analysis](#dependency-analysis) -7. [Performance Considerations](#performance-considerations) -8. [Security Considerations](#security-considerations) -9. [Configuration Guide](#configuration-guide) -10. [Troubleshooting Guide](#troubleshooting-guide) -11. [Logging and Diagnostics](#logging-and-diagnostics) -12. [Conclusion](#conclusion) - -## Introduction -The Webserver Plugin provides HTTP and WebSocket endpoints for JSON-RPC API access to the VIZ blockchain node. It serves as a bridge between external clients and the internal JSON-RPC system, offering both persistent WebSocket connections for real-time updates and standard HTTP endpoints for traditional API calls. The plugin includes an intelligent caching mechanisms that automatically classifies requests as mutating or non-mutating using robust fc::variant-based JSON parsing, optimizing performance for frequently accessed read-only API methods while preventing cache pollution from state-changing operations. - -**Updated** Enhanced with major performance optimizations to the JSON-RPC caching mechanism, including fc::variant-based JSON parsing, id-independent keys, robust request classification, comprehensive cache validation, improved WebSocket/HTTP handler support with proper JSON-RPC 2.0 response ID patching, modernized Boost library usage with std::bind and std::placeholders for better C++11 compatibility, and enhanced diagnostic logging with ANSI gray color support for improved debugging capabilities. - -## Project Structure -The webserver plugin is organized within the plugins/webserver directory structure, following the standard VIZ plugin architecture pattern: - -```mermaid -graph TB -subgraph "Webserver Plugin Structure" -A[webserver_plugin.hpp] --> B[Public Header] -C[webserver_plugin.cpp] --> D[Implementation] -subgraph "Dependencies" -E[json_rpc/plugin.hpp] --> F[JSON-RPC Plugin] -G[appbase/application.hpp] --> H[Application Framework] -I[websocketpp/server.hpp] --> J[WebSocket Library] -K[std::bind/std::placeholders] --> L[C++11 Standard Bindings] -M[fc::json::from_string] --> N[Proper JSON Parsing] -O[fc::variant] --> P[Robust Data Structures] -Q[ANSI Color Logging] --> R[Enhanced Diagnostics] -end -D --> E -D --> G -D --> I -D --> K -D --> M -D --> O -D --> Q -end -``` - -**Diagram sources** -- [webserver_plugin.hpp:1-62](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L1-L62) -- [webserver_plugin.cpp:1-605](file://plugins/webserver/webserver_plugin.cpp#L1-L605) - -**Section sources** -- [webserver_plugin.hpp:1-62](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L1-L62) -- [webserver_plugin.cpp:1-605](file://plugins/webserver/webserver_plugin.cpp#L1-L605) - -## Core Components -The webserver plugin consists of several key components working together to provide HTTP and WebSocket API services: - -### Main Plugin Class -The primary interface is the `webserver_plugin` class that inherits from appbase's plugin system, providing lifecycle management and configuration options. - -### Implementation Container -The `webserver_plugin_impl` struct contains all the internal state and functionality, including: -- HTTP and WebSocket server instances with separate io_service instances -- Thread pool management for concurrent request processing using appbase scheduler with modernized std::bind bindings -- Intelligent response caching mechanism with request classification and block-based invalidation using fc::variant parsing -- Connection handling for both HTTP and WebSocket protocols with std::bind-based message handlers -- Signal connections for blockchain event monitoring and cache management - -### JSON-RPC Integration -The plugin integrates with the JSON-RPC plugin to handle API method dispatching and response generation, supporting both individual requests and batch processing with comprehensive error handling using fc::json::from_string for proper JSON parsing. - -**Section sources** -- [webserver_plugin.hpp:32-57](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L32-L57) -- [webserver_plugin.cpp:190-234](file://plugins/webserver/webserver_plugin.cpp#L190-L234) - -## Architecture Overview -The webserver plugin follows a sophisticated multi-threaded architecture designed for high concurrency and reliability with intelligent caching control using fc::variant-based JSON parsing and modernized std::bind bindings: - -```mermaid -graph TB -subgraph "Client Layer" -A[HTTP Clients] -B[WebSocket Clients] -end -subgraph "Webserver Plugin" -C[HTTP Server Thread] -D[WebSocket Server Thread] -E[Thread Pool with std::bind] -F[Intelligent Cache System] -G[Request Classifier] -H[fc::variant Parser] -I[Response ID Patcher] -J[Cache Key Generator] -K[Malformed Request Handler] -end -subgraph "JSON-RPC Layer" -L[JSON-RPC Plugin with Enhanced Logging] -M[API Registry] -N[fc::json::from_string] -O[fc::variant Objects] -P[ANSI Color Logging] -Q[Diagnostic Timers] -end -subgraph "Blockchain Layer" -R[Chain Plugin] -S[Database] -T[Block Event Signals] -end -A --> C -B --> D -C --> E -D --> E -E --> G -G --> F -F --> I -F --> H -F --> J -F --> K -H --> N -H --> O -I --> N -J --> N -K --> N -K --> O -F -.-> C -F -.-> D -F -.-> E -G --> L -L --> M -M --> N -N --> R -O --> R -R --> S -R --> T -T --> F -P --> Q -``` - -**Diagram sources** -- [webserver_plugin.cpp:190-234](file://plugins/webserver/webserver_plugin.cpp#L190-L234) -- [webserver_plugin.cpp:236-289](file://plugins/webserver/webserver_plugin.cpp#L236-L289) -- [webserver_plugin.cpp:352-416](file://plugins/webserver/webserver_plugin.cpp#L352-L416) -- [webserver_plugin.cpp:418-495](file://plugins/webserver/webserver_plugin.cpp#L418-L495) -- [plugin.cpp:258-288](file://plugins/json_rpc/plugin.cpp#L258-L288) - -The architecture implements several key design patterns: -- **Separation of Concerns**: HTTP and WebSocket servers run in separate threads with dedicated io_service instances -- **Thread Pool Pattern**: Concurrent request processing with configurable thread count using appbase scheduler and modernized std::bind bindings -- **Intelligent Caching Pattern**: Request classification system with automatic cache control based on API mutability using fc::variant parsing -- **Observer Pattern**: Chain event subscription for automatic cache management on block application -- **Blacklist Pattern**: Mutating API detection and prevention of cache pollution using fc::variant-based method analysis -- **fc::variant Pattern**: Robust JSON parsing and manipulation using fc library variants for optimal performance -- **Error Handling Pattern**: Comprehensive fc::json::from_string-based error handling preventing crashes and improving reliability -- **Enhanced Logging Pattern**: ANSI color-coded diagnostic logging for improved debugging and monitoring - -**Updated** Enhanced with fc::variant-based JSON parsing, response ID patching, comprehensive error handling capabilities, improved cache management using fc::variant objects, modernized std::bind-based thread pool management, and enhanced diagnostic logging with ANSI gray color support. - -## Detailed Component Analysis - -### Modernized Thread Pool Management -The plugin uses the appbase scheduler for request processing with modernized std::bind bindings, providing a dedicated thread pool separate from the main application thread: - -```mermaid -classDiagram -class webserver_plugin_impl { -+thread_pool_size_t thread_pool_size -+asio : : io_service thread_pool_ios -+asio : : io_service : : work thread_pool_work -+vector worker_threads -+start_webserver() -+stop_webserver() -+handle_ws_message() -+handle_http_message() -+is_cacheable_request() -+make_cache_key() -+extract_request_id() -+patch_response_id() -+fc : : variant Parser -} -class ThreadGroup { -+create_thread(std : : bind) -+join_all() -+ioservice scheduler -} -webserver_plugin_impl --> ThreadGroup : "uses modernized std : : bind" -``` - -**Diagram sources** -- [webserver_plugin.cpp:190-197](file://plugins/webserver/webserver_plugin.cpp#L190-L197) - -**Updated** Enhanced with comprehensive fc::variant-based JSON parsing methods, improved cache management capabilities, robust fc::variant object handling, and modernized std::bind-based thread pool management using std::placeholders. - -### Enhanced WebSocket Message Handlers -The plugin implements modernized std::bind-based WebSocket message handlers with proper std::placeholders for parameter binding: - -```mermaid -sequenceDiagram -participant WS as "WebSocket Server" -participant Handler as "std : : bind Handler" -participant Parser as "fc : : json : : from_string Parser" -participant Variant as "fc : : variant Request" -participant Classifier as "Request Classifier" -participant Cache as "Cache System" -participant API as "JSON-RPC API" -WS->>Handler : WebSocket Message -Handler->>Parser : fc : : json : : from_string -Parser-->>Handler : fc : : variant Request -Handler->>Variant : Process request object -Handler->>Classifier : Check Mutating API -Classifier-->>Handler : Cache Decision -alt Cacheable Request -Handler->>Cache : Lookup Cache -Cache-->>Handler : Cache Hit/Miss -alt Cache Hit -Handler->>WS : Return Cached Response -else Cache Miss -Handler->>API : Process Request -API-->>Handler : Response -Handler->>Cache : Store Response -Handler->>WS : Return Response -end -else Non-Cacheable Request -Handler->>API : Process Request -API-->>Handler : Response -Handler->>WS : Return Response -end -``` - -**Diagram sources** -- [webserver_plugin.cpp:236-289](file://plugins/webserver/webserver_plugin.cpp#L236-L289) -- [webserver_plugin.cpp:352-416](file://plugins/webserver/webserver_plugin.cpp#L352-L416) - -**Updated** Enhanced with fc::variant-based JSON parsing, robust error handling, improved cache management, comprehensive fc::variant object processing, and modernized std::bind-based WebSocket message handlers using std::placeholders. - -### Intelligent Request Classification System -The plugin implements an advanced request classification system that automatically determines whether a JSON-RPC request should be cached based on its API mutability using robust fc::variant parsing: - -```mermaid -sequenceDiagram -participant Client as "Client" -participant Server as "Webserver Plugin" -participant Parser as "fc : : json : : from_string Parser" -participant Variant as "fc : : variant Object" -participant Classifier as "Request Classifier" -participant Cache as "Response Cache" -participant JSONRPC as "JSON-RPC Plugin" -Client->>Server : JSON-RPC Request -Server->>Parser : fc : : json : : from_string(request) -Parser-->>Server : fc : : variant object -Server->>Variant : Extract request object -Server->>Classifier : Analyze request method -Classifier->>Variant : Extract method name using fc : : variant -Classifier->>Variant : Check blacklist (network_broadcast_api, debug_node) -Classifier-->>Server : Cacheable/Non-cacheable decision -alt Cacheable request -Server->>Cache : Check cache availability -Cache-->>Server : Cache hit/miss -alt Cache miss -Server->>JSONRPC : Process request -JSONRPC-->>Server : Response -Server->>Cache : Store response -end -else Non-cacheable request -Server->>JSONRPC : Process request (bypass cache) -JSONRPC-->>Server : Response -end -Server-->>Client : Send response -``` - -**Diagram sources** -- [webserver_plugin.cpp:87-113](file://plugins/webserver/webserver_plugin.cpp#L87-L113) -- [webserver_plugin.cpp:352-416](file://plugins/webserver/webserver_plugin.cpp#L352-L416) -- [webserver_plugin.cpp:418-495](file://plugins/webserver/webserver_plugin.cpp#L418-L495) - -**Updated** Enhanced with fc::variant-based JSON parsing and robust request classification logic using fc::variant objects for method extraction and blacklist checking. - -### Response Caching Mechanism -The caching system provides significant performance improvements for frequently accessed API methods with sophisticated block-based invalidation and intelligent request filtering using fc::variant-based JSON parsing: - -```mermaid -flowchart TD -Start([Request Received]) --> Parse[Parse with fc::json::from_string] -Parse --> Valid{Valid JSON?} -Valid --> |No| ProcessDirect["Process Directly (No Cache)"] -Valid --> |Yes| Classify["Classify Request Type"] -Classify --> Mutating{"Mutating API?"} -Mutating --> |Yes| ProcessDirect -Mutating --> |No| Hash["Generate SHA256 Hash"] -Hash --> CacheCheck{"Cache Enabled?"} -CacheCheck --> |No| ProcessDirect -CacheCheck --> |Yes| LookupCache["Lookup Cache Entry"] -LookupCache --> CacheHit{"Cache Hit?"} -CacheHit --> |Yes| BlockCheck{"Block Valid?"} -CacheHit --> |No| ProcessRequest["Process Request"] -BlockCheck --> |Yes| PatchID["Patch Response ID"] -PatchID --> ReturnCached["Return Cached Response"] -BlockCheck --> |No| ProcessRequest -ProcessRequest --> StoreCache["Store in Cache"] -StoreCache --> ReturnResponse["Return Response"] -ProcessDirect --> ReturnResponse -ReturnCached --> End([Complete]) -ReturnResponse --> End -``` - -**Diagram sources** -- [webserver_plugin.cpp:207-234](file://plugins/webserver/webserver_plugin.cpp#L207-L234) -- [webserver_plugin.cpp:87-113](file://plugins/webserver/webserver_plugin.cpp#L87-L113) -- [webserver_plugin.cpp:136-158](file://plugins/webserver/webserver_plugin.cpp#L136-L158) - -**Updated** Enhanced with fc::variant-based JSON parsing, response ID patching, comprehensive cache validation, and improved error handling using fc::variant objects. - -### Enhanced Cache Key Generation -The plugin now implements id-independent cache keys to prevent cache bypass attacks and improve cache efficiency using fc::variant-based request parsing: - -```mermaid -flowchart TD -Request[JSON-RPC Request] --> Parse[fc::json::from_string] -Parse --> Batch{Is Array Request?} -Batch --> |Yes| FullHash[SHA256 Hash Full Array] -Batch --> |No| BuildKey[Build Key Material] -BuildKey --> Method{Has Method?} -Method --> |Yes| AddMethod[Add Method to Key] -Method --> |No| ParamsCheck{Has Params?} -AddMethod --> ParamsCheck -ParamsCheck --> |Yes| AddParams[Add Params to Key] -ParamsCheck --> |No| EmptyKey[Empty Key Material] -AddParams --> HashKey[SHA256 Hash Key Material] -EmptyKey --> HashKey -HashKey --> CacheKey[Unique Cache Key] -FullHash --> CacheKey -CacheKey --> UseKey[Use for Cache Lookup] -``` - -**Diagram sources** -- [webserver_plugin.cpp:136-158](file://plugins/webserver/webserver_plugin.cpp#L136-L158) - -**Updated** Enhanced with fc::variant-based JSON parsing and id-independent cache key generation that prevents cache bypass attacks using fc::variant objects for method and parameter extraction. - -### Enhanced Response ID Handling -The plugin implements proper JSON-RPC 2.0 compliance with response ID patching using fc::json::from_string for accurate ID extraction and replacement: - -```mermaid -flowchart TD -Response[JSON-RPC Response] --> ParseResp[fc::json::from_string] -ParseResp --> HasID{Has ID Field?} -HasID --> |No| ReturnResp[Return Original Response] -HasID --> |Yes| ExtractID[Extract Request ID] -ExtractID --> PatchResp[Replace Response ID] -PatchResp --> Serialize[fc::json::to_string] -Serialize --> ReturnPatched[Return Patched Response] -ReturnResp --> End([Complete]) -ReturnPatched --> End -``` - -**Diagram sources** -- [webserver_plugin.cpp:160-182](file://plugins/webserver/webserver_plugin.cpp#L160-L182) - -**Updated** Enhanced with fc::variant-based JSON parsing for accurate response ID extraction and proper JSON-RPC 2.0 compliance using fc::variant objects. - -### Enhanced Request Processing Pipeline -The plugin now includes improved WebSocket and HTTP handler support with better request processing using fc::variant-based JSON parsing and modernized std::bind bindings: - -```mermaid -sequenceDiagram -participant Client as "Client" -participant Handler as "HTTP/WebSocket Handler" -participant Parser as "fc : : json : : from_string Parser" -participant Variant as "fc : : variant Request" -participant Classifier as "Request Classifier" -participant Cache as "Cache System" -participant API as "JSON-RPC API" -Client->>Handler : Raw Request -Handler->>Parser : fc : : json : : from_string -Parser-->>Handler : fc : : variant Request -Handler->>Variant : Process request object -Handler->>Classifier : Check Mutating API -Classifier-->>Handler : Cache Decision -alt Cacheable Request -Handler->>Cache : Lookup Cache -Cache-->>Handler : Cache Hit/Miss -alt Cache Hit -Handler->>Client : Return Cached Response -else Cache Miss -Handler->>API : Process Request -API-->>Handler : Response -Handler->>Cache : Store Response -Handler->>Client : Return Response -end -else Non-Cacheable Request -Handler->>API : Process Request -API-->>Handler : Response -Handler->>Client : Return Response -end -``` - -**Diagram sources** -- [webserver_plugin.cpp:352-495](file://plugins/webserver/webserver_plugin.cpp#L352-L495) - -**Updated** Enhanced with fc::variant-based JSON parsing, robust error handling, improved cache management, comprehensive fc::variant object processing, and modernized std::bind-based handler implementations. - -### Configuration and Options -The plugin supports extensive configuration through command-line options and configuration files with enhanced processing order: - -| Option | Default | Description | -|--------|---------|-------------| -| `webserver-http-endpoint` | (none) | HTTP listen endpoint (IP:port) | -| `webserver-ws-endpoint` | (none) | WebSocket listen endpoint (IP:port) | -| `rpc-endpoint` | (none) | Combined HTTP/WS endpoint (deprecated) | -| `webserver-thread-pool-size` | 256 | Number of handler threads | -| `webserver-cache-enabled` | true | Enable response caching | -| `webserver-cache-size` | 10000 | Maximum cached responses | - -**Updated** Enhanced with actual implementation details and current default values, including improved rpc-endpoint processing order that prioritizes specific endpoints over deprecated combined endpoints. - -**Section sources** -- [webserver_plugin.cpp:503-517](file://plugins/webserver/webserver_plugin.cpp#L503-L517) -- [webserver-plugin.md:111-124](file://documentation/webserver-plugin.md#L111-L124) - -## Dependency Analysis -The webserver plugin has well-defined dependencies that enable its functionality with enhanced fc::variant integration and modernized std::bind usage: - -```mermaid -graph LR -subgraph "External Dependencies" -A[Boost.Asio] -B[websocketpp] -C[FC Library] -D[AppBase Framework] -E[fc::json::from_string] -F[fc::variant] -G[fc::mutable_variant_object] -H[std::bind/std::placeholders] -I[ANSI Color Sequences] -J[Terminal Logging] -end -subgraph "Internal Dependencies" -K[JSON-RPC Plugin with Enhanced Logging] -L[Chain Plugin] -M[Application Core] -end -subgraph "Webserver Plugin" -N[webserver_plugin] -O[is_cacheable_request] -P[make_cache_key] -Q[extract_request_id] -R[patch_response_id] -S[fc::variant Parser] -T[Cache Key Generator] -U[Response ID Patcher] -V[std::bind Message Handlers] -W[Diagnostic Logging] -X[Colorized Output] -end -J --> I -K --> W -W --> X -L --> K -L --> M -L --> N -N --> A -N --> B -N --> C -N --> D -N --> E -N --> F -N --> G -N --> H -N --> I -N --> J -O --> P -P --> Q -Q --> R -R --> S -S --> T -T --> U -U --> V -W --> X -``` - -**Diagram sources** -- [webserver_plugin.hpp:3-9](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L3-L9) -- [webserver_plugin.cpp:12-31](file://plugins/webserver/webserver_plugin.cpp#L12-L31) -- [webserver_plugin.cpp:87-113](file://plugins/webserver/webserver_plugin.cpp#L87-L113) -- [plugin.cpp:258-288](file://plugins/json_rpc/plugin.cpp#L258-L288) - -### JSON-RPC Integration Details -The plugin integrates with the JSON-RPC system through method registration and call delegation using fc::json::from_string for proper JSON parsing with enhanced fc::variant object handling, modernized std::bind-based handler implementations, and enhanced diagnostic logging with ANSI color support: - -```mermaid -sequenceDiagram -participant WS as "Webserver Plugin" -participant JR as "JSON-RPC Plugin with Enhanced Logging" -participant AR as "API Registry" -participant AP as "API Method" -WS->>JR : call(json_body, callback) -JR->>AR : Find API method -AR->>AP : Execute method(args) -AP-->>AR : Return result -AR-->>JR : Return variant result -JR-->>WS : Stringified response with colored diagnostics -WS-->>Client : Send response -``` - -**Diagram sources** -- [plugin.cpp:180-200](file://plugins/json_rpc/plugin.cpp#L180-L200) -- [webserver_plugin.cpp:398](file://plugins/webserver/webserver_plugin.cpp#L398) -- [webserver_plugin.cpp:468](file://plugins/webserver/webserver_plugin.cpp#L468) - -**Updated** Enhanced with fc::json::from_string-based JSON parsing, robust error handling, comprehensive fc::variant object processing, modernized std::bind-based handler implementations, and enhanced diagnostic logging with ANSI gray color support. - -**Section sources** -- [webserver_plugin.hpp:38](file://plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp#L38) -- [webserver_plugin.cpp:224](file://plugins/webserver/webserver_plugin.cpp#L224) -- [plugin.cpp:159-178](file://plugins/json_rpc/plugin.cpp#L159-L178) - -## Performance Considerations -The webserver plugin implements several performance optimization strategies with intelligent caching control using fc::variant-based JSON parsing and modernized std::bind bindings: - -### Intelligent Caching Strategy -- **Request Classification**: Automatic determination of cacheable vs non-cacheable requests based on API mutability using fc::variant parsing -- **SHA256 Hash Keys**: Unique request identification for cache entries using cryptographic hashing with fc::json::from_string -- **Block-Based Invalidation**: Cache cleared on each new block to prevent stale data through blockchain event subscription -- **Thread-Safe Operations**: Mutex protection for concurrent access across multiple worker threads -- **Eviction Policy**: Automatic cache clearing when maximum size is reached to prevent memory exhaustion -- **Selective Caching**: Prevents cache pollution from mutating API calls (network_broadcast_api, debug_node) -- **Id-Independent Keys**: Prevents cache bypass attacks via id rotation patterns using fc::variant-based key generation -- **Robust JSON Parsing**: Reliable fc::json::from_string-based request parsing instead of complex object traversal -- **fc::variant Efficiency**: Optimized fc::variant usage for JSON parsing and manipulation with proper object lifetime management -- **Modernized Bindings**: Efficient std::bind usage with std::placeholders for better C++11 compatibility and performance -- **Enhanced Logging Performance**: ANSI color logging with minimal overhead for improved debugging without impacting performance - -### Concurrency Model -- **Separate IO Services**: HTTP and WebSocket servers use dedicated io_service instances for isolation -- **Configurable Thread Pool**: Adjustable worker thread count based on workload using appbase scheduler with modernized std::bind bindings -- **Non-blocking Operations**: Async processing prevents thread starvation and improves throughput -- **Connection Pooling**: Efficient WebSocket connection handling with proper resource management - -### Memory Management -- **Smart Pointers**: Proper resource management for server instances and cache entries -- **RAII Patterns**: Automatic cleanup on plugin shutdown through destructor implementations -- **Cache Size Limits**: Configurable maximum cache size to prevent unbounded memory growth -- **Blacklist Optimization**: Reduces unnecessary cache storage for mutating API calls -- **fc::variant Efficiency**: Optimized fc::variant usage for JSON parsing and manipulation with proper memory management -- **Modernized Bindings**: Efficient std::bind usage with std::placeholders for better performance and C++11 compatibility -- **Color Logging Overhead**: Minimal performance impact from ANSI color sequences in diagnostic logging - -**Updated** Enhanced with fc::variant-based JSON parsing, robust cache validation, comprehensive performance optimizations, improved fc::variant object handling, modernized std::bind-based thread pool management, and enhanced diagnostic logging with ANSI gray color support. - -**Section sources** -- [webserver-plugin.md:29-64](file://documentation/webserver-plugin.md#L29-L64) -- [webserver_plugin.cpp:207-234](file://plugins/webserver/webserver_plugin.cpp#L207-L234) -- [webserver_plugin.cpp:87-113](file://plugins/webserver/webserver_plugin.cpp#L87-L113) - -## Security Considerations -The webserver plugin provides multiple layers of security for production deployments with enhanced API access control using fc::variant-based JSON parsing and modernized std::bind bindings: - -### Network Security -- **Localhost Binding**: Recommended practice for internal services using 127.0.0.1 binding -- **External Access Control**: Use 0.0.0.0 binding only for trusted networks -- **Port Management**: Separate HTTP (8090) and WebSocket (8091) ports for different access patterns - -### API Access Control -- **Public API Restriction**: Use `public-api` configuration to limit exposed API surface -- **Authentication**: Implement `api-user` authentication for sensitive operations -- **Rate Limiting**: Consider external rate limiting solutions for public APIs -- **Mutating API Protection**: Automatic blacklist prevents caching of state-changing operations - -### Input Validation -- **JSON-RPC Validation**: Built-in validation of JSON-RPC 2.0 compliance using fc::json::from_string -- **Method Whitelisting**: Only registered API methods are callable -- **Parameter Validation**: Type checking and parameter validation for API calls -- **Request Classification**: Automatic detection of potentially malicious mutating requests using fc::variant parsing - -### Resource Protection -- **Thread Pool Limits**: Configurable thread pool size prevents resource exhaustion -- **Cache Size Limits**: Configurable cache limits prevent memory abuse -- **Connection Limits**: WebSocket connections managed through proper thread pool utilization -- **Blacklist Enforcement**: Prevents cache poisoning from mutating API calls -- **Cache Bypass Prevention**: Id-independent keys prevent cache bypass attacks via request id rotation -- **Robust Error Handling**: Comprehensive fc::variant-based error handling prevents crashes -- **Malformed Request Protection**: Invalid JSON requests are handled gracefully without cache pollution -- **Modernized Bindings**: Efficient std::bind usage with std::placeholders for better performance and security -- **Enhanced Logging Security**: ANSI color logging provides clear visibility without exposing sensitive data - -**Updated** Enhanced with fc::variant-based JSON parsing, robust error handling, comprehensive security measures, improved fc::variant object validation, modernized std::bind-based security implementations, and enhanced diagnostic logging with ANSI gray color support. - -**Section sources** -- [webserver-plugin.md:77-108](file://documentation/webserver-plugin.md#L77-L108) - -## Configuration Guide - -### Basic Configuration -Enable the webserver plugin in `config.ini`: - -```ini -plugin = webserver - -# HTTP endpoint (required for HTTP API access) -webserver-http-endpoint = 127.0.0.1:8090 - -# WebSocket endpoint (required for WebSocket API access) -webserver-ws-endpoint = 127.0.0.1:8091 - -# Or use a single endpoint for both (deprecated) -# rpc-endpoint = 127.0.0.1:8090 -``` - -### Advanced Configuration -```ini -# Thread pool configuration for high concurrency -webserver-thread-pool-size = 256 - -# Response caching configuration -webserver-cache-enabled = true -webserver-cache-size = 10000 - -# API access control -public-api = database_api -public-api = network_broadcast_api - -# Authentication -api-user = username:password:database_api -``` - -### Production Configuration -For production deployments, consider: - -```ini -# High performance settings -webserver-thread-pool-size = 512 -webserver-cache-size = 50000 - -# Security settings -webserver-http-endpoint = 127.0.0.1:8090 -webserver-ws-endpoint = 127.0.0.1:8091 - -# API restrictions -public-api = database_api -public-api = account_by_key -``` - -**Updated** Enhanced with actual implementation details and current configuration options, including improved rpc-endpoint processing order that prioritizes specific endpoints over deprecated combined endpoints. - -**Section sources** -- [webserver-plugin.md:12-27](file://documentation/webserver-plugin.md#L12-L27) -- [webserver-plugin.md:40-48](file://documentation/webserver-plugin.md#L40-L48) -- [webserver-plugin.md:109-125](file://documentation/webserver-plugin.md#L109-L125) - -## Troubleshooting Guide - -### Common Issues and Solutions - -#### Server Binding Failures -**Problem**: Unable to bind to specified endpoints -**Solution**: Verify port availability and network permissions -- Check if ports are already in use -- Ensure proper network interface binding -- Verify firewall configuration - -#### High Memory Usage -**Problem**: Excessive memory consumption from caching -**Solution**: Adjust cache configuration -- Reduce `webserver-cache-size` value -- Disable caching for low-traffic scenarios -- Monitor cache hit ratios and memory usage - -#### Performance Degradation -**Problem**: Slow response times under load -**Solution**: Optimize thread pool configuration -- Increase `webserver-thread-pool-size` -- Monitor thread utilization and queue lengths -- Consider hardware resource allocation - -### Error Handling Patterns -The plugin implements comprehensive error handling with intelligent request classification using fc::variant-based JSON parsing: - -```mermaid -flowchart TD -Request[Incoming Request] --> Parse[fc::json::from_string] -Parse --> Valid{Valid JSON?} -Valid --> |No| ParseError[Return Parse Error] -Valid --> |Yes| Classify[Classify Request Type] -Classify --> Mutating{Mutating API?} -Mutating --> |Yes| ProcessDirect[Process Directly] -Mutating --> |No| CacheCheck[Check Cache] -CacheCheck --> CacheHit{Cache Hit?} -CacheHit --> |Yes| PatchID[Patch Response ID] -PatchID --> SendCached[Send Cached Response] -CacheHit --> |No| Process[Process Request] -ProcessDirect --> SendResponse[Send Response] -Process --> Success{Success?} -Success --> |Yes| CacheStore[Store in Cache] -Success --> |No| SendError[Return Error Response] -CacheStore --> SendResponse -SendCached --> Complete[Complete] -SendResponse --> Complete -SendError --> Complete -ParseError --> Complete -``` - -**Diagram sources** -- [webserver_plugin.cpp:352-416](file://plugins/webserver/webserver_plugin.cpp#L352-L416) -- [webserver_plugin.cpp:418-495](file://plugins/webserver/webserver_plugin.cpp#L418-L495) -- [webserver_plugin.cpp:398](file://plugins/webserver/webserver_plugin.cpp#L398) - -**Updated** Enhanced with fc::variant-based JSON parsing, robust error handling, intelligent request classification, comprehensive fc::variant object processing, and modernized std::bind-based error handling implementations. - -### Debugging and Monitoring -- **Log Levels**: Configure appropriate log levels for debugging -- **Connection Monitoring**: Monitor active WebSocket connections -- **Performance Metrics**: Track cache hit rates and thread pool utilization -- **Error Analysis**: Review error logs for common issues -- **Request Classification**: Monitor which requests are classified as mutating vs non-mutating -- **Cache Efficiency**: Monitor cache key generation and collision rates -- **fc::variant Parsing**: Monitor JSON parsing performance and error rates -- **Configuration Processing**: Monitor rpc-endpoint processing order and endpoint resolution -- **Modernized Bindings**: Monitor std::bind usage and std::placeholders performance -- **Enhanced Logging**: Monitor ANSI color logging output for improved debugging visibility - -**Updated** Enhanced with fc::variant-based JSON parsing monitoring, comprehensive debugging capabilities, improved configuration processing diagnostics, modernized std::bind-based monitoring implementations, and enhanced diagnostic logging with ANSI gray color support. - -**Section sources** -- [webserver_plugin.cpp:352-416](file://plugins/webserver/webserver_plugin.cpp#L352-L416) -- [webserver_plugin.cpp:418-495](file://plugins/webserver/webserver_plugin.cpp#L418-L495) -- [webserver_plugin.cpp:398](file://plugins/webserver/webserver_plugin.cpp#L398) - -## Logging and Diagnostics - -### Enhanced Diagnostic Logging with ANSI Color Support -The JSON RPC plugin now includes enhanced diagnostic logging with ANSI gray color support to help developers quickly distinguish between normal operation and diagnostic information: - -```mermaid -sequenceDiagram -participant Client as "Client" -participant JSONRPC as "JSON-RPC Plugin with Enhanced Logging" -participant Timer as "dump_rpc_time" -participant Logger as "Diagnostic Logger" -Client->>JSONRPC : JSON-RPC Request -JSONRPC->>Timer : Create timer instance -Timer->>Logger : Log colored diagnostic data -Logger-->>Timer : Colored output : "data : ${data}" (ANSI Gray) -Timer->>JSONRPC : Process request -JSONRPC->>Timer : Handle completion -alt Success -Timer->>Logger : Log elapsed time and data -Logger-->>Client : Colored output : "elapsed : ${time} sec, data : ${data}" (ANSI Gray) -else Error -Timer->>Logger : Log elapsed time, error, and data -Logger-->>Client : Colored output : "elapsed : ${time} sec, error : '${error}', data : ${data}" (ANSI Gray) -end -``` - -**Diagram sources** -- [plugin.cpp:258-288](file://plugins/json_rpc/plugin.cpp#L258-L288) - -### ANSI Color Coding Features -The diagnostic logging system uses ANSI escape sequences for visual distinction: - -- **Gray Color Codes**: `\033[90m` for diagnostic information -- **Reset Code**: `\033[0m` to restore normal terminal colors -- **Timing Information**: Elapsed time measurements in seconds -- **Data Processing**: Request/response data visualization with enhanced readability -- **Error Context**: Error messages with associated timing data -- **Improved Debugging**: Visual separation between normal application logs and diagnostic information - -### Developer Experience Improvements -- **Visual Separation**: Gray-colored diagnostic logs help distinguish from normal application logs -- **Real-time Timing**: Precise timing measurements for request processing -- **Data Visibility**: Structured logging of request/response data with enhanced formatting -- **Error Tracking**: Comprehensive error logging with timing context for better debugging -- **Performance Insights**: Easy identification of slow operations through timing data -- **Enhanced Readability**: Improved log readability with color-coded diagnostic information - -**Updated** Enhanced with fc::variant-based JSON parsing and comprehensive diagnostic logging with ANSI gray color support for improved developer experience and debugging capabilities. - -**Section sources** -- [plugin.cpp:258-288](file://plugins/json_rpc/plugin.cpp#L258-L288) - -## Conclusion -The Webserver Plugin provides a robust, high-performance solution for exposing VIZ blockchain functionality through HTTP and WebSocket interfaces. Its architecture emphasizes scalability through concurrent processing, reliability through comprehensive error handling, and efficiency through intelligent caching mechanisms with request classification using fc::variant-based JSON parsing. The plugin's modular design and extensive configuration options make it suitable for various deployment scenarios, from development environments to production public API services. - -Key strengths of the implementation include: -- **High Concurrency**: Thread pool architecture supporting thousands of concurrent requests with modernized std::bind bindings -- **Intelligent Caching**: Block-aware cache invalidation with automatic request classification preventing stale data using fc::variant parsing -- **Selective Caching**: Automatic blacklist for mutating API calls (network_broadcast_api, debug_node) preventing cache pollution -- **Flexible Deployment**: Separate HTTP and WebSocket endpoints with independent configuration -- **Production Ready**: Comprehensive error handling and graceful degradation -- **Security Features**: Multiple layers of security including intelligent request classification for mutating APIs -- **Enhanced Diagnostics**: ANSI color-coded logging system with gray color support for improved developer experience -- **Extensible Design**: Clean separation of concerns enabling easy maintenance and enhancement -- **Performance Optimizations**: Major improvements to caching mechanism with id-independent keys and fc::variant-based JSON parsing -- **Robust Request Processing**: Enhanced WebSocket/HTTP handler support with improved request classification and cache management using modernized std::bind bindings -- **fc::variant Integration**: Comprehensive fc::variant-based JSON parsing and manipulation for optimal performance -- **Comprehensive Error Handling**: Robust fc::json::from_string-based error handling preventing crashes and improving reliability -- **Enhanced Configuration Management**: Improved rpc-endpoint processing order with proper endpoint resolution and deprecation warnings -- **Modernized C++11 Compatibility**: Efficient std::bind usage with std::placeholders for better C++11 compatibility and performance -- **Efficient Thread Pool Management**: Modernized std::bind-based thread pool management with std::placeholders for optimal performance -- **Enhanced Logging System**: ANSI color-coded diagnostic logging with gray color support for improved debugging and monitoring capabilities - -The plugin serves as an excellent foundation for building applications that require programmatic access to VIZ blockchain data and operations, with performance characteristics suitable for both private deployments and public API services. Its sophisticated caching mechanism with intelligent request classification, multi-threaded architecture with modernized std::bind bindings, comprehensive error handling, fc::variant-based JSON parsing, enhanced diagnostic logging with ANSI gray color support, improved configuration management with rpc-endpoint processing order changes, and efficient std::bind-based thread pool management make it a production-ready solution for enterprise-grade blockchain applications. - -**Updated** Enhanced conclusion reflecting the expanded implementation details, fc::variant-based JSON parsing, intelligent request classification system, selective caching mechanisms, enhanced WebSocket/HTTP handler support with modernized std::bind bindings, major performance optimizations including id-independent cache keys, comprehensive cache configuration options, improved configuration management with rpc-endpoint processing order changes, modernized C++11 compatibility with std::bind and std::placeholders usage, and enhanced diagnostic logging with ANSI gray color support for improved debugging capabilities. \ No newline at end of file diff --git a/.qoder/repowiki/en/meta/repowiki-metadata.json b/.qoder/repowiki/en/meta/repowiki-metadata.json deleted file mode 100644 index fb570f482d..0000000000 --- a/.qoder/repowiki/en/meta/repowiki-metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"code_snippets":[{"id":"e6b951c57c5f8fb2b3553ce8db430760","path":"plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","line_range":"18-55","gmt_create":"2026-04-28T09:55:13.5601426+04:00","gmt_modified":"2026-04-28T09:55:13.5601426+04:00"},{"id":"467b9030fcd47369d3417c84998c20d0","path":"plugins/p2p/p2p_plugin.cpp","line_range":"910-979","gmt_create":"2026-04-28T09:55:13.5607279+04:00","gmt_modified":"2026-04-28T09:55:13.5607279+04:00"},{"id":"cbfe85acce275b65a2edb3315aec2941","path":"libraries/network/include/graphene/network/node.hpp","line_range":"190-320","gmt_create":"2026-04-28T09:55:13.5617916+04:00","gmt_modified":"2026-04-28T09:55:13.5617916+04:00"},{"id":"9e18fa1bdbee1d9c96d8437bfe20515c","path":"plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","line_range":"1-57","gmt_create":"2026-04-28T09:55:13.5619843+04:00","gmt_modified":"2026-04-28T09:55:13.5619843+04:00"},{"id":"5dce2933cfb42430c2bbcdf0cacc25c3","path":"plugins/p2p/CMakeLists.txt","line_range":"1-49","gmt_create":"2026-04-28T09:55:13.562487+04:00","gmt_modified":"2026-04-28T09:55:13.562487+04:00"},{"id":"1b55f505ae64e9ea22be5142cfa67f93","path":"plugins/p2p/p2p_plugin.cpp","line_range":"49-126","gmt_create":"2026-04-28T09:55:13.5630878+04:00","gmt_modified":"2026-04-28T09:55:13.5630878+04:00"},{"id":"8a161abeb389c25b1279bc23d6ff4e57","path":"libraries/network/include/graphene/network/node.hpp","line_range":"60-167","gmt_create":"2026-04-28T09:55:13.5637371+04:00","gmt_modified":"2026-04-28T09:55:13.5637371+04:00"},{"id":"f85f57d0c6b461ab78f906ef6d5854c0","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"79-354","gmt_create":"2026-04-28T09:55:13.5646848+04:00","gmt_modified":"2026-04-28T09:55:13.5646848+04:00"},{"id":"21076248fc123c7aacc8cb6e67cd0068","path":"plugins/p2p/p2p_plugin.cpp","line_range":"758-823","gmt_create":"2026-04-28T09:55:13.5651893+04:00","gmt_modified":"2026-04-28T09:55:13.5651893+04:00"},{"id":"4e79c62ed5491dcd85510f3dab144813","path":"libraries/network/node.cpp","line_range":"1-200","gmt_create":"2026-04-28T09:55:13.5661929+04:00","gmt_modified":"2026-04-28T09:55:13.5661929+04:00"},{"id":"fe82427a3c86c02f05708734fa4c589c","path":"plugins/p2p/p2p_plugin.cpp","line_range":"216-245","gmt_create":"2026-04-28T09:55:13.5665017+04:00","gmt_modified":"2026-04-28T09:55:13.5665017+04:00"},{"id":"15bc72540b02de4c57e4c976c8150f35","path":"plugins/p2p/p2p_plugin.cpp","line_range":"855-865","gmt_create":"2026-04-28T09:55:13.5671967+04:00","gmt_modified":"2026-04-28T09:55:13.5671967+04:00"},{"id":"cd0a62c9a78bb77d3b59a9d5872577f4","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"82-106","gmt_create":"2026-04-28T09:55:13.5671967+04:00","gmt_modified":"2026-04-28T09:55:13.5671967+04:00"},{"id":"7eaff221b9d5916b8e18aa7786630566","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"188-218","gmt_create":"2026-04-28T09:55:13.5671967+04:00","gmt_modified":"2026-04-28T09:55:13.5671967+04:00"},{"id":"9ec3ef7b5beba7ccd4c8172983e359a7","path":"plugins/p2p/p2p_plugin.cpp","line_range":"247-301","gmt_create":"2026-04-28T09:55:13.5681968+04:00","gmt_modified":"2026-04-28T09:55:13.5681968+04:00"},{"id":"685c2a44fc90d96152180fc6a5a63df4","path":"plugins/p2p/p2p_plugin.cpp","line_range":"129-208","gmt_create":"2026-04-28T09:55:13.5681968+04:00","gmt_modified":"2026-04-28T09:55:13.5681968+04:00"},{"id":"9ad98a7be17ee5186d08088816474c52","path":"plugins/witness/witness.cpp","line_range":"540-552","gmt_create":"2026-04-28T09:55:13.5691969+04:00","gmt_modified":"2026-04-28T09:55:13.5691969+04:00"},{"id":"a273323d20c428afec092114bb480a23","path":"thirdparty/chainbase/include/chainbase/chainbase.hpp","line_range":"1078-1115","gmt_create":"2026-04-28T09:55:13.5714297+04:00","gmt_modified":"2026-04-28T09:55:13.5714297+04:00"},{"id":"27c21c9dfb07f579bd0db9fa97c8fd19","path":"thirdparty/chainbase/include/chainbase/chainbase.hpp","line_range":"1130-1137","gmt_create":"2026-04-28T09:55:13.5724293+04:00","gmt_modified":"2026-04-28T09:55:13.5724293+04:00"},{"id":"3b4fd0aa5c9c47621979a050b80b5fc2","path":"plugins/p2p/p2p_plugin.cpp","line_range":"173-208","gmt_create":"2026-04-28T09:55:13.5724293+04:00","gmt_modified":"2026-04-28T09:55:13.5724293+04:00"},{"id":"411e466fa1c626bc1fff0647607acabd","path":"plugins/p2p/p2p_plugin.cpp","line_range":"151-156","gmt_create":"2026-04-28T09:55:13.5734294+04:00","gmt_modified":"2026-04-28T09:55:13.5734294+04:00"},{"id":"8eb355e55d14f0a3eec62805ff783a2f","path":"plugins/p2p/CMakeLists.txt","line_range":"27-34","gmt_create":"2026-04-28T09:55:13.5734294+04:00","gmt_modified":"2026-04-28T09:55:13.5734294+04:00"},{"id":"4fc812a0df4303ac6e74df39697a0893","path":"plugins/p2p/p2p_plugin.cpp","line_range":"1-13","gmt_create":"2026-04-28T09:55:13.5734294+04:00","gmt_modified":"2026-04-28T09:55:13.5734294+04:00"},{"id":"cd8c02da5ea31d3411ad151149d2f64e","path":"programs/vizd/main.cpp","line_range":"63-92","gmt_create":"2026-04-28T09:57:03.8238509+04:00","gmt_modified":"2026-04-28T09:57:03.8238509+04:00"},{"id":"2d883d06f58fd34d81e8588c75185aa9","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","line_range":"34-68","gmt_create":"2026-04-28T09:57:03.8238509+04:00","gmt_modified":"2026-04-28T09:57:03.8238509+04:00"},{"id":"b6e11846d82ee129d5956cf9b8cbbea8","path":"plugins/witness/witness.cpp","line_range":"59-118","gmt_create":"2026-04-28T09:57:03.8256998+04:00","gmt_modified":"2026-04-28T09:57:03.8256998+04:00"},{"id":"cda9a1d47dfdb3a7374fa817887892c0","path":"plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp","line_range":"56-98","gmt_create":"2026-04-28T09:57:03.8256998+04:00","gmt_modified":"2026-04-28T09:57:03.8256998+04:00"},{"id":"f5891db138d66a58674791b9e99bd337","path":"plugins/witness_api/plugin.cpp","line_range":"13-28","gmt_create":"2026-04-28T09:57:03.8256998+04:00","gmt_modified":"2026-04-28T09:57:03.8256998+04:00"},{"id":"51169b91af554f837e41d2913dacad48","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"37-83","gmt_create":"2026-04-28T09:57:03.8262406+04:00","gmt_modified":"2026-04-28T09:57:03.8262406+04:00"},{"id":"5aed64f2f61be210a303b341a970b0cc","path":"libraries/chain/include/graphene/chain/witness_objects.hpp","line_range":"27-132","gmt_create":"2026-04-28T09:57:03.8262406+04:00","gmt_modified":"2026-04-28T09:57:03.8262406+04:00"},{"id":"47b98e10075d52a89428f617d837a5e5","path":"libraries/chain/include/graphene/chain/chain_objects.hpp","line_range":"174-201","gmt_create":"2026-04-28T09:57:03.8262406+04:00","gmt_modified":"2026-04-28T09:57:03.8262406+04:00"},{"id":"42aa356d9e26fadf05fda749f1d89cff","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"53-81","gmt_create":"2026-04-28T09:57:03.8262406+04:00","gmt_modified":"2026-04-28T09:57:03.8262406+04:00"},{"id":"19e78b124cb1653f5e72af6789493e08","path":"libraries/time/time.cpp","line_range":"13-53","gmt_create":"2026-04-28T09:57:03.8267656+04:00","gmt_modified":"2026-04-28T09:57:03.8267656+04:00"},{"id":"1bf53ebbc25ba8c147446f02ce5e44e2","path":"plugins/snapshot/plugin.cpp","line_range":"1267-1276","gmt_create":"2026-04-28T09:57:03.8272866+04:00","gmt_modified":"2026-04-28T09:57:03.8272866+04:00"},{"id":"cecb2c27bddde9783761743ffbbfac88","path":"plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","line_range":"50-55","gmt_create":"2026-04-28T09:57:03.8272866+04:00","gmt_modified":"2026-04-28T09:57:03.8272866+04:00"},{"id":"4a84a6b27fbcb47e0994f0bda545816f","path":"plugins/witness/witness.cpp","line_range":"206-249","gmt_create":"2026-04-28T09:57:03.8278629+04:00","gmt_modified":"2026-04-28T09:57:03.8278629+04:00"},{"id":"3d0cbd79a1648b655b90b7629307cad1","path":"plugins/witness/witness.cpp","line_range":"206-276","gmt_create":"2026-04-28T09:57:03.8283666+04:00","gmt_modified":"2026-04-28T09:57:03.8283666+04:00"},{"id":"6abe7f6efde355ee9f2d7cf0776677ff","path":"plugins/witness/witness.cpp","line_range":"278-423","gmt_create":"2026-04-28T09:57:03.8288828+04:00","gmt_modified":"2026-04-28T09:57:03.8288828+04:00"},{"id":"1cf7bf28dd5011954754492ccf7873f5","path":"plugins/witness/witness.cpp","line_range":"447-471","gmt_create":"2026-04-28T09:57:03.8288828+04:00","gmt_modified":"2026-04-28T09:57:03.8288828+04:00"},{"id":"31e5e32f87baccd25fbb2183951a67bd","path":"plugins/witness/witness.cpp","line_range":"590-695","gmt_create":"2026-04-28T09:57:03.8288828+04:00","gmt_modified":"2026-04-28T09:57:03.8288828+04:00"},{"id":"1844592144295f47f4238341e8868e6b","path":"plugins/witness/witness.cpp","line_range":"263-266","gmt_create":"2026-04-28T09:57:03.8288828+04:00","gmt_modified":"2026-04-28T09:57:03.8288828+04:00"},{"id":"15a3537ebe2816e5402e7fb60462b58e","path":"libraries/chain/database.cpp","line_range":"4317-4332","gmt_create":"2026-04-28T09:57:03.8293937+04:00","gmt_modified":"2026-04-28T09:57:03.8293937+04:00"},{"id":"6af53e8de30910078bc6fd9dab1d2f7b","path":"libraries/time/time.cpp","line_range":"74-76","gmt_create":"2026-04-28T09:57:03.8293937+04:00","gmt_modified":"2026-04-28T09:57:03.8293937+04:00"},{"id":"0ef7b0d7933da804905c2ff76f92cd94","path":"libraries/chain/database.cpp","line_range":"2824-2839","gmt_create":"2026-04-28T09:57:03.8293937+04:00","gmt_modified":"2026-04-28T09:57:03.8293937+04:00"},{"id":"667d252413c23e04beb2c069531a1372","path":"libraries/chain/database.cpp","line_range":"2871-2886","gmt_create":"2026-04-28T09:57:03.8293937+04:00","gmt_modified":"2026-04-28T09:57:03.8293937+04:00"},{"id":"62ea8f0eed608d8eb1dd0911e43f28c3","path":"libraries/chain/database.cpp","line_range":"1223-1267","gmt_create":"2026-04-28T09:57:03.8299086+04:00","gmt_modified":"2026-04-28T09:57:03.8299086+04:00"},{"id":"1b91062fbd3e8ce21a7ec705a5ef21ae","path":"plugins/witness/witness.cpp","line_range":"125-133","gmt_create":"2026-04-28T09:57:03.8304179+04:00","gmt_modified":"2026-04-28T09:57:03.8304179+04:00"},{"id":"c57b368c9aec32de084799a61fc21d81","path":"plugins/witness/witness.cpp","line_range":"149-155","gmt_create":"2026-04-28T09:57:03.8304179+04:00","gmt_modified":"2026-04-28T09:57:03.8304179+04:00"},{"id":"9a1726ad4c4d7942894eafbb2fb7c20a","path":"plugins/witness/witness.cpp","line_range":"222-224","gmt_create":"2026-04-28T09:57:03.8309331+04:00","gmt_modified":"2026-04-28T09:57:03.8309331+04:00"},{"id":"d572e2edecf45b7b050d30cbb14368d8","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"57-58","gmt_create":"2026-04-28T09:57:03.8309331+04:00","gmt_modified":"2026-04-28T09:57:03.8309331+04:00"},{"id":"a3b204b149312d56bea1667800a95fb6","path":"share/vizd/config/config.ini","line_range":"99-103","gmt_create":"2026-04-28T09:57:03.8314489+04:00","gmt_modified":"2026-04-28T09:57:03.8314489+04:00"},{"id":"f72faf82a21dd9049d68549d9c7e5c4f","path":"share/vizd/config/config_witness.ini","line_range":"76-80","gmt_create":"2026-04-28T09:57:03.8319591+04:00","gmt_modified":"2026-04-28T09:57:03.8319591+04:00"},{"id":"c5701be8b76f9a4a85b659f219a10f95","path":"plugins/witness/witness.cpp","line_range":"509-555","gmt_create":"2026-04-28T09:57:03.8329908+04:00","gmt_modified":"2026-04-28T09:57:03.8329908+04:00"},{"id":"7f2c3ab5ba63b977d9642aa1c78a2c43","path":"plugins/witness/witness.cpp","line_range":"120-169","gmt_create":"2026-04-28T09:57:03.8335072+04:00","gmt_modified":"2026-04-28T09:57:03.8335072+04:00"},{"id":"737d623fe091f7ae2629dcda699b0efa","path":"plugins/witness/witness.cpp","line_range":"171-192","gmt_create":"2026-04-28T09:57:03.8335072+04:00","gmt_modified":"2026-04-28T09:57:03.8335072+04:00"},{"id":"fba8be1bbb523071f8ce7ad55a13d0f2","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","line_range":"31","gmt_create":"2026-04-28T09:57:03.8345403+04:00","gmt_modified":"2026-04-28T09:57:03.8345403+04:00"},{"id":"2a7a24b7119edca00eb0b21200f484ec","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"88","gmt_create":"2026-04-28T09:57:03.8345403+04:00","gmt_modified":"2026-04-28T09:57:03.8345403+04:00"},{"id":"c9048f7e0344e917d91b3b45d3804a0d","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"73","gmt_create":"2026-04-28T09:57:03.8345403+04:00","gmt_modified":"2026-04-28T09:57:03.8345403+04:00"},{"id":"ff4cacfd8a6a1ce746ce49bd2259ef47","path":"libraries/chain/fork_database.cpp","line_range":"151-166","gmt_create":"2026-04-28T09:57:03.8350501+04:00","gmt_modified":"2026-04-28T09:57:03.8350501+04:00"},{"id":"78279a6057ca0eaea7f987e920fada7f","path":"libraries/chain/database.cpp","line_range":"1456-1471","gmt_create":"2026-04-28T09:57:03.8350501+04:00","gmt_modified":"2026-04-28T09:57:03.8350501+04:00"},{"id":"95e9eb6df5c5b54fc25131e15cebca7b","path":"libraries/chain/fork_database.cpp","line_range":"269-274","gmt_create":"2026-04-28T09:57:03.8350501+04:00","gmt_modified":"2026-04-28T09:57:03.8350501+04:00"},{"id":"4c8c235c40a9885b9ce181cc9af786fe","path":"libraries/chain/database.cpp","line_range":"2807-2839","gmt_create":"2026-04-28T09:57:03.8361473+04:00","gmt_modified":"2026-04-28T09:57:03.8361473+04:00"},{"id":"29354043db86eeb48566453a908645c7","path":"libraries/chain/database.cpp","line_range":"2897-2914","gmt_create":"2026-04-28T09:57:03.8361473+04:00","gmt_modified":"2026-04-28T09:57:03.8361473+04:00"},{"id":"6e4547d3d8a2b1ce7fb2eb8442ba9631","path":"libraries/chain/database.cpp","line_range":"1294-1311","gmt_create":"2026-04-28T09:57:03.8366585+04:00","gmt_modified":"2026-04-28T09:57:03.8366585+04:00"},{"id":"0dec6783542ea54ba7d81fbeb930e442","path":"plugins/witness_api/plugin.cpp","line_range":"30-49","gmt_create":"2026-04-28T09:57:03.8366585+04:00","gmt_modified":"2026-04-28T09:57:03.8366585+04:00"},{"id":"85675dcfc30f216052bdb5cedc7b435c","path":"plugins/witness_api/plugin.cpp","line_range":"75-91","gmt_create":"2026-04-28T09:57:03.8366585+04:00","gmt_modified":"2026-04-28T09:57:03.8366585+04:00"},{"id":"7f42fa8b501a589d403cb682d1620581","path":"plugins/witness_api/plugin.cpp","line_range":"102-125","gmt_create":"2026-04-28T09:57:03.8371754+04:00","gmt_modified":"2026-04-28T09:57:03.8371754+04:00"},{"id":"17a2c941cfb814db6c1623046d8dac1e","path":"plugins/witness_api/plugin.cpp","line_range":"127-159","gmt_create":"2026-04-28T09:57:03.8371754+04:00","gmt_modified":"2026-04-28T09:57:03.8371754+04:00"},{"id":"53a82d703bff53e589353336964d2eed","path":"plugins/witness_api/plugin.cpp","line_range":"161-169","gmt_create":"2026-04-28T09:57:03.8371754+04:00","gmt_modified":"2026-04-28T09:57:03.8371754+04:00"},{"id":"4fcf4072cf8ebe5cd019e9a0da762901","path":"plugins/witness_api/plugin.cpp","line_range":"171-203","gmt_create":"2026-04-28T09:57:03.8371754+04:00","gmt_modified":"2026-04-28T09:57:03.8371754+04:00"},{"id":"c4f66fb8fb1d6eeb25bd48eec2ba80a6","path":"plugins/witness_api/plugin.cpp","line_range":"102-159","gmt_create":"2026-04-28T09:57:03.837694+04:00","gmt_modified":"2026-04-28T09:57:03.837694+04:00"},{"id":"68e0135d2e3b03eff760c57654b97092","path":"plugins/witness_api/plugin.cpp","line_range":"161-203","gmt_create":"2026-04-28T09:57:03.837694+04:00","gmt_modified":"2026-04-28T09:57:03.837694+04:00"},{"id":"eeebc7e5a0ce3570715e7391da03b065","path":"libraries/chain/include/graphene/chain/witness_objects.hpp","line_range":"104-171","gmt_create":"2026-04-28T09:57:03.8382054+04:00","gmt_modified":"2026-04-28T09:57:03.8382054+04:00"},{"id":"ef4797348572b382b96b9d19a362eed7","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"90-95","gmt_create":"2026-04-28T09:57:03.8382054+04:00","gmt_modified":"2026-04-28T09:57:03.8382054+04:00"},{"id":"85e55e5f6d83a36cd6afac5fcb62fb42","path":"libraries/chain/database.cpp","line_range":"1626-1805","gmt_create":"2026-04-28T09:57:03.8392306+04:00","gmt_modified":"2026-04-28T09:57:03.8392306+04:00"},{"id":"1d8a3a8529f55f725cbd52ef78db6a1f","path":"libraries/chain/database.cpp","line_range":"4334-4463","gmt_create":"2026-04-28T09:57:03.8392306+04:00","gmt_modified":"2026-04-28T09:57:03.8392306+04:00"},{"id":"87584d47dc52c8658341b565e96c989d","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"492-499","gmt_create":"2026-04-28T09:57:03.8392306+04:00","gmt_modified":"2026-04-28T09:57:03.8392306+04:00"},{"id":"cda7dad93173bc2161ab2ff3c92e81ce","path":"libraries/time/time.cpp","line_range":"36-39","gmt_create":"2026-04-28T09:57:03.8402638+04:00","gmt_modified":"2026-04-28T09:57:03.8402638+04:00"},{"id":"73dca3cd312efbc6ecb517ae118fd869","path":"thirdparty/fc/src/network/ntp.cpp","line_range":"184-201","gmt_create":"2026-04-28T09:57:03.8402638+04:00","gmt_modified":"2026-04-28T09:57:03.8402638+04:00"},{"id":"c69f6ef1a64e88829a12d7ab4c190897","path":"thirdparty/fc/src/network/ntp.cpp","line_range":"236-266","gmt_create":"2026-04-28T09:57:03.8402638+04:00","gmt_modified":"2026-04-28T09:57:03.8402638+04:00"},{"id":"26905e829dbbc0af740b97400068843b","path":"plugins/witness/witness.cpp","line_range":"255-271","gmt_create":"2026-04-28T09:57:03.8423223+04:00","gmt_modified":"2026-04-28T09:57:03.8423223+04:00"},{"id":"1eb7cc21b9daf17a2c908f3121bcf2f0","path":"plugins/witness/witness.cpp","line_range":"387-396","gmt_create":"2026-04-28T09:57:03.8423223+04:00","gmt_modified":"2026-04-28T09:57:03.8423223+04:00"},{"id":"cc4d2fca7cfbc2ed2aa9dc9016360fb5","path":"libraries/chain/database.cpp","line_range":"2826-2836","gmt_create":"2026-04-28T09:57:03.8433619+04:00","gmt_modified":"2026-04-28T09:57:03.8433619+04:00"},{"id":"e0e1ada694da4e9e256e0d6d39aa73e5","path":"libraries/chain/database.cpp","line_range":"2873-2883","gmt_create":"2026-04-28T09:57:03.8433619+04:00","gmt_modified":"2026-04-28T09:57:03.8433619+04:00"},{"id":"27593eff1e7989c53fb119e30b38a106","path":"libraries/chain/fork_database.cpp","line_range":"81-88","gmt_create":"2026-04-28T09:57:57.433751+04:00","gmt_modified":"2026-04-28T09:57:57.433751+04:00"},{"id":"188a46b66d800240516e280b06e7f041","path":"libraries/chain/include/graphene/chain/global_property_object.hpp","line_range":"24-146","gmt_create":"2026-04-28T09:57:57.4342664+04:00","gmt_modified":"2026-04-28T09:57:57.4342664+04:00"},{"id":"02a1a9fcc78ccfc4daf328d04696eb6f","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"114-124","gmt_create":"2026-04-28T09:57:57.4347869+04:00","gmt_modified":"2026-04-28T09:57:57.4347869+04:00"},{"id":"971acfd6fe75fe0b8a1522de5c46bf48","path":"libraries/chain/include/graphene/chain/witness_objects.hpp","line_range":"47-61","gmt_create":"2026-04-28T09:57:57.4347869+04:00","gmt_modified":"2026-04-28T09:57:57.4347869+04:00"},{"id":"ad1ea51f6c6764f6694b4b89a834e8f6","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"110-128","gmt_create":"2026-04-28T09:57:57.4347869+04:00","gmt_modified":"2026-04-28T09:57:57.4347869+04:00"},{"id":"3c48450ddf4126f562a2691715c15905","path":"libraries/chain/fork_database.cpp","line_range":"80-87","gmt_create":"2026-04-28T09:57:57.4373768+04:00","gmt_modified":"2026-04-28T09:57:57.4373768+04:00"},{"id":"e2e78ec9bb315562ae4436bac3d06fb5","path":"libraries/chain/database.cpp","line_range":"1556","gmt_create":"2026-04-28T09:57:57.4390702+04:00","gmt_modified":"2026-04-28T09:57:57.4390702+04:00"},{"id":"0fda0369897c65053dee851ef9ec01f9","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"37-612","gmt_create":"2026-04-28T09:57:57.4401671+04:00","gmt_modified":"2026-04-28T09:57:57.4401671+04:00"},{"id":"2ad3ade1c893ee16ff7640c396428e46","path":"thirdparty/chainbase/include/chainbase/chainbase.hpp","line_range":"1078-1120","gmt_create":"2026-04-28T10:02:32.5941108+04:00","gmt_modified":"2026-04-28T10:02:32.5941108+04:00"},{"id":"27490494b39fa9968667b183f60e215a","path":"thirdparty/chainbase/src/chainbase.cpp","line_range":"1-200","gmt_create":"2026-04-28T10:02:32.5941108+04:00","gmt_modified":"2026-04-28T10:02:32.5941108+04:00"},{"id":"2c17018b7a7ecf1dd7b6432e97d0a586","path":"libraries/chain/include/graphene/chain/block_log.hpp","line_range":"1-75","gmt_create":"2026-04-28T10:02:32.5941108+04:00","gmt_modified":"2026-04-28T10:02:32.5941108+04:00"},{"id":"fef8201a8783aa794440fbd2f9b1ff17","path":"libraries/chain/block_log.cpp","line_range":"1-302","gmt_create":"2026-04-28T10:02:32.5941108+04:00","gmt_modified":"2026-04-28T10:02:32.5941108+04:00"},{"id":"6ebf29a038d578e8864ca6c9c9366bda","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"1-76","gmt_create":"2026-04-28T10:02:32.5941108+04:00","gmt_modified":"2026-04-28T10:02:32.5941108+04:00"},{"id":"d3bee62a496fa43717911daed4f0bb13","path":"libraries/chain/fork_database.cpp","line_range":"1-271","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"ba99a3c0ac8bffedf50f03f7f8a09c06","path":"libraries/chain/include/graphene/chain/database_exceptions.hpp","line_range":"1-136","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"0397697ee2095dedcc01fa148c7079b9","path":"libraries/chain/include/graphene/chain/db_with.hpp","line_range":"1-154","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"b2901b8a7a24569b4a61c38b4448db06","path":"plugins/snapshot/plugin.cpp","line_range":"1180-1379","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"77520f9eb913c04b5230e07f8f3f4ef8","path":"plugins/witness/witness.cpp","line_range":"270-469","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"340636e744f71de65b91146c5b8a20bd","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","line_range":"38-73","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"c05f511b950fdae3b96260134880f8e4","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"111-118","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"101205728a0401f1dc07b5e31abe4b30","path":"libraries/network/node.cpp","line_range":"3185-3384","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"832619f974a664566d9bae6712c5e6a1","path":"libraries/network/include/graphene/network/exceptions.hpp","line_range":"27-48","gmt_create":"2026-04-28T10:02:32.5950686+04:00","gmt_modified":"2026-04-28T10:02:32.5950686+04:00"},{"id":"8939bbbd6e1bb4a18e6bb534a037d0b8","path":"plugins/p2p/p2p_plugin.cpp","line_range":"225-424","gmt_create":"2026-04-28T10:02:32.5960631+04:00","gmt_modified":"2026-04-28T10:02:32.5960631+04:00"},{"id":"b437e864a36924899b70d6b9295e4ac0","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"61-115","gmt_create":"2026-04-28T10:02:32.5980608+04:00","gmt_modified":"2026-04-28T10:02:32.5980608+04:00"},{"id":"476cdc272b600cf1a7bfe2b524767872","path":"libraries/chain/database.cpp","line_range":"281-324","gmt_create":"2026-04-28T10:02:32.5980608+04:00","gmt_modified":"2026-04-28T10:02:32.5980608+04:00"},{"id":"5c73e14f98ff45ef03bb3a7c9e413218","path":"libraries/chain/include/graphene/chain/block_log.hpp","line_range":"38-75","gmt_create":"2026-04-28T10:02:32.5980608+04:00","gmt_modified":"2026-04-28T10:02:32.5980608+04:00"},{"id":"6742da18bac301be1056c8e6e5adcf69","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"35-72","gmt_create":"2026-04-28T10:02:32.5980608+04:00","gmt_modified":"2026-04-28T10:02:32.5980608+04:00"},{"id":"6d7b6e4198a348bc27c0bb28230cb3f9","path":"libraries/chain/database.cpp","line_range":"929-984","gmt_create":"2026-04-28T10:02:32.5980608+04:00","gmt_modified":"2026-04-28T10:02:32.5980608+04:00"},{"id":"186fa6ce927d55c0b153413a2981237e","path":"libraries/chain/include/graphene/chain/db_with.hpp","line_range":"33-100","gmt_create":"2026-04-28T10:02:32.5995674+04:00","gmt_modified":"2026-04-28T10:02:32.5995674+04:00"},{"id":"99a2db97d03705f31a3929010634a458","path":"thirdparty/chainbase/src/chainbase.cpp","line_range":"225-279","gmt_create":"2026-04-28T10:02:32.5995674+04:00","gmt_modified":"2026-04-28T10:02:32.5995674+04:00"},{"id":"30a9101c41868617ba6f2a2daa15b849","path":"libraries/chain/database.cpp","line_range":"94-184","gmt_create":"2026-04-28T10:02:32.6005802+04:00","gmt_modified":"2026-04-28T10:02:32.6005802+04:00"},{"id":"3e02993eaec7af3cec0bfb1f83a665d3","path":"libraries/chain/include/graphene/chain/database_exceptions.hpp","line_range":"83","gmt_create":"2026-04-28T10:02:32.6005802+04:00","gmt_modified":"2026-04-28T10:02:32.6005802+04:00"},{"id":"99f54a4b93eefbb48ed1667eff0fa9d3","path":"libraries/chain/database.cpp","line_range":"330-410","gmt_create":"2026-04-28T10:02:32.6015813+04:00","gmt_modified":"2026-04-28T10:02:32.6015813+04:00"},{"id":"408bde042901c8590a87dffdf56f7b44","path":"libraries/chain/database.cpp","line_range":"134-184","gmt_create":"2026-04-28T10:02:32.6015813+04:00","gmt_modified":"2026-04-28T10:02:32.6015813+04:00"},{"id":"e69115f35f4572b29fb65e977de046e0","path":"libraries/chain/database.cpp","line_range":"503-519","gmt_create":"2026-04-28T10:02:32.6015813+04:00","gmt_modified":"2026-04-28T10:02:32.6015813+04:00"},{"id":"1198bd7cc69669237a261463e8cd3c9d","path":"libraries/chain/database.cpp","line_range":"3986-4039","gmt_create":"2026-04-28T10:02:32.603573+04:00","gmt_modified":"2026-04-28T10:02:32.603573+04:00"},{"id":"a8eaae161939961c892a4e5ff9b1b68f","path":"libraries/chain/database.cpp","line_range":"4144-4175","gmt_create":"2026-04-28T10:02:32.603573+04:00","gmt_modified":"2026-04-28T10:02:32.603573+04:00"},{"id":"17b75751a836de13af2093485914a80b","path":"libraries/chain/database.cpp","line_range":"1147-1202","gmt_create":"2026-04-28T10:02:32.6055731+04:00","gmt_modified":"2026-04-28T10:02:32.6055731+04:00"},{"id":"3fd79dc7253b83f3fd4a1db36b691537","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"79-96","gmt_create":"2026-04-28T10:02:32.6055731+04:00","gmt_modified":"2026-04-28T10:02:32.6055731+04:00"},{"id":"fc817fa1448591c306f1d8c94a4912b0","path":"libraries/chain/database.cpp","line_range":"340-350","gmt_create":"2026-04-28T10:02:32.6055731+04:00","gmt_modified":"2026-04-28T10:02:32.6055731+04:00"},{"id":"2b22b083099e12af5fb5eff69ac7c45b","path":"libraries/chain/database.cpp","line_range":"4346-4366","gmt_create":"2026-04-28T10:02:32.6055731+04:00","gmt_modified":"2026-04-28T10:02:32.6055731+04:00"},{"id":"ad0c51e89fbda89a4c7ba4a8b1c23f6e","path":"libraries/chain/database.cpp","line_range":"948-970","gmt_create":"2026-04-28T10:02:32.6055731+04:00","gmt_modified":"2026-04-28T10:02:32.6055731+04:00"},{"id":"1bc38fc420c17ebaaf14eb62dd9dd77e","path":"libraries/chain/database.cpp","line_range":"3652-3711","gmt_create":"2026-04-28T10:02:32.6065844+04:00","gmt_modified":"2026-04-28T10:02:32.6065844+04:00"},{"id":"a0ef31a365b500c015295fa0a77cb02a","path":"libraries/chain/database.cpp","line_range":"639-673","gmt_create":"2026-04-28T10:02:32.6065844+04:00","gmt_modified":"2026-04-28T10:02:32.6065844+04:00"},{"id":"bad0cf5ac8c5e25ab7697c72386f0c6e","path":"libraries/chain/database.cpp","line_range":"562-605","gmt_create":"2026-04-28T10:02:32.6065844+04:00","gmt_modified":"2026-04-28T10:02:32.6065844+04:00"},{"id":"145b14d90766df6ec3acffdb3b52a1a7","path":"libraries/chain/database.cpp","line_range":"412-422","gmt_create":"2026-04-28T10:02:32.6075786+04:00","gmt_modified":"2026-04-28T10:02:32.6075786+04:00"},{"id":"5ae77d32160a48045d586a25c4949703","path":"libraries/chain/database.cpp","line_range":"454-482","gmt_create":"2026-04-28T10:02:32.6075786+04:00","gmt_modified":"2026-04-28T10:02:32.6075786+04:00"},{"id":"fb42540947b6b7d56487ccd9ec782f86","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"148-164","gmt_create":"2026-04-28T10:02:32.6075786+04:00","gmt_modified":"2026-04-28T10:02:32.6075786+04:00"},{"id":"4eb8ce2a423658c16ae4df90aeba2184","path":"libraries/chain/database.cpp","line_range":"546-556","gmt_create":"2026-04-28T10:02:32.6075786+04:00","gmt_modified":"2026-04-28T10:02:32.6075786+04:00"},{"id":"0172b0ea2a031177f1c72341a3922614","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"631-632","gmt_create":"2026-04-28T10:02:32.6085719+04:00","gmt_modified":"2026-04-28T10:02:32.6085719+04:00"},{"id":"e8dd15ab10626a2ce715fe8c3f03ca85","path":"libraries/chain/database.cpp","line_range":"1106-1145","gmt_create":"2026-04-28T10:02:32.6085719+04:00","gmt_modified":"2026-04-28T10:02:32.6085719+04:00"},{"id":"8509be38b1ab9f14d108445df0dcd2f1","path":"libraries/chain/database.cpp","line_range":"1460-1470","gmt_create":"2026-04-28T10:02:32.6085719+04:00","gmt_modified":"2026-04-28T10:02:32.6085719+04:00"},{"id":"732bc579d5f86ebf0e986ecfbdfa490d","path":"libraries/chain/fork_database.cpp","line_range":"34-46","gmt_create":"2026-04-28T10:02:32.6085719+04:00","gmt_modified":"2026-04-28T10:02:32.6085719+04:00"},{"id":"d1e1e1bc28ff1dfbd77617edbf4a23b0","path":"libraries/chain/database.cpp","line_range":"1295-1377","gmt_create":"2026-04-28T10:02:32.6085719+04:00","gmt_modified":"2026-04-28T10:02:32.6085719+04:00"},{"id":"42f304cea32d3254d3d28d390f99a4f4","path":"libraries/chain/database.cpp","line_range":"1216-1286","gmt_create":"2026-04-28T10:02:32.6085719+04:00","gmt_modified":"2026-04-28T10:02:32.6085719+04:00"},{"id":"21021513e3f47136fbd9b77f25ac0dda","path":"plugins/p2p/p2p_plugin.cpp","line_range":"175-192","gmt_create":"2026-04-28T10:02:32.6100752+04:00","gmt_modified":"2026-04-28T10:02:32.6100752+04:00"},{"id":"978bc8c4cf93eea58a5004412f5a0740","path":"libraries/network/node.cpp","line_range":"3192-3211","gmt_create":"2026-04-28T10:02:32.6100752+04:00","gmt_modified":"2026-04-28T10:02:32.6100752+04:00"},{"id":"9a7186666130ca998f114a82a0decdff","path":"plugins/p2p/p2p_plugin.cpp","line_range":"181-196","gmt_create":"2026-04-28T10:02:32.6100752+04:00","gmt_modified":"2026-04-28T10:02:32.6100752+04:00"},{"id":"ac1d251c502e1e4341518133a47346b6","path":"libraries/chain/database.cpp","line_range":"1556-1588","gmt_create":"2026-04-28T10:02:32.6110794+04:00","gmt_modified":"2026-04-28T10:02:32.6110794+04:00"},{"id":"03c29e794349e78edd559069a4ea589f","path":"libraries/chain/database.cpp","line_range":"1593-1594","gmt_create":"2026-04-28T10:02:32.6110794+04:00","gmt_modified":"2026-04-28T10:02:32.6110794+04:00"},{"id":"bc5ca04bd394cfbe05b91187be1ef3e1","path":"plugins/witness/witness.cpp","line_range":"271-300","gmt_create":"2026-04-28T10:02:32.6110794+04:00","gmt_modified":"2026-04-28T10:02:32.6110794+04:00"},{"id":"32c3cd87cb292dbfecb6d81dc0fc6739","path":"plugins/witness/witness.cpp","line_range":"506-507","gmt_create":"2026-04-28T10:02:32.6110794+04:00","gmt_modified":"2026-04-28T10:02:32.6110794+04:00"},{"id":"ba68b0a6c770e5eb7e9c206147e04add","path":"plugins/p2p/p2p_plugin.cpp","line_range":"232-243","gmt_create":"2026-04-28T10:02:32.6110794+04:00","gmt_modified":"2026-04-28T10:02:32.6110794+04:00"},{"id":"bf868153550f3b995f99ef4e396ddd77","path":"libraries/chain/database.cpp","line_range":"3444-3499","gmt_create":"2026-04-28T10:02:32.6130814+04:00","gmt_modified":"2026-04-28T10:02:32.6130814+04:00"},{"id":"e5c1c7808f2985bb68bc174eb390298a","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"218-224","gmt_create":"2026-04-28T10:02:32.6130814+04:00","gmt_modified":"2026-04-28T10:02:32.6130814+04:00"},{"id":"e6c849182922d3df412daeb900b9e173","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"284-307","gmt_create":"2026-04-28T10:02:32.6150792+04:00","gmt_modified":"2026-04-28T10:02:32.6150792+04:00"},{"id":"05db694a94704c3e51ed70ce0118a1da","path":"libraries/chain/database.cpp","line_range":"1158-1198","gmt_create":"2026-04-28T10:02:32.6150792+04:00","gmt_modified":"2026-04-28T10:02:32.6150792+04:00"},{"id":"e6a9a48c0e930773f0a82fa246c53f98","path":"libraries/chain/database.cpp","line_range":"3652-3655","gmt_create":"2026-04-28T10:02:32.6150792+04:00","gmt_modified":"2026-04-28T10:02:32.6150792+04:00"},{"id":"bd530cb9e845767f0a9b3ff967997984","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"93-141","gmt_create":"2026-04-28T10:02:32.6150792+04:00","gmt_modified":"2026-04-28T10:02:32.6150792+04:00"},{"id":"f10623a85ff8726287ec33204df6d61a","path":"libraries/chain/database.cpp","line_range":"458-584","gmt_create":"2026-04-28T10:02:32.6150792+04:00","gmt_modified":"2026-04-28T10:02:32.6150792+04:00"},{"id":"8f391118507fdd830a986c86e989a317","path":"libraries/chain/database.cpp","line_range":"2047-2144","gmt_create":"2026-04-28T10:02:32.6160791+04:00","gmt_modified":"2026-04-28T10:02:32.6160791+04:00"},{"id":"fa62adcb3d13201bfc9729dde67be04f","path":"libraries/chain/database.cpp","line_range":"4378-4416","gmt_create":"2026-04-28T10:02:32.6160791+04:00","gmt_modified":"2026-04-28T10:02:32.6160791+04:00"},{"id":"98ed598d82d28fa1553560148bcda24e","path":"libraries/chain/database.cpp","line_range":"2125-2142","gmt_create":"2026-04-28T10:02:32.6160791+04:00","gmt_modified":"2026-04-28T10:02:32.6160791+04:00"},{"id":"5a5c235262f9722ff7bdf0586bfd8e26","path":"libraries/chain/database.cpp","line_range":"4220-4230","gmt_create":"2026-04-28T10:02:32.6297326+04:00","gmt_modified":"2026-04-28T10:02:32.6297326+04:00"},{"id":"39b6820362e87b0f937201fb8f54dee6","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"1-10","gmt_create":"2026-04-28T10:02:32.6313259+04:00","gmt_modified":"2026-04-28T10:02:32.6313259+04:00"},{"id":"1efc9fdbd77068cf27cb00354deff17a","path":"libraries/chain/database.cpp","line_range":"1-30","gmt_create":"2026-04-28T10:02:32.631839+04:00","gmt_modified":"2026-04-28T10:02:32.631839+04:00"},{"id":"d0e2616c3e70f0e809256dcb448916ab","path":"libraries/chain/database.cpp","line_range":"270-279","gmt_create":"2026-04-28T10:02:32.6329165+04:00","gmt_modified":"2026-04-28T10:02:32.6329165+04:00"},{"id":"0855c233a79de418e4577d44bd54b9ba","path":"libraries/chain/database.cpp","line_range":"492-501","gmt_create":"2026-04-28T10:02:32.6329165+04:00","gmt_modified":"2026-04-28T10:02:32.6329165+04:00"},{"id":"f2c0625ed2d13bb0a3699a45bf25d6cc","path":"plugins/snapshot/plugin.cpp","line_range":"1-50","gmt_create":"2026-04-28T12:27:40.7724634+04:00","gmt_modified":"2026-04-28T12:27:40.7724634+04:00"},{"id":"b49193d53f2bca5e5a84c905c1f90429","path":"plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp","line_range":"1-88","gmt_create":"2026-04-28T12:27:40.7724634+04:00","gmt_modified":"2026-04-28T12:27:40.7724634+04:00"},{"id":"ca74187bf1151ee3b389754df4ce08d1","path":"plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp","line_range":"1-52","gmt_create":"2026-04-28T12:27:40.7724634+04:00","gmt_modified":"2026-04-28T12:27:40.7724634+04:00"},{"id":"60ec378a66a7653324d69f456e1242c4","path":"plugins/snapshot/CMakeLists.txt","line_range":"1-52","gmt_create":"2026-04-28T12:27:40.7730421+04:00","gmt_modified":"2026-04-28T12:27:40.7730421+04:00"},{"id":"93f965590673de9c5fe2a34fbc24af1c","path":"plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp","line_range":"42-76","gmt_create":"2026-04-28T12:27:40.7730421+04:00","gmt_modified":"2026-04-28T12:27:40.7730421+04:00"},{"id":"98907d07c594fe14c157ba203649b596","path":"plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp","line_range":"16-52","gmt_create":"2026-04-28T12:27:40.7735457+04:00","gmt_modified":"2026-04-28T12:27:40.7735457+04:00"},{"id":"aba83bcb80fe7ecb8f1f224f2fca05da","path":"plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp","line_range":"30-158","gmt_create":"2026-04-28T12:27:40.7735457+04:00","gmt_modified":"2026-04-28T12:27:40.7735457+04:00"},{"id":"33fc7efcd171685ef3235423dc636724","path":"plugins/snapshot/plugin.cpp","line_range":"675-780","gmt_create":"2026-04-28T12:27:40.7735457+04:00","gmt_modified":"2026-04-28T12:27:40.7735457+04:00"},{"id":"0a6e409382fdf7cc6924a9f918b5a4d8","path":"plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp","line_range":"37-107","gmt_create":"2026-04-28T12:27:40.7735457+04:00","gmt_modified":"2026-04-28T12:27:40.7735457+04:00"},{"id":"d419300bbe8000129fd9765417390c9d","path":"plugins/snapshot/plugin.cpp","line_range":"885-987","gmt_create":"2026-04-28T12:27:40.7740619+04:00","gmt_modified":"2026-04-28T12:27:40.7740619+04:00"},{"id":"c331a645eb7848bda9b3f651866478ec","path":"plugins/snapshot/plugin.cpp","line_range":"789-883","gmt_create":"2026-04-28T12:27:40.7740619+04:00","gmt_modified":"2026-04-28T12:27:40.7740619+04:00"},{"id":"06ca849a27b79d1edd6a6faaabf7a1fa","path":"plugins/snapshot/plugin.cpp","line_range":"1400-1484","gmt_create":"2026-04-28T12:27:40.7740619+04:00","gmt_modified":"2026-04-28T12:27:40.7740619+04:00"},{"id":"9102ca0b920a6d527e069776bf786565","path":"plugins/snapshot/plugin.cpp","line_range":"1046-1288","gmt_create":"2026-04-28T12:27:40.7745804+04:00","gmt_modified":"2026-04-28T12:27:40.7745804+04:00"},{"id":"272ad74758e94f746c862c4c91c17496","path":"plugins/snapshot/plugin.cpp","line_range":"1902-2038","gmt_create":"2026-04-28T12:27:40.7746421+04:00","gmt_modified":"2026-04-28T12:27:40.7746421+04:00"},{"id":"ea6973621046033cdcd6899b7a2bcb0f","path":"plugins/snapshot/plugin.cpp","line_range":"1470-1599","gmt_create":"2026-04-28T12:27:40.7746421+04:00","gmt_modified":"2026-04-28T12:27:40.7746421+04:00"},{"id":"2f7386ded19c5e4ae6581ef6148fdd81","path":"plugins/snapshot/plugin.cpp","line_range":"2473-2510","gmt_create":"2026-04-28T12:27:40.7746421+04:00","gmt_modified":"2026-04-28T12:27:40.7746421+04:00"},{"id":"5fcc5bdcf25fd73b110eede565277186","path":"documentation/snapshot-plugin.md","line_range":"247-273","gmt_create":"2026-04-28T12:27:40.7746421+04:00","gmt_modified":"2026-04-28T12:27:40.7746421+04:00"},{"id":"7c62e5936f0fc7f60f9a5587800a2578","path":"plugins/snapshot/plugin.cpp","line_range":"1418-1436","gmt_create":"2026-04-28T12:27:40.7752108+04:00","gmt_modified":"2026-04-28T12:27:40.7752108+04:00"},{"id":"983d0185105181e4f539d636abc549c9","path":"plugins/snapshot/plugin.cpp","line_range":"737-743","gmt_create":"2026-04-28T12:27:40.7757149+04:00","gmt_modified":"2026-04-28T12:27:40.7757149+04:00"},{"id":"82acc28cade2d06dac2a207816ccf888","path":"plugins/snapshot/plugin.cpp","line_range":"1390-1484","gmt_create":"2026-04-28T12:27:40.7757149+04:00","gmt_modified":"2026-04-28T12:27:40.7757149+04:00"},{"id":"1d3d2cb1a9886c18b5c14d6f4bbbec7c","path":"plugins/snapshot/plugin.cpp","line_range":"1440-1449","gmt_create":"2026-04-28T12:27:40.7757149+04:00","gmt_modified":"2026-04-28T12:27:40.7757149+04:00"},{"id":"de42aaaef014331a40dc6a645093a785","path":"plugins/witness/witness.cpp","line_range":"335-551","gmt_create":"2026-04-28T12:27:40.7757149+04:00","gmt_modified":"2026-04-28T12:27:40.7757149+04:00"},{"id":"e9427d96ecb45f8e18859dbaff6e2b3a","path":"plugins/snapshot/plugin.cpp","line_range":"1326-1376","gmt_create":"2026-04-28T12:27:40.7757149+04:00","gmt_modified":"2026-04-28T12:27:40.7757149+04:00"},{"id":"ddc5b6727e7907a53519cc63983aabb7","path":"plugins/snapshot/plugin.cpp","line_range":"1426-1435","gmt_create":"2026-04-28T12:27:40.7762434+04:00","gmt_modified":"2026-04-28T12:27:40.7762434+04:00"},{"id":"a54238157d7db2d12e89ea767738f5ea","path":"plugins/snapshot/plugin.cpp","line_range":"745-750","gmt_create":"2026-04-28T12:27:40.776339+04:00","gmt_modified":"2026-04-28T12:27:40.776339+04:00"},{"id":"8481665c6e787cad6255e9ace82fe2c2","path":"plugins/snapshot/plugin.cpp","line_range":"697-700","gmt_create":"2026-04-28T12:27:40.776339+04:00","gmt_modified":"2026-04-28T12:27:40.776339+04:00"},{"id":"91b6373025370a39784ff61586453267","path":"plugins/snapshot/plugin.cpp","line_range":"2831-2845","gmt_create":"2026-04-28T12:27:40.776339+04:00","gmt_modified":"2026-04-28T12:27:40.776339+04:00"},{"id":"fa6088c57e5552b03a53d22a8b3371cd","path":"plugins/snapshot/plugin.cpp","line_range":"1719-1748","gmt_create":"2026-04-28T12:27:40.776339+04:00","gmt_modified":"2026-04-28T12:27:40.776339+04:00"},{"id":"2ef1c85090d0812eda040eb10dbfab00","path":"plugins/snapshot/plugin.cpp","line_range":"1706-1748","gmt_create":"2026-04-28T12:27:40.776339+04:00","gmt_modified":"2026-04-28T12:27:40.776339+04:00"},{"id":"6c4fffc1a4b8a3dccdccf3bc8b6e478c","path":"plugins/chain/plugin.cpp","line_range":"490-560","gmt_create":"2026-04-28T12:27:40.7768431+04:00","gmt_modified":"2026-04-28T12:27:40.7768431+04:00"},{"id":"002084afc1181b400d0ac1f626010b50","path":"plugins/snapshot/plugin.cpp","line_range":"2945-2959","gmt_create":"2026-04-28T12:27:40.7769365+04:00","gmt_modified":"2026-04-28T12:27:40.7769365+04:00"},{"id":"a9ac85c210bf25017dc2f3043429bcff","path":"libraries/chain/database.cpp","line_range":"441-5201","gmt_create":"2026-04-28T12:27:40.7769365+04:00","gmt_modified":"2026-04-28T12:27:40.7769365+04:00"},{"id":"4083d0aedccff773e235c49f483acdee","path":"plugins/chain/plugin.cpp","line_range":"542-559","gmt_create":"2026-04-28T12:27:40.7769365+04:00","gmt_modified":"2026-04-28T12:27:40.7769365+04:00"},{"id":"5b61f5ff88b75190fc4b2051750444dd","path":"plugins/snapshot/plugin.cpp","line_range":"2976-3009","gmt_create":"2026-04-28T12:27:40.7774392+04:00","gmt_modified":"2026-04-28T12:27:40.7774392+04:00"},{"id":"133becde6f5eef3f56c38aa3650af39f","path":"plugins/snapshot/plugin.cpp","line_range":"2468-2570","gmt_create":"2026-04-28T12:27:40.7774392+04:00","gmt_modified":"2026-04-28T12:27:40.7774392+04:00"},{"id":"7c3bbfa78db4dbdd170795e4ff786767","path":"plugins/p2p/p2p_plugin.cpp","line_range":"689-697","gmt_create":"2026-04-28T12:27:40.7774392+04:00","gmt_modified":"2026-04-28T12:27:40.7774392+04:00"},{"id":"de95ec3c9606ef77f32c319a1cee9d67","path":"libraries/network/node.cpp","line_range":"5241-5274","gmt_create":"2026-04-28T12:27:40.7774392+04:00","gmt_modified":"2026-04-28T12:27:40.7774392+04:00"},{"id":"4ad076dd99ea06a22ba2703dfdfbef50","path":"libraries/network/include/graphene/network/node.hpp","line_range":"284-290","gmt_create":"2026-04-28T12:27:40.7774392+04:00","gmt_modified":"2026-04-28T12:27:40.7774392+04:00"},{"id":"d21d0e6c03944b9bd0683917197d45ec","path":"plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp","line_range":"86-88","gmt_create":"2026-04-28T12:27:40.7779679+04:00","gmt_modified":"2026-04-28T12:27:40.7779679+04:00"},{"id":"ce8c1f15287ef92650480373eef95fb8","path":"plugins/snapshot/plugin.cpp","line_range":"735-740","gmt_create":"2026-04-28T12:27:40.7779679+04:00","gmt_modified":"2026-04-28T12:27:40.7779679+04:00"},{"id":"474f1f81f21526cb90fc2564fdc36457","path":"plugins/snapshot/plugin.cpp","line_range":"1814-1862","gmt_create":"2026-04-28T12:27:40.7779679+04:00","gmt_modified":"2026-04-28T12:27:40.7779679+04:00"},{"id":"3fcde09e22fa6291183075924ea0ea2a","path":"plugins/snapshot/plugin.cpp","line_range":"772-785","gmt_create":"2026-04-28T12:27:40.7779679+04:00","gmt_modified":"2026-04-28T12:27:40.7779679+04:00"},{"id":"25271321cea3b037da1bd49f4a9c73e3","path":"documentation/snapshot-plugin.md","line_range":"339-374","gmt_create":"2026-04-28T12:27:40.7784837+04:00","gmt_modified":"2026-04-28T12:27:40.7784837+04:00"},{"id":"ffac59891823846e038c48de0f7fa754","path":"plugins/p2p/p2p_plugin.cpp","line_range":"585-649","gmt_create":"2026-04-28T12:27:40.7784837+04:00","gmt_modified":"2026-04-28T12:27:40.7784837+04:00"},{"id":"ce8fa82841cb9ff1bb572dde21cd2dca","path":"plugins/p2p/p2p_plugin.cpp","line_range":"673-677","gmt_create":"2026-04-28T12:27:40.7784837+04:00","gmt_modified":"2026-04-28T12:27:40.7784837+04:00"},{"id":"31a0e1cdb8f61a661ea048c18a0c31ab","path":"plugins/p2p/p2p_plugin.cpp","line_range":"744-755","gmt_create":"2026-04-28T12:27:40.7784837+04:00","gmt_modified":"2026-04-28T12:27:40.7784837+04:00"},{"id":"e360b37afb60df02b8a51d89f141bf95","path":"plugins/snapshot/plugin.cpp","line_range":"165-176","gmt_create":"2026-04-28T12:27:40.7784837+04:00","gmt_modified":"2026-04-28T12:27:40.7784837+04:00"},{"id":"a4b87380168ea56eda2691254c138879","path":"plugins/snapshot/plugin.cpp","line_range":"1587-1596","gmt_create":"2026-04-28T12:27:40.7784837+04:00","gmt_modified":"2026-04-28T12:27:40.7784837+04:00"},{"id":"0442d9dfa5137479a458da6803ce6d7d","path":"plugins/snapshot/plugin.cpp","line_range":"1610-1620","gmt_create":"2026-04-28T12:27:40.7790074+04:00","gmt_modified":"2026-04-28T12:27:40.7790074+04:00"},{"id":"5f1666b71febd2ce3545e7b9c05598fa","path":"plugins/snapshot/plugin.cpp","line_range":"1812-1877","gmt_create":"2026-04-28T12:27:40.7790074+04:00","gmt_modified":"2026-04-28T12:27:40.7790074+04:00"},{"id":"f7cc310f8ee11ed3c2ac13ce575146d4","path":"plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp","line_range":"24-34","gmt_create":"2026-04-28T12:27:40.7790074+04:00","gmt_modified":"2026-04-28T12:27:40.7790074+04:00"},{"id":"24d1f458412bbfade9d25c0ed1521322","path":"plugins/snapshot/plugin.cpp","line_range":"2598-2680","gmt_create":"2026-04-28T12:27:40.7795307+04:00","gmt_modified":"2026-04-28T12:27:40.7795307+04:00"},{"id":"985521912575155bfda8b7bce74494df","path":"plugins/chain/plugin.cpp","line_range":"364-432","gmt_create":"2026-04-28T12:27:40.7795307+04:00","gmt_modified":"2026-04-28T12:27:40.7795307+04:00"},{"id":"299aedc88851dabd155fc2edbbad96ef","path":"plugins/snapshot/CMakeLists.txt","line_range":"27-38","gmt_create":"2026-04-28T12:27:40.7795307+04:00","gmt_modified":"2026-04-28T12:27:40.7795307+04:00"},{"id":"2a76e2c48df7946c93b84c330a1c08ae","path":"plugins/snapshot/plugin.cpp","line_range":"2294-2464","gmt_create":"2026-04-28T12:27:40.7795307+04:00","gmt_modified":"2026-04-28T12:27:40.7795307+04:00"},{"id":"d7fc3783ff3c58fa7beaff10948417fa","path":"plugins/snapshot/plugin.cpp","line_range":"1378-1464","gmt_create":"2026-04-28T12:27:40.7800446+04:00","gmt_modified":"2026-04-28T12:27:40.7800446+04:00"},{"id":"0acaf479cbd8b11ed2774cc96aa68335","path":"libraries/protocol/include/graphene/protocol/block_header.hpp","line_range":"1-43","gmt_create":"2026-04-28T12:54:08.069392+04:00","gmt_modified":"2026-04-28T12:54:08.069392+04:00"},{"id":"65b16a5b2283f9a71e063b5381577bae","path":"libraries/protocol/include/graphene/protocol/block.hpp","line_range":"1-19","gmt_create":"2026-04-28T12:54:08.0699133+04:00","gmt_modified":"2026-04-28T12:54:08.0699133+04:00"},{"id":"54d3aec8b80d5706beb75720a6232861","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"1-561","gmt_create":"2026-04-28T12:54:08.0699133+04:00","gmt_modified":"2026-04-28T12:54:08.0699133+04:00"},{"id":"0b148db227f6081cc819c2f686550d84","path":"libraries/chain/database.cpp","line_range":"737-913","gmt_create":"2026-04-28T12:54:08.0699133+04:00","gmt_modified":"2026-04-28T12:54:08.0699133+04:00"},{"id":"3409eb023f550b5d11bccc50d317764a","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"1-125","gmt_create":"2026-04-28T12:54:08.0699133+04:00","gmt_modified":"2026-04-28T12:54:08.0699133+04:00"},{"id":"e29d6a6310d6247eaf38fa2c35b35373","path":"libraries/chain/fork_database.cpp","line_range":"33-90","gmt_create":"2026-04-28T12:54:08.0699133+04:00","gmt_modified":"2026-04-28T12:54:08.0699133+04:00"},{"id":"faea01a77c78aa0e75d22b0d5cafa704","path":"libraries/chain/block_log.cpp","line_range":"238-300","gmt_create":"2026-04-28T12:54:08.070423+04:00","gmt_modified":"2026-04-28T12:54:08.070423+04:00"},{"id":"60cef3a5a468a40533451ed7d7e09503","path":"libraries/chain/dlt_block_log.cpp","line_range":"162-242","gmt_create":"2026-04-28T12:54:08.070423+04:00","gmt_modified":"2026-04-28T12:54:08.070423+04:00"},{"id":"10f717e60b716201555827209e101cba","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","line_range":"1-70","gmt_create":"2026-04-28T12:54:08.0709497+04:00","gmt_modified":"2026-04-28T12:54:08.0709497+04:00"},{"id":"04c7b01cbc51b1f23ab4704279190eda","path":"plugins/witness/witness.cpp","line_range":"295-341","gmt_create":"2026-04-28T12:54:08.0709497+04:00","gmt_modified":"2026-04-28T12:54:08.0709497+04:00"},{"id":"11090a858982a9991f12a111ff65ebec","path":"libraries/protocol/include/graphene/protocol/block.hpp","line_range":"9-13","gmt_create":"2026-04-28T12:54:08.0719836+04:00","gmt_modified":"2026-04-28T12:54:08.0719836+04:00"},{"id":"91b8b7279712d5789fb67e0c06442732","path":"libraries/protocol/include/graphene/protocol/block_header.hpp","line_range":"25-35","gmt_create":"2026-04-28T12:54:08.0719836+04:00","gmt_modified":"2026-04-28T12:54:08.0719836+04:00"},{"id":"5800e8f7ef6353fa3f37c39ea75ff454","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"53-96","gmt_create":"2026-04-28T12:54:08.0719836+04:00","gmt_modified":"2026-04-28T12:54:08.0719836+04:00"},{"id":"6da448acd4e86b4234427febab48fbca","path":"libraries/chain/include/graphene/chain/block_log.hpp","line_range":"38-68","gmt_create":"2026-04-28T12:54:08.0719836+04:00","gmt_modified":"2026-04-28T12:54:08.0719836+04:00"},{"id":"35d8d0314587b8714a154437fe7243af","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"13-33","gmt_create":"2026-04-28T12:54:08.0725034+04:00","gmt_modified":"2026-04-28T12:54:08.0725034+04:00"},{"id":"827fa8459b891ae8c31d3194640245da","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"36-287","gmt_create":"2026-04-28T12:54:08.0725034+04:00","gmt_modified":"2026-04-28T12:54:08.0725034+04:00"},{"id":"7d0d371be208eb98ecc21b182f56ab1b","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","line_range":"20-32","gmt_create":"2026-04-28T12:54:08.0725034+04:00","gmt_modified":"2026-04-28T12:54:08.0725034+04:00"},{"id":"0b84b82025c87f3b725ddf0c3804f197","path":"libraries/chain/block_log.cpp","line_range":"253-257","gmt_create":"2026-04-28T12:54:08.0730148+04:00","gmt_modified":"2026-04-28T12:54:08.0730148+04:00"},{"id":"01b277ebba52e972b94566a19ab057fd","path":"libraries/chain/dlt_block_log.cpp","line_range":"336-340","gmt_create":"2026-04-28T12:54:08.0730148+04:00","gmt_modified":"2026-04-28T12:54:08.0730148+04:00"},{"id":"161ddc3f0d369da41719390d93b735c9","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"194-206","gmt_create":"2026-04-28T12:54:08.0735948+04:00","gmt_modified":"2026-04-28T12:54:08.0735948+04:00"},{"id":"d4957d3a60b33265b5b19edffd627403","path":"libraries/chain/database.cpp","line_range":"737-757","gmt_create":"2026-04-28T12:54:08.0735948+04:00","gmt_modified":"2026-04-28T12:54:08.0735948+04:00"},{"id":"9025ea64db0b2dfc92ca8bab0394cc08","path":"libraries/chain/database.cpp","line_range":"3443-3509","gmt_create":"2026-04-28T12:54:08.0735948+04:00","gmt_modified":"2026-04-28T12:54:08.0735948+04:00"},{"id":"fa4bc24d813810d4fe658d78919900b7","path":"libraries/chain/database.cpp","line_range":"812-825","gmt_create":"2026-04-28T12:54:08.0735948+04:00","gmt_modified":"2026-04-28T12:54:08.0735948+04:00"},{"id":"423e0e34f575bf3a7563c57bb997739b","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"56-73","gmt_create":"2026-04-28T12:54:08.0735948+04:00","gmt_modified":"2026-04-28T12:54:08.0735948+04:00"},{"id":"c7518bf7e6927e1b54afebcbb8cf3d5a","path":"libraries/chain/fork_database.cpp","line_range":"168-210","gmt_create":"2026-04-28T12:54:08.0751505+04:00","gmt_modified":"2026-04-28T12:54:08.0751505+04:00"},{"id":"f8faa71211c346e22bdea2dbbd1cc994","path":"libraries/chain/block_log.cpp","line_range":"134-193","gmt_create":"2026-04-28T12:54:08.0751505+04:00","gmt_modified":"2026-04-28T12:54:08.0751505+04:00"},{"id":"fd6ccb853bf752497d776a99eba9c7e2","path":"libraries/chain/block_log.cpp","line_range":"115-132","gmt_create":"2026-04-28T12:54:08.0751505+04:00","gmt_modified":"2026-04-28T12:54:08.0751505+04:00"},{"id":"6d71835687e91bd31fc0bba06adc43a1","path":"libraries/chain/block_log.cpp","line_range":"195-219","gmt_create":"2026-04-28T12:54:08.0751505+04:00","gmt_modified":"2026-04-28T12:54:08.0751505+04:00"},{"id":"21313db2130a5ad3661163e46e6eb28d","path":"libraries/chain/include/graphene/chain/block_log.hpp","line_range":"13-36","gmt_create":"2026-04-28T12:54:08.0756778+04:00","gmt_modified":"2026-04-28T12:54:08.0756778+04:00"},{"id":"1cd86ca915ed082dac6b3930d1822c8d","path":"libraries/chain/database.cpp","line_range":"846-913","gmt_create":"2026-04-28T12:54:08.0761996+04:00","gmt_modified":"2026-04-28T12:54:08.0761996+04:00"},{"id":"180a1810a21b0ec9cfa9bf2239b68849","path":"libraries/chain/fork_database.cpp","line_range":"47-71","gmt_create":"2026-04-28T12:54:08.0767246+04:00","gmt_modified":"2026-04-28T12:54:08.0767246+04:00"},{"id":"3e995e6e2441ab172c06a81ab175d467","path":"libraries/chain/fork_database.cpp","line_range":"79-90","gmt_create":"2026-04-28T12:54:08.0767246+04:00","gmt_modified":"2026-04-28T12:54:08.0767246+04:00"},{"id":"742afd64fbe28a0b4b264ad2c80c3f13","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","line_range":"34-65","gmt_create":"2026-04-28T12:54:08.0767246+04:00","gmt_modified":"2026-04-28T12:54:08.0767246+04:00"},{"id":"8982624b20d4012f524d7608d0e898df","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"214-226","gmt_create":"2026-04-28T12:54:08.0772386+04:00","gmt_modified":"2026-04-28T12:54:08.0772386+04:00"},{"id":"ed8569143dcfa15ac6f3519b0aa6b31b","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"423-428","gmt_create":"2026-04-28T12:54:08.0777619+04:00","gmt_modified":"2026-04-28T12:54:08.0777619+04:00"},{"id":"dea1056d264deb0399cea4b15ce31eff","path":"libraries/chain/fork_database.cpp","line_range":"38-44","gmt_create":"2026-04-28T12:54:08.0798427+04:00","gmt_modified":"2026-04-28T12:54:08.0798427+04:00"},{"id":"9050957c1241bdff17fa2cda2294cc02","path":"libraries/chain/include/graphene/chain/database_exceptions.hpp","line_range":"83-83","gmt_create":"2026-04-28T12:54:08.0798427+04:00","gmt_modified":"2026-04-28T12:54:08.0798427+04:00"},{"id":"69496892894b525caf293d463cec60ac","path":"libraries/chain/block_log.cpp","line_range":"163-193","gmt_create":"2026-04-28T12:54:08.0798427+04:00","gmt_modified":"2026-04-28T12:54:08.0798427+04:00"},{"id":"7a0064bf94d855137c23263ebefc4086","path":"plugins/witness/witness.cpp","line_range":"305-307","gmt_create":"2026-04-28T12:54:08.0798427+04:00","gmt_modified":"2026-04-28T12:54:08.0798427+04:00"},{"id":"4f05f855970de061eacdd41742b1c8f6","path":"libraries/chain/database.cpp","line_range":"5395-5419","gmt_create":"2026-04-28T12:54:08.0803549+04:00","gmt_modified":"2026-04-28T12:54:08.0803549+04:00"},{"id":"620825701e1a1b114e822bbda1ceb234","path":"libraries/chain/dlt_block_log.cpp","line_range":"1-454","gmt_create":"2026-04-28T12:55:56.0777355+04:00","gmt_modified":"2026-04-28T12:55:56.0777355+04:00"},{"id":"233a2d8be5340ec151c42900fcd58a96","path":"libraries/chain/database.cpp","line_range":"220-271","gmt_create":"2026-04-28T12:55:56.0782548+04:00","gmt_modified":"2026-04-28T12:55:56.0782548+04:00"},{"id":"02b88f06fab1f8f2b384ec38dfea95a8","path":"libraries/chain/fork_database.cpp","line_range":"1-258","gmt_create":"2026-04-28T12:55:56.0787776+04:00","gmt_modified":"2026-04-28T12:55:56.0787776+04:00"},{"id":"c6c440b24137364c19bc7fe7a800ce3e","path":"plugins/chain/plugin.cpp","line_range":"320-330","gmt_create":"2026-04-28T12:55:56.0787776+04:00","gmt_modified":"2026-04-28T12:55:56.0787776+04:00"},{"id":"7c7114ff694eca47458deb9031af3874","path":"plugins/snapshot/plugin.cpp","line_range":"1960-2039","gmt_create":"2026-04-28T12:55:56.0792994+04:00","gmt_modified":"2026-04-28T12:55:56.0792994+04:00"},{"id":"5c9a153931730742e72d3183f4f76128","path":"plugins/p2p/p2p_plugin.cpp","line_range":"255-286","gmt_create":"2026-04-28T12:55:56.0792994+04:00","gmt_modified":"2026-04-28T12:55:56.0792994+04:00"},{"id":"77740b8f659f6e4296624292e035f0b6","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"515-516","gmt_create":"2026-04-28T12:55:56.0792994+04:00","gmt_modified":"2026-04-28T12:55:56.0792994+04:00"},{"id":"44c647e6bdaeba8176c9fd58d7bf204f","path":"libraries/chain/dlt_block_log.cpp","line_range":"18-278","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"1fec0c281b15b8cb253758a81bda9d53","path":"libraries/chain/database.cpp","line_range":"230-231","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"20be4db8cc88d28b2576cc8c6640d89d","path":"libraries/chain/fork_database.cpp","line_range":"24-28","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"4aaf8248e16bc831caf0d6215227a347","path":"plugins/chain/plugin.cpp","line_range":"327-329","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"2496b05edb9e2cce32ff03d614866f80","path":"plugins/snapshot/plugin.cpp","line_range":"1968-1970","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"bd936d7f114a4d7505d38477591a432b","path":"plugins/p2p/p2p_plugin.cpp","line_range":"265-272","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"09e49fa0e3657a309d205da532bae0d4","path":"plugins/snapshot/plugin.cpp","line_range":"1414-1500","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"89fd5aa6e93582e3684130e9c0920e33","path":"libraries/chain/database.cpp","line_range":"438-544","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"1e55d2aee2f6d3f5d6247eb14ca9350a","path":"libraries/chain/database.cpp","line_range":"230-268","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"05c7b54032266aa804fecdd672849759","path":"libraries/chain/database.cpp","line_range":"560-627","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"d00cc75cac98bab547f7549076e3504d","path":"libraries/chain/block_log.cpp","line_range":"238-241","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"3f58d42a32421c25e81f24320b1d1f20","path":"libraries/chain/dlt_block_log.cpp","line_range":"313-328","gmt_create":"2026-04-28T12:55:56.081347+04:00","gmt_modified":"2026-04-28T12:55:56.081347+04:00"},{"id":"7a9e26fca49ab5e068040d1db9fed41f","path":"plugins/p2p/p2p_plugin.cpp","line_range":"259-286","gmt_create":"2026-04-28T12:55:56.0829766+04:00","gmt_modified":"2026-04-28T12:55:56.0829766+04:00"},{"id":"d059ab60b812d6e824e8fef796efa497","path":"libraries/chain/dlt_block_log.cpp","line_range":"161-209","gmt_create":"2026-04-28T12:55:56.0829766+04:00","gmt_modified":"2026-04-28T12:55:56.0829766+04:00"},{"id":"343b12b915179b0ceab0474a3260def1","path":"libraries/chain/dlt_block_log.cpp","line_range":"125-159","gmt_create":"2026-04-28T12:55:56.0829766+04:00","gmt_modified":"2026-04-28T12:55:56.0829766+04:00"},{"id":"edc66ae920408eb6412dad9e65c69f63","path":"libraries/chain/dlt_block_log.cpp","line_range":"211-268","gmt_create":"2026-04-28T12:55:56.0829766+04:00","gmt_modified":"2026-04-28T12:55:56.0829766+04:00"},{"id":"c87f750598b8e3aeb391ebd49f3c8730","path":"libraries/chain/dlt_block_log.cpp","line_range":"356-411","gmt_create":"2026-04-28T12:55:56.0839825+04:00","gmt_modified":"2026-04-28T12:55:56.0839825+04:00"},{"id":"85e42052e68826778bf6fc0a5c348061","path":"libraries/chain/database.cpp","line_range":"266-292","gmt_create":"2026-04-28T12:55:56.0839825+04:00","gmt_modified":"2026-04-28T12:55:56.0839825+04:00"},{"id":"44fcac467d4197ec65011a88dcc9b983","path":"plugins/snapshot/plugin.cpp","line_range":"942-1054","gmt_create":"2026-04-28T12:55:56.0849828+04:00","gmt_modified":"2026-04-28T12:55:56.0849828+04:00"},{"id":"d866d0710f48a708f384cfe833d19819","path":"plugins/snapshot/plugin.cpp","line_range":"2790-2791","gmt_create":"2026-04-28T12:55:56.0849828+04:00","gmt_modified":"2026-04-28T12:55:56.0849828+04:00"},{"id":"cc2057fd8aeaa6b63ca70e0d4192d8f6","path":"plugins/chain/plugin.cpp","line_range":"542-555","gmt_create":"2026-04-28T12:55:56.0849828+04:00","gmt_modified":"2026-04-28T12:55:56.0849828+04:00"},{"id":"5985a883b7cd6e373fa45e06af549458","path":"libraries/chain/dlt_block_log.cpp","line_range":"172-202","gmt_create":"2026-04-28T12:55:56.0859831+04:00","gmt_modified":"2026-04-28T12:55:56.0859831+04:00"},{"id":"27ad3a44022480b087c9dee522cf8f53","path":"libraries/chain/dlt_block_log.cpp","line_range":"432-444","gmt_create":"2026-04-28T12:55:56.0859831+04:00","gmt_modified":"2026-04-28T12:55:56.0859831+04:00"},{"id":"5df133f8f7e5465f2256280233f7492a","path":"libraries/chain/database.cpp","line_range":"4005-4036","gmt_create":"2026-04-28T12:55:56.0864871+04:00","gmt_modified":"2026-04-28T12:55:56.0864871+04:00"},{"id":"7ed16e639125b23aeb82bbb3768334fb","path":"libraries/chain/database.cpp","line_range":"4170-4172","gmt_create":"2026-04-28T12:55:56.0864871+04:00","gmt_modified":"2026-04-28T12:55:56.0864871+04:00"},{"id":"ed64be85dbceaa49c4d38b1fd7b0537f","path":"libraries/chain/database.cpp","line_range":"4392-4394","gmt_create":"2026-04-28T12:55:56.0864871+04:00","gmt_modified":"2026-04-28T12:55:56.0864871+04:00"},{"id":"770f933f07c1261bce810845c6711ec4","path":"libraries/chain/database.cpp","line_range":"4043-4047","gmt_create":"2026-04-28T12:55:56.0864871+04:00","gmt_modified":"2026-04-28T12:55:56.0864871+04:00"},{"id":"5988d2bd7b0d5bec2b97a885d1928110","path":"libraries/chain/database.cpp","line_range":"4189-4192","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"a9078dfa0e86bfbf3c1bef4a473eb117","path":"libraries/chain/database.cpp","line_range":"4419-4421","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"4af3002393266cb7f00332d69f2019ca","path":"plugins/chain/plugin.cpp","line_range":"233-236","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"36faa15d1b9669c10ef981b57dd230bc","path":"plugins/chain/plugin.cpp","line_range":"326-329","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"69112cf16468224f02ecd1fbd49ea64f","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"1-10","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"6456782ce4d45cbdc58f90d5677ca2c2","path":"libraries/chain/dlt_block_log.cpp","line_range":"1-7","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"fce2bc849f6a01aeb1da861120260037","path":"libraries/chain/block_log.cpp","line_range":"1-6","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"ab61b06105c0618ce476f62fc42d04c5","path":"libraries/chain/database.cpp","line_range":"1-10","gmt_create":"2026-04-28T12:55:56.0874924+04:00","gmt_modified":"2026-04-28T12:55:56.0874924+04:00"},{"id":"cf34d537814cc823a0786b3fa43ffca0","path":"libraries/chain/fork_database.cpp","line_range":"1-6","gmt_create":"2026-04-28T12:55:56.0884917+04:00","gmt_modified":"2026-04-28T12:55:56.0884917+04:00"},{"id":"7bd018a7b3dab28cc6792e89ffa39efd","path":"plugins/chain/plugin.cpp","line_range":"1-10","gmt_create":"2026-04-28T12:55:56.0884917+04:00","gmt_modified":"2026-04-28T12:55:56.0884917+04:00"},{"id":"96e5aca4a37cf627dff138e440efffa2","path":"plugins/p2p/p2p_plugin.cpp","line_range":"1-10","gmt_create":"2026-04-28T12:55:56.0884917+04:00","gmt_modified":"2026-04-28T12:55:56.0884917+04:00"},{"id":"8e436d067efeeee9365369d51dc3fd02","path":"libraries/chain/database.cpp","line_range":"250-271","gmt_create":"2026-04-28T12:55:56.0894921+04:00","gmt_modified":"2026-04-28T12:55:56.0894921+04:00"},{"id":"2fb2b5eefe1dda70f9332bca41575f3d","path":"libraries/chain/database.cpp","line_range":"259-268","gmt_create":"2026-04-28T12:55:56.0894921+04:00","gmt_modified":"2026-04-28T12:55:56.0894921+04:00"},{"id":"5864019a1f5c741338aa5810a4a9a2a4","path":"libraries/chain/database.cpp","line_range":"262-267","gmt_create":"2026-04-28T12:55:56.0894921+04:00","gmt_modified":"2026-04-28T12:55:56.0894921+04:00"},{"id":"ac84b774b43ade0dc3ca1ecbd3b60ea5","path":"libraries/chain/database.cpp","line_range":"576-580","gmt_create":"2026-04-28T12:55:56.0904911+04:00","gmt_modified":"2026-04-28T12:55:56.0904911+04:00"},{"id":"29ef92399939d0e10862168a6d547c18","path":"libraries/chain/database.cpp","line_range":"609-613","gmt_create":"2026-04-28T12:55:56.0904911+04:00","gmt_modified":"2026-04-28T12:55:56.0904911+04:00"},{"id":"50f4656fe0d323b661f41cd93f59398f","path":"libraries/chain/database.cpp","line_range":"599-621","gmt_create":"2026-04-28T12:55:56.0904911+04:00","gmt_modified":"2026-04-28T12:55:56.0904911+04:00"},{"id":"f1468b7365f4d06080c1395db0eb5819","path":"libraries/chain/database.cpp","line_range":"623-640","gmt_create":"2026-04-28T12:55:56.0904911+04:00","gmt_modified":"2026-04-28T12:55:56.0904911+04:00"},{"id":"06b382c82ee3f42c24d858503dcde41b","path":"libraries/chain/dlt_block_log.cpp","line_range":"241-249","gmt_create":"2026-04-28T12:55:56.0904911+04:00","gmt_modified":"2026-04-28T12:55:56.0904911+04:00"},{"id":"ce3f0826aff2367e46f4cd37417c2f67","path":"libraries/chain/dlt_block_log.cpp","line_range":"320-325","gmt_create":"2026-04-28T12:55:56.0904911+04:00","gmt_modified":"2026-04-28T12:55:56.0904911+04:00"},{"id":"55755f9121d991a7682722b9a8b3ab80","path":"libraries/chain/database.cpp","line_range":"560-595","gmt_create":"2026-04-28T12:55:56.0919937+04:00","gmt_modified":"2026-04-28T12:55:56.0919937+04:00"},{"id":"86ba814ebeaa92690f62613e16aa5e7d","path":"libraries/chain/database.cpp","line_range":"656-697","gmt_create":"2026-04-28T12:55:56.0919937+04:00","gmt_modified":"2026-04-28T12:55:56.0919937+04:00"},{"id":"ce99ae12d13aaa7b2653e47db773bcce","path":"plugins/snapshot/plugin.cpp","line_range":"1435-1500","gmt_create":"2026-04-28T12:55:56.0919937+04:00","gmt_modified":"2026-04-28T12:55:56.0919937+04:00"},{"id":"a370301bb49e9efcf0ef5d6724ac9dd2","path":"plugins/snapshot/plugin.cpp","line_range":"2691-2696","gmt_create":"2026-04-28T12:55:56.0919937+04:00","gmt_modified":"2026-04-28T12:55:56.0919937+04:00"},{"id":"09819e68e897288cd817074594e4f548","path":"plugins/snapshot/plugin.cpp","line_range":"2863-2866","gmt_create":"2026-04-28T12:55:56.0929972+04:00","gmt_modified":"2026-04-28T12:55:56.0929972+04:00"},{"id":"b44834f88165b1116477f30dc5f88a3e","path":"libraries/chain/database.cpp","line_range":"4581-4608","gmt_create":"2026-04-28T12:55:56.0929972+04:00","gmt_modified":"2026-04-28T12:55:56.0929972+04:00"},{"id":"c6612598fe076c0f7d5ca15ca94cf26b","path":"plugins/chain/plugin.cpp","line_range":"627-627","gmt_create":"2026-04-28T12:55:56.0929972+04:00","gmt_modified":"2026-04-28T12:55:56.0929972+04:00"},{"id":"62962be4cb55c4466a47fba7a814b326","path":"plugins/snapshot/plugin.cpp","line_range":"1473-1476","gmt_create":"2026-04-28T12:55:56.0929972+04:00","gmt_modified":"2026-04-28T12:55:56.0929972+04:00"},{"id":"798b9ae312576643f8fefd434b40bddc","path":"plugins/chain/plugin.cpp","line_range":"626-632","gmt_create":"2026-04-28T12:55:56.0929972+04:00","gmt_modified":"2026-04-28T12:55:56.0929972+04:00"},{"id":"02c2555684691e747182b4078de2ed9b","path":"plugins/snapshot/plugin.cpp","line_range":"1472-1477","gmt_create":"2026-04-28T12:55:56.0929972+04:00","gmt_modified":"2026-04-28T12:55:56.0929972+04:00"},{"id":"590d1dfeeb49a4029c52d3c49b2f0362","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"1-144","gmt_create":"2026-04-28T13:00:52.0145244+04:00","gmt_modified":"2026-04-28T13:00:52.0145244+04:00"},{"id":"d599d8eb734647e07f075785246b447f","path":"libraries/chain/fork_database.cpp","line_range":"1-278","gmt_create":"2026-04-28T13:00:52.0150341+04:00","gmt_modified":"2026-04-28T13:00:52.0150341+04:00"},{"id":"268918c988500bd0972ad80cef38bdd3","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"53-144","gmt_create":"2026-04-28T13:00:52.0199161+04:00","gmt_modified":"2026-04-28T13:00:52.0199161+04:00"},{"id":"24e53b6bba24767d436a386f584d39b1","path":"plugins/p2p/p2p_plugin.cpp","line_range":"330-364","gmt_create":"2026-04-28T14:54:15.686411+04:00","gmt_modified":"2026-04-28T14:54:15.686411+04:00"},{"id":"e24259987d3c50dee1a87e13d63ccf03","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"57-78","gmt_create":"2026-04-28T14:54:15.686411+04:00","gmt_modified":"2026-04-28T14:54:15.686411+04:00"},{"id":"163647b506865a0b9039e6ab0e61b35b","path":"plugins/p2p/p2p_plugin.cpp","line_range":"290-364","gmt_create":"2026-04-28T14:54:15.686411+04:00","gmt_modified":"2026-04-28T14:54:15.686411+04:00"},{"id":"909f520b94e82193bac04c745bf8f67c","path":"plugins/p2p/p2p_plugin.cpp","line_range":"614-650","gmt_create":"2026-04-28T14:54:15.687411+04:00","gmt_modified":"2026-04-28T14:54:15.687411+04:00"},{"id":"c45e7282b5fb1668e8ae6f5a8da708ea","path":"plugins/p2p/p2p_plugin.cpp","line_range":"151-208","gmt_create":"2026-04-28T14:54:15.688411+04:00","gmt_modified":"2026-04-28T14:54:15.688411+04:00"},{"id":"29d1adb11bc96c1e7216be1c31cb57f7","path":"plugins/p2p/p2p_plugin.cpp","line_range":"173-204","gmt_create":"2026-04-28T14:54:15.6894111+04:00","gmt_modified":"2026-04-28T14:54:15.6894111+04:00"},{"id":"b1416c15172aac5cdff13f12c0d385b6","path":"libraries/network/include/graphene/network/node.hpp","line_range":"180-355","gmt_create":"2026-04-28T14:54:34.9410488+04:00","gmt_modified":"2026-04-28T14:54:34.9410488+04:00"},{"id":"b2e1954ed604c23c3fe69090870838ec","path":"libraries/network/node.cpp","line_range":"869-905","gmt_create":"2026-04-28T14:54:34.9410488+04:00","gmt_modified":"2026-04-28T14:54:34.9410488+04:00"},{"id":"7b85de17d00c3c33f3e6ce72493cea24","path":"libraries/network/include/graphene/network/peer_database.hpp","line_range":"104-134","gmt_create":"2026-04-28T14:54:34.9410488+04:00","gmt_modified":"2026-04-28T14:54:34.9410488+04:00"},{"id":"4a47af89ac294fea1a93e8ab87bc62a8","path":"libraries/network/include/graphene/network/message.hpp","line_range":"42-114","gmt_create":"2026-04-28T14:54:34.9410488+04:00","gmt_modified":"2026-04-28T14:54:34.9410488+04:00"},{"id":"fcf5471e2941c73aa4796f0d15ca9d4f","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"111-120","gmt_create":"2026-04-28T14:54:34.9410488+04:00","gmt_modified":"2026-04-28T14:54:34.9410488+04:00"},{"id":"7d072be9a3f767f13a99da4cd6df4783","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"110-123","gmt_create":"2026-04-28T14:54:34.9421314+04:00","gmt_modified":"2026-04-28T14:54:34.9421314+04:00"},{"id":"4f55b43e070bade6f117e02d6faaaac2","path":"libraries/chain/dlt_block_log.cpp","line_range":"368-379","gmt_create":"2026-04-28T14:54:34.9421314+04:00","gmt_modified":"2026-04-28T14:54:34.9421314+04:00"},{"id":"5b2dac9b59c644b7b47e991af481e627","path":"plugins/p2p/p2p_plugin.cpp","line_range":"330-360","gmt_create":"2026-04-28T14:54:34.9426338+04:00","gmt_modified":"2026-04-28T14:54:34.9426338+04:00"},{"id":"d9910f1ea9014fede12810a52c49c4a4","path":"libraries/network/node.cpp","line_range":"952-1047","gmt_create":"2026-04-28T14:54:34.9441396+04:00","gmt_modified":"2026-04-28T14:54:34.9441396+04:00"},{"id":"2d0f35d8035d544c59943ce9488d042e","path":"libraries/network/node.cpp","line_range":"1623-1654","gmt_create":"2026-04-28T14:54:34.9451427+04:00","gmt_modified":"2026-04-28T14:54:34.9451427+04:00"},{"id":"4cb233e3e08980c2c2c76e7762ad457b","path":"libraries/network/node.cpp","line_range":"2282-2350","gmt_create":"2026-04-28T14:54:34.9451427+04:00","gmt_modified":"2026-04-28T14:54:34.9451427+04:00"},{"id":"465b6e48aad6641d48e75039b1ba6cf7","path":"libraries/network/node.cpp","line_range":"869-931","gmt_create":"2026-04-28T14:54:34.9451427+04:00","gmt_modified":"2026-04-28T14:54:34.9451427+04:00"},{"id":"64eac8af7d3939075658361c76c2a7f4","path":"libraries/network/node.cpp","line_range":"2029-2230","gmt_create":"2026-04-28T14:54:34.9451427+04:00","gmt_modified":"2026-04-28T14:54:34.9451427+04:00"},{"id":"63a72fb69b4b837d5841f87d654e6835","path":"libraries/network/node.cpp","line_range":"2232-2250","gmt_create":"2026-04-28T14:54:34.9451427+04:00","gmt_modified":"2026-04-28T14:54:34.9451427+04:00"},{"id":"7d1df83c34f12b0c857fb327e688236b","path":"libraries/network/node.cpp","line_range":"1400-1621","gmt_create":"2026-04-28T14:54:34.9474075+04:00","gmt_modified":"2026-04-28T14:54:34.9474075+04:00"},{"id":"3497c413bfbe054ece4588e4c9764e38","path":"libraries/network/include/graphene/network/node.hpp","line_range":"79-80","gmt_create":"2026-04-28T14:54:34.9480006+04:00","gmt_modified":"2026-04-28T14:54:34.9480006+04:00"},{"id":"832f176e5f919dc138e93f3d848899bb","path":"libraries/network/node.cpp","line_range":"3117-3199","gmt_create":"2026-04-28T14:54:34.9480006+04:00","gmt_modified":"2026-04-28T14:54:34.9480006+04:00"},{"id":"d93f64e1090b7fb0448056ce453003b2","path":"libraries/network/include/graphene/network/node.hpp","line_range":"200-294","gmt_create":"2026-04-28T14:54:34.9485694+04:00","gmt_modified":"2026-04-28T14:54:34.9485694+04:00"},{"id":"8422adca0104a424dd0293e0e0b74399","path":"libraries/network/node.cpp","line_range":"933-950","gmt_create":"2026-04-28T14:54:34.9485694+04:00","gmt_modified":"2026-04-28T14:54:34.9485694+04:00"},{"id":"f88975c9b8df22394830934bbd2b8fec","path":"libraries/network/node.cpp","line_range":"1686-1713","gmt_create":"2026-04-28T14:54:34.9491379+04:00","gmt_modified":"2026-04-28T14:54:34.9491379+04:00"},{"id":"140ecf3fedde255af2f0a4ce07a0edaa","path":"libraries/network/include/graphene/network/node.hpp","line_range":"211-296","gmt_create":"2026-04-28T14:54:34.9491379+04:00","gmt_modified":"2026-04-28T14:54:34.9491379+04:00"},{"id":"2a546861a7d7c1c0f13a5e81a60a2663","path":"libraries/network/node.cpp","line_range":"1788-1841","gmt_create":"2026-04-28T14:54:34.9491379+04:00","gmt_modified":"2026-04-28T14:54:34.9491379+04:00"},{"id":"e9dd01a70c4ccc4eff5ea6c67ea751d4","path":"libraries/network/node.cpp","line_range":"1326-1398","gmt_create":"2026-04-28T14:54:34.9497751+04:00","gmt_modified":"2026-04-28T14:54:34.9497751+04:00"},{"id":"e396600c03187cea5577399428a6bc7a","path":"libraries/network/node.cpp","line_range":"2830-2892","gmt_create":"2026-04-28T14:54:34.9497751+04:00","gmt_modified":"2026-04-28T14:54:34.9497751+04:00"},{"id":"4d84381505174d8f3190a51bf7d50f11","path":"libraries/network/node.cpp","line_range":"111-217","gmt_create":"2026-04-28T14:54:34.9497751+04:00","gmt_modified":"2026-04-28T14:54:34.9497751+04:00"},{"id":"d9e91362d84f2e5c27205bacead0d8e5","path":"libraries/network/node.cpp","line_range":"2251-2280","gmt_create":"2026-04-28T14:54:34.9582805+04:00","gmt_modified":"2026-04-28T14:54:34.9582805+04:00"},{"id":"b0c24aab481fd56f8134b091b5bf0525","path":"libraries/network/node.cpp","line_range":"2137-2168","gmt_create":"2026-04-28T14:54:34.9582805+04:00","gmt_modified":"2026-04-28T14:54:34.9582805+04:00"},{"id":"731063774f04d46e1f2c0d64963dfe33","path":"libraries/chain/database.cpp","line_range":"4455-4460","gmt_create":"2026-04-28T14:54:34.9592806+04:00","gmt_modified":"2026-04-28T14:54:34.9592806+04:00"},{"id":"733f077750798ad212e3907d2e15c841","path":"libraries/chain/database.cpp","line_range":"835-858","gmt_create":"2026-04-28T14:55:54.6404091+04:00","gmt_modified":"2026-04-28T14:55:54.6404091+04:00"},{"id":"c31e2a585be22edd01d86dba51c51e79","path":"plugins/p2p/p2p_plugin.cpp","line_range":"294-302","gmt_create":"2026-04-28T14:55:54.6601866+04:00","gmt_modified":"2026-04-28T14:55:54.6601866+04:00"},{"id":"6ef04fbc3dcdfebcf2580c7e7146cbc3","path":"plugins/p2p/p2p_plugin.cpp","line_range":"317-323","gmt_create":"2026-04-28T14:55:54.6601866+04:00","gmt_modified":"2026-04-28T14:55:54.6601866+04:00"},{"id":"5e4bd94274850728b00115526a2479ee","path":"libraries/chain/database.cpp","line_range":"860-882","gmt_create":"2026-04-28T14:55:54.6617664+04:00","gmt_modified":"2026-04-28T14:55:54.6617664+04:00"},{"id":"c53a6db24cea4db25b30783459c40125","path":"libraries/chain/database.cpp","line_range":"884-901","gmt_create":"2026-04-28T14:55:54.6622692+04:00","gmt_modified":"2026-04-28T14:55:54.6622692+04:00"},{"id":"88d1d1609201e580c6cdf488fcd22fad","path":"plugins/p2p/p2p_plugin.cpp","line_range":"370-489","gmt_create":"2026-04-28T14:55:54.6622692+04:00","gmt_modified":"2026-04-28T14:55:54.6622692+04:00"},{"id":"929cc0269a5814a951b45c3300f44597","path":"libraries/chain/database.cpp","line_range":"789-827","gmt_create":"2026-04-28T15:02:14.7298629+04:00","gmt_modified":"2026-04-28T15:02:14.7298629+04:00"},{"id":"6abfbb22aee54a4b386173dfc74da72b","path":"libraries/chain/database.cpp","line_range":"5452-5482","gmt_create":"2026-04-28T15:02:14.7314238+04:00","gmt_modified":"2026-04-28T15:02:14.7314238+04:00"},{"id":"2ba1e391be7a79f5151c8abeb655315d","path":"libraries/chain/database.cpp","line_range":"5467-5480","gmt_create":"2026-04-28T15:02:14.7314853+04:00","gmt_modified":"2026-04-28T15:02:14.7314853+04:00"},{"id":"55ed0cee7e5f683b8caac133899cb8cd","path":"plugins/p2p/p2p_plugin.cpp","line_range":"295-340","gmt_create":"2026-04-28T17:51:11.5333641+04:00","gmt_modified":"2026-04-28T17:51:11.5333641+04:00"},{"id":"0dc933c238ae7ab73de36f0171dfb48b","path":"plugins/p2p/p2p_plugin.cpp","line_range":"371-405","gmt_create":"2026-04-28T17:51:11.5338875+04:00","gmt_modified":"2026-04-28T17:51:11.5338875+04:00"},{"id":"5a09c4b30cf4ad7363c8ddc12bc7c8db","path":"plugins/p2p/p2p_plugin.cpp","line_range":"290-405","gmt_create":"2026-04-28T17:51:11.5344061+04:00","gmt_modified":"2026-04-28T17:51:11.5344061+04:00"},{"id":"d8698088d1f1c1cd1343a5552104c443","path":"plugins/p2p/p2p_plugin.cpp","line_range":"295-405","gmt_create":"2026-04-28T17:51:11.5375192+04:00","gmt_modified":"2026-04-28T17:51:11.5375192+04:00"},{"id":"bccfbcb90cc33fcb1bad654d203eab3a","path":"plugins/p2p/p2p_plugin.cpp","line_range":"308-340","gmt_create":"2026-04-28T17:51:11.5386681+04:00","gmt_modified":"2026-04-28T17:51:11.5386681+04:00"},{"id":"3e4f7ec74d72ee9c1f4f5cf2a0f86844","path":"plugins/p2p/p2p_plugin.cpp","line_range":"298-302","gmt_create":"2026-04-28T17:51:11.5389893+04:00","gmt_modified":"2026-04-28T17:51:11.5389893+04:00"},{"id":"e13810e67e91d0b4940124f48b98095c","path":"plugins/p2p/p2p_plugin.cpp","line_range":"335-338","gmt_create":"2026-04-28T17:51:11.5394927+04:00","gmt_modified":"2026-04-28T17:51:11.5394927+04:00"},{"id":"30fcbbca25b602851122e4d2b5ae4754","path":"plugins/p2p/p2p_plugin.cpp","line_range":"701-765","gmt_create":"2026-04-28T17:51:11.5400458+04:00","gmt_modified":"2026-04-28T17:51:11.5400458+04:00"},{"id":"2d0ff08ccbd1994ff4f46985361fc3d4","path":"plugins/p2p/p2p_plugin.cpp","line_range":"355-364","gmt_create":"2026-04-28T17:51:11.541049+04:00","gmt_modified":"2026-04-28T17:51:11.541049+04:00"},{"id":"f195c24b5ab419ffbcc0c59a70fc3b0b","path":"plugins/p2p/p2p_plugin.cpp","line_range":"520-528","gmt_create":"2026-04-28T17:51:11.541049+04:00","gmt_modified":"2026-04-28T17:51:11.541049+04:00"},{"id":"1638be790588edb6a3913a76064f24e0","path":"plugins/p2p/p2p_plugin.cpp","line_range":"992-1061","gmt_create":"2026-04-28T17:51:11.5471656+04:00","gmt_modified":"2026-04-28T17:51:11.5471656+04:00"},{"id":"cfa97ba993c799f350895b1deb5bfa1a","path":"plugins/p2p/p2p_plugin.cpp","line_range":"596-699","gmt_create":"2026-04-28T17:51:11.5541826+04:00","gmt_modified":"2026-04-28T17:51:11.5541826+04:00"},{"id":"87dbe393410d68cf0b46ab22827e33fe","path":"plugins/snapshot/plugin.cpp","line_range":"3252-3290","gmt_create":"2026-04-28T17:52:33.4426757+04:00","gmt_modified":"2026-04-28T17:52:33.4426757+04:00"},{"id":"98c57a9cc27d3a9e444c74d23a63bf9e","path":"libraries/chain/database.cpp","line_range":"4945-4947","gmt_create":"2026-04-28T17:52:33.4431784+04:00","gmt_modified":"2026-04-28T17:52:33.4431784+04:00"},{"id":"2d706a9e6bc734297fc6693a4cec7176","path":"libraries/chain/database.cpp","line_range":"5139-5140","gmt_create":"2026-04-28T17:52:33.4431784+04:00","gmt_modified":"2026-04-28T17:52:33.4431784+04:00"},{"id":"95d10140a46b090a8cea978c1c73a9bb","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"337-338","gmt_create":"2026-04-28T17:52:33.4431784+04:00","gmt_modified":"2026-04-28T17:52:33.4431784+04:00"},{"id":"95da2daaa0065c2230410221cb5b5dbd","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"1-670","gmt_create":"2026-04-28T17:55:35.3290491+04:00","gmt_modified":"2026-04-28T17:55:35.3290491+04:00"},{"id":"86991432e69878d08cc76b9d386b7d8f","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"1-80","gmt_create":"2026-04-28T17:55:35.3305561+04:00","gmt_modified":"2026-04-28T17:55:35.3305561+04:00"},{"id":"cdc31aad39121f507723919db7d70dee","path":"libraries/chain/dlt_block_log.cpp","line_range":"1-476","gmt_create":"2026-04-28T17:55:35.3305561+04:00","gmt_modified":"2026-04-28T17:55:35.3305561+04:00"},{"id":"31f6fa48f80c0c6d8c5b09074b2f3f45","path":"libraries/chain/database.cpp","line_range":"4910-5150","gmt_create":"2026-04-28T17:57:38.432734+04:00","gmt_modified":"2026-04-28T17:57:38.432734+04:00"},{"id":"7f9a564b8011c536e8ff555516dffbae","path":"plugins/snapshot/plugin.cpp","line_range":"3254","gmt_create":"2026-04-28T17:57:38.4363285+04:00","gmt_modified":"2026-04-28T17:57:38.4363285+04:00"},{"id":"17512b930411cc392fa7b28fa3ad2b84","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"332-338","gmt_create":"2026-04-28T17:57:38.4456022+04:00","gmt_modified":"2026-04-28T17:57:38.4456022+04:00"},{"id":"930f47bc2c95f0cac4086eeca5749e2c","path":"libraries/network/include/graphene/network/node.hpp","line_range":"190-304","gmt_create":"2026-04-28T18:50:34.3821408+04:00","gmt_modified":"2026-04-28T18:50:34.3821408+04:00"},{"id":"9f8690911ef66c966743475f294ffb7e","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"79-351","gmt_create":"2026-04-28T18:50:34.3821408+04:00","gmt_modified":"2026-04-28T18:50:34.3821408+04:00"},{"id":"a480bfeb1604076e7d1db7746687a3b4","path":"libraries/network/include/graphene/network/message.hpp","line_range":"42-106","gmt_create":"2026-04-28T18:50:34.382644+04:00","gmt_modified":"2026-04-28T18:50:34.382644+04:00"},{"id":"c54e54629fd9cd255341b311da9b6be1","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"72-573","gmt_create":"2026-04-28T18:50:34.382644+04:00","gmt_modified":"2026-04-28T18:50:34.382644+04:00"},{"id":"4e8bc2cdd3d4fc68b2f7138c54288174","path":"libraries/network/include/graphene/network/stcp_socket.hpp","line_range":"37-93","gmt_create":"2026-04-28T18:50:34.382644+04:00","gmt_modified":"2026-04-28T18:50:34.382644+04:00"},{"id":"ff11a77da594972e0b8a78df4289ada5","path":"libraries/network/include/graphene/network/message_oriented_connection.hpp","line_range":"45-79","gmt_create":"2026-04-28T18:50:34.382644+04:00","gmt_modified":"2026-04-28T18:50:34.382644+04:00"},{"id":"d614fc51360bb61b8f521167a6db1e11","path":"libraries/network/include/graphene/network/config.hpp","line_range":"26-106","gmt_create":"2026-04-28T18:50:34.382644+04:00","gmt_modified":"2026-04-28T18:50:34.382644+04:00"},{"id":"411a87e4e085e019827e9bd8085b8328","path":"plugins/p2p/p2p_plugin.cpp","line_range":"500-560","gmt_create":"2026-04-28T18:50:34.3836471+04:00","gmt_modified":"2026-04-28T18:50:34.3836471+04:00"},{"id":"6a050f797e4d75bb2867072fae629c2a","path":"libraries/network/node.cpp","line_range":"5281-5286","gmt_create":"2026-04-28T18:50:34.3836471+04:00","gmt_modified":"2026-04-28T18:50:34.3836471+04:00"},{"id":"448218400a050ac2d3a0f1ee290faf50","path":"libraries/network/node.cpp","line_range":"346-347","gmt_create":"2026-04-28T18:50:34.3836471+04:00","gmt_modified":"2026-04-28T18:50:34.3836471+04:00"},{"id":"34550b8d6e01a13e99a0ed70072db86b","path":"libraries/network/include/graphene/network/node.hpp","line_range":"1-355","gmt_create":"2026-04-28T18:50:34.3846488+04:00","gmt_modified":"2026-04-28T18:50:34.3846488+04:00"},{"id":"9847129d410beb4256be18f2e7489d18","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"1-383","gmt_create":"2026-04-28T18:50:34.3849215+04:00","gmt_modified":"2026-04-28T18:50:34.3849215+04:00"},{"id":"a3e6f8b4e51c838c44eaef16bb205031","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"1-573","gmt_create":"2026-04-28T18:50:34.3849215+04:00","gmt_modified":"2026-04-28T18:50:34.3849215+04:00"},{"id":"53a676bf1df5198ba31b6ebe848a29bd","path":"libraries/network/include/graphene/network/stcp_socket.hpp","line_range":"1-99","gmt_create":"2026-04-28T18:50:34.3855102+04:00","gmt_modified":"2026-04-28T18:50:34.3855102+04:00"},{"id":"f14b0ce0c1ba142fb55271fdc6c2ee9f","path":"libraries/network/include/graphene/network/peer_database.hpp","line_range":"1-141","gmt_create":"2026-04-28T18:50:34.3860136+04:00","gmt_modified":"2026-04-28T18:50:34.3860136+04:00"},{"id":"f59f370133646ba0c40ebdd11ee6d697","path":"libraries/network/include/graphene/network/message.hpp","line_range":"1-114","gmt_create":"2026-04-28T18:50:34.3860943+04:00","gmt_modified":"2026-04-28T18:50:34.3860943+04:00"},{"id":"762c4b81ed454ad789c0b3b3cb00cc88","path":"libraries/network/include/graphene/network/message_oriented_connection.hpp","line_range":"1-85","gmt_create":"2026-04-28T18:50:34.3860943+04:00","gmt_modified":"2026-04-28T18:50:34.3860943+04:00"},{"id":"bc9b507990af077f95d7bbe7203d51f0","path":"libraries/network/include/graphene/network/config.hpp","line_range":"1-106","gmt_create":"2026-04-28T18:50:34.3865969+04:00","gmt_modified":"2026-04-28T18:50:34.3865969+04:00"},{"id":"b54da9d3dceb59e39a6cd88a5a99b593","path":"plugins/p2p/p2p_plugin.cpp","line_range":"1-742","gmt_create":"2026-04-28T18:50:34.3866975+04:00","gmt_modified":"2026-04-28T18:50:34.3866975+04:00"},{"id":"54ba2e42561f4bf9913449b2a78838bb","path":"libraries/network/include/graphene/network/node.hpp","line_range":"182-304","gmt_create":"2026-04-28T18:50:34.3866975+04:00","gmt_modified":"2026-04-28T18:50:34.3866975+04:00"},{"id":"94c69e0f6c696d5856c9a52b4dbcda00","path":"libraries/network/node.cpp","line_range":"780-790","gmt_create":"2026-04-28T18:50:34.3882034+04:00","gmt_modified":"2026-04-28T18:50:34.3882034+04:00"},{"id":"d6f1f5c1ab7e33b9a558f648113330fb","path":"libraries/network/peer_connection.cpp","line_range":"208-242","gmt_create":"2026-04-28T18:50:34.3882034+04:00","gmt_modified":"2026-04-28T18:50:34.3882034+04:00"},{"id":"db2f58e50720e139a95c598355919158","path":"libraries/network/stcp_socket.cpp","line_range":"69-72","gmt_create":"2026-04-28T18:50:34.3882034+04:00","gmt_modified":"2026-04-28T18:50:34.3882034+04:00"},{"id":"a6b2b3362f68c7523a4e0d06a2a4352f","path":"libraries/network/node.cpp","line_range":"424-799","gmt_create":"2026-04-28T18:50:34.3893282+04:00","gmt_modified":"2026-04-28T18:50:34.3893282+04:00"},{"id":"20359a9271432a216b6faf9dc97fa5c6","path":"libraries/network/peer_connection.cpp","line_range":"169-206","gmt_create":"2026-04-28T18:50:34.3905804+04:00","gmt_modified":"2026-04-28T18:50:34.3905804+04:00"},{"id":"fee43b750e5fa7d56e56ac6251e6b5bb","path":"libraries/network/peer_connection.cpp","line_range":"41-66","gmt_create":"2026-04-28T18:50:34.3917964+04:00","gmt_modified":"2026-04-28T18:50:34.3917964+04:00"},{"id":"25c0bdec5e0fb6c8d410577e1478ea78","path":"libraries/network/peer_connection.cpp","line_range":"310-354","gmt_create":"2026-04-28T18:50:34.3923001+04:00","gmt_modified":"2026-04-28T18:50:34.3923001+04:00"},{"id":"e81b900c80dc9c266a93a66cc60486de","path":"libraries/network/core_messages.cpp","line_range":"30-49","gmt_create":"2026-04-28T18:50:34.3930151+04:00","gmt_modified":"2026-04-28T18:50:34.3930151+04:00"},{"id":"0b26d723a87937f5a3f58770c522d067","path":"libraries/network/stcp_socket.cpp","line_range":"49-72","gmt_create":"2026-04-28T18:50:34.3940938+04:00","gmt_modified":"2026-04-28T18:50:34.3940938+04:00"},{"id":"d4b4e356cc64c1eac490f25138ad8337","path":"libraries/network/stcp_socket.cpp","line_range":"132-177","gmt_create":"2026-04-28T18:50:34.3940938+04:00","gmt_modified":"2026-04-28T18:50:34.3940938+04:00"},{"id":"b43af1b0b5dbbe32011b0ab877770800","path":"libraries/network/stcp_socket.cpp","line_range":"49-177","gmt_create":"2026-04-28T18:50:34.3940938+04:00","gmt_modified":"2026-04-28T18:50:34.3940938+04:00"},{"id":"fd44e8ec8ed78651240bac55efcc1f20","path":"libraries/network/peer_database.cpp","line_range":"41-82","gmt_create":"2026-04-28T18:50:34.3950971+04:00","gmt_modified":"2026-04-28T18:50:34.3950971+04:00"},{"id":"ee53a7a714337d597cc0a626fceec27f","path":"libraries/network/peer_database.cpp","line_range":"100-174","gmt_create":"2026-04-28T18:50:34.3951753+04:00","gmt_modified":"2026-04-28T18:50:34.3951753+04:00"},{"id":"aae2609ed41db2a866f07ce766cbb938","path":"libraries/network/include/graphene/network/message.hpp","line_range":"70-105","gmt_create":"2026-04-28T18:50:34.3956782+04:00","gmt_modified":"2026-04-28T18:50:34.3956782+04:00"},{"id":"023d67279bae7acc551e276826dc0ff1","path":"libraries/network/node.cpp","line_range":"3710-3723","gmt_create":"2026-04-28T18:50:34.3956782+04:00","gmt_modified":"2026-04-28T18:50:34.3956782+04:00"},{"id":"b7e9099b5e14dfe6396c0a1c5c308eb7","path":"libraries/network/node.cpp","line_range":"312-381","gmt_create":"2026-04-28T18:50:34.3956782+04:00","gmt_modified":"2026-04-28T18:50:34.3956782+04:00"},{"id":"26b49296ed2024fd0dde20e6f7e40b88","path":"libraries/network/node.cpp","line_range":"383-420","gmt_create":"2026-04-28T18:50:34.3956782+04:00","gmt_modified":"2026-04-28T18:50:34.3956782+04:00"},{"id":"41d27526c610abf125f7a7d008632585","path":"libraries/network/include/graphene/network/node.hpp","line_range":"173-179","gmt_create":"2026-04-28T18:50:34.3956782+04:00","gmt_modified":"2026-04-28T18:50:34.3956782+04:00"},{"id":"868fa39acf5aeee17dc7a8161740807d","path":"libraries/network/node.cpp","line_range":"4920-4970","gmt_create":"2026-04-28T18:50:34.3966815+04:00","gmt_modified":"2026-04-28T18:50:34.3966815+04:00"},{"id":"9814b4d33f73344843377eb1e1b063d8","path":"libraries/network/node.cpp","line_range":"5128-5131","gmt_create":"2026-04-28T18:50:34.3966815+04:00","gmt_modified":"2026-04-28T18:50:34.3966815+04:00"},{"id":"380d27e76ccd3471d37efe9e980c19ae","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"322-346","gmt_create":"2026-04-28T18:50:34.3966815+04:00","gmt_modified":"2026-04-28T18:50:34.3966815+04:00"},{"id":"569091d2a8631dcfd98eaa50ae2bfc44","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"428-448","gmt_create":"2026-04-28T18:50:34.3966815+04:00","gmt_modified":"2026-04-28T18:50:34.3966815+04:00"},{"id":"1ed9d91d6dd0223209ee65abf9542242","path":"libraries/network/node.cpp","line_range":"4900-4970","gmt_create":"2026-04-28T18:50:34.3976813+04:00","gmt_modified":"2026-04-28T18:50:34.3976813+04:00"},{"id":"b8d925dd432a091aeeb11648caa2eb44","path":"libraries/network/node.cpp","line_range":"4164-4168","gmt_create":"2026-04-28T18:50:34.3987829+04:00","gmt_modified":"2026-04-28T18:50:34.3987829+04:00"},{"id":"bf71a71910c5567123121789e437c062","path":"libraries/network/include/graphene/network/node.hpp","line_range":"298-304","gmt_create":"2026-04-28T18:50:34.399451+04:00","gmt_modified":"2026-04-28T18:50:34.399451+04:00"},{"id":"690241d99c53c94e7cfd1435215c90f7","path":"plugins/p2p/p2p_plugin.cpp","line_range":"616-618","gmt_create":"2026-04-28T18:50:34.4000241+04:00","gmt_modified":"2026-04-28T18:50:34.4000241+04:00"},{"id":"8df3a4b9e7bead2487cc1ffc6cd49cae","path":"plugins/p2p/p2p_plugin.cpp","line_range":"16-19","gmt_create":"2026-04-28T18:50:34.4005266+04:00","gmt_modified":"2026-04-28T18:50:34.4005266+04:00"},{"id":"84d9f61aa92a7de46576a47f269a5dcd","path":"plugins/p2p/p2p_plugin.cpp","line_range":"169-171","gmt_create":"2026-04-28T18:50:34.4005266+04:00","gmt_modified":"2026-04-28T18:50:34.4005266+04:00"},{"id":"0be17f1ab260a4b3bcead7596649bfc6","path":"libraries/network/node.cpp","line_range":"81-81","gmt_create":"2026-04-28T18:50:34.4005266+04:00","gmt_modified":"2026-04-28T18:50:34.4005266+04:00"},{"id":"271449375aea639d1f03a23e291eaea3","path":"libraries/network/node.cpp","line_range":"1187-1194","gmt_create":"2026-04-28T18:50:34.4005266+04:00","gmt_modified":"2026-04-28T18:50:34.4005266+04:00"},{"id":"14c432cf8aa5e9ce797534c3d40b187f","path":"libraries/network/node.cpp","line_range":"1200-1202","gmt_create":"2026-04-28T18:50:34.4015296+04:00","gmt_modified":"2026-04-28T18:50:34.4015296+04:00"},{"id":"c63bedc7eec5da82c3e0783176a7a564","path":"libraries/network/node.cpp","line_range":"2651-2663","gmt_create":"2026-04-28T18:50:34.4015296+04:00","gmt_modified":"2026-04-28T18:50:34.4015296+04:00"},{"id":"95b247631c8b269b2a98c7e385bac407","path":"libraries/network/node.cpp","line_range":"2772-2779","gmt_create":"2026-04-28T18:50:34.4015296+04:00","gmt_modified":"2026-04-28T18:50:34.4015296+04:00"},{"id":"e5fedb1d60d7f5a29bddbc403ff3d857","path":"libraries/network/node.cpp","line_range":"2790-2796","gmt_create":"2026-04-28T18:50:34.4015296+04:00","gmt_modified":"2026-04-28T18:50:34.4015296+04:00"},{"id":"b30577f67d2d7eafe760daf3f3f386b0","path":"libraries/network/include/graphene/network/node.hpp","line_range":"26-28","gmt_create":"2026-04-28T18:50:34.4015296+04:00","gmt_modified":"2026-04-28T18:50:34.4015296+04:00"},{"id":"8d12769b91a5f0a7c02fe172ecffd5ef","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"26-29","gmt_create":"2026-04-28T18:50:34.4015296+04:00","gmt_modified":"2026-04-28T18:50:34.4015296+04:00"},{"id":"b4c2646d470a9f4262288b71e7b10e89","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"26-28","gmt_create":"2026-04-28T18:50:34.4025295+04:00","gmt_modified":"2026-04-28T18:50:34.4025295+04:00"},{"id":"055bd4ac99dff0f7dad3d2d929f83b6a","path":"libraries/network/include/graphene/network/stcp_socket.hpp","line_range":"26-28","gmt_create":"2026-04-28T18:50:34.4026147+04:00","gmt_modified":"2026-04-28T18:50:34.4026147+04:00"},{"id":"eb6ddb4ecbe9eb87c395f2865107ea0e","path":"libraries/network/include/graphene/network/message_oriented_connection.hpp","line_range":"26-27","gmt_create":"2026-04-28T18:50:34.4026147+04:00","gmt_modified":"2026-04-28T18:50:34.4026147+04:00"},{"id":"660390a5b68b3f21fd886800f24fcc78","path":"libraries/network/include/graphene/network/peer_database.hpp","line_range":"39-45","gmt_create":"2026-04-28T18:50:34.4041203+04:00","gmt_modified":"2026-04-28T18:50:34.4041203+04:00"},{"id":"688ff5b9bf8ff49c734540b2a73a1251","path":"libraries/network/include/graphene/network/node.hpp","line_range":"288-298","gmt_create":"2026-04-28T18:50:34.4050997+04:00","gmt_modified":"2026-04-28T18:50:34.4050997+04:00"},{"id":"34e2f989664fd5eff332f3ac19b8b16e","path":"libraries/network/include/graphene/network/message.hpp","line_range":"85-105","gmt_create":"2026-04-28T18:50:34.4056028+04:00","gmt_modified":"2026-04-28T18:50:34.4056028+04:00"},{"id":"16865a1a8de25574f1f934ab4b21167f","path":"plugins/snapshot/plugin.cpp","line_range":"1595-1624","gmt_create":"2026-04-28T18:53:18.2448781+04:00","gmt_modified":"2026-04-28T18:53:18.2448781+04:00"},{"id":"9a668be3a6e1e9f3afa6fcd90677d0b3","path":"libraries/chain/database.cpp","line_range":"5482-5499","gmt_create":"2026-04-28T18:54:23.6340341+04:00","gmt_modified":"2026-04-28T18:54:23.6340341+04:00"},{"id":"e2711f14cc959993e82e19c033761fff","path":"plugins/p2p/p2p_plugin.cpp","line_range":"16-21","gmt_create":"2026-04-28T19:27:53.5877256+04:00","gmt_modified":"2026-04-28T19:27:53.5877256+04:00"},{"id":"c66e1ad4dc89d485e77e61d3776d5e80","path":"plugins/p2p/p2p_plugin.cpp","line_range":"299-301","gmt_create":"2026-04-28T19:27:53.5877256+04:00","gmt_modified":"2026-04-28T19:27:53.5877256+04:00"},{"id":"d6af3938508b615a82ba5a3820d850c0","path":"plugins/p2p/p2p_plugin.cpp","line_range":"522-528","gmt_create":"2026-04-28T19:27:53.5877256+04:00","gmt_modified":"2026-04-28T19:27:53.5877256+04:00"},{"id":"10e2e9b3ec934c86877d6a4b57e21d34","path":"plugins/p2p/p2p_plugin.cpp","line_range":"321-327","gmt_create":"2026-04-28T19:27:53.5877256+04:00","gmt_modified":"2026-04-28T19:27:53.5877256+04:00"},{"id":"5cdb837c5a6e5f183ed9a818f50b2abf","path":"plugins/p2p/p2p_plugin.cpp","line_range":"336-338","gmt_create":"2026-04-28T19:27:53.5877256+04:00","gmt_modified":"2026-04-28T19:27:53.5877256+04:00"},{"id":"4a61426778eec404639d3aa8059fbe1a","path":"plugins/p2p/p2p_plugin.cpp","line_range":"357-364","gmt_create":"2026-04-28T19:27:53.5877256+04:00","gmt_modified":"2026-04-28T19:27:53.5877256+04:00"},{"id":"f9ff6c6bb4dec49a31a1b770a5d8e89f","path":"plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp","line_range":"38-108","gmt_create":"2026-04-28T19:48:38.8563572+04:00","gmt_modified":"2026-04-28T19:48:38.8563572+04:00"},{"id":"c914b1a14377d172e66d3320eee0a043","path":"plugins/debug_node/plugin.cpp","line_range":"25-94","gmt_create":"2026-04-28T19:48:38.8563572+04:00","gmt_modified":"2026-04-28T19:48:38.8563572+04:00"},{"id":"f3cc232f0f51afc8747a00f6b9906c3d","path":"plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp","line_range":"1-108","gmt_create":"2026-04-28T19:48:38.8563572+04:00","gmt_modified":"2026-04-28T19:48:38.8563572+04:00"},{"id":"b9491b3f2a4f089e194b8c382d40e3ed","path":"programs/util/sign_transaction.cpp","line_range":"12-26","gmt_create":"2026-04-28T19:48:38.8574734+04:00","gmt_modified":"2026-04-28T19:48:38.8574734+04:00"},{"id":"80d96f5a237fbc996d591a3a6738455e","path":"programs/util/sign_digest.cpp","line_range":"12-24","gmt_create":"2026-04-28T19:48:38.8574734+04:00","gmt_modified":"2026-04-28T19:48:38.8574734+04:00"},{"id":"3aa63104c48be9af56ac2394a0ff0465","path":"plugins/p2p/p2p_plugin.cpp","line_range":"1-200","gmt_create":"2026-04-28T19:48:38.857976+04:00","gmt_modified":"2026-04-28T19:48:38.857976+04:00"},{"id":"035cfc0826dd0b5915c1d3c58d1ef13d","path":"libraries/network/include/graphene/network/node.hpp","line_range":"190-200","gmt_create":"2026-04-28T19:48:38.857976+04:00","gmt_modified":"2026-04-28T19:48:38.857976+04:00"},{"id":"775f3dad042fb99fc87d2a984fb10026","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"79-200","gmt_create":"2026-04-28T19:48:38.857976+04:00","gmt_modified":"2026-04-28T19:48:38.857976+04:00"},{"id":"ab666734d7a7b8fd90bcfb1f1a312813","path":"share/vizd/config/config_debug.ini","line_range":"1-126","gmt_create":"2026-04-28T19:48:38.857976+04:00","gmt_modified":"2026-04-28T19:48:38.857976+04:00"},{"id":"6fca2c5700dd39ca35022a36a9d989cf","path":"plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp","line_range":"1-111","gmt_create":"2026-04-28T19:48:38.857976+04:00","gmt_modified":"2026-04-28T19:48:38.857976+04:00"},{"id":"12ee4287ffb75b424c50950e8f5b2df5","path":"plugins/debug_node/plugin.cpp","line_range":"1-668","gmt_create":"2026-04-28T19:48:38.857976+04:00","gmt_modified":"2026-04-28T19:48:38.857976+04:00"},{"id":"1bbdcbd551f6b6db613208a506ed663b","path":"programs/util/sign_transaction.cpp","line_range":"1-54","gmt_create":"2026-04-28T19:48:38.8589794+04:00","gmt_modified":"2026-04-28T19:48:38.8589794+04:00"},{"id":"fca351b915d815422a74e940fcde8cdb","path":"programs/util/sign_digest.cpp","line_range":"1-49","gmt_create":"2026-04-28T19:48:38.8589794+04:00","gmt_modified":"2026-04-28T19:48:38.8589794+04:00"},{"id":"07bbb44ee7833965e9eb8064c7dca3a5","path":"libraries/network/include/graphene/network/node.hpp","line_range":"1-200","gmt_create":"2026-04-28T19:48:38.8589794+04:00","gmt_modified":"2026-04-28T19:48:38.8589794+04:00"},{"id":"5dbe534429ba35b6a93bafccda79ab2b","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"1-200","gmt_create":"2026-04-28T19:48:38.8599791+04:00","gmt_modified":"2026-04-28T19:48:38.8599791+04:00"},{"id":"e4d3b0996d354976a0f8d7be27bba43f","path":"plugins/debug_node/plugin.cpp","line_range":"222-288","gmt_create":"2026-04-28T19:48:38.8599791+04:00","gmt_modified":"2026-04-28T19:48:38.8599791+04:00"},{"id":"073296e0c0907087372d72bd8ed5a30d","path":"plugins/debug_node/plugin.cpp","line_range":"321-420","gmt_create":"2026-04-28T19:48:38.8609791+04:00","gmt_modified":"2026-04-28T19:48:38.8609791+04:00"},{"id":"8a25fc8080abd566cbef2d584b42c23b","path":"plugins/debug_node/plugin.cpp","line_range":"489-555","gmt_create":"2026-04-28T19:48:38.8609791+04:00","gmt_modified":"2026-04-28T19:48:38.8609791+04:00"},{"id":"06bbb8421071aa4e70ada689b0e8d0de","path":"plugins/p2p/p2p_plugin.cpp","line_range":"118-170","gmt_create":"2026-04-28T19:48:38.8609791+04:00","gmt_modified":"2026-04-28T19:48:38.8609791+04:00"},{"id":"be7f7c455b691c95a3dd6a2789b92a2a","path":"plugins/debug_node/plugin.cpp","line_range":"489-511","gmt_create":"2026-04-28T19:48:38.8609791+04:00","gmt_modified":"2026-04-28T19:48:38.8609791+04:00"},{"id":"3e4f072495a91bf2b13739f1f9d95a9c","path":"plugins/debug_node/plugin.cpp","line_range":"321-372","gmt_create":"2026-04-28T19:48:38.8609791+04:00","gmt_modified":"2026-04-28T19:48:38.8609791+04:00"},{"id":"a31a8b940a678fa2f3d45d33ed52e2a8","path":"plugins/debug_node/plugin.cpp","line_range":"222-555","gmt_create":"2026-04-28T19:48:38.863177+04:00","gmt_modified":"2026-04-28T19:48:38.863177+04:00"},{"id":"b7b962f2d1cc506c60851cf76e545c68","path":"plugins/debug_node/plugin.cpp","line_range":"441-454","gmt_create":"2026-04-28T19:48:38.8637428+04:00","gmt_modified":"2026-04-28T19:48:38.8637428+04:00"},{"id":"5ad89d11099370a27a89a9f7d0346730","path":"plugins/debug_node/plugin.cpp","line_range":"422-430","gmt_create":"2026-04-28T19:48:38.8637428+04:00","gmt_modified":"2026-04-28T19:48:38.8637428+04:00"},{"id":"20bc4d17331e9bb80414df521e933386","path":"plugins/debug_node/plugin.cpp","line_range":"117-136","gmt_create":"2026-04-28T19:48:38.8637428+04:00","gmt_modified":"2026-04-28T19:48:38.8637428+04:00"},{"id":"1a03b72ef4884dc486a6cf695bdec9e1","path":"programs/util/sign_transaction.cpp","line_range":"28-53","gmt_create":"2026-04-28T19:48:38.8643339+04:00","gmt_modified":"2026-04-28T19:48:38.8643339+04:00"},{"id":"ececdaf0999d8a83addb93f95d0039d3","path":"programs/util/sign_digest.cpp","line_range":"26-48","gmt_create":"2026-04-28T19:48:38.8643339+04:00","gmt_modified":"2026-04-28T19:48:38.8643339+04:00"},{"id":"8e15df455552a1b9105a9a8b25797f1e","path":"plugins/p2p/p2p_plugin.cpp","line_range":"169-172","gmt_create":"2026-04-28T19:48:38.8658392+04:00","gmt_modified":"2026-04-28T19:48:38.8658392+04:00"},{"id":"9be51c5b9724dc743dc15a2758b6e4b2","path":"plugins/p2p/p2p_plugin.cpp","line_range":"605-652","gmt_create":"2026-04-28T19:48:38.8658392+04:00","gmt_modified":"2026-04-28T19:48:38.8658392+04:00"},{"id":"7232f04ac5a3f960659a84db72e3c8c6","path":"libraries/network/node.cpp","line_range":"79-83","gmt_create":"2026-04-28T19:48:38.8658392+04:00","gmt_modified":"2026-04-28T19:48:38.8658392+04:00"},{"id":"86b86c367842460e75e043fb87b0f88f","path":"libraries/network/node.cpp","line_range":"5091-5108","gmt_create":"2026-04-28T19:48:38.8658392+04:00","gmt_modified":"2026-04-28T19:48:38.8658392+04:00"},{"id":"d46277833fc133df8624346cd33cb6f3","path":"plugins/p2p/p2p_plugin.cpp","line_range":"298-365","gmt_create":"2026-04-28T19:48:38.866962+04:00","gmt_modified":"2026-04-28T19:48:38.866962+04:00"},{"id":"9178051ee4765210ad69eba44f39c87a","path":"plugins/p2p/p2p_plugin.cpp","line_range":"521-530","gmt_create":"2026-04-28T19:48:38.866962+04:00","gmt_modified":"2026-04-28T19:48:38.866962+04:00"},{"id":"cd9f1eabe9bd89866fea8372b8de3fb2","path":"plugins/p2p/p2p_plugin.cpp","line_range":"605-686","gmt_create":"2026-04-28T19:48:38.8674647+04:00","gmt_modified":"2026-04-28T19:48:38.8674647+04:00"},{"id":"b988e082f7d48dc4995d1e872598437c","path":"plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp","line_range":"40-41","gmt_create":"2026-04-28T19:48:38.868048+04:00","gmt_modified":"2026-04-28T19:48:38.868048+04:00"},{"id":"870cbffea14b19ef183293221413ec5c","path":"share/vizd/config/config_debug.ini","line_range":"36-47","gmt_create":"2026-04-28T19:48:38.8696234+04:00","gmt_modified":"2026-04-28T19:48:38.8696234+04:00"},{"id":"1448971e80d721aa1c85a46aea3da577","path":"share/vizd/config/config_debug.ini","line_range":"49-67","gmt_create":"2026-04-28T19:48:38.8696234+04:00","gmt_modified":"2026-04-28T19:48:38.8696234+04:00"},{"id":"d64fee9bc095b55a1d7636f437075323","path":"plugins/debug_node/plugin.cpp","line_range":"244-248","gmt_create":"2026-04-28T19:48:38.8726263+04:00","gmt_modified":"2026-04-28T19:48:38.8726263+04:00"},{"id":"076badf84cd673f3b9244504dca95861","path":"plugins/debug_node/plugin.cpp","line_range":"363-366","gmt_create":"2026-04-28T19:48:38.8736263+04:00","gmt_modified":"2026-04-28T19:48:38.8736263+04:00"},{"id":"a9154fa6ace3118ba37f67a132a8951a","path":"plugins/p2p/p2p_plugin.cpp","line_range":"124-133","gmt_create":"2026-04-28T19:48:38.8736263+04:00","gmt_modified":"2026-04-28T19:48:38.8736263+04:00"},{"id":"d7d14eee16cef3e1cf0039ec5f52febf","path":"share/vizd/config/config_debug.ini","line_range":"107-126","gmt_create":"2026-04-28T19:48:38.8736263+04:00","gmt_modified":"2026-04-28T19:48:38.8736263+04:00"},{"id":"154f8101227c15750c6aa53260e9b84f","path":"plugins/debug_node/plugin.cpp","line_range":"374-420","gmt_create":"2026-04-28T19:48:38.8746264+04:00","gmt_modified":"2026-04-28T19:48:38.8746264+04:00"},{"id":"020b183f2967c754e5327defd9a63415","path":"documentation/debug_node_plugin.md","line_range":"50-134","gmt_create":"2026-04-28T19:48:38.8746264+04:00","gmt_modified":"2026-04-28T19:48:38.8746264+04:00"},{"id":"f7356b2eafc5252755db0df36458992f","path":"plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp","line_range":"62-90","gmt_create":"2026-04-28T19:48:38.8746264+04:00","gmt_modified":"2026-04-28T19:48:38.8746264+04:00"},{"id":"82787c8d2e7394cf00fa87735394c3d5","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"72-95","gmt_create":"2026-04-28T20:32:44.1474859+04:00","gmt_modified":"2026-04-28T20:32:44.1474859+04:00"},{"id":"ab73f6cda0f4389d2bef214161afef81","path":"libraries/network/node.cpp","line_range":"593-601","gmt_create":"2026-04-28T20:32:44.1488964+04:00","gmt_modified":"2026-04-28T20:32:44.1488964+04:00"},{"id":"8caf3345fc407dc61382d80fab63bc93","path":"libraries/network/node.cpp","line_range":"5240-5274","gmt_create":"2026-04-28T20:32:44.1488964+04:00","gmt_modified":"2026-04-28T20:32:44.1488964+04:00"},{"id":"8e54da669dc427d415acec15fb6a804a","path":"plugins/snapshot/plugin.cpp","line_range":"3039-3045","gmt_create":"2026-04-28T20:32:44.1488964+04:00","gmt_modified":"2026-04-28T20:32:44.1488964+04:00"},{"id":"e6da7f673c730dddfe0373c2e796f71a","path":"libraries/chain/database.cpp","line_range":"1215-1246","gmt_create":"2026-04-28T20:32:44.1499023+04:00","gmt_modified":"2026-04-28T20:32:44.1499023+04:00"},{"id":"f02c5e6d0090d5de89e01b0a1d478c5c","path":"libraries/network/include/graphene/network/exceptions.hpp","line_range":"33-45","gmt_create":"2026-04-28T20:32:44.1499023+04:00","gmt_modified":"2026-04-28T20:32:44.1499023+04:00"},{"id":"f5f7d5764818ed749bc9b829f7aea2ff","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"1-386","gmt_create":"2026-04-28T20:32:44.1499023+04:00","gmt_modified":"2026-04-28T20:32:44.1499023+04:00"},{"id":"56968eec4c8adc4f9edd153c6ce9e231","path":"libraries/network/include/graphene/network/node.hpp","line_range":"1-374","gmt_create":"2026-04-28T20:32:44.1516513+04:00","gmt_modified":"2026-04-28T20:32:44.1516513+04:00"},{"id":"8923fae736f09b1cb255636af9a52069","path":"libraries/chain/database.cpp","line_range":"1-6389","gmt_create":"2026-04-28T20:32:44.1521543+04:00","gmt_modified":"2026-04-28T20:32:44.1521543+04:00"},{"id":"18382ca1b0f5cd7c4280e057d0242410","path":"libraries/network/include/graphene/network/exceptions.hpp","line_range":"1-49","gmt_create":"2026-04-28T20:32:44.1521543+04:00","gmt_modified":"2026-04-28T20:32:44.1521543+04:00"},{"id":"a41d1e5edd6d2706424bcd3b39295264","path":"libraries/network/peer_connection.cpp","line_range":"68-162","gmt_create":"2026-04-28T20:32:44.1537603+04:00","gmt_modified":"2026-04-28T20:32:44.1537603+04:00"},{"id":"3de0360b8d19b4e6a521e737d44e1eec","path":"libraries/network/message_oriented_connection.cpp","line_range":"128-140","gmt_create":"2026-04-28T20:32:44.1537603+04:00","gmt_modified":"2026-04-28T20:32:44.1537603+04:00"},{"id":"df02d50a1fa9064e2cfe4b033b6ecfc8","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"233-306","gmt_create":"2026-04-28T20:32:44.1547962+04:00","gmt_modified":"2026-04-28T20:32:44.1547962+04:00"},{"id":"c32e26ac2223fcbd9ec8280f135d338c","path":"libraries/network/message_oriented_connection.cpp","line_range":"135-140","gmt_create":"2026-04-28T20:32:44.1578223+04:00","gmt_modified":"2026-04-28T20:32:44.1578223+04:00"},{"id":"0543ad31da0059b627fc7cfd5bac0cad","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"233-272","gmt_create":"2026-04-28T20:32:44.1578223+04:00","gmt_modified":"2026-04-28T20:32:44.1578223+04:00"},{"id":"fa47faf491ab252556d3fc50f00c6a8d","path":"libraries/network/node.cpp","line_range":"662-718","gmt_create":"2026-04-28T20:32:44.1583243+04:00","gmt_modified":"2026-04-28T20:32:44.1583243+04:00"},{"id":"09cde54d90657149861df59444b291ba","path":"libraries/network/peer_connection.cpp","line_range":"244-338","gmt_create":"2026-04-28T20:32:44.1597444+04:00","gmt_modified":"2026-04-28T20:32:44.1597444+04:00"},{"id":"e3f78790c3dfc7c45569ce78697bf9ef","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"240-278","gmt_create":"2026-04-28T20:32:44.1597444+04:00","gmt_modified":"2026-04-28T20:32:44.1597444+04:00"},{"id":"5212be0912badd41a65263c4ef5ab6fc","path":"libraries/network/message_oriented_connection.cpp","line_range":"237-283","gmt_create":"2026-04-28T20:32:44.1597444+04:00","gmt_modified":"2026-04-28T20:32:44.1597444+04:00"},{"id":"8743a349115da7520b9567a7d855ca18","path":"libraries/network/message_oriented_connection.cpp","line_range":"148-235","gmt_create":"2026-04-28T20:32:44.1597444+04:00","gmt_modified":"2026-04-28T20:32:44.1597444+04:00"},{"id":"8c4475690fa2dd5b99756637ea5c7358","path":"libraries/network/peer_connection.cpp","line_range":"356-369","gmt_create":"2026-04-28T20:32:44.1639126+04:00","gmt_modified":"2026-04-28T20:32:44.1639126+04:00"},{"id":"c71708bd330ca79e1ba386a1763f5154","path":"libraries/network/node.cpp","line_range":"718-740","gmt_create":"2026-04-28T20:32:44.1639126+04:00","gmt_modified":"2026-04-28T20:32:44.1639126+04:00"},{"id":"9aacd835d38b395734ce6c87619ff59b","path":"libraries/network/node.cpp","line_range":"5272-5274","gmt_create":"2026-04-28T20:32:44.1649088+04:00","gmt_modified":"2026-04-28T20:32:44.1649088+04:00"},{"id":"a1b4ebbf3bdbfda373c476ae64175283","path":"libraries/network/peer_connection.cpp","line_range":"169-242","gmt_create":"2026-04-28T20:32:44.1649088+04:00","gmt_modified":"2026-04-28T20:32:44.1649088+04:00"},{"id":"179c82a5b5dc0ef88ef0f3d348aeabf7","path":"libraries/network/peer_connection.cpp","line_range":"310-338","gmt_create":"2026-04-28T20:32:44.1659092+04:00","gmt_modified":"2026-04-28T20:32:44.1659092+04:00"},{"id":"50fe97d449649d053646df017c7d5108","path":"libraries/network/peer_connection.cpp","line_range":"255-308","gmt_create":"2026-04-28T20:32:44.1659092+04:00","gmt_modified":"2026-04-28T20:32:44.1659092+04:00"},{"id":"707909d344d5db986aaec2161fe7ec64","path":"libraries/network/include/graphene/network/config.hpp","line_range":"58-58","gmt_create":"2026-04-28T20:32:44.1659092+04:00","gmt_modified":"2026-04-28T20:32:44.1659092+04:00"},{"id":"b07239f8068fdc811cbe0313fbee8a56","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"175-279","gmt_create":"2026-04-28T20:32:44.1676511+04:00","gmt_modified":"2026-04-28T20:32:44.1676511+04:00"},{"id":"89fa671fe61671f5b5ab01a94646d7a3","path":"libraries/network/peer_connection.cpp","line_range":"428-480","gmt_create":"2026-04-28T20:32:44.1676511+04:00","gmt_modified":"2026-04-28T20:32:44.1676511+04:00"},{"id":"f57a019298b7b9496ef4f4a85f921f75","path":"libraries/network/include/graphene/network/peer_database.hpp","line_range":"47-71","gmt_create":"2026-04-28T20:32:44.1681537+04:00","gmt_modified":"2026-04-28T20:32:44.1681537+04:00"},{"id":"228fc59a5268956db45d2f696afd5586","path":"libraries/network/node.cpp","line_range":"518-526","gmt_create":"2026-04-28T20:32:44.1685282+04:00","gmt_modified":"2026-04-28T20:32:44.1685282+04:00"},{"id":"8f4a83adbe444c72194effc3001fe32c","path":"libraries/network/node.cpp","line_range":"5265-5274","gmt_create":"2026-04-28T20:32:44.1691095+04:00","gmt_modified":"2026-04-28T20:32:44.1691095+04:00"},{"id":"4488509fca59b45f940f83b5db6daa2a","path":"libraries/network/node.cpp","line_range":"3874-3908","gmt_create":"2026-04-28T20:32:44.1696126+04:00","gmt_modified":"2026-04-28T20:32:44.1696126+04:00"},{"id":"313eca37529d6d0a87af6db00b3cae76","path":"libraries/network/node.cpp","line_range":"3598-3626","gmt_create":"2026-04-28T20:32:44.1696126+04:00","gmt_modified":"2026-04-28T20:32:44.1696126+04:00"},{"id":"88cfd8edcbcced125d9358898750ad6a","path":"plugins/p2p/p2p_plugin.cpp","line_range":"172-182","gmt_create":"2026-04-28T20:32:44.1707128+04:00","gmt_modified":"2026-04-28T20:32:44.1707128+04:00"},{"id":"3442c112ec40ff3a7ce211827dd731fb","path":"libraries/network/node.cpp","line_range":"599-600","gmt_create":"2026-04-28T20:32:44.1707128+04:00","gmt_modified":"2026-04-28T20:32:44.1707128+04:00"},{"id":"d2465d6814378cac309bb4813312e463","path":"libraries/network/node.cpp","line_range":"4472-4479","gmt_create":"2026-04-28T20:32:44.1725426+04:00","gmt_modified":"2026-04-28T20:32:44.1725426+04:00"},{"id":"028e9730d5a18007052fca00e208de6c","path":"libraries/network/node.cpp","line_range":"5016-5021","gmt_create":"2026-04-28T20:32:44.1730455+04:00","gmt_modified":"2026-04-28T20:32:44.1730455+04:00"},{"id":"22da7c51b4640e33df6b1774de67e615","path":"libraries/network/peer_database.cpp","line_range":"120-137","gmt_create":"2026-04-28T20:32:44.1730455+04:00","gmt_modified":"2026-04-28T20:32:44.1730455+04:00"},{"id":"0ff96f9f66fe11cb2bba96b2257c56cc","path":"libraries/network/node.cpp","line_range":"5013-5014","gmt_create":"2026-04-28T20:32:44.1740487+04:00","gmt_modified":"2026-04-28T20:32:44.1740487+04:00"},{"id":"f6e4014b53decf8540f03050dcb248a7","path":"libraries/network/node.cpp","line_range":"3061-3062","gmt_create":"2026-04-28T20:32:44.1740487+04:00","gmt_modified":"2026-04-28T20:32:44.1740487+04:00"},{"id":"3a3515773dce3d23274f19f11b24b8a1","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"279-283","gmt_create":"2026-04-28T20:32:44.1751837+04:00","gmt_modified":"2026-04-28T20:32:44.1751837+04:00"},{"id":"bced6dc0057c5231cbd868d55b0f5331","path":"libraries/network/peer_connection.cpp","line_range":"340-354","gmt_create":"2026-04-28T20:32:44.1763468+04:00","gmt_modified":"2026-04-28T20:32:44.1763468+04:00"},{"id":"1496fd024a306e65ca11a1074f83e158","path":"libraries/network/peer_connection.cpp","line_range":"371-399","gmt_create":"2026-04-28T20:32:44.1763468+04:00","gmt_modified":"2026-04-28T20:32:44.1763468+04:00"},{"id":"e196c8cfa8e7dbe5852841c2e099bad5","path":"share/vizd/config/config.ini","line_range":"96-101","gmt_create":"2026-04-28T20:32:44.17685+04:00","gmt_modified":"2026-04-28T20:32:44.17685+04:00"},{"id":"c1db40762caab4c9462bb6bc70748152","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"26-45","gmt_create":"2026-04-28T20:32:44.1798586+04:00","gmt_modified":"2026-04-28T20:32:44.1798586+04:00"},{"id":"973153247aad2260b57a9e4842ddfeb7","path":"libraries/network/include/graphene/network/message_oriented_connection.hpp","line_range":"26-28","gmt_create":"2026-04-28T20:32:44.1798586+04:00","gmt_modified":"2026-04-28T20:32:44.1798586+04:00"},{"id":"37d9a1a0250d6f2b30d0b5acfef7b606","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"26-35","gmt_create":"2026-04-28T20:32:44.1808586+04:00","gmt_modified":"2026-04-28T20:32:44.1808586+04:00"},{"id":"9fbb8ea1084712b6e00c9f6b9ad60c72","path":"libraries/network/include/graphene/network/node.hpp","line_range":"26-31","gmt_create":"2026-04-28T20:32:44.1808586+04:00","gmt_modified":"2026-04-28T20:32:44.1808586+04:00"},{"id":"63f471b93ce70266253197686f8d7df6","path":"libraries/network/include/graphene/network/peer_database.hpp","line_range":"26-35","gmt_create":"2026-04-28T20:32:44.1815978+04:00","gmt_modified":"2026-04-28T20:32:44.1815978+04:00"},{"id":"f18f4bdd5e9956c9abb9a7a52882581a","path":"libraries/network/include/graphene/network/message.hpp","line_range":"26-31","gmt_create":"2026-04-28T20:32:44.1815978+04:00","gmt_modified":"2026-04-28T20:32:44.1815978+04:00"},{"id":"03beec6b648d43a165d35e195b3eb81e","path":"libraries/network/include/graphene/network/core_messages.hpp","line_range":"285-306","gmt_create":"2026-04-28T20:32:44.1841058+04:00","gmt_modified":"2026-04-28T20:32:44.1841058+04:00"},{"id":"8620e966ce1a76d2ab405f5a49743457","path":"libraries/network/include/graphene/network/config.hpp","line_range":"48-50","gmt_create":"2026-04-28T20:32:44.184706+04:00","gmt_modified":"2026-04-28T20:32:44.184706+04:00"},{"id":"d6ed83bad3a3124e13f09c4c1191b96a","path":"libraries/network/peer_connection.cpp","line_range":"314-325","gmt_create":"2026-04-28T20:32:44.1852092+04:00","gmt_modified":"2026-04-28T20:32:44.1852092+04:00"},{"id":"fcdabf2022a050e8a2414fb979f5c419","path":"libraries/network/node.cpp","line_range":"3448-3470","gmt_create":"2026-04-28T20:32:44.1852906+04:00","gmt_modified":"2026-04-28T20:32:44.1852906+04:00"},{"id":"70f9beafdbca4132e0349e28e51fcd6a","path":"libraries/chain/database.cpp","line_range":"1239-1241","gmt_create":"2026-04-28T20:32:44.1868738+04:00","gmt_modified":"2026-04-28T20:32:44.1868738+04:00"},{"id":"e4313416f9a67f60a9a17f977b2367f6","path":"libraries/chain/include/graphene/chain/database_exceptions.hpp","line_range":"86-86","gmt_create":"2026-04-28T20:32:44.1868738+04:00","gmt_modified":"2026-04-28T20:32:44.1868738+04:00"},{"id":"b0498790fa704db3f0e0545f4299a7ba","path":"libraries/network/node.cpp","line_range":"79-82","gmt_create":"2026-04-28T20:32:44.1868738+04:00","gmt_modified":"2026-04-28T20:32:44.1868738+04:00"},{"id":"34cf64d4d37824f3883c4c577708cd70","path":"libraries/network/node.cpp","line_range":"3278-3281","gmt_create":"2026-04-28T20:32:44.1868738+04:00","gmt_modified":"2026-04-28T20:32:44.1868738+04:00"},{"id":"9619a9ec94b261a592033e0c0b3be117","path":"libraries/network/node.cpp","line_range":"3633-3636","gmt_create":"2026-04-28T20:32:44.1878771+04:00","gmt_modified":"2026-04-28T20:32:44.1878771+04:00"},{"id":"99d4fcb1177774e0710d2bd69e6b48d3","path":"libraries/network/node.cpp","line_range":"3653-3656","gmt_create":"2026-04-28T20:32:44.1878771+04:00","gmt_modified":"2026-04-28T20:32:44.1878771+04:00"},{"id":"f652bd37bb37781168ae6561fb03f9b4","path":"libraries/network/node.cpp","line_range":"3671-3674","gmt_create":"2026-04-28T20:32:44.1878771+04:00","gmt_modified":"2026-04-28T20:32:44.1878771+04:00"},{"id":"1fc0915e82dd4c943724dcd2e29106cb","path":"plugins/p2p/p2p_plugin.cpp","line_range":"159-175","gmt_create":"2026-04-28T20:38:58.1508088+04:00","gmt_modified":"2026-04-28T20:38:58.1508088+04:00"},{"id":"b89fc00b26b800a29e1692b23ada0d56","path":"plugins/p2p/p2p_plugin.cpp","line_range":"168-172","gmt_create":"2026-04-28T20:38:58.1560489+04:00","gmt_modified":"2026-04-28T20:38:58.1560489+04:00"},{"id":"4c7480a472e5a837a77edfce9accd782","path":"libraries/chain/database.cpp","line_range":"1360-1380","gmt_create":"2026-04-28T20:42:58.7136976+04:00","gmt_modified":"2026-04-28T20:42:58.7136976+04:00"},{"id":"509be78f5e90ad720bc5cb62f8252773","path":"libraries/chain/database.cpp","line_range":"1204-1270","gmt_create":"2026-04-28T21:02:01.9400592+04:00","gmt_modified":"2026-04-28T21:02:01.9400592+04:00"},{"id":"2f3627b50fb7b7e496ab7f3d82bb011c","path":"plugins/witness/witness.cpp","line_range":"521-544","gmt_create":"2026-04-28T21:02:01.9400592+04:00","gmt_modified":"2026-04-28T21:02:01.9400592+04:00"},{"id":"2426f410a167c0334267f66a00c3b706","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"1-200","gmt_create":"2026-04-28T21:02:01.941062+04:00","gmt_modified":"2026-04-28T21:02:01.941062+04:00"},{"id":"13ad0bfb014506790d3eea0ac1e240eb","path":"plugins/witness/witness.cpp","line_range":"1-697","gmt_create":"2026-04-28T21:02:01.9420619+04:00","gmt_modified":"2026-04-28T21:02:01.9420619+04:00"},{"id":"adbfa85db79875fa5ba5450da427c626","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"110-124","gmt_create":"2026-04-28T21:02:01.9420619+04:00","gmt_modified":"2026-04-28T21:02:01.9420619+04:00"},{"id":"9f5944a7feb01c0c201e4a490c1c6d47","path":"libraries/chain/hardfork.d/12.hf","line_range":"1-7","gmt_create":"2026-04-28T21:02:01.9420619+04:00","gmt_modified":"2026-04-28T21:02:01.9420619+04:00"},{"id":"3697c4125ef62ecb76b9032b46a67ad2","path":"libraries/chain/fork_database.cpp","line_range":"33-92","gmt_create":"2026-04-28T21:02:01.9431433+04:00","gmt_modified":"2026-04-28T21:02:01.9431433+04:00"},{"id":"3ae8431619379889035afee8a7f98a96","path":"libraries/chain/database.cpp","line_range":"1300-1399","gmt_create":"2026-04-28T21:02:01.9442084+04:00","gmt_modified":"2026-04-28T21:02:01.9442084+04:00"},{"id":"4dbf49507663ef2d477a4e56e7ad4113","path":"libraries/chain/fork_database.cpp","line_range":"48-84","gmt_create":"2026-04-28T21:02:01.9453075+04:00","gmt_modified":"2026-04-28T21:02:01.9453075+04:00"},{"id":"a0f13c35d9ba18928ea0803560d59338","path":"libraries/chain/fork_database.cpp","line_range":"48-55","gmt_create":"2026-04-28T21:02:01.9453075+04:00","gmt_modified":"2026-04-28T21:02:01.9453075+04:00"},{"id":"78b7d2f924e066f18e5bf4ed42de6954","path":"libraries/chain/fork_database.cpp","line_range":"33-278","gmt_create":"2026-04-28T21:02:01.9458107+04:00","gmt_modified":"2026-04-28T21:02:01.9458107+04:00"},{"id":"f32c5cb0846168684a2574ccb17d2cd7","path":"libraries/chain/fork_database.cpp","line_range":"189-231","gmt_create":"2026-04-28T21:02:01.9468841+04:00","gmt_modified":"2026-04-28T21:02:01.9468841+04:00"},{"id":"7066405016bb2acea5a040cdac13b646","path":"libraries/chain/database.cpp","line_range":"1037-1177","gmt_create":"2026-04-28T21:02:01.94698+04:00","gmt_modified":"2026-04-28T21:02:01.94698+04:00"},{"id":"1c914e70d8a846e0ed416af007bd2b78","path":"libraries/chain/database.cpp","line_range":"259-294","gmt_create":"2026-04-28T21:02:01.948086+04:00","gmt_modified":"2026-04-28T21:02:01.948086+04:00"},{"id":"d2b082cb10acf9968b8eb975d61abc92","path":"libraries/chain/database.cpp","line_range":"4444-4533","gmt_create":"2026-04-28T21:02:01.948086+04:00","gmt_modified":"2026-04-28T21:02:01.948086+04:00"},{"id":"da4792d808b920542e8e3f85c533f97d","path":"plugins/p2p/p2p_plugin.cpp","line_range":"118-164","gmt_create":"2026-04-28T21:02:01.9490892+04:00","gmt_modified":"2026-04-28T21:02:01.9490892+04:00"},{"id":"fb711bedba071dd61482af3dd825bb98","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"115-128","gmt_create":"2026-04-28T21:02:01.9490892+04:00","gmt_modified":"2026-04-28T21:02:01.9490892+04:00"},{"id":"83d7c3cd276c8b6c3a77f6379e8968e4","path":"libraries/chain/database.cpp","line_range":"561-580","gmt_create":"2026-04-28T21:02:01.9500444+04:00","gmt_modified":"2026-04-28T21:02:01.9500444+04:00"},{"id":"420ba4d931c9392dd8824c0faaf3b99b","path":"libraries/chain/database.cpp","line_range":"738-792","gmt_create":"2026-04-28T21:02:01.9500444+04:00","gmt_modified":"2026-04-28T21:02:01.9500444+04:00"},{"id":"fc699eb749d790d44345a92dd356956e","path":"libraries/chain/database.cpp","line_range":"206-230","gmt_create":"2026-04-28T21:02:01.9505472+04:00","gmt_modified":"2026-04-28T21:02:01.9505472+04:00"},{"id":"217f231a3e1f1591acfb14f4b40fb2b3","path":"libraries/chain/database.cpp","line_range":"476-515","gmt_create":"2026-04-28T21:02:01.9506352+04:00","gmt_modified":"2026-04-28T21:02:01.9506352+04:00"},{"id":"b25bd7f79844225b0a1356e5a6dbee8b","path":"libraries/chain/fork_database.cpp","line_range":"92-103","gmt_create":"2026-04-28T21:02:01.9517314+04:00","gmt_modified":"2026-04-28T21:02:01.9517314+04:00"},{"id":"447323529866a4410bcb34a227ea5b5a","path":"libraries/chain/database.cpp","line_range":"1075-1087","gmt_create":"2026-04-28T21:02:01.9517314+04:00","gmt_modified":"2026-04-28T21:02:01.9517314+04:00"},{"id":"d59a4116db64f54b5d2ce9c64e1c2c50","path":"libraries/chain/database.cpp","line_range":"4581-4594","gmt_create":"2026-04-28T21:02:01.9517314+04:00","gmt_modified":"2026-04-28T21:02:01.9517314+04:00"},{"id":"aa98d4cf2cd33ba5c1e2a0fee44c3645","path":"plugins/witness/witness.cpp","line_range":"597-612","gmt_create":"2026-04-28T21:02:01.9527344+04:00","gmt_modified":"2026-04-28T21:02:01.9527344+04:00"},{"id":"a8acea32ccb60d16cf2952aa7df51926","path":"libraries/chain/database.cpp","line_range":"4334-4438","gmt_create":"2026-04-28T21:02:01.9527344+04:00","gmt_modified":"2026-04-28T21:02:01.9527344+04:00"},{"id":"cda8d76def01f28cd58e347939faf10a","path":"libraries/chain/database.cpp","line_range":"4420-4438","gmt_create":"2026-04-28T21:02:01.9527344+04:00","gmt_modified":"2026-04-28T21:02:01.9527344+04:00"},{"id":"119ec7c643a34aa808f9d5b76965a662","path":"libraries/chain/database.cpp","line_range":"4444-4450","gmt_create":"2026-04-28T21:02:01.9527344+04:00","gmt_modified":"2026-04-28T21:02:01.9527344+04:00"},{"id":"9d352185df5c8af06a8101e30a248ef8","path":"libraries/chain/database.cpp","line_range":"4360-4398","gmt_create":"2026-04-28T21:02:01.9537343+04:00","gmt_modified":"2026-04-28T21:02:01.9537343+04:00"},{"id":"315cdb5412a417207b9932317c120c97","path":"libraries/chain/database.cpp","line_range":"4400-4419","gmt_create":"2026-04-28T21:02:01.9537343+04:00","gmt_modified":"2026-04-28T21:02:01.9537343+04:00"},{"id":"450f29f1b3febed1e121639037851576","path":"plugins/witness/witness.cpp","line_range":"521-526","gmt_create":"2026-04-28T21:02:01.9537343+04:00","gmt_modified":"2026-04-28T21:02:01.9537343+04:00"},{"id":"17c7b337a56de7a5cd94cd10c3d44dac","path":"libraries/chain/database.cpp","line_range":"4428-4430","gmt_create":"2026-04-28T21:02:01.954817+04:00","gmt_modified":"2026-04-28T21:02:01.954817+04:00"},{"id":"48593fdf91cfb9e4763322dc0efe26f4","path":"plugins/witness/witness.cpp","line_range":"565-656","gmt_create":"2026-04-28T21:02:01.954817+04:00","gmt_modified":"2026-04-28T21:02:01.954817+04:00"},{"id":"b71278e4b4266bb8a15b6aba152b2962","path":"plugins/witness/witness.cpp","line_range":"121","gmt_create":"2026-04-28T21:02:01.9553197+04:00","gmt_modified":"2026-04-28T21:02:01.9553197+04:00"},{"id":"065d68f5f096b48e88dfb2982a30bcb6","path":"libraries/chain/fork_database.cpp","line_range":"114-146","gmt_create":"2026-04-28T21:02:01.9563228+04:00","gmt_modified":"2026-04-28T21:02:01.9563228+04:00"},{"id":"c2ea6047c89205fb8308a32ce5248eee","path":"libraries/chain/fork_database.cpp","line_range":"48-103","gmt_create":"2026-04-28T21:02:01.9616165+04:00","gmt_modified":"2026-04-28T21:02:01.9616165+04:00"},{"id":"811ab9cb3d00924a080202c009434d6e","path":"libraries/chain/database.cpp","line_range":"1254-1298","gmt_create":"2026-04-28T21:02:01.9616165+04:00","gmt_modified":"2026-04-28T21:02:01.9616165+04:00"},{"id":"33f7190f97ab910175bbc25c0d3288cc","path":"libraries/chain/fork_database.cpp","line_range":"38-46","gmt_create":"2026-04-28T21:02:01.9645537+04:00","gmt_modified":"2026-04-28T21:02:01.9645537+04:00"},{"id":"536ce91c592f598c4853f295a9a28e59","path":"libraries/chain/fork_database.cpp","line_range":"59-75","gmt_create":"2026-04-28T21:02:01.9650569+04:00","gmt_modified":"2026-04-28T21:02:01.9650569+04:00"},{"id":"49ed36643f75c3d722e6b1a2ff17d7e0","path":"libraries/chain/database.cpp","line_range":"1390-1465","gmt_create":"2026-04-28T21:02:01.9650569+04:00","gmt_modified":"2026-04-28T21:02:01.9650569+04:00"},{"id":"58d38a217f74656ded3587bd3c6f7dc1","path":"plugins/witness/witness.cpp","line_range":"614-646","gmt_create":"2026-04-28T21:02:01.9650569+04:00","gmt_modified":"2026-04-28T21:02:01.9650569+04:00"},{"id":"73975f89dd3f307861db3a250bbdd78c","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"36-200","gmt_create":"2026-04-28T21:03:48.5316429+04:00","gmt_modified":"2026-04-28T21:03:48.5316429+04:00"},{"id":"7624493910e6464a6ff493726bdeee9d","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"53-122","gmt_create":"2026-04-28T21:03:48.5322599+04:00","gmt_modified":"2026-04-28T21:03:48.5322599+04:00"},{"id":"945c89d3db9c4818f2b0a69c5686f53c","path":"libraries/chain/include/graphene/chain/block_log.hpp","line_range":"38-71","gmt_create":"2026-04-28T21:03:48.5322599+04:00","gmt_modified":"2026-04-28T21:03:48.5322599+04:00"},{"id":"6e1a4f8dfd8b1b6545342d2b70d91400","path":"libraries/chain/include/graphene/chain/block_summary_object.hpp","line_range":"19-42","gmt_create":"2026-04-28T21:03:48.5322599+04:00","gmt_modified":"2026-04-28T21:03:48.5322599+04:00"},{"id":"8fef9b15033051e7fdc3504d87a3d365","path":"libraries/network/node.cpp","line_range":"3354-3366","gmt_create":"2026-04-28T21:03:48.5328419+04:00","gmt_modified":"2026-04-28T21:03:48.5328419+04:00"},{"id":"eb846b95ed45c45ecbc3ee5ab1ebd24f","path":"plugins/p2p/p2p_plugin.cpp","line_range":"152-157","gmt_create":"2026-04-28T21:03:48.5328419+04:00","gmt_modified":"2026-04-28T21:03:48.5328419+04:00"},{"id":"5b3217d2dd702e5291fda9a14af9defe","path":"plugins/chain/plugin.cpp","line_range":"104-121","gmt_create":"2026-04-28T21:03:48.5333464+04:00","gmt_modified":"2026-04-28T21:03:48.5333464+04:00"},{"id":"b18325dfe4c9f546850aa53aae935765","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"193-196","gmt_create":"2026-04-28T21:03:48.535692+04:00","gmt_modified":"2026-04-28T21:03:48.535692+04:00"},{"id":"7f585b7ee9cce4a8ab1f33b5b34f7f81","path":"libraries/chain/database.cpp","line_range":"737-792","gmt_create":"2026-04-28T21:03:48.535692+04:00","gmt_modified":"2026-04-28T21:03:48.535692+04:00"},{"id":"a2dca3272c082a5aafb990a745f3fd75","path":"plugins/p2p/p2p_plugin.cpp","line_range":"142-174","gmt_create":"2026-04-28T21:03:48.5366953+04:00","gmt_modified":"2026-04-28T21:03:48.5366953+04:00"},{"id":"5256db9d4dcb3c7fd627ebb8f8547f74","path":"plugins/chain/plugin.cpp","line_range":"103-142","gmt_create":"2026-04-28T21:03:48.5366953+04:00","gmt_modified":"2026-04-28T21:03:48.5366953+04:00"},{"id":"6e54a9c0d543d98384d205535ead2c39","path":"libraries/chain/database.cpp","line_range":"800-925","gmt_create":"2026-04-28T21:03:48.5366953+04:00","gmt_modified":"2026-04-28T21:03:48.5366953+04:00"},{"id":"d2efa60ce77e110221f81af1427289e6","path":"libraries/chain/block_log.cpp","line_range":"195-226","gmt_create":"2026-04-28T21:03:48.5384867+04:00","gmt_modified":"2026-04-28T21:03:48.5384867+04:00"},{"id":"fdfd8e0c768f197ad065fbd7ffc0d0c1","path":"libraries/chain/block_log.cpp","line_range":"263-299","gmt_create":"2026-04-28T21:03:48.5384867+04:00","gmt_modified":"2026-04-28T21:03:48.5384867+04:00"},{"id":"5573208de1a8f72a275c35db65fc7bd8","path":"libraries/chain/database.cpp","line_range":"847-925","gmt_create":"2026-04-28T21:03:48.5423108+04:00","gmt_modified":"2026-04-28T21:03:48.5423108+04:00"},{"id":"2f1395bd486711cb5d7bc0cd80794c96","path":"libraries/chain/database.cpp","line_range":"3443-3500","gmt_create":"2026-04-28T21:03:48.5428819+04:00","gmt_modified":"2026-04-28T21:03:48.5428819+04:00"},{"id":"1c4d9b7ff7c2fd8f72e2d64d37f89138","path":"libraries/chain/database.cpp","line_range":"3723-3748","gmt_create":"2026-04-28T21:03:48.5428819+04:00","gmt_modified":"2026-04-28T21:03:48.5428819+04:00"},{"id":"a5660f23ac4e94b12f678a1bbc729490","path":"libraries/chain/database.cpp","line_range":"3750-3757","gmt_create":"2026-04-28T21:03:48.5428819+04:00","gmt_modified":"2026-04-28T21:03:48.5428819+04:00"},{"id":"54e518a8354b5d7fb8af8a31a68d3a41","path":"libraries/chain/database.cpp","line_range":"3759-3873","gmt_create":"2026-04-28T21:03:48.5433845+04:00","gmt_modified":"2026-04-28T21:03:48.5433845+04:00"},{"id":"bbb78af6252229f84570f752f6e862a7","path":"libraries/chain/database.cpp","line_range":"2824-2837","gmt_create":"2026-04-28T21:03:48.5434552+04:00","gmt_modified":"2026-04-28T21:03:48.5434552+04:00"},{"id":"a18ffbd92c9d4b692fc557c3652088aa","path":"libraries/chain/database.cpp","line_range":"2871-2884","gmt_create":"2026-04-28T21:03:48.5439578+04:00","gmt_modified":"2026-04-28T21:03:48.5439578+04:00"},{"id":"fce8b0371886adfbccd297ca25bedd14","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"185-187","gmt_create":"2026-04-28T21:03:48.5444769+04:00","gmt_modified":"2026-04-28T21:03:48.5444769+04:00"},{"id":"6f9d8d9e0dc35b4525248327639277c9","path":"libraries/chain/database.cpp","line_range":"3724-3748","gmt_create":"2026-04-28T21:03:48.5444769+04:00","gmt_modified":"2026-04-28T21:03:48.5444769+04:00"},{"id":"601b79126e6723e1b2405eb7e5f8be93","path":"libraries/chain/database.cpp","line_range":"270-300","gmt_create":"2026-04-28T21:03:48.5459287+04:00","gmt_modified":"2026-04-28T21:03:48.5459287+04:00"},{"id":"5f0e48d3f7fad098a67eed7a88dc6134","path":"libraries/chain/database.cpp","line_range":"250-257","gmt_create":"2026-04-28T21:03:48.5459287+04:00","gmt_modified":"2026-04-28T21:03:48.5459287+04:00"},{"id":"a0d3e919d8c2dcef090c5eb962daae1a","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"3-8","gmt_create":"2026-04-28T21:03:48.5481192+04:00","gmt_modified":"2026-04-28T21:03:48.5481192+04:00"},{"id":"54b31fbe33ef1eb12566c9f84fcf779d","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"3-18","gmt_create":"2026-04-28T21:03:48.5491225+04:00","gmt_modified":"2026-04-28T21:03:48.5491225+04:00"},{"id":"3503ce5765018277af90477a590846e3","path":"libraries/chain/include/graphene/chain/block_log.hpp","line_range":"3-9","gmt_create":"2026-04-28T21:03:48.5491225+04:00","gmt_modified":"2026-04-28T21:03:48.5491225+04:00"},{"id":"24fdd3fcf0b1451195e987e21e994f65","path":"libraries/chain/database.cpp","line_range":"804-823","gmt_create":"2026-04-28T21:03:48.5511255+04:00","gmt_modified":"2026-04-28T21:03:48.5511255+04:00"},{"id":"3758345d86110e6830e316917030c83c","path":"libraries/chain/database.cpp","line_range":"1-6669","gmt_create":"2026-04-28T22:02:38.4590665+04:00","gmt_modified":"2026-04-28T22:02:38.4590665+04:00"},{"id":"73a54c033320ed8363fc781d798df0be","path":"libraries/chain/database.cpp","line_range":"1420-1510","gmt_create":"2026-04-28T22:02:38.4631999+04:00","gmt_modified":"2026-04-28T22:02:38.4631999+04:00"},{"id":"21fa05f4a65dabefa29dcd9515e99399","path":"libraries/chain/database.cpp","line_range":"1432-1498","gmt_create":"2026-04-28T22:02:38.4631999+04:00","gmt_modified":"2026-04-28T22:02:38.4631999+04:00"},{"id":"67ee60a8299f84cdefc03cf72aeb2083","path":"thirdparty/fc/include/fc/exception/exception.hpp","line_range":"177-215","gmt_create":"2026-04-28T22:07:48.9822613+04:00","gmt_modified":"2026-04-28T22:07:48.9822613+04:00"},{"id":"6b69ae0fdd0cccce913fc2d7aaa732b0","path":"thirdparty/fc/src/exception.cpp","line_range":"166-186","gmt_create":"2026-04-28T22:07:48.9822613+04:00","gmt_modified":"2026-04-28T22:07:48.9822613+04:00"},{"id":"7df358ffe779de2ba25c910aced557fd","path":"libraries/protocol/include/graphene/protocol/exceptions.hpp","line_range":"21-46","gmt_create":"2026-04-28T22:07:48.9822613+04:00","gmt_modified":"2026-04-28T22:07:48.9822613+04:00"},{"id":"6fb08c879f27957a5d358cff4532c031","path":"libraries/chain/database.cpp","line_range":"1440-1500","gmt_create":"2026-04-28T22:07:49.0133794+04:00","gmt_modified":"2026-04-28T22:07:49.0133794+04:00"},{"id":"672cdc6d6a7b32cf5afabd6fdbce20d3","path":"libraries/network/node.cpp","line_range":"3540-3562","gmt_create":"2026-04-28T22:30:12.955321+04:00","gmt_modified":"2026-04-28T22:30:12.955321+04:00"},{"id":"4c8e75baa374d11c9e8793b8772d0db9","path":"libraries/network/node.cpp","line_range":"3920-3940","gmt_create":"2026-04-28T22:30:12.955321+04:00","gmt_modified":"2026-04-28T22:30:12.955321+04:00"},{"id":"a6bfdd40fb3a5836fbabdb5158fa34fe","path":"share/vizd/config/config.ini","line_range":"103-108","gmt_create":"2026-04-28T22:30:12.955321+04:00","gmt_modified":"2026-04-28T22:30:12.955321+04:00"},{"id":"209759d869965a0c3eba1e658fa84845","path":"plugins/p2p/p2p_plugin.cpp","line_range":"633-689","gmt_create":"2026-04-28T22:30:12.9593209+04:00","gmt_modified":"2026-04-28T22:30:12.9593209+04:00"},{"id":"3d75047030281d13fb2d55905e0c69c8","path":"plugins/chain/plugin.cpp","line_range":"183-649","gmt_create":"2026-04-29T06:59:11.2767199+04:00","gmt_modified":"2026-04-29T06:59:11.2767199+04:00"},{"id":"f75d35fc9523fc188eb18b8741e33f6d","path":"libraries/chain/database.cpp","line_range":"351-544","gmt_create":"2026-04-29T06:59:11.2772632+04:00","gmt_modified":"2026-04-29T06:59:11.2772632+04:00"},{"id":"22a0b873abfd87b49bc4ca088c641dcd","path":"plugins/snapshot/plugin.cpp","line_range":"3031-3118","gmt_create":"2026-04-29T06:59:11.2784558+04:00","gmt_modified":"2026-04-29T06:59:11.2784558+04:00"},{"id":"2568823f5e70aacc0929c3200c44c688","path":"plugins/chain/plugin.cpp","line_range":"1-694","gmt_create":"2026-04-29T06:59:11.2791061+04:00","gmt_modified":"2026-04-29T06:59:11.2791061+04:00"},{"id":"7171e5fb841e1d5c8e4435a9e0e75f56","path":"libraries/chain/database.cpp","line_range":"1-6314","gmt_create":"2026-04-29T06:59:11.2796088+04:00","gmt_modified":"2026-04-29T06:59:11.2796088+04:00"},{"id":"668e6362de53625901201e8370b90e80","path":"plugins/chain/include/graphene/plugins/chain/plugin.hpp","line_range":"21-124","gmt_create":"2026-04-29T06:59:11.2796088+04:00","gmt_modified":"2026-04-29T06:59:11.2796088+04:00"},{"id":"86f066e47903bbd61b02a545ae4e6551","path":"plugins/chain/plugin.cpp","line_range":"21-93","gmt_create":"2026-04-29T06:59:11.2801286+04:00","gmt_modified":"2026-04-29T06:59:11.2801286+04:00"},{"id":"9c28f276f937ea7573f48b90b8f92d7f","path":"plugins/chain/plugin.cpp","line_range":"103-183","gmt_create":"2026-04-29T06:59:11.2806462+04:00","gmt_modified":"2026-04-29T06:59:11.2806462+04:00"},{"id":"371ae6e78ddcaea6c7ab821b81b12e35","path":"plugins/chain/plugin.cpp","line_range":"650-666","gmt_create":"2026-04-29T06:59:11.2811934+04:00","gmt_modified":"2026-04-29T06:59:11.2811934+04:00"},{"id":"13058dc3cfe4c9f00b04c9e22c6c9b70","path":"plugins/chain/plugin.cpp","line_range":"197-272","gmt_create":"2026-04-29T06:59:11.2811934+04:00","gmt_modified":"2026-04-29T06:59:11.2811934+04:00"},{"id":"16a421cb814c03e0297d814e4c7fcbcf","path":"plugins/chain/plugin.cpp","line_range":"274-386","gmt_create":"2026-04-29T06:59:11.2817133+04:00","gmt_modified":"2026-04-29T06:59:11.2817133+04:00"},{"id":"e76a2e15b7d528b5eb96fad4fe23bd9c","path":"plugins/chain/plugin.cpp","line_range":"388-649","gmt_create":"2026-04-29T06:59:11.2817133+04:00","gmt_modified":"2026-04-29T06:59:11.2817133+04:00"},{"id":"2fa9c0e59e88a84f214e07f4a5eba712","path":"libraries/chain/database.cpp","line_range":"4253-4323","gmt_create":"2026-04-29T06:59:11.2822306+04:00","gmt_modified":"2026-04-29T06:59:11.2822306+04:00"},{"id":"e9a71b0c283c28e9108702f7ca2163f7","path":"libraries/chain/database.cpp","line_range":"4314-4323","gmt_create":"2026-04-29T06:59:11.2827407+04:00","gmt_modified":"2026-04-29T06:59:11.2827407+04:00"},{"id":"97ee13777a0aa5665ed9d742b01a6df1","path":"plugins/chain/plugin.cpp","line_range":"420-475","gmt_create":"2026-04-29T06:59:11.283867+04:00","gmt_modified":"2026-04-29T06:59:11.283867+04:00"},{"id":"bd4c9ed516dc18cb55f239f6feab6a30","path":"plugins/snapshot/plugin.cpp","line_range":"3031-3042","gmt_create":"2026-04-29T06:59:11.2843824+04:00","gmt_modified":"2026-04-29T06:59:11.2843824+04:00"},{"id":"284384a88a8e22967e75aef3b6e3562e","path":"plugins/chain/plugin.cpp","line_range":"566-649","gmt_create":"2026-04-29T06:59:11.2848966+04:00","gmt_modified":"2026-04-29T06:59:11.2848966+04:00"},{"id":"00e1ac390ce60cf05032160e029fc05c","path":"plugins/chain/plugin.cpp","line_range":"344-382","gmt_create":"2026-04-29T06:59:11.2854101+04:00","gmt_modified":"2026-04-29T06:59:11.2854101+04:00"},{"id":"759a8a5afbc2bd120cc23c1a54071c79","path":"plugins/snapshot/plugin.cpp","line_range":"2817-2861","gmt_create":"2026-04-29T06:59:11.2854101+04:00","gmt_modified":"2026-04-29T06:59:11.2854101+04:00"},{"id":"4370e8763bbca8a166f9af80993701e9","path":"plugins/snapshot/plugin.cpp","line_range":"2908-2920","gmt_create":"2026-04-29T06:59:11.2859241+04:00","gmt_modified":"2026-04-29T06:59:11.2859241+04:00"},{"id":"8c0eb1fd78ceb38fa43fd2a1b7f10412","path":"plugins/chain/plugin.cpp","line_range":"105-121","gmt_create":"2026-04-29T06:59:11.2869502+04:00","gmt_modified":"2026-04-29T06:59:11.2869502+04:00"},{"id":"a4377fbb29614b9499ed7cba50185337","path":"thirdparty/fc/src/log/console_appender.cpp","line_range":"71-84","gmt_create":"2026-04-29T06:59:11.2869502+04:00","gmt_modified":"2026-04-29T06:59:11.2869502+04:00"},{"id":"9d89f7774c243fa2dcd1a6b44f750558","path":"thirdparty/fc/src/log/console_defines.h","line_range":"146-188","gmt_create":"2026-04-29T06:59:11.2874695+04:00","gmt_modified":"2026-04-29T06:59:11.2874695+04:00"},{"id":"8a80f90958428689e890e5b0a8e84f60","path":"thirdparty/fc/src/log/logger_config.cpp","line_range":"69-89","gmt_create":"2026-04-29T06:59:11.2874695+04:00","gmt_modified":"2026-04-29T06:59:11.2874695+04:00"},{"id":"65bbe0fb033855becef1089d08ed50bf","path":"programs/vizd/main.cpp","line_range":"234-250","gmt_create":"2026-04-29T06:59:11.2879852+04:00","gmt_modified":"2026-04-29T06:59:11.2879852+04:00"},{"id":"37d1a2d00fcd56aea0848ebca3f2a926","path":"plugins/chain/plugin.cpp","line_range":"757-816","gmt_create":"2026-04-29T06:59:11.2879852+04:00","gmt_modified":"2026-04-29T06:59:11.2879852+04:00"},{"id":"621d366163e32d2a00e77366a0cca1ea","path":"plugins/chain/plugin.cpp","line_range":"547-600","gmt_create":"2026-04-29T06:59:11.2879852+04:00","gmt_modified":"2026-04-29T06:59:11.2879852+04:00"},{"id":"be8f40bd072828763bbdf031b6f1620e","path":"libraries/chain/include/graphene/chain/database_exceptions.hpp","line_range":"122","gmt_create":"2026-04-29T06:59:11.2885025+04:00","gmt_modified":"2026-04-29T06:59:11.2885025+04:00"},{"id":"db43b1af690cc5f915a6971cef72f698","path":"plugins/chain/plugin.cpp","line_range":"1-12","gmt_create":"2026-04-29T06:59:11.2890192+04:00","gmt_modified":"2026-04-29T06:59:11.2890192+04:00"},{"id":"46d15c6ef6c17169ac51cbdc06ed7053","path":"plugins/chain/include/graphene/plugins/chain/plugin.hpp","line_range":"23-24","gmt_create":"2026-04-29T06:59:11.2890192+04:00","gmt_modified":"2026-04-29T06:59:11.2890192+04:00"},{"id":"2727250a5ed0225f9707859ab095dfe8","path":"plugins/chain/plugin.cpp","line_range":"92-105","gmt_create":"2026-04-29T06:59:11.2895379+04:00","gmt_modified":"2026-04-29T06:59:11.2895379+04:00"},{"id":"9bf43751ce7ff4f9e6f159edc4314caf","path":"plugins/chain/plugin.cpp","line_range":"24-51","gmt_create":"2026-04-29T06:59:11.2895379+04:00","gmt_modified":"2026-04-29T06:59:11.2895379+04:00"},{"id":"0cd78f3f9caeaceed6acf82a3b374de0","path":"plugins/chain/plugin.cpp","line_range":"398-418","gmt_create":"2026-04-29T06:59:11.2895379+04:00","gmt_modified":"2026-04-29T06:59:11.2895379+04:00"},{"id":"c0c7e7261cd9841beac6c26b47f47cf6","path":"plugins/chain/plugin.cpp","line_range":"562-601","gmt_create":"2026-04-29T06:59:11.2895379+04:00","gmt_modified":"2026-04-29T06:59:11.2895379+04:00"},{"id":"a8aa23bf7495e9cc8c7a2560a6157222","path":"plugins/chain/plugin.cpp","line_range":"251-271","gmt_create":"2026-04-29T06:59:11.2895379+04:00","gmt_modified":"2026-04-29T06:59:11.2895379+04:00"},{"id":"a7270a78978a13ee55ddc7f94813e3df","path":"libraries/chain/database.cpp","line_range":"1680-1693","gmt_create":"2026-04-29T07:04:36.8236407+04:00","gmt_modified":"2026-04-29T07:04:36.8236407+04:00"},{"id":"a7f5c35a309d6a9851c0491c125c63d9","path":"libraries/chain/database.cpp","line_range":"3224-3236","gmt_create":"2026-04-29T07:04:36.8236407+04:00","gmt_modified":"2026-04-29T07:04:36.8236407+04:00"},{"id":"2f5c46c8352dc73f0b90e01cef1bcc9d","path":"libraries/chain/database.cpp","line_range":"3272-3284","gmt_create":"2026-04-29T07:04:36.8243301+04:00","gmt_modified":"2026-04-29T07:04:36.8243301+04:00"},{"id":"26495d5b0fffb6fefe7f8c69d291521e","path":"plugins/witness/witness.cpp","line_range":"738-742","gmt_create":"2026-04-29T07:04:36.8243301+04:00","gmt_modified":"2026-04-29T07:04:36.8243301+04:00"},{"id":"3bdfaf9a009f1c5236532831eedfa66b","path":"plugins/chain/plugin.cpp","line_range":"760-770","gmt_create":"2026-04-29T07:04:36.8243301+04:00","gmt_modified":"2026-04-29T07:04:36.8243301+04:00"},{"id":"943ef4fb5df40c9942aadddd7040bc7c","path":"libraries/chain/database.cpp","line_range":"4863-5004","gmt_create":"2026-04-29T22:58:12.6480888+04:00","gmt_modified":"2026-04-29T22:58:12.6480888+04:00"},{"id":"1273c494621ef1c9351fa8b783dc6ae6","path":"plugins/witness/witness.cpp","line_range":"422-427","gmt_create":"2026-04-29T22:58:12.649406+04:00","gmt_modified":"2026-04-29T22:58:12.649406+04:00"},{"id":"cf67769b74f5699e4a347dc2d7092ceb","path":"thirdparty/chainbase/include/chainbase/chainbase.hpp","line_range":"1097-1115","gmt_create":"2026-04-29T22:58:12.6499089+04:00","gmt_modified":"2026-04-29T22:58:12.6499089+04:00"},{"id":"ffdc85ed129bbf1af13ac8fb289ef5cc","path":"libraries/chain/database.cpp","line_range":"4887-4906","gmt_create":"2026-04-29T22:58:12.6537575+04:00","gmt_modified":"2026-04-29T22:58:12.6537575+04:00"},{"id":"c1530ddb1ea5d36cd757849d7b1bae47","path":"libraries/chain/database.cpp","line_range":"2614-2631","gmt_create":"2026-04-29T22:58:12.6558059+04:00","gmt_modified":"2026-04-29T22:58:12.6558059+04:00"},{"id":"e233bb5840d58fff00c516dfc7186b2f","path":"libraries/protocol/include/graphene/protocol/config.hpp","line_range":"125-128","gmt_create":"2026-04-29T22:58:12.6558059+04:00","gmt_modified":"2026-04-29T22:58:12.6558059+04:00"},{"id":"d2f89808b761a3c0eca43468d945f372","path":"libraries/chain/database.cpp","line_range":"1721","gmt_create":"2026-04-29T22:58:12.6575821+04:00","gmt_modified":"2026-04-29T22:58:12.6575821+04:00"},{"id":"2b851e123aa78f2afb16a52472781e64","path":"plugins/witness/witness.cpp","line_range":"228-233","gmt_create":"2026-04-29T23:00:33.6611122+04:00","gmt_modified":"2026-04-29T23:00:33.6611122+04:00"},{"id":"0c5e33130a4b0128f85c5ef5837b2837","path":"plugins/witness/witness.cpp","line_range":"338-407","gmt_create":"2026-04-29T23:00:33.6751487+04:00","gmt_modified":"2026-04-29T23:00:33.6751487+04:00"},{"id":"b4b9171ebcdf9b3750b66cea688f5e6b","path":"plugins/witness/witness.cpp","line_range":"411-419","gmt_create":"2026-04-29T23:00:33.6751487+04:00","gmt_modified":"2026-04-29T23:00:33.6751487+04:00"},{"id":"673f7fff8a03580c3caedc629fc67cd4","path":"libraries/chain/include/graphene/chain/database.hpp","line_range":"60","gmt_create":"2026-04-29T23:00:33.6751487+04:00","gmt_modified":"2026-04-29T23:00:33.6751487+04:00"},{"id":"dbf1a067fe97d23702c1dcb8e2df53b6","path":"libraries/chain/database.cpp","line_range":"1890-1892","gmt_create":"2026-04-29T23:00:33.6761486+04:00","gmt_modified":"2026-04-29T23:00:33.6761486+04:00"},{"id":"2ad181689b70c433e1ec262fbbedb608","path":"libraries/chain/database.cpp","line_range":"4536-4573","gmt_create":"2026-04-29T23:00:33.6762262+04:00","gmt_modified":"2026-04-29T23:00:33.6762262+04:00"},{"id":"73337c954cb4cc654d9a0b55b95b2480","path":"libraries/chain/database.cpp","line_range":"5530-5655","gmt_create":"2026-04-29T23:00:33.6762262+04:00","gmt_modified":"2026-04-29T23:00:33.6762262+04:00"},{"id":"8c1b3bca4f56b317e9e0acdb4be83a00","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"1-89","gmt_create":"2026-04-29T23:01:32.2785851+04:00","gmt_modified":"2026-04-29T23:01:32.2785851+04:00"},{"id":"a3ae1dbcc1c9bd6ef59dec7fb6a6e6be","path":"libraries/chain/dlt_block_log.cpp","line_range":"1-582","gmt_create":"2026-04-29T23:01:32.2801985+04:00","gmt_modified":"2026-04-29T23:01:32.2801985+04:00"},{"id":"445ddb5941dc16c8a6313b9ec556f75a","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","line_range":"35-89","gmt_create":"2026-04-29T23:01:32.282913+04:00","gmt_modified":"2026-04-29T23:01:32.282913+04:00"},{"id":"8d97625477363c05b9feab2d2cbb781b","path":"libraries/chain/dlt_block_log.cpp","line_range":"31-38","gmt_create":"2026-04-29T23:01:32.2889728+04:00","gmt_modified":"2026-04-29T23:01:32.2889728+04:00"},{"id":"1b89c3c75aec06d4b094f1f6692436f9","path":"libraries/chain/dlt_block_log.cpp","line_range":"59-66","gmt_create":"2026-04-29T23:01:32.2889728+04:00","gmt_modified":"2026-04-29T23:01:32.2889728+04:00"},{"id":"3f4e4b4d64c6750b34e1fe180b376017","path":"libraries/chain/dlt_block_log.cpp","line_range":"119-136","gmt_create":"2026-04-29T23:01:32.2894757+04:00","gmt_modified":"2026-04-29T23:01:32.2894757+04:00"},{"id":"1853975a7087e8a5073352a9a8ea53c6","path":"libraries/chain/dlt_block_log.cpp","line_range":"138-155","gmt_create":"2026-04-29T23:01:32.2897898+04:00","gmt_modified":"2026-04-29T23:01:32.2897898+04:00"},{"id":"23c70bfc8bc9d7d6ea64c01cbc8cfa74","path":"libraries/chain/dlt_block_log.cpp","line_range":"545-579","gmt_create":"2026-04-29T23:01:32.2936387+04:00","gmt_modified":"2026-04-29T23:01:32.2936387+04:00"},{"id":"57b5a41ba76b9bfcb250e93e89805515","path":"plugins/p2p/p2p_plugin.cpp","line_range":"761-765","gmt_create":"2026-04-29T23:01:32.2940378+04:00","gmt_modified":"2026-04-29T23:01:32.2940378+04:00"},{"id":"3397b02998da3d1df1abfc39e178cd35","path":"libraries/chain/dlt_block_log.cpp","line_range":"74-100","gmt_create":"2026-04-29T23:01:32.2982799+04:00","gmt_modified":"2026-04-29T23:01:32.2982799+04:00"},{"id":"dfc65f88824f860423d799827fdee7aa","path":"libraries/chain/dlt_block_log.cpp","line_range":"545-574","gmt_create":"2026-04-29T23:01:32.2982799+04:00","gmt_modified":"2026-04-29T23:01:32.2982799+04:00"},{"id":"443c70bf08a7229603f07d4b6e2ceb04","path":"libraries/chain/dlt_block_log.cpp","line_range":"304-369","gmt_create":"2026-04-29T23:01:32.2994015+04:00","gmt_modified":"2026-04-29T23:01:32.2994015+04:00"},{"id":"766ad50513305c2c4955788de08b2f70","path":"plugins/p2p/p2p_plugin.cpp","line_range":"757-765","gmt_create":"2026-04-29T23:01:32.3140419+04:00","gmt_modified":"2026-04-29T23:01:32.3140419+04:00"},{"id":"61447f1b270e050eaf1594622e8344cb","path":"libraries/chain/database.cpp","line_range":"303-357","gmt_create":"2026-04-30T07:13:19.6582717+04:00","gmt_modified":"2026-04-30T07:13:19.6582717+04:00"},{"id":"87460891e469970ebd1b4357e65a305d","path":"libraries/chain/database.cpp","line_range":"2561-2591","gmt_create":"2026-04-30T07:13:19.658785+04:00","gmt_modified":"2026-04-30T07:13:19.658785+04:00"},{"id":"e8bcacd1aeb9acccff2e7846bdb418ea","path":"libraries/chain/database.cpp","line_range":"2596-2612","gmt_create":"2026-04-30T07:13:19.658785+04:00","gmt_modified":"2026-04-30T07:13:19.658785+04:00"},{"id":"3addab2306735a8fd2b97c7337a752aa","path":"libraries/chain/database.cpp","line_range":"5473-5545","gmt_create":"2026-04-30T07:13:19.6598205+04:00","gmt_modified":"2026-04-30T07:13:19.6598205+04:00"},{"id":"cf15af9ccdfa43fad1c61be4c143bb9f","path":"libraries/chain/database.cpp","line_range":"5515-5529","gmt_create":"2026-04-30T07:13:19.6603381+04:00","gmt_modified":"2026-04-30T07:13:19.6603381+04:00"},{"id":"4e999921565c672e1c3f31f949a25803","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"128-150","gmt_create":"2026-04-30T07:19:20.4192293+04:00","gmt_modified":"2026-04-30T07:19:20.4192293+04:00"},{"id":"2f228d36a18a021f1d730c1fa50466da","path":"plugins/p2p/p2p_plugin.cpp","line_range":"739-760","gmt_create":"2026-04-30T07:19:20.4207886+04:00","gmt_modified":"2026-04-30T07:19:20.4207886+04:00"},{"id":"da0076fc30299f9f1dd09e50dc181609","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"1-168","gmt_create":"2026-04-30T07:19:20.4207886+04:00","gmt_modified":"2026-04-30T07:19:20.4207886+04:00"},{"id":"24e9e8b3ff6a7469a09e87e94f9832b8","path":"plugins/p2p/p2p_plugin.cpp","line_range":"735-771","gmt_create":"2026-04-30T07:19:20.422349+04:00","gmt_modified":"2026-04-30T07:19:20.422349+04:00"},{"id":"532021a3aea4c3d7bfe25231df5687c0","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"53-168","gmt_create":"2026-04-30T07:19:20.4229287+04:00","gmt_modified":"2026-04-30T07:19:20.4229287+04:00"},{"id":"32f0435e4c1faa3d0978c1dcac09d7ce","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"20-168","gmt_create":"2026-04-30T07:19:20.4250024+04:00","gmt_modified":"2026-04-30T07:19:20.4250024+04:00"},{"id":"de96a3aab8e8260a79d1b9c380481b26","path":"libraries/chain/include/graphene/chain/fork_database.hpp","line_range":"111-168","gmt_create":"2026-04-30T07:19:20.4250024+04:00","gmt_modified":"2026-04-30T07:19:20.4250024+04:00"},{"id":"cde5dc8aa90fe3ae3c802f8d7f235e74","path":"plugins/p2p/p2p_plugin.cpp","line_range":"762-770","gmt_create":"2026-04-30T07:19:20.4318176+04:00","gmt_modified":"2026-04-30T07:19:20.4318176+04:00"},{"id":"51d09407b23ce3234d4e1de6142dce78","path":"plugins/p2p/p2p_plugin.cpp","line_range":"739-771","gmt_create":"2026-04-30T07:19:20.4318176+04:00","gmt_modified":"2026-04-30T07:19:20.4318176+04:00"},{"id":"45d31a7a748eda3ea48f7df17810d4cb","path":"libraries/network/peer_connection.cpp","line_range":"419-448","gmt_create":"2026-04-30T07:20:23.7936849+04:00","gmt_modified":"2026-04-30T07:20:23.7936849+04:00"},{"id":"b06d8268387488f1ba89995b6c673d7f","path":"libraries/network/node.cpp","line_range":"1805-1865","gmt_create":"2026-04-30T07:20:23.7987029+04:00","gmt_modified":"2026-04-30T07:20:23.7987029+04:00"},{"id":"d1aff9a930eaeb19127cc400cd6df14b","path":"libraries/network/node.cpp","line_range":"5281-5320","gmt_create":"2026-04-30T07:20:23.7992062+04:00","gmt_modified":"2026-04-30T07:20:23.7992062+04:00"},{"id":"4f2bc001f6d8d7b016c08476699c545c","path":"libraries/network/node.cpp","line_range":"3396-3475","gmt_create":"2026-04-30T07:20:23.7992652+04:00","gmt_modified":"2026-04-30T07:20:23.7992652+04:00"},{"id":"a3ff28e788c1b6c8bf86034c0dc8dfec","path":"libraries/network/node.cpp","line_range":"3280-3351","gmt_create":"2026-04-30T07:20:23.7992652+04:00","gmt_modified":"2026-04-30T07:20:23.7992652+04:00"},{"id":"fa40cce63b48435566df6c01e8048d9f","path":"libraries/network/peer_connection.cpp","line_range":"428-448","gmt_create":"2026-04-30T07:20:23.7992652+04:00","gmt_modified":"2026-04-30T07:20:23.7992652+04:00"},{"id":"1029e6d2173388f17be8ad02924bec51","path":"libraries/network/node.cpp","line_range":"5321-5351","gmt_create":"2026-04-30T07:20:23.7992652+04:00","gmt_modified":"2026-04-30T07:20:23.7992652+04:00"},{"id":"9448259d344c27f9c6996ae60cf5aca0","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"276-298","gmt_create":"2026-04-30T07:20:23.7997683+04:00","gmt_modified":"2026-04-30T07:20:23.7997683+04:00"},{"id":"6e13c0b7bd5f1729508c365e49c421e2","path":"libraries/network/node.cpp","line_range":"3413-3428","gmt_create":"2026-04-30T07:20:23.802979+04:00","gmt_modified":"2026-04-30T07:20:23.802979+04:00"},{"id":"50f93c7183f911eb73a238f8bcc6a562","path":"libraries/network/node.cpp","line_range":"3355-3394","gmt_create":"2026-04-30T07:20:23.802979+04:00","gmt_modified":"2026-04-30T07:20:23.802979+04:00"},{"id":"90fd78bbe7cb7704cadf5d5f0735cb01","path":"libraries/network/node.cpp","line_range":"3334-3351","gmt_create":"2026-04-30T07:20:23.8034959+04:00","gmt_modified":"2026-04-30T07:20:23.8034959+04:00"},{"id":"3926b37345d6d1e98bd8faf06e492e48","path":"plugins/p2p/p2p_plugin.cpp","line_range":"722-771","gmt_create":"2026-04-30T07:26:56.344451+04:00","gmt_modified":"2026-04-30T07:26:56.344451+04:00"},{"id":"f00d25f601ba380af2e6c7e1cc5c9bd5","path":"libraries/chain/database.cpp","line_range":"1-6760","gmt_create":"2026-04-30T07:30:11.7801188+04:00","gmt_modified":"2026-04-30T07:30:11.7801188+04:00"},{"id":"02d86b87b26250b29b6e613abead3df4","path":"thirdparty/fc/src/stacktrace.cpp","line_range":"1-78","gmt_create":"2026-04-30T07:30:11.7827475+04:00","gmt_modified":"2026-04-30T07:30:11.7827475+04:00"},{"id":"b730f8795ffebcda8a8f085fd025dc0b","path":"thirdparty/fc/src/stacktrace.cpp","line_range":"72-78","gmt_create":"2026-04-30T07:30:11.7878598+04:00","gmt_modified":"2026-04-30T07:30:11.7878598+04:00"},{"id":"67e81d60b5109e19786b8bfbc4a80abd","path":"libraries/chain/database.cpp","line_range":"2281-2283","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"09f8b4a6f66776095afcdc7473efbaf2","path":"libraries/chain/database.cpp","line_range":"2466-2467","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"9a263f97b2eae42c0e192fb1cfee8776","path":"libraries/chain/database.cpp","line_range":"2526-2527","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"ea9495482bd4ecf48df793f9abf39cc1","path":"libraries/chain/database.cpp","line_range":"2536-2537","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"47d651650f297dcd4ffcf47ee28027f2","path":"libraries/chain/database.cpp","line_range":"4536-4537","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"5452e208808a1bd3d416cced7e6d6f0a","path":"libraries/chain/database.cpp","line_range":"4538-4539","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"5affc96a80f081a4ef8bd4b5177ef1a8","path":"libraries/chain/database.cpp","line_range":"4544-4545","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"dd981f6523bfe5386b3d4a97ca575fdf","path":"libraries/chain/database.cpp","line_range":"4567-4568","gmt_create":"2026-04-30T07:30:11.8080168+04:00","gmt_modified":"2026-04-30T07:30:11.8080168+04:00"},{"id":"e0ef3c7d489444d0a7bf6cd609a98c05","path":"libraries/chain/database.cpp","line_range":"4569-4570","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"13ee871790dddd2f670c1493c6665f41","path":"libraries/chain/database.cpp","line_range":"4571-4572","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"90331ddec7a3725739758ff2180d5b8e","path":"libraries/chain/database.cpp","line_range":"4573-4574","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"654c2896a03be742e37554a614a0244c","path":"libraries/chain/database.cpp","line_range":"5530-5531","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"e9e61fd4b1cf8706755eb1dfa541ccc7","path":"libraries/chain/database.cpp","line_range":"5543-5544","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"b7aaa429e07e8abc0d4999c4c496d854","path":"libraries/chain/database.cpp","line_range":"5677-5678","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"1f43be6f2eea9de96bd24d20b575c860","path":"libraries/chain/database.cpp","line_range":"5680-5681","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"d9dbf24fcc1a502e794d92f729bcd372","path":"plugins/witness/witness.cpp","line_range":"159-160","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"abeb5e992b22812a0cdfc0c3dca91067","path":"plugins/witness/witness.cpp","line_range":"338-340","gmt_create":"2026-04-30T07:30:11.8090159+04:00","gmt_modified":"2026-04-30T07:30:11.8090159+04:00"},{"id":"956e60e52272d2047c3d89d0f1fe96a3","path":"plugins/witness/witness.cpp","line_range":"356-357","gmt_create":"2026-04-30T07:30:11.8100159+04:00","gmt_modified":"2026-04-30T07:30:11.8100159+04:00"},{"id":"70499ce1f567f2564154072ea2576c3b","path":"plugins/witness/witness.cpp","line_range":"403-405","gmt_create":"2026-04-30T07:30:11.8100159+04:00","gmt_modified":"2026-04-30T07:30:11.8100159+04:00"},{"id":"aad86469ecab79c7481892069f6100df","path":"plugins/witness/witness.cpp","line_range":"411-412","gmt_create":"2026-04-30T07:30:11.8100159+04:00","gmt_modified":"2026-04-30T07:30:11.8100159+04:00"},{"id":"57d63528cfa5d7410afabeee4a42845e","path":"plugins/witness/witness.cpp","line_range":"416-417","gmt_create":"2026-04-30T07:30:11.8100159+04:00","gmt_modified":"2026-04-30T07:30:11.8100159+04:00"},{"id":"daa5dca768a6e2ed062d953de56c3dc6","path":"plugins/witness/witness.cpp","line_range":"418-419","gmt_create":"2026-04-30T07:30:11.8100159+04:00","gmt_modified":"2026-04-30T07:30:11.8100159+04:00"},{"id":"f0fa917f2268095d00a5c33920129393","path":"thirdparty/fc/src/stacktrace.cpp","line_range":"48-78","gmt_create":"2026-04-30T07:30:11.8100159+04:00","gmt_modified":"2026-04-30T07:30:11.8100159+04:00"},{"id":"b372b2c20cc0d9d9abd9e2254bfefb17","path":"libraries/chain/dlt_block_log.cpp","line_range":"576-602","gmt_create":"2026-04-30T07:46:04.9316707+04:00","gmt_modified":"2026-04-30T07:46:04.9316707+04:00"},{"id":"a1ef6908e910771a074aa8ccbc10adef","path":"plugins/p2p/p2p_plugin.cpp","line_range":"773-795","gmt_create":"2026-04-30T07:47:41.3249647+04:00","gmt_modified":"2026-04-30T07:47:41.3249647+04:00"},{"id":"463f1a4ea4700713bc6ca4afa0c86f4f","path":"share/vizd/config/config.ini","line_range":"1-143","gmt_create":"2026-04-30T07:47:41.332047+04:00","gmt_modified":"2026-04-30T07:47:41.332047+04:00"},{"id":"321d8cd55620ec252c0b570fa95fd592","path":"libraries/chain/database.cpp","line_range":"5092-5105","gmt_create":"2026-04-30T08:00:09.9159109+04:00","gmt_modified":"2026-04-30T08:00:09.9159109+04:00"},{"id":"118584993a6d2e21fefe6c8f98dbe2f6","path":"libraries/chain/database.cpp","line_range":"5283-5297","gmt_create":"2026-04-30T08:00:09.9169083+04:00","gmt_modified":"2026-04-30T08:00:09.9169083+04:00"},{"id":"26650c218a6c596d28668797b6045c8c","path":"libraries/chain/database.cpp","line_range":"5616-5635","gmt_create":"2026-04-30T08:00:09.9169083+04:00","gmt_modified":"2026-04-30T08:00:09.9169083+04:00"},{"id":"82dadfd972805d70bffa99ce756460c4","path":"plugins/p2p/p2p_plugin.cpp","line_range":"307-327","gmt_create":"2026-04-30T08:47:09.8060145+04:00","gmt_modified":"2026-04-30T08:47:09.8060145+04:00"},{"id":"ee32480b501d0e9284fc1a7aa7d20b4c","path":"plugins/p2p/p2p_plugin.cpp","line_range":"1042-1113","gmt_create":"2026-04-30T08:47:09.8065171+04:00","gmt_modified":"2026-04-30T08:47:09.8065171+04:00"},{"id":"f12cdb54f6351fe38c307c4907f93868","path":"plugins/p2p/p2p_plugin.cpp","line_range":"783-791","gmt_create":"2026-04-30T08:47:09.814313+04:00","gmt_modified":"2026-04-30T08:47:09.814313+04:00"},{"id":"8c9490ebae702a4725d10dc509074042","path":"plugins/p2p/p2p_plugin.cpp","line_range":"812-816","gmt_create":"2026-04-30T08:47:09.814313+04:00","gmt_modified":"2026-04-30T08:47:09.814313+04:00"},{"id":"04677ace577bc003b34701bfd451a89d","path":"libraries/chain/dlt_block_log.cpp","line_range":"523-543","gmt_create":"2026-04-30T11:11:44.1591074+04:00","gmt_modified":"2026-04-30T11:11:44.1591074+04:00"},{"id":"16437c2399403aa03ec5eb0bceca78b8","path":"plugins/p2p/p2p_plugin.cpp","line_range":"653-770","gmt_create":"2026-04-30T11:12:37.4404904+04:00","gmt_modified":"2026-04-30T11:12:37.4404904+04:00"},{"id":"d74767289d3d8b429633502bc075cc48","path":"plugins/p2p/p2p_plugin.cpp","line_range":"773-813","gmt_create":"2026-04-30T11:12:37.4404904+04:00","gmt_modified":"2026-04-30T11:12:37.4404904+04:00"},{"id":"5eaed48196ffe1d0e73f557aacdc6096","path":"plugins/p2p/p2p_plugin.cpp","line_range":"760-768","gmt_create":"2026-04-30T11:12:37.4404904+04:00","gmt_modified":"2026-04-30T11:12:37.4404904+04:00"},{"id":"18300366cc04c934739121e96aa33a18","path":"libraries/network/node.cpp","line_range":"2520-2590","gmt_create":"2026-04-30T12:37:20.9950535+04:00","gmt_modified":"2026-04-30T12:37:20.9950535+04:00"},{"id":"5c8f4ea594d62170eaa300eb27c60cab","path":"libraries/network/include/graphene/network/peer_connection.hpp","line_range":"285-289","gmt_create":"2026-04-30T12:37:20.9950535+04:00","gmt_modified":"2026-04-30T12:37:20.9950535+04:00"},{"id":"68421d68d4a7045f55767bcf48a402ad","path":"plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp","line_range":"11-48","gmt_create":"2026-04-30T12:39:24.14723+04:00","gmt_modified":"2026-04-30T12:39:24.14723+04:00"},{"id":"262c71a35dc67c134fb0ac33f44ffb3c","path":"plugins/witness_guard/witness_guard.cpp","line_range":"27-78","gmt_create":"2026-04-30T12:39:24.1477324+04:00","gmt_modified":"2026-04-30T12:39:24.1477324+04:00"},{"id":"2768582067cb0ed11345dd25d7b2a582","path":"plugins/witness_guard/witness_guard.cpp","line_range":"83-191","gmt_create":"2026-04-30T12:39:24.1493137+04:00","gmt_modified":"2026-04-30T12:39:24.1493137+04:00"},{"id":"9bfd91580e85c0c0436b214ea3ff332a","path":"plugins/witness_guard/witness_guard.cpp","line_range":"360-369","gmt_create":"2026-04-30T12:39:24.1498381+04:00","gmt_modified":"2026-04-30T12:39:24.1498381+04:00"},{"id":"f82119bed73a4a0e0870145102b96214","path":"plugins/witness_guard/witness_guard.cpp","line_range":"455-544","gmt_create":"2026-04-30T12:39:24.1515583+04:00","gmt_modified":"2026-04-30T12:39:24.1515583+04:00"},{"id":"410cd37dc3943354d37ce7542843e6c0","path":"plugins/witness_guard/witness_guard.cpp","line_range":"301-328","gmt_create":"2026-04-30T12:39:24.1536407+04:00","gmt_modified":"2026-04-30T12:39:24.1536407+04:00"},{"id":"804dc1cd57f82825de2acc450a8496c3","path":"plugins/witness_guard/witness_guard.cpp","line_range":"330-408","gmt_create":"2026-04-30T12:39:24.1536407+04:00","gmt_modified":"2026-04-30T12:39:24.1536407+04:00"},{"id":"9ea04ad05deddb408f8a972fd04ba0d0","path":"share/vizd/config/config_witness.ini","line_range":"128-141","gmt_create":"2026-04-30T12:39:24.1556439+04:00","gmt_modified":"2026-04-30T12:39:24.1556439+04:00"},{"id":"f46e3fd7859017b5e2c1e7b5c296cb4f","path":"plugins/witness_guard/witness_guard.cpp","line_range":"197-246","gmt_create":"2026-04-30T12:39:24.1682132+04:00","gmt_modified":"2026-04-30T12:39:24.1682132+04:00"},{"id":"9d47a587156b7ae6cd73b0e5b5ad4f56","path":"plugins/witness_guard/witness_guard.cpp","line_range":"252-294","gmt_create":"2026-04-30T12:39:24.1682132+04:00","gmt_modified":"2026-04-30T12:39:24.1682132+04:00"},{"id":"322bbb26c4fa89c5b6c197da6defb3b0","path":"plugins/witness_guard/witness_guard.cpp","line_range":"301-408","gmt_create":"2026-04-30T12:39:24.1682132+04:00","gmt_modified":"2026-04-30T12:39:24.1682132+04:00"},{"id":"4c7449e318e56d7531d2b650c48cda91","path":"plugins/witness_guard/witness_guard.cpp","line_range":"410-555","gmt_create":"2026-04-30T12:39:24.1687158+04:00","gmt_modified":"2026-04-30T12:39:24.1687158+04:00"},{"id":"09dcfe86b88d969bbe651733cb34fc2d","path":"libraries/chain/include/graphene/chain/global_property_object.hpp","line_range":"139","gmt_create":"2026-04-30T12:39:24.173875+04:00","gmt_modified":"2026-04-30T12:39:24.173875+04:00"}],"knowledge_relations":[{"id":34629,"source_id":"448c0fd26faf791081a54cd407662e4d","target_id":"e6b951c57c5f8fb2b3553ce8db430760","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 18-55","gmt_create":"2026-04-28T09:55:13.582936+04:00","gmt_modified":"2026-04-28T09:55:13.582936+04:00"},{"id":34631,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"467b9030fcd47369d3417c84998c20d0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 910-979","gmt_create":"2026-04-28T09:55:13.582936+04:00","gmt_modified":"2026-04-28T09:55:13.582936+04:00"},{"id":34633,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"cbfe85acce275b65a2edb3315aec2941","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 190-320","gmt_create":"2026-04-28T09:55:13.5839359+04:00","gmt_modified":"2026-04-28T09:55:13.5839359+04:00"},{"id":34635,"source_id":"448c0fd26faf791081a54cd407662e4d","target_id":"9e18fa1bdbee1d9c96d8437bfe20515c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-57","gmt_create":"2026-04-28T09:55:13.5864433+04:00","gmt_modified":"2026-04-28T09:55:13.5864433+04:00"},{"id":34637,"source_id":"f7be79ec222a56c210b7999322790a2f","target_id":"5dce2933cfb42430c2bbcdf0cacc25c3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-49","gmt_create":"2026-04-28T09:55:13.5874434+04:00","gmt_modified":"2026-04-28T09:55:13.5874434+04:00"},{"id":34639,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"1b55f505ae64e9ea22be5142cfa67f93","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 49-126","gmt_create":"2026-04-28T09:55:13.588443+04:00","gmt_modified":"2026-04-28T09:55:13.588443+04:00"},{"id":34641,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"8a161abeb389c25b1279bc23d6ff4e57","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 60-167","gmt_create":"2026-04-28T09:55:13.588443+04:00","gmt_modified":"2026-04-28T09:55:13.588443+04:00"},{"id":34643,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"f85f57d0c6b461ab78f906ef6d5854c0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-354","gmt_create":"2026-04-28T09:55:13.5894427+04:00","gmt_modified":"2026-04-28T09:55:13.5894427+04:00"},{"id":34645,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"21076248fc123c7aacc8cb6e67cd0068","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 758-823","gmt_create":"2026-04-28T09:55:13.5904431+04:00","gmt_modified":"2026-04-28T09:55:13.5904431+04:00"},{"id":34647,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4e79c62ed5491dcd85510f3dab144813","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-200","gmt_create":"2026-04-28T09:55:13.5904431+04:00","gmt_modified":"2026-04-28T09:55:13.5904431+04:00"},{"id":34649,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"fe82427a3c86c02f05708734fa4c589c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 216-245","gmt_create":"2026-04-28T09:55:13.5924442+04:00","gmt_modified":"2026-04-28T09:55:13.5924442+04:00"},{"id":34651,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"15bc72540b02de4c57e4c976c8150f35","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 855-865","gmt_create":"2026-04-28T09:55:13.5924442+04:00","gmt_modified":"2026-04-28T09:55:13.5924442+04:00"},{"id":34653,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"cd0a62c9a78bb77d3b59a9d5872577f4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 82-106","gmt_create":"2026-04-28T09:55:13.5934435+04:00","gmt_modified":"2026-04-28T09:55:13.5934435+04:00"},{"id":34655,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"7eaff221b9d5916b8e18aa7786630566","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 188-218","gmt_create":"2026-04-28T09:55:13.5944434+04:00","gmt_modified":"2026-04-28T09:55:13.5944434+04:00"},{"id":34657,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"9ec3ef7b5beba7ccd4c8172983e359a7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 247-301","gmt_create":"2026-04-28T09:55:13.5944434+04:00","gmt_modified":"2026-04-28T09:55:13.5944434+04:00"},{"id":34659,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"685c2a44fc90d96152180fc6a5a63df4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 129-208","gmt_create":"2026-04-28T09:55:13.5959468+04:00","gmt_modified":"2026-04-28T09:55:13.5959468+04:00"},{"id":34661,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"9ad98a7be17ee5186d08088816474c52","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 540-552","gmt_create":"2026-04-28T09:55:13.5969503+04:00","gmt_modified":"2026-04-28T09:55:13.5969503+04:00"},{"id":34663,"source_id":"ee77bf4eb6bfbfb3636aa0bd57416552","target_id":"a273323d20c428afec092114bb480a23","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1078-1115","gmt_create":"2026-04-28T09:55:13.5999505+04:00","gmt_modified":"2026-04-28T09:55:13.5999505+04:00"},{"id":34665,"source_id":"ee77bf4eb6bfbfb3636aa0bd57416552","target_id":"27c21c9dfb07f579bd0db9fa97c8fd19","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1130-1137","gmt_create":"2026-04-28T09:55:13.6009501+04:00","gmt_modified":"2026-04-28T09:55:13.6009501+04:00"},{"id":34667,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"3b4fd0aa5c9c47621979a050b80b5fc2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 173-208","gmt_create":"2026-04-28T09:55:13.6009501+04:00","gmt_modified":"2026-04-28T09:55:13.6009501+04:00"},{"id":34669,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"411e466fa1c626bc1fff0647607acabd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 151-156","gmt_create":"2026-04-28T09:55:13.6019502+04:00","gmt_modified":"2026-04-28T09:55:13.6019502+04:00"},{"id":34671,"source_id":"f7be79ec222a56c210b7999322790a2f","target_id":"8eb355e55d14f0a3eec62805ff783a2f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 27-34","gmt_create":"2026-04-28T09:55:13.60295+04:00","gmt_modified":"2026-04-28T09:55:13.60295+04:00"},{"id":34673,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"4fc812a0df4303ac6e74df39697a0893","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-13","gmt_create":"2026-04-28T09:55:13.60295+04:00","gmt_modified":"2026-04-28T09:55:13.60295+04:00"},{"id":34675,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"5870c5d584940972a4d3fe2081558f6a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 659-756","gmt_create":"2026-04-28T09:55:13.6039501+04:00","gmt_modified":"2026-04-28T09:55:13.6039501+04:00"},{"id":34677,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"2c4431ca7f506fb549c312462fdb7a89","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 512-649","gmt_create":"2026-04-28T09:55:13.6039501+04:00","gmt_modified":"2026-04-28T09:55:13.6039501+04:00"},{"id":34679,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"ffb31b89372e5944d03cf7f549ce0151","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 659-683","gmt_create":"2026-04-28T09:55:13.60495+04:00","gmt_modified":"2026-04-28T09:55:13.60495+04:00"},{"id":34681,"source_id":"18555f254f50536a15d8591acf982406","target_id":"adf436395e284632e3b1ef4d74d647f4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-136","gmt_create":"2026-04-28T09:55:13.60495+04:00","gmt_modified":"2026-04-28T09:55:13.60495+04:00"},{"id":34702,"source_id":"fcabf234b34f00b60b0d784b2da5a052","target_id":"cd8c02da5ea31d3411ad151149d2f64e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 63-92","gmt_create":"2026-04-28T09:57:03.8479631+04:00","gmt_modified":"2026-04-28T09:57:03.8479631+04:00"},{"id":34704,"source_id":"0dd2a38630da83b11fb3596ad4d60705","target_id":"2d883d06f58fd34d81e8588c75185aa9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 34-68","gmt_create":"2026-04-28T09:57:03.8479631+04:00","gmt_modified":"2026-04-28T09:57:03.8479631+04:00"},{"id":34706,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"b6e11846d82ee129d5956cf9b8cbbea8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 59-118","gmt_create":"2026-04-28T09:57:03.8494864+04:00","gmt_modified":"2026-04-28T09:57:03.8494864+04:00"},{"id":34708,"source_id":"f8bd5a2c3a4664ae9d5ec472684610dc","target_id":"cda9a1d47dfdb3a7374fa817887892c0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 56-98","gmt_create":"2026-04-28T09:57:03.8494864+04:00","gmt_modified":"2026-04-28T09:57:03.8494864+04:00"},{"id":34710,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"f5891db138d66a58674791b9e99bd337","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 13-28","gmt_create":"2026-04-28T09:57:03.8504899+04:00","gmt_modified":"2026-04-28T09:57:03.8504899+04:00"},{"id":34712,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"51169b91af554f837e41d2913dacad48","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 37-83","gmt_create":"2026-04-28T09:57:03.8504899+04:00","gmt_modified":"2026-04-28T09:57:03.8504899+04:00"},{"id":34714,"source_id":"cf72debd284e30d5218a88ae08868205","target_id":"5aed64f2f61be210a303b341a970b0cc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 27-132","gmt_create":"2026-04-28T09:57:03.8514893+04:00","gmt_modified":"2026-04-28T09:57:03.8514893+04:00"},{"id":34716,"source_id":"bebd7920dd0967c6039c2adb16d4c52c","target_id":"47b98e10075d52a89428f617d837a5e5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 174-201","gmt_create":"2026-04-28T09:57:03.8524901+04:00","gmt_modified":"2026-04-28T09:57:03.8524901+04:00"},{"id":34718,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"42aa356d9e26fadf05fda749f1d89cff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 53-81","gmt_create":"2026-04-28T09:57:03.8524901+04:00","gmt_modified":"2026-04-28T09:57:03.8524901+04:00"},{"id":34720,"source_id":"13c87583e5739bb6062ee5706cbed132","target_id":"19e78b124cb1653f5e72af6789493e08","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 13-53","gmt_create":"2026-04-28T09:57:03.8535253+04:00","gmt_modified":"2026-04-28T09:57:03.8535253+04:00"},{"id":34722,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"1bf53ebbc25ba8c147446f02ce5e44e2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1267-1276","gmt_create":"2026-04-28T09:57:03.8545251+04:00","gmt_modified":"2026-04-28T09:57:03.8545251+04:00"},{"id":34724,"source_id":"448c0fd26faf791081a54cd407662e4d","target_id":"cecb2c27bddde9783761743ffbbfac88","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 50-55","gmt_create":"2026-04-28T09:57:03.855525+04:00","gmt_modified":"2026-04-28T09:57:03.855525+04:00"},{"id":34726,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"4a84a6b27fbcb47e0994f0bda545816f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 206-249","gmt_create":"2026-04-28T09:57:03.855525+04:00","gmt_modified":"2026-04-28T09:57:03.855525+04:00"},{"id":34728,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"3d0cbd79a1648b655b90b7629307cad1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 206-276","gmt_create":"2026-04-28T09:57:03.8565256+04:00","gmt_modified":"2026-04-28T09:57:03.8565256+04:00"},{"id":34730,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"6abe7f6efde355ee9f2d7cf0776677ff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 278-423","gmt_create":"2026-04-28T09:57:03.8565256+04:00","gmt_modified":"2026-04-28T09:57:03.8565256+04:00"},{"id":34732,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"1cf7bf28dd5011954754492ccf7873f5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 447-471","gmt_create":"2026-04-28T09:57:03.8580305+04:00","gmt_modified":"2026-04-28T09:57:03.8580305+04:00"},{"id":34734,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"31e5e32f87baccd25fbb2183951a67bd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 590-695","gmt_create":"2026-04-28T09:57:03.8580305+04:00","gmt_modified":"2026-04-28T09:57:03.8580305+04:00"},{"id":34736,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"1844592144295f47f4238341e8868e6b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 263-266","gmt_create":"2026-04-28T09:57:03.8590354+04:00","gmt_modified":"2026-04-28T09:57:03.8590354+04:00"},{"id":34738,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"15a3537ebe2816e5402e7fb60462b58e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4317-4332","gmt_create":"2026-04-28T09:57:03.8590354+04:00","gmt_modified":"2026-04-28T09:57:03.8590354+04:00"},{"id":34740,"source_id":"13c87583e5739bb6062ee5706cbed132","target_id":"6af53e8de30910078bc6fd9dab1d2f7b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 74-76","gmt_create":"2026-04-28T09:57:03.8590354+04:00","gmt_modified":"2026-04-28T09:57:03.8590354+04:00"},{"id":34742,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"0ef7b0d7933da804905c2ff76f92cd94","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2824-2839","gmt_create":"2026-04-28T09:57:03.8600352+04:00","gmt_modified":"2026-04-28T09:57:03.8600352+04:00"},{"id":34744,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"667d252413c23e04beb2c069531a1372","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2871-2886","gmt_create":"2026-04-28T09:57:03.8610357+04:00","gmt_modified":"2026-04-28T09:57:03.8610357+04:00"},{"id":34746,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"62ea8f0eed608d8eb1dd0911e43f28c3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1223-1267","gmt_create":"2026-04-28T09:57:03.8610357+04:00","gmt_modified":"2026-04-28T09:57:03.8610357+04:00"},{"id":34748,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"1b91062fbd3e8ce21a7ec705a5ef21ae","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 125-133","gmt_create":"2026-04-28T09:57:03.8610357+04:00","gmt_modified":"2026-04-28T09:57:03.8610357+04:00"},{"id":34750,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"c57b368c9aec32de084799a61fc21d81","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 149-155","gmt_create":"2026-04-28T09:57:03.8620352+04:00","gmt_modified":"2026-04-28T09:57:03.8620352+04:00"},{"id":34752,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"9a1726ad4c4d7942894eafbb2fb7c20a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 222-224","gmt_create":"2026-04-28T09:57:03.8620352+04:00","gmt_modified":"2026-04-28T09:57:03.8620352+04:00"},{"id":34754,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"d572e2edecf45b7b050d30cbb14368d8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 57-58","gmt_create":"2026-04-28T09:57:03.8620352+04:00","gmt_modified":"2026-04-28T09:57:03.8620352+04:00"},{"id":34756,"source_id":"18555f254f50536a15d8591acf982406","target_id":"a3b204b149312d56bea1667800a95fb6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 99-103","gmt_create":"2026-04-28T09:57:03.8640358+04:00","gmt_modified":"2026-04-28T09:57:03.8640358+04:00"},{"id":34758,"source_id":"bb293be9318768f10f69c80fd6b68517","target_id":"f72faf82a21dd9049d68549d9c7e5c4f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 76-80","gmt_create":"2026-04-28T09:57:03.8640358+04:00","gmt_modified":"2026-04-28T09:57:03.8640358+04:00"},{"id":34760,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"c5701be8b76f9a4a85b659f219a10f95","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 509-555","gmt_create":"2026-04-28T09:57:03.8655386+04:00","gmt_modified":"2026-04-28T09:57:03.8655386+04:00"},{"id":34762,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"7f2c3ab5ba63b977d9642aa1c78a2c43","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 120-169","gmt_create":"2026-04-28T09:57:03.8655386+04:00","gmt_modified":"2026-04-28T09:57:03.8655386+04:00"},{"id":34764,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"737d623fe091f7ae2629dcda699b0efa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 171-192","gmt_create":"2026-04-28T09:57:03.8655386+04:00","gmt_modified":"2026-04-28T09:57:03.8655386+04:00"},{"id":34766,"source_id":"0dd2a38630da83b11fb3596ad4d60705","target_id":"fba8be1bbb523071f8ce7ad55a13d0f2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 31","gmt_create":"2026-04-28T09:57:03.8775694+04:00","gmt_modified":"2026-04-28T09:57:03.8775694+04:00"},{"id":34768,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"2a7a24b7119edca00eb0b21200f484ec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 88","gmt_create":"2026-04-28T09:57:03.8785683+04:00","gmt_modified":"2026-04-28T09:57:03.8785683+04:00"},{"id":34770,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"c9048f7e0344e917d91b3b45d3804a0d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 73","gmt_create":"2026-04-28T09:57:03.8785683+04:00","gmt_modified":"2026-04-28T09:57:03.8785683+04:00"},{"id":34772,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"ff4cacfd8a6a1ce746ce49bd2259ef47","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 151-166","gmt_create":"2026-04-28T09:57:03.8795944+04:00","gmt_modified":"2026-04-28T09:57:03.8795944+04:00"},{"id":34774,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"78279a6057ca0eaea7f987e920fada7f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1456-1471","gmt_create":"2026-04-28T09:57:03.8795944+04:00","gmt_modified":"2026-04-28T09:57:03.8795944+04:00"},{"id":34776,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"95e9eb6df5c5b54fc25131e15cebca7b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 269-274","gmt_create":"2026-04-28T09:57:03.8795944+04:00","gmt_modified":"2026-04-28T09:57:03.8795944+04:00"},{"id":34778,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"4c8c235c40a9885b9ce181cc9af786fe","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2807-2839","gmt_create":"2026-04-28T09:57:03.8805955+04:00","gmt_modified":"2026-04-28T09:57:03.8805955+04:00"},{"id":34780,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"29354043db86eeb48566453a908645c7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2897-2914","gmt_create":"2026-04-28T09:57:03.8815941+04:00","gmt_modified":"2026-04-28T09:57:03.8815941+04:00"},{"id":34782,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"6e4547d3d8a2b1ce7fb2eb8442ba9631","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1294-1311","gmt_create":"2026-04-28T09:57:03.8871036+04:00","gmt_modified":"2026-04-28T09:57:03.8871036+04:00"},{"id":34784,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"0dec6783542ea54ba7d81fbeb930e442","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 30-49","gmt_create":"2026-04-28T09:57:03.8871036+04:00","gmt_modified":"2026-04-28T09:57:03.8871036+04:00"},{"id":34786,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"85675dcfc30f216052bdb5cedc7b435c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 75-91","gmt_create":"2026-04-28T09:57:03.8886221+04:00","gmt_modified":"2026-04-28T09:57:03.8886221+04:00"},{"id":34788,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"7f42fa8b501a589d403cb682d1620581","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 102-125","gmt_create":"2026-04-28T09:57:03.8886221+04:00","gmt_modified":"2026-04-28T09:57:03.8886221+04:00"},{"id":34790,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"17a2c941cfb814db6c1623046d8dac1e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 127-159","gmt_create":"2026-04-28T09:57:03.8891498+04:00","gmt_modified":"2026-04-28T09:57:03.8891498+04:00"},{"id":34792,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"53a82d703bff53e589353336964d2eed","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 161-169","gmt_create":"2026-04-28T09:57:03.8896673+04:00","gmt_modified":"2026-04-28T09:57:03.8896673+04:00"},{"id":34794,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"4fcf4072cf8ebe5cd019e9a0da762901","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 171-203","gmt_create":"2026-04-28T09:57:03.8896673+04:00","gmt_modified":"2026-04-28T09:57:03.8896673+04:00"},{"id":34796,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"c4f66fb8fb1d6eeb25bd48eec2ba80a6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 102-159","gmt_create":"2026-04-28T09:57:03.8901745+04:00","gmt_modified":"2026-04-28T09:57:03.8901745+04:00"},{"id":34798,"source_id":"a4f11ca2018649a28877cfdeecdff9a6","target_id":"68e0135d2e3b03eff760c57654b97092","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 161-203","gmt_create":"2026-04-28T09:57:03.8906938+04:00","gmt_modified":"2026-04-28T09:57:03.8906938+04:00"},{"id":34800,"source_id":"cf72debd284e30d5218a88ae08868205","target_id":"eeebc7e5a0ce3570715e7391da03b065","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 104-171","gmt_create":"2026-04-28T09:57:03.8912137+04:00","gmt_modified":"2026-04-28T09:57:03.8912137+04:00"},{"id":34802,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"ef4797348572b382b96b9d19a362eed7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 90-95","gmt_create":"2026-04-28T09:57:03.8917283+04:00","gmt_modified":"2026-04-28T09:57:03.8917283+04:00"},{"id":34804,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"85e55e5f6d83a36cd6afac5fcb62fb42","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1626-1805","gmt_create":"2026-04-28T09:57:03.8927583+04:00","gmt_modified":"2026-04-28T09:57:03.8927583+04:00"},{"id":34806,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1d8a3a8529f55f725cbd52ef78db6a1f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4334-4463","gmt_create":"2026-04-28T09:57:03.8932701+04:00","gmt_modified":"2026-04-28T09:57:03.8932701+04:00"},{"id":34808,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"87584d47dc52c8658341b565e96c989d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 492-499","gmt_create":"2026-04-28T09:57:03.8932701+04:00","gmt_modified":"2026-04-28T09:57:03.8932701+04:00"},{"id":34810,"source_id":"13c87583e5739bb6062ee5706cbed132","target_id":"cda7dad93173bc2161ab2ff3c92e81ce","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 36-39","gmt_create":"2026-04-28T09:57:03.8937802+04:00","gmt_modified":"2026-04-28T09:57:03.8937802+04:00"},{"id":34812,"source_id":"b58bf8be210d82c70605d7f2482ced82","target_id":"73dca3cd312efbc6ecb517ae118fd869","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 184-201","gmt_create":"2026-04-28T09:57:03.894298+04:00","gmt_modified":"2026-04-28T09:57:03.894298+04:00"},{"id":34814,"source_id":"b58bf8be210d82c70605d7f2482ced82","target_id":"c69f6ef1a64e88829a12d7ab4c190897","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 236-266","gmt_create":"2026-04-28T09:57:03.8948136+04:00","gmt_modified":"2026-04-28T09:57:03.8948136+04:00"},{"id":34816,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"26905e829dbbc0af740b97400068843b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 255-271","gmt_create":"2026-04-28T09:57:03.8963611+04:00","gmt_modified":"2026-04-28T09:57:03.8963611+04:00"},{"id":34818,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"1eb7cc21b9daf17a2c908f3121bcf2f0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 387-396","gmt_create":"2026-04-28T09:57:03.8968723+04:00","gmt_modified":"2026-04-28T09:57:03.8968723+04:00"},{"id":34820,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"cc4d2fca7cfbc2ed2aa9dc9016360fb5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2826-2836","gmt_create":"2026-04-28T09:57:03.8974708+04:00","gmt_modified":"2026-04-28T09:57:03.8974708+04:00"},{"id":34822,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e0e1ada694da4e9e256e0d6d39aa73e5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2873-2883","gmt_create":"2026-04-28T09:57:03.8979743+04:00","gmt_modified":"2026-04-28T09:57:03.8979743+04:00"},{"id":34837,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d74594b2821f5c9e747d2630c3784bb1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4669-4822","gmt_create":"2026-04-28T09:57:57.4427714+04:00","gmt_modified":"2026-04-28T09:57:57.4427714+04:00"},{"id":34839,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"27593eff1e7989c53fb119e30b38a106","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 81-88","gmt_create":"2026-04-28T09:57:57.4432949+04:00","gmt_modified":"2026-04-28T09:57:57.4432949+04:00"},{"id":34841,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"f5099185b2e70ff258ed1ff6f4405b90","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 509-524","gmt_create":"2026-04-28T09:57:57.4438097+04:00","gmt_modified":"2026-04-28T09:57:57.4438097+04:00"},{"id":34843,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"3613ea0b8ae4f848819d0ac0b87c1bc9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 562-590","gmt_create":"2026-04-28T09:57:57.4438097+04:00","gmt_modified":"2026-04-28T09:57:57.4438097+04:00"},{"id":34845,"source_id":"ee77bf4eb6bfbfb3636aa0bd57416552","target_id":"4988a66040303855b9d94a173232f966","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1075-1115","gmt_create":"2026-04-28T09:57:57.4448562+04:00","gmt_modified":"2026-04-28T09:57:57.4448562+04:00"},{"id":34847,"source_id":"7ae785f9d5ab154dce6f8eb295b93456","target_id":"188a46b66d800240516e280b06e7f041","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 24-146","gmt_create":"2026-04-28T09:57:57.4448562+04:00","gmt_modified":"2026-04-28T09:57:57.4448562+04:00"},{"id":34850,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"02a1a9fcc78ccfc4daf328d04696eb6f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 114-124","gmt_create":"2026-04-28T09:57:57.4453669+04:00","gmt_modified":"2026-04-28T09:57:57.4453669+04:00"},{"id":34852,"source_id":"cf72debd284e30d5218a88ae08868205","target_id":"971acfd6fe75fe0b8a1522de5c46bf48","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 47-61","gmt_create":"2026-04-28T09:57:57.445884+04:00","gmt_modified":"2026-04-28T09:57:57.445884+04:00"},{"id":34854,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"ad1ea51f6c6764f6694b4b89a834e8f6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 110-128","gmt_create":"2026-04-28T09:57:57.4463953+04:00","gmt_modified":"2026-04-28T09:57:57.4463953+04:00"},{"id":34856,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"9fae7d65cba1bc4e0e4e2ca7efd9c99b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4675-4703","gmt_create":"2026-04-28T09:57:57.446906+04:00","gmt_modified":"2026-04-28T09:57:57.446906+04:00"},{"id":34858,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e454b40f3a2a945e952f73a4f2009cad","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4707-4720","gmt_create":"2026-04-28T09:57:57.446906+04:00","gmt_modified":"2026-04-28T09:57:57.446906+04:00"},{"id":34860,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"255c8e4bd9e7ab0c2242bce46c0e863e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 405-406","gmt_create":"2026-04-28T09:57:57.4474213+04:00","gmt_modified":"2026-04-28T09:57:57.4474213+04:00"},{"id":34862,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"3c48450ddf4126f562a2691715c15905","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 80-87","gmt_create":"2026-04-28T09:57:57.4479563+04:00","gmt_modified":"2026-04-28T09:57:57.4479563+04:00"},{"id":34864,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"cc9483d821847e02b4d24e6c9a0d47b3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 260-262","gmt_create":"2026-04-28T09:57:57.4479563+04:00","gmt_modified":"2026-04-28T09:57:57.4479563+04:00"},{"id":34866,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"79f094abff66ff28914dcda338045f88","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2428-2444","gmt_create":"2026-04-28T09:57:57.4484877+04:00","gmt_modified":"2026-04-28T09:57:57.4484877+04:00"},{"id":34868,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"fc2ee24e4f7c33ca03ec4cd65b733437","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 126-128","gmt_create":"2026-04-28T09:57:57.4490006+04:00","gmt_modified":"2026-04-28T09:57:57.4490006+04:00"},{"id":34870,"source_id":"8ede002b6c76d0a07d75e34f812e8305","target_id":"11e880557c78471a43830064f7284407","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6","gmt_create":"2026-04-28T09:57:57.44951+04:00","gmt_modified":"2026-04-28T09:57:57.44951+04:00"},{"id":34872,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e2e78ec9bb315562ae4436bac3d06fb5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1556","gmt_create":"2026-04-28T09:57:57.450026+04:00","gmt_modified":"2026-04-28T09:57:57.450026+04:00"},{"id":34874,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"b77936a3b7e26cb05376897c149ba871","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1593","gmt_create":"2026-04-28T09:57:57.4505548+04:00","gmt_modified":"2026-04-28T09:57:57.4505548+04:00"},{"id":34876,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"0fda0369897c65053dee851ef9ec01f9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 37-612","gmt_create":"2026-04-28T09:57:57.4515744+04:00","gmt_modified":"2026-04-28T09:57:57.4515744+04:00"},{"id":34878,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"56869b4c37aaf198953050ee291096aa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 113-145","gmt_create":"2026-04-28T09:57:57.4589075+04:00","gmt_modified":"2026-04-28T09:57:57.4589075+04:00"},{"id":34899,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"825feab7f9d0991ba63a9c56f38beff9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-642","gmt_create":"2026-04-28T10:02:32.6413191+04:00","gmt_modified":"2026-04-28T10:02:32.6413191+04:00"},{"id":34901,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"4715ce884086b88c16e3424c68d37757","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6506","gmt_create":"2026-04-28T10:02:32.6418484+04:00","gmt_modified":"2026-04-28T10:02:32.6418484+04:00"},{"id":34903,"source_id":"ee77bf4eb6bfbfb3636aa0bd57416552","target_id":"2ad3ade1c893ee16ff7640c396428e46","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1078-1120","gmt_create":"2026-04-28T10:02:32.6423686+04:00","gmt_modified":"2026-04-28T10:02:32.6423686+04:00"},{"id":34905,"source_id":"1ade3cebbc11a4634bcdf1a7fdb2756e","target_id":"27490494b39fa9968667b183f60e215a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-200","gmt_create":"2026-04-28T10:02:32.6423686+04:00","gmt_modified":"2026-04-28T10:02:32.6423686+04:00"},{"id":34907,"source_id":"4238a9561f85e50a38f76813baeadd7e","target_id":"2c17018b7a7ecf1dd7b6432e97d0a586","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-75","gmt_create":"2026-04-28T10:02:32.6428831+04:00","gmt_modified":"2026-04-28T10:02:32.6428831+04:00"},{"id":34909,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"fef8201a8783aa794440fbd2f9b1ff17","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-302","gmt_create":"2026-04-28T10:02:32.6434036+04:00","gmt_modified":"2026-04-28T10:02:32.6434036+04:00"},{"id":34911,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"6ebf29a038d578e8864ca6c9c9366bda","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-76","gmt_create":"2026-04-28T10:02:32.6439232+04:00","gmt_modified":"2026-04-28T10:02:32.6439232+04:00"},{"id":34913,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"1a161423d84a286d1a1c4eeb95bcfd01","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-414","gmt_create":"2026-04-28T10:02:32.6439232+04:00","gmt_modified":"2026-04-28T10:02:32.6439232+04:00"},{"id":34915,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"d2a63eaaf3635885b145b52cf4640747","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-138","gmt_create":"2026-04-28T10:02:32.6444419+04:00","gmt_modified":"2026-04-28T10:02:32.6444419+04:00"},{"id":34917,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"d3bee62a496fa43717911daed4f0bb13","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-271","gmt_create":"2026-04-28T10:02:32.644965+04:00","gmt_modified":"2026-04-28T10:02:32.644965+04:00"},{"id":34919,"source_id":"cb29035725926be38d36ad8c01792b7e","target_id":"ba99a3c0ac8bffedf50f03f7f8a09c06","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-136","gmt_create":"2026-04-28T10:02:32.6454737+04:00","gmt_modified":"2026-04-28T10:02:32.6454737+04:00"},{"id":34921,"source_id":"57e07111ef7b80720c419255780e7ece","target_id":"0397697ee2095dedcc01fa148c7079b9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-154","gmt_create":"2026-04-28T10:02:32.6459947+04:00","gmt_modified":"2026-04-28T10:02:32.6459947+04:00"},{"id":34923,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"b2901b8a7a24569b4a61c38b4448db06","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1180-1379","gmt_create":"2026-04-28T10:02:32.647113+04:00","gmt_modified":"2026-04-28T10:02:32.647113+04:00"},{"id":34925,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"77520f9eb913c04b5230e07f8f3f4ef8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 270-469","gmt_create":"2026-04-28T10:02:32.647623+04:00","gmt_modified":"2026-04-28T10:02:32.647623+04:00"},{"id":34927,"source_id":"0dd2a38630da83b11fb3596ad4d60705","target_id":"340636e744f71de65b91146c5b8a20bd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-73","gmt_create":"2026-04-28T10:02:32.6481554+04:00","gmt_modified":"2026-04-28T10:02:32.6481554+04:00"},{"id":34929,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"c05f511b950fdae3b96260134880f8e4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 111-118","gmt_create":"2026-04-28T10:02:32.6486856+04:00","gmt_modified":"2026-04-28T10:02:32.6486856+04:00"},{"id":34931,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"101205728a0401f1dc07b5e31abe4b30","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3185-3384","gmt_create":"2026-04-28T10:02:32.6486856+04:00","gmt_modified":"2026-04-28T10:02:32.6486856+04:00"},{"id":34933,"source_id":"3948eb588d15d01acf21ffd439ec508c","target_id":"832619f974a664566d9bae6712c5e6a1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 27-48","gmt_create":"2026-04-28T10:02:32.6491998+04:00","gmt_modified":"2026-04-28T10:02:32.6491998+04:00"},{"id":34935,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"8939bbbd6e1bb4a18e6bb534a037d0b8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 225-424","gmt_create":"2026-04-28T10:02:32.6491998+04:00","gmt_modified":"2026-04-28T10:02:32.6491998+04:00"},{"id":34937,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"b437e864a36924899b70d6b9295e4ac0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 61-115","gmt_create":"2026-04-28T10:02:32.6513176+04:00","gmt_modified":"2026-04-28T10:02:32.6513176+04:00"},{"id":34939,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"476cdc272b600cf1a7bfe2b524767872","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 281-324","gmt_create":"2026-04-28T10:02:32.6518456+04:00","gmt_modified":"2026-04-28T10:02:32.6518456+04:00"},{"id":34941,"source_id":"4238a9561f85e50a38f76813baeadd7e","target_id":"5c73e14f98ff45ef03bb3a7c9e413218","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-75","gmt_create":"2026-04-28T10:02:32.6523696+04:00","gmt_modified":"2026-04-28T10:02:32.6523696+04:00"},{"id":34943,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"6742da18bac301be1056c8e6e5adcf69","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 35-72","gmt_create":"2026-04-28T10:02:32.6523696+04:00","gmt_modified":"2026-04-28T10:02:32.6523696+04:00"},{"id":34945,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"8f2e19d22fb34a00bbf819d0f5088b92","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 53-138","gmt_create":"2026-04-28T10:02:32.6528954+04:00","gmt_modified":"2026-04-28T10:02:32.6528954+04:00"},{"id":34947,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"6d7b6e4198a348bc27c0bb28230cb3f9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 929-984","gmt_create":"2026-04-28T10:02:32.6528954+04:00","gmt_modified":"2026-04-28T10:02:32.6528954+04:00"},{"id":34949,"source_id":"57e07111ef7b80720c419255780e7ece","target_id":"186fa6ce927d55c0b153413a2981237e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 33-100","gmt_create":"2026-04-28T10:02:32.6528954+04:00","gmt_modified":"2026-04-28T10:02:32.6528954+04:00"},{"id":34951,"source_id":"1ade3cebbc11a4634bcdf1a7fdb2756e","target_id":"99a2db97d03705f31a3929010634a458","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 225-279","gmt_create":"2026-04-28T10:02:32.6539489+04:00","gmt_modified":"2026-04-28T10:02:32.6539489+04:00"},{"id":34953,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"30a9101c41868617ba6f2a2daa15b849","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 94-184","gmt_create":"2026-04-28T10:02:32.6555587+04:00","gmt_modified":"2026-04-28T10:02:32.6555587+04:00"},{"id":34955,"source_id":"cb29035725926be38d36ad8c01792b7e","target_id":"3e02993eaec7af3cec0bfb1f83a665d3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 83","gmt_create":"2026-04-28T10:02:32.6560893+04:00","gmt_modified":"2026-04-28T10:02:32.6560893+04:00"},{"id":34957,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"99f54a4b93eefbb48ed1667eff0fa9d3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 330-410","gmt_create":"2026-04-28T10:02:32.6566219+04:00","gmt_modified":"2026-04-28T10:02:32.6566219+04:00"},{"id":34959,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"408bde042901c8590a87dffdf56f7b44","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 134-184","gmt_create":"2026-04-28T10:02:32.6566219+04:00","gmt_modified":"2026-04-28T10:02:32.6566219+04:00"},{"id":34961,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e69115f35f4572b29fb65e977de046e0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 503-519","gmt_create":"2026-04-28T10:02:32.6571435+04:00","gmt_modified":"2026-04-28T10:02:32.6571435+04:00"},{"id":34963,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"c7f51cc5681ac282586b460766078a35","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 61-68","gmt_create":"2026-04-28T10:02:32.6576613+04:00","gmt_modified":"2026-04-28T10:02:32.6576613+04:00"},{"id":34965,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"bc9cfd1e8fd5529888f9a16b60e4ac68","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1424-1426","gmt_create":"2026-04-28T10:02:32.658182+04:00","gmt_modified":"2026-04-28T10:02:32.658182+04:00"},{"id":34967,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e5fa51f083d1ac49273aea5904dea32f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 704-752","gmt_create":"2026-04-28T10:02:32.6587013+04:00","gmt_modified":"2026-04-28T10:02:32.6587013+04:00"},{"id":34969,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1198bd7cc69669237a261463e8cd3c9d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3986-4039","gmt_create":"2026-04-28T10:02:32.6592137+04:00","gmt_modified":"2026-04-28T10:02:32.6592137+04:00"},{"id":34971,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a8eaae161939961c892a4e5ff9b1b68f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4144-4175","gmt_create":"2026-04-28T10:02:32.6592137+04:00","gmt_modified":"2026-04-28T10:02:32.6592137+04:00"},{"id":34973,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"7cb385b30473b1f25ba53b2a6825c874","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4384-4424","gmt_create":"2026-04-28T10:02:32.6602175+04:00","gmt_modified":"2026-04-28T10:02:32.6602175+04:00"},{"id":34975,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"ec354a277b7a096cd5e5a4cebe2b7d7a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 70-73","gmt_create":"2026-04-28T10:02:32.6602175+04:00","gmt_modified":"2026-04-28T10:02:32.6602175+04:00"},{"id":34977,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"b86a1f199ae24604cb9b125824afc0a0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 292-292","gmt_create":"2026-04-28T10:02:32.6602175+04:00","gmt_modified":"2026-04-28T10:02:32.6602175+04:00"},{"id":34979,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ddaa6fa138e94fe8af5d2b77014c29f0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4460-4490","gmt_create":"2026-04-28T10:02:32.6627707+04:00","gmt_modified":"2026-04-28T10:02:32.6627707+04:00"},{"id":34981,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"47787fc2ed2dde075eda3ebf991e2fc9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 75-77","gmt_create":"2026-04-28T10:02:32.6627707+04:00","gmt_modified":"2026-04-28T10:02:32.6627707+04:00"},{"id":34983,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"17b75751a836de13af2093485914a80b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1147-1202","gmt_create":"2026-04-28T10:02:32.6637686+04:00","gmt_modified":"2026-04-28T10:02:32.6637686+04:00"},{"id":34985,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"3fd79dc7253b83f3fd4a1db36b691537","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-96","gmt_create":"2026-04-28T10:02:32.6637686+04:00","gmt_modified":"2026-04-28T10:02:32.6637686+04:00"},{"id":34987,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"fc817fa1448591c306f1d8c94a4912b0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 340-350","gmt_create":"2026-04-28T10:02:32.6637686+04:00","gmt_modified":"2026-04-28T10:02:32.6637686+04:00"},{"id":34989,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2b22b083099e12af5fb5eff69ac7c45b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4346-4366","gmt_create":"2026-04-28T10:02:32.6647727+04:00","gmt_modified":"2026-04-28T10:02:32.6647727+04:00"},{"id":34991,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ad0c51e89fbda89a4c7ba4a8b1c23f6e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 948-970","gmt_create":"2026-04-28T10:02:32.6647727+04:00","gmt_modified":"2026-04-28T10:02:32.6647727+04:00"},{"id":34993,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1bc38fc420c17ebaaf14eb62dd9dd77e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3652-3711","gmt_create":"2026-04-28T10:02:32.6652839+04:00","gmt_modified":"2026-04-28T10:02:32.6652839+04:00"},{"id":34995,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a0ef31a365b500c015295fa0a77cb02a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 639-673","gmt_create":"2026-04-28T10:02:32.6652839+04:00","gmt_modified":"2026-04-28T10:02:32.6652839+04:00"},{"id":34997,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"bad0cf5ac8c5e25ab7697c72386f0c6e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 562-605","gmt_create":"2026-04-28T10:02:32.6652839+04:00","gmt_modified":"2026-04-28T10:02:32.6652839+04:00"},{"id":34999,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"145b14d90766df6ec3acffdb3b52a1a7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 412-422","gmt_create":"2026-04-28T10:02:32.6662888+04:00","gmt_modified":"2026-04-28T10:02:32.6662888+04:00"},{"id":35001,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5ae77d32160a48045d586a25c4949703","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 454-482","gmt_create":"2026-04-28T10:02:32.6753196+04:00","gmt_modified":"2026-04-28T10:02:32.6753196+04:00"},{"id":35003,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"fb42540947b6b7d56487ccd9ec782f86","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 148-164","gmt_create":"2026-04-28T10:02:32.6773154+04:00","gmt_modified":"2026-04-28T10:02:32.6773154+04:00"},{"id":35005,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"4eb8ce2a423658c16ae4df90aeba2184","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 546-556","gmt_create":"2026-04-28T10:02:32.6773154+04:00","gmt_modified":"2026-04-28T10:02:32.6773154+04:00"},{"id":35007,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"0172b0ea2a031177f1c72341a3922614","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 631-632","gmt_create":"2026-04-28T10:02:32.6783155+04:00","gmt_modified":"2026-04-28T10:02:32.6783155+04:00"},{"id":35009,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e8dd15ab10626a2ce715fe8c3f03ca85","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1106-1145","gmt_create":"2026-04-28T10:02:32.6783155+04:00","gmt_modified":"2026-04-28T10:02:32.6783155+04:00"},{"id":35011,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"8509be38b1ab9f14d108445df0dcd2f1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1460-1470","gmt_create":"2026-04-28T10:02:32.6793146+04:00","gmt_modified":"2026-04-28T10:02:32.6793146+04:00"},{"id":35013,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"732bc579d5f86ebf0e986ecfbdfa490d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 34-46","gmt_create":"2026-04-28T10:02:32.6793146+04:00","gmt_modified":"2026-04-28T10:02:32.6793146+04:00"},{"id":35016,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d1e1e1bc28ff1dfbd77617edbf4a23b0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1295-1377","gmt_create":"2026-04-28T10:02:32.680817+04:00","gmt_modified":"2026-04-28T10:02:32.680817+04:00"},{"id":35018,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"42f304cea32d3254d3d28d390f99a4f4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1216-1286","gmt_create":"2026-04-28T10:02:32.6818238+04:00","gmt_modified":"2026-04-28T10:02:32.6818238+04:00"},{"id":35020,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"21021513e3f47136fbd9b77f25ac0dda","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 175-192","gmt_create":"2026-04-28T10:02:32.6828252+04:00","gmt_modified":"2026-04-28T10:02:32.6828252+04:00"},{"id":35022,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"978bc8c4cf93eea58a5004412f5a0740","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3192-3211","gmt_create":"2026-04-28T10:02:32.6828252+04:00","gmt_modified":"2026-04-28T10:02:32.6828252+04:00"},{"id":35024,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"9a7186666130ca998f114a82a0decdff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 181-196","gmt_create":"2026-04-28T10:02:32.6838216+04:00","gmt_modified":"2026-04-28T10:02:32.6838216+04:00"},{"id":35026,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ac1d251c502e1e4341518133a47346b6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1556-1588","gmt_create":"2026-04-28T10:02:32.6838216+04:00","gmt_modified":"2026-04-28T10:02:32.6838216+04:00"},{"id":35028,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"03c29e794349e78edd559069a4ea589f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1593-1594","gmt_create":"2026-04-28T10:02:32.6848229+04:00","gmt_modified":"2026-04-28T10:02:32.6848229+04:00"},{"id":35030,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"bc5ca04bd394cfbe05b91187be1ef3e1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 271-300","gmt_create":"2026-04-28T10:02:32.6848229+04:00","gmt_modified":"2026-04-28T10:02:32.6848229+04:00"},{"id":35032,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"32c3cd87cb292dbfecb6d81dc0fc6739","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 506-507","gmt_create":"2026-04-28T10:02:32.6848229+04:00","gmt_modified":"2026-04-28T10:02:32.6848229+04:00"},{"id":35034,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"ba68b0a6c770e5eb7e9c206147e04add","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 232-243","gmt_create":"2026-04-28T10:02:32.6858226+04:00","gmt_modified":"2026-04-28T10:02:32.6858226+04:00"},{"id":35036,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"bf868153550f3b995f99ef4e396ddd77","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3444-3499","gmt_create":"2026-04-28T10:02:32.6868226+04:00","gmt_modified":"2026-04-28T10:02:32.6868226+04:00"},{"id":35038,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"e5c1c7808f2985bb68bc174eb390298a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 218-224","gmt_create":"2026-04-28T10:02:32.6878231+04:00","gmt_modified":"2026-04-28T10:02:32.6878231+04:00"},{"id":35040,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"e6c849182922d3df412daeb900b9e173","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 284-307","gmt_create":"2026-04-28T10:02:32.6888247+04:00","gmt_modified":"2026-04-28T10:02:32.6888247+04:00"},{"id":35042,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"05db694a94704c3e51ed70ce0118a1da","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1158-1198","gmt_create":"2026-04-28T10:02:32.6888247+04:00","gmt_modified":"2026-04-28T10:02:32.6888247+04:00"},{"id":35044,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e6a9a48c0e930773f0a82fa246c53f98","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3652-3655","gmt_create":"2026-04-28T10:02:32.6898255+04:00","gmt_modified":"2026-04-28T10:02:32.6898255+04:00"},{"id":35046,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"bd530cb9e845767f0a9b3ff967997984","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 93-141","gmt_create":"2026-04-28T10:02:32.6898255+04:00","gmt_modified":"2026-04-28T10:02:32.6898255+04:00"},{"id":35048,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"f10623a85ff8726287ec33204df6d61a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 458-584","gmt_create":"2026-04-28T10:02:32.6913308+04:00","gmt_modified":"2026-04-28T10:02:32.6913308+04:00"},{"id":35051,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"8f391118507fdd830a986c86e989a317","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2047-2144","gmt_create":"2026-04-28T10:02:32.6923361+04:00","gmt_modified":"2026-04-28T10:02:32.6923361+04:00"},{"id":35053,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"fa62adcb3d13201bfc9729dde67be04f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4378-4416","gmt_create":"2026-04-28T10:02:32.6923361+04:00","gmt_modified":"2026-04-28T10:02:32.6923361+04:00"},{"id":35055,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"98ed598d82d28fa1553560148bcda24e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2125-2142","gmt_create":"2026-04-28T10:02:32.6933365+04:00","gmt_modified":"2026-04-28T10:02:32.6933365+04:00"},{"id":35057,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5a5c235262f9722ff7bdf0586bfd8e26","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4220-4230","gmt_create":"2026-04-28T10:02:32.6933365+04:00","gmt_modified":"2026-04-28T10:02:32.6933365+04:00"},{"id":35059,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a8be8b7489d70072d66f1155610965d8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4517-4620","gmt_create":"2026-04-28T10:02:32.694335+04:00","gmt_modified":"2026-04-28T10:02:32.694335+04:00"},{"id":35061,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"39b6820362e87b0f937201fb8f54dee6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-10","gmt_create":"2026-04-28T10:02:32.694335+04:00","gmt_modified":"2026-04-28T10:02:32.694335+04:00"},{"id":35063,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1efc9fdbd77068cf27cb00354deff17a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-30","gmt_create":"2026-04-28T10:02:32.6953369+04:00","gmt_modified":"2026-04-28T10:02:32.6953369+04:00"},{"id":35065,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"92782cde6e026e561c0d55de0457f7a7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 800-830","gmt_create":"2026-04-28T10:02:32.6973382+04:00","gmt_modified":"2026-04-28T10:02:32.6973382+04:00"},{"id":35067,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d0e2616c3e70f0e809256dcb448916ab","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 270-279","gmt_create":"2026-04-28T10:02:32.6973382+04:00","gmt_modified":"2026-04-28T10:02:32.6973382+04:00"},{"id":35069,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"0855c233a79de418e4577d44bd54b9ba","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 492-501","gmt_create":"2026-04-28T10:02:32.6983352+04:00","gmt_modified":"2026-04-28T10:02:32.6983352+04:00"},{"id":35070,"source_id":"419b0fb1-31a5-4b4b-87f1-7adba3743630","target_id":"fec1e146-3e5c-4c90-9824-44c18fd37d36","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 419b0fb1-31a5-4b4b-87f1-7adba3743630 -\u003e fec1e146-3e5c-4c90-9824-44c18fd37d36","gmt_create":"2026-04-28T10:05:07.3069155+04:00","gmt_modified":"2026-04-28T10:05:07.3069155+04:00"},{"id":35071,"source_id":"419b0fb1-31a5-4b4b-87f1-7adba3743630","target_id":"c6931d42-c58d-4696-88f0-c17996821ffa","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 419b0fb1-31a5-4b4b-87f1-7adba3743630 -\u003e c6931d42-c58d-4696-88f0-c17996821ffa","gmt_create":"2026-04-28T10:05:07.3069155+04:00","gmt_modified":"2026-04-28T10:05:07.3069155+04:00"},{"id":35072,"source_id":"419b0fb1-31a5-4b4b-87f1-7adba3743630","target_id":"724010bf-a048-4a08-bf63-fc4bcac656b4","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 419b0fb1-31a5-4b4b-87f1-7adba3743630 -\u003e 724010bf-a048-4a08-bf63-fc4bcac656b4","gmt_create":"2026-04-28T10:05:07.3074363+04:00","gmt_modified":"2026-04-28T10:05:07.3074363+04:00"},{"id":35073,"source_id":"419b0fb1-31a5-4b4b-87f1-7adba3743630","target_id":"9e5f8d60-bebb-4161-a630-0880704f9f81","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 419b0fb1-31a5-4b4b-87f1-7adba3743630 -\u003e 9e5f8d60-bebb-4161-a630-0880704f9f81","gmt_create":"2026-04-28T10:05:07.3079528+04:00","gmt_modified":"2026-04-28T10:05:07.3079528+04:00"},{"id":35074,"source_id":"419b0fb1-31a5-4b4b-87f1-7adba3743630","target_id":"06bc216a-07e4-48fb-bdff-38335db957cc","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 419b0fb1-31a5-4b4b-87f1-7adba3743630 -\u003e 06bc216a-07e4-48fb-bdff-38335db957cc","gmt_create":"2026-04-28T10:05:07.3079528+04:00","gmt_modified":"2026-04-28T10:05:07.3079528+04:00"},{"id":35075,"source_id":"f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce","target_id":"4d58ea88-1cbb-46a1-9bb9-477c8e3d9846","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce -\u003e 4d58ea88-1cbb-46a1-9bb9-477c8e3d9846","gmt_create":"2026-04-28T10:05:07.3084628+04:00","gmt_modified":"2026-04-28T10:05:07.3084628+04:00"},{"id":35077,"source_id":"f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce","target_id":"24d2e14a-472d-4051-8c51-776999cfe4cc","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce -\u003e 24d2e14a-472d-4051-8c51-776999cfe4cc","gmt_create":"2026-04-28T10:05:07.3089777+04:00","gmt_modified":"2026-04-28T10:05:07.3089777+04:00"},{"id":35079,"source_id":"ecd87c29-4938-4318-9bbb-b3c2a87a2d22","target_id":"0be54f9c-0831-4de9-a839-fe492741178a","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ecd87c29-4938-4318-9bbb-b3c2a87a2d22 -\u003e 0be54f9c-0831-4de9-a839-fe492741178a","gmt_create":"2026-04-28T10:05:07.3100135+04:00","gmt_modified":"2026-04-28T10:05:07.3100135+04:00"},{"id":35080,"source_id":"4493b728-953f-4c7d-9b30-89aa33255a3b","target_id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4493b728-953f-4c7d-9b30-89aa33255a3b -\u003e c4b8579d-6410-4a31-8523-e38a9b1d69d3","gmt_create":"2026-04-28T10:05:07.3105347+04:00","gmt_modified":"2026-04-28T10:05:07.3105347+04:00"},{"id":35081,"source_id":"4493b728-953f-4c7d-9b30-89aa33255a3b","target_id":"0cb251fd-c588-4acc-8c03-1b2980db606b","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4493b728-953f-4c7d-9b30-89aa33255a3b -\u003e 0cb251fd-c588-4acc-8c03-1b2980db606b","gmt_create":"2026-04-28T10:05:07.3105347+04:00","gmt_modified":"2026-04-28T10:05:07.3105347+04:00"},{"id":35083,"source_id":"4493b728-953f-4c7d-9b30-89aa33255a3b","target_id":"be44fe88-0512-4e4b-a4b8-9e082214a604","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4493b728-953f-4c7d-9b30-89aa33255a3b -\u003e be44fe88-0512-4e4b-a4b8-9e082214a604","gmt_create":"2026-04-28T10:05:07.3110487+04:00","gmt_modified":"2026-04-28T10:05:07.3110487+04:00"},{"id":35084,"source_id":"e885f837-dcb8-4f4a-b0ff-5c620c375d5e","target_id":"0fc8c6be-5e2e-4d16-aa17-5e6b88335450","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e885f837-dcb8-4f4a-b0ff-5c620c375d5e -\u003e 0fc8c6be-5e2e-4d16-aa17-5e6b88335450","gmt_create":"2026-04-28T10:05:07.3110487+04:00","gmt_modified":"2026-04-28T10:05:07.3110487+04:00"},{"id":35085,"source_id":"e885f837-dcb8-4f4a-b0ff-5c620c375d5e","target_id":"efbfc523-380a-4c5c-89b1-b1b22375fdd1","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e885f837-dcb8-4f4a-b0ff-5c620c375d5e -\u003e efbfc523-380a-4c5c-89b1-b1b22375fdd1","gmt_create":"2026-04-28T10:05:07.3115703+04:00","gmt_modified":"2026-04-28T10:05:07.3115703+04:00"},{"id":35086,"source_id":"e885f837-dcb8-4f4a-b0ff-5c620c375d5e","target_id":"ecdb8c95-8cbb-4c16-b315-822594095634","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e885f837-dcb8-4f4a-b0ff-5c620c375d5e -\u003e ecdb8c95-8cbb-4c16-b315-822594095634","gmt_create":"2026-04-28T10:05:07.3115703+04:00","gmt_modified":"2026-04-28T10:05:07.3115703+04:00"},{"id":35087,"source_id":"e885f837-dcb8-4f4a-b0ff-5c620c375d5e","target_id":"52d914f6-5cef-4a53-bd37-4c7c074ddcb2","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e885f837-dcb8-4f4a-b0ff-5c620c375d5e -\u003e 52d914f6-5cef-4a53-bd37-4c7c074ddcb2","gmt_create":"2026-04-28T10:05:07.3120911+04:00","gmt_modified":"2026-04-28T10:05:07.3120911+04:00"},{"id":35088,"source_id":"cafaced8-cceb-47ca-9052-70e14192d570","target_id":"101024ac-5da4-4315-80c7-0cd7955ea4f8","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: cafaced8-cceb-47ca-9052-70e14192d570 -\u003e 101024ac-5da4-4315-80c7-0cd7955ea4f8","gmt_create":"2026-04-28T10:05:07.3120911+04:00","gmt_modified":"2026-04-28T10:05:07.3120911+04:00"},{"id":35089,"source_id":"cafaced8-cceb-47ca-9052-70e14192d570","target_id":"46914ef3-870f-4299-b0b1-9b5d299c5cad","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: cafaced8-cceb-47ca-9052-70e14192d570 -\u003e 46914ef3-870f-4299-b0b1-9b5d299c5cad","gmt_create":"2026-04-28T10:05:07.3224035+04:00","gmt_modified":"2026-04-28T10:05:07.3224035+04:00"},{"id":35090,"source_id":"cafaced8-cceb-47ca-9052-70e14192d570","target_id":"760f37d6-5fcd-49a4-ac1d-f5e886377331","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: cafaced8-cceb-47ca-9052-70e14192d570 -\u003e 760f37d6-5fcd-49a4-ac1d-f5e886377331","gmt_create":"2026-04-28T10:05:07.3224035+04:00","gmt_modified":"2026-04-28T10:05:07.3224035+04:00"},{"id":35091,"source_id":"cafaced8-cceb-47ca-9052-70e14192d570","target_id":"a3afccb6-22df-4d5b-bb93-2f591af782bf","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: cafaced8-cceb-47ca-9052-70e14192d570 -\u003e a3afccb6-22df-4d5b-bb93-2f591af782bf","gmt_create":"2026-04-28T10:05:07.3224035+04:00","gmt_modified":"2026-04-28T10:05:07.3224035+04:00"},{"id":35092,"source_id":"43570872-cdce-4428-b8dd-dd01164aa9b3","target_id":"d992f398-cce4-49e2-a21a-44ff13893da3","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 43570872-cdce-4428-b8dd-dd01164aa9b3 -\u003e d992f398-cce4-49e2-a21a-44ff13893da3","gmt_create":"2026-04-28T10:05:07.3234036+04:00","gmt_modified":"2026-04-28T10:05:07.3234036+04:00"},{"id":35093,"source_id":"43570872-cdce-4428-b8dd-dd01164aa9b3","target_id":"c9cc2572-d693-45ec-a7b0-6a1fa0a3d47e","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 43570872-cdce-4428-b8dd-dd01164aa9b3 -\u003e c9cc2572-d693-45ec-a7b0-6a1fa0a3d47e","gmt_create":"2026-04-28T10:05:07.3234036+04:00","gmt_modified":"2026-04-28T10:05:07.3234036+04:00"},{"id":35094,"source_id":"43570872-cdce-4428-b8dd-dd01164aa9b3","target_id":"4f51c881-94ed-4e49-b8cf-e3b1fab21e0e","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 43570872-cdce-4428-b8dd-dd01164aa9b3 -\u003e 4f51c881-94ed-4e49-b8cf-e3b1fab21e0e","gmt_create":"2026-04-28T10:05:07.3234036+04:00","gmt_modified":"2026-04-28T10:05:07.3234036+04:00"},{"id":35095,"source_id":"43570872-cdce-4428-b8dd-dd01164aa9b3","target_id":"09728486-f6d3-4cf1-bb8b-b5b404117331","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 43570872-cdce-4428-b8dd-dd01164aa9b3 -\u003e 09728486-f6d3-4cf1-bb8b-b5b404117331","gmt_create":"2026-04-28T10:05:07.3234036+04:00","gmt_modified":"2026-04-28T10:05:07.3234036+04:00"},{"id":35096,"source_id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","target_id":"e9540698-3e7d-4a1c-bdaa-0d96c8be0745","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c4b8579d-6410-4a31-8523-e38a9b1d69d3 -\u003e e9540698-3e7d-4a1c-bdaa-0d96c8be0745","gmt_create":"2026-04-28T10:05:07.3244037+04:00","gmt_modified":"2026-04-28T10:05:07.3244037+04:00"},{"id":35097,"source_id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","target_id":"18a3eced-7204-4ae6-ace6-9325629f7540","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c4b8579d-6410-4a31-8523-e38a9b1d69d3 -\u003e 18a3eced-7204-4ae6-ace6-9325629f7540","gmt_create":"2026-04-28T10:05:07.3244037+04:00","gmt_modified":"2026-04-28T10:05:07.3244037+04:00"},{"id":35098,"source_id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","target_id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c4b8579d-6410-4a31-8523-e38a9b1d69d3 -\u003e bf24a62f-5138-4dfb-9ecb-9d4133953010","gmt_create":"2026-04-28T10:05:07.3244037+04:00","gmt_modified":"2026-04-28T10:05:07.3244037+04:00"},{"id":35099,"source_id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","target_id":"85f42623-d6f4-4a64-9df9-84d74edb3ff8","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c4b8579d-6410-4a31-8523-e38a9b1d69d3 -\u003e 85f42623-d6f4-4a64-9df9-84d74edb3ff8","gmt_create":"2026-04-28T10:05:07.3244037+04:00","gmt_modified":"2026-04-28T10:05:07.3244037+04:00"},{"id":35100,"source_id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","target_id":"f1d8e320-e614-4981-b139-99ffa4534e71","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c4b8579d-6410-4a31-8523-e38a9b1d69d3 -\u003e f1d8e320-e614-4981-b139-99ffa4534e71","gmt_create":"2026-04-28T10:05:07.3254054+04:00","gmt_modified":"2026-04-28T10:05:07.3254054+04:00"},{"id":35101,"source_id":"101024ac-5da4-4315-80c7-0cd7955ea4f8","target_id":"f7e77aea-7354-410f-a8b6-ad12fbed8361","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 101024ac-5da4-4315-80c7-0cd7955ea4f8 -\u003e f7e77aea-7354-410f-a8b6-ad12fbed8361","gmt_create":"2026-04-28T10:05:07.3254054+04:00","gmt_modified":"2026-04-28T10:05:07.3254054+04:00"},{"id":35102,"source_id":"101024ac-5da4-4315-80c7-0cd7955ea4f8","target_id":"7e78b6cc-7d2d-4326-820e-37969b08647b","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 101024ac-5da4-4315-80c7-0cd7955ea4f8 -\u003e 7e78b6cc-7d2d-4326-820e-37969b08647b","gmt_create":"2026-04-28T10:05:07.3254054+04:00","gmt_modified":"2026-04-28T10:05:07.3254054+04:00"},{"id":35103,"source_id":"101024ac-5da4-4315-80c7-0cd7955ea4f8","target_id":"3b02cb64-064b-4b4c-804e-62b2ba00d9ad","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 101024ac-5da4-4315-80c7-0cd7955ea4f8 -\u003e 3b02cb64-064b-4b4c-804e-62b2ba00d9ad","gmt_create":"2026-04-28T10:05:07.3254054+04:00","gmt_modified":"2026-04-28T10:05:07.3254054+04:00"},{"id":35104,"source_id":"101024ac-5da4-4315-80c7-0cd7955ea4f8","target_id":"64d83c3f-4775-41fd-9d42-d6c12ca8054b","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 101024ac-5da4-4315-80c7-0cd7955ea4f8 -\u003e 64d83c3f-4775-41fd-9d42-d6c12ca8054b","gmt_create":"2026-04-28T10:05:07.3254054+04:00","gmt_modified":"2026-04-28T10:05:07.3254054+04:00"},{"id":35105,"source_id":"0cb251fd-c588-4acc-8c03-1b2980db606b","target_id":"c08ac7ca-a94d-4e15-8e9e-3dae3b1a5752","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 0cb251fd-c588-4acc-8c03-1b2980db606b -\u003e c08ac7ca-a94d-4e15-8e9e-3dae3b1a5752","gmt_create":"2026-04-28T10:05:07.3269109+04:00","gmt_modified":"2026-04-28T10:05:07.3269109+04:00"},{"id":35106,"source_id":"0cb251fd-c588-4acc-8c03-1b2980db606b","target_id":"36300f0a-f814-4ee7-8fea-443822b5ee7e","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 0cb251fd-c588-4acc-8c03-1b2980db606b -\u003e 36300f0a-f814-4ee7-8fea-443822b5ee7e","gmt_create":"2026-04-28T10:05:07.3269109+04:00","gmt_modified":"2026-04-28T10:05:07.3269109+04:00"},{"id":35107,"source_id":"c6931d42-c58d-4696-88f0-c17996821ffa","target_id":"cc17934e-5862-4f4b-99a7-0ed4fd1c2e2a","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c6931d42-c58d-4696-88f0-c17996821ffa -\u003e cc17934e-5862-4f4b-99a7-0ed4fd1c2e2a","gmt_create":"2026-04-28T10:05:07.3269109+04:00","gmt_modified":"2026-04-28T10:05:07.3269109+04:00"},{"id":35108,"source_id":"c6931d42-c58d-4696-88f0-c17996821ffa","target_id":"6ff3d5ed-5a93-44e9-b8fc-ee03618c7b1c","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c6931d42-c58d-4696-88f0-c17996821ffa -\u003e 6ff3d5ed-5a93-44e9-b8fc-ee03618c7b1c","gmt_create":"2026-04-28T10:05:07.3269109+04:00","gmt_modified":"2026-04-28T10:05:07.3269109+04:00"},{"id":35109,"source_id":"c6931d42-c58d-4696-88f0-c17996821ffa","target_id":"20e80b63-adb3-4fae-a0ff-7b4553335673","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c6931d42-c58d-4696-88f0-c17996821ffa -\u003e 20e80b63-adb3-4fae-a0ff-7b4553335673","gmt_create":"2026-04-28T10:05:07.3279896+04:00","gmt_modified":"2026-04-28T10:05:07.3279896+04:00"},{"id":35110,"source_id":"c6931d42-c58d-4696-88f0-c17996821ffa","target_id":"faf4c29b-a303-4f0e-b3de-f9679789aa09","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: c6931d42-c58d-4696-88f0-c17996821ffa -\u003e faf4c29b-a303-4f0e-b3de-f9679789aa09","gmt_create":"2026-04-28T10:05:07.3279896+04:00","gmt_modified":"2026-04-28T10:05:07.3279896+04:00"},{"id":35115,"source_id":"724010bf-a048-4a08-bf63-fc4bcac656b4","target_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 724010bf-a048-4a08-bf63-fc4bcac656b4 -\u003e 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","gmt_create":"2026-04-28T10:05:07.3289902+04:00","gmt_modified":"2026-04-28T10:05:07.3289902+04:00"},{"id":35116,"source_id":"724010bf-a048-4a08-bf63-fc4bcac656b4","target_id":"b52202cc-02f2-44ed-bba8-d9428b217809","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 724010bf-a048-4a08-bf63-fc4bcac656b4 -\u003e b52202cc-02f2-44ed-bba8-d9428b217809","gmt_create":"2026-04-28T10:05:07.32999+04:00","gmt_modified":"2026-04-28T10:05:07.32999+04:00"},{"id":35118,"source_id":"724010bf-a048-4a08-bf63-fc4bcac656b4","target_id":"32f6a790-8183-436f-a550-04552fa6b462","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 724010bf-a048-4a08-bf63-fc4bcac656b4 -\u003e 32f6a790-8183-436f-a550-04552fa6b462","gmt_create":"2026-04-28T10:05:07.32999+04:00","gmt_modified":"2026-04-28T10:05:07.32999+04:00"},{"id":35119,"source_id":"9e5f8d60-bebb-4161-a630-0880704f9f81","target_id":"28f563ee-bb1b-42be-8243-e7bfa17eb793","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 9e5f8d60-bebb-4161-a630-0880704f9f81 -\u003e 28f563ee-bb1b-42be-8243-e7bfa17eb793","gmt_create":"2026-04-28T10:05:07.32999+04:00","gmt_modified":"2026-04-28T10:05:07.32999+04:00"},{"id":35121,"source_id":"9e5f8d60-bebb-4161-a630-0880704f9f81","target_id":"f8aac207-e623-4724-8003-ced4b4d90bf8","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 9e5f8d60-bebb-4161-a630-0880704f9f81 -\u003e f8aac207-e623-4724-8003-ced4b4d90bf8","gmt_create":"2026-04-28T10:05:07.3309899+04:00","gmt_modified":"2026-04-28T10:05:07.3309899+04:00"},{"id":35122,"source_id":"9e5f8d60-bebb-4161-a630-0880704f9f81","target_id":"aedcc69e-d00f-471d-a338-b290a71a8310","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 9e5f8d60-bebb-4161-a630-0880704f9f81 -\u003e aedcc69e-d00f-471d-a338-b290a71a8310","gmt_create":"2026-04-28T10:05:07.3309899+04:00","gmt_modified":"2026-04-28T10:05:07.3309899+04:00"},{"id":35123,"source_id":"e9540698-3e7d-4a1c-bdaa-0d96c8be0745","target_id":"371d43b9-4b79-40d6-a740-d0e09043a493","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e9540698-3e7d-4a1c-bdaa-0d96c8be0745 -\u003e 371d43b9-4b79-40d6-a740-d0e09043a493","gmt_create":"2026-04-28T10:05:07.3319916+04:00","gmt_modified":"2026-04-28T10:05:07.3319916+04:00"},{"id":35124,"source_id":"e9540698-3e7d-4a1c-bdaa-0d96c8be0745","target_id":"818151df-a208-4937-9c96-909884f6bbb8","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e9540698-3e7d-4a1c-bdaa-0d96c8be0745 -\u003e 818151df-a208-4937-9c96-909884f6bbb8","gmt_create":"2026-04-28T10:05:07.3319916+04:00","gmt_modified":"2026-04-28T10:05:07.3319916+04:00"},{"id":35125,"source_id":"e9540698-3e7d-4a1c-bdaa-0d96c8be0745","target_id":"93891608-7b30-4aca-9ab2-5d88a2e3225f","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e9540698-3e7d-4a1c-bdaa-0d96c8be0745 -\u003e 93891608-7b30-4aca-9ab2-5d88a2e3225f","gmt_create":"2026-04-28T10:05:07.3319916+04:00","gmt_modified":"2026-04-28T10:05:07.3319916+04:00"},{"id":35126,"source_id":"e9540698-3e7d-4a1c-bdaa-0d96c8be0745","target_id":"3e146dd5-1515-4f08-a766-7c555069570d","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: e9540698-3e7d-4a1c-bdaa-0d96c8be0745 -\u003e 3e146dd5-1515-4f08-a766-7c555069570d","gmt_create":"2026-04-28T10:05:07.3329938+04:00","gmt_modified":"2026-04-28T10:05:07.3329938+04:00"},{"id":35128,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"81bc0ef1-cb71-44d4-a313-c0adacd3b67b","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e 81bc0ef1-cb71-44d4-a313-c0adacd3b67b","gmt_create":"2026-04-28T10:05:07.3339936+04:00","gmt_modified":"2026-04-28T10:05:07.3339936+04:00"},{"id":35131,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"0c2a4f4d-a142-41b8-81d4-a4c72022cd6d","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e 0c2a4f4d-a142-41b8-81d4-a4c72022cd6d","gmt_create":"2026-04-28T10:05:07.3349907+04:00","gmt_modified":"2026-04-28T10:05:07.3349907+04:00"},{"id":35132,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"03ac3143-5983-4c15-be9a-0e032445a800","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e 03ac3143-5983-4c15-be9a-0e032445a800","gmt_create":"2026-04-28T10:05:07.3349907+04:00","gmt_modified":"2026-04-28T10:05:07.3349907+04:00"},{"id":35134,"source_id":"b52202cc-02f2-44ed-bba8-d9428b217809","target_id":"83f9e213-4dbf-4d44-8484-71f9339f5ed7","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: b52202cc-02f2-44ed-bba8-d9428b217809 -\u003e 83f9e213-4dbf-4d44-8484-71f9339f5ed7","gmt_create":"2026-04-28T10:05:07.3349907+04:00","gmt_modified":"2026-04-28T10:05:07.3349907+04:00"},{"id":35135,"source_id":"b52202cc-02f2-44ed-bba8-d9428b217809","target_id":"c911bbfb-6d46-458b-ac1c-dbba112490e9","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: b52202cc-02f2-44ed-bba8-d9428b217809 -\u003e c911bbfb-6d46-458b-ac1c-dbba112490e9","gmt_create":"2026-04-28T10:05:07.3359902+04:00","gmt_modified":"2026-04-28T10:05:07.3359902+04:00"},{"id":35136,"source_id":"b52202cc-02f2-44ed-bba8-d9428b217809","target_id":"0f6511c5-05bc-4ef7-8e04-442a302db49c","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: b52202cc-02f2-44ed-bba8-d9428b217809 -\u003e 0f6511c5-05bc-4ef7-8e04-442a302db49c","gmt_create":"2026-04-28T10:05:07.3359902+04:00","gmt_modified":"2026-04-28T10:05:07.3359902+04:00"},{"id":35137,"source_id":"b52202cc-02f2-44ed-bba8-d9428b217809","target_id":"d7562df5-2f68-4c8b-9b1b-1aff56d025ab","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: b52202cc-02f2-44ed-bba8-d9428b217809 -\u003e d7562df5-2f68-4c8b-9b1b-1aff56d025ab","gmt_create":"2026-04-28T10:05:07.3359902+04:00","gmt_modified":"2026-04-28T10:05:07.3359902+04:00"},{"id":35138,"source_id":"b52202cc-02f2-44ed-bba8-d9428b217809","target_id":"52188625-f997-45b2-ab6a-1b01391bde00","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: b52202cc-02f2-44ed-bba8-d9428b217809 -\u003e 52188625-f997-45b2-ab6a-1b01391bde00","gmt_create":"2026-04-28T10:05:07.3359902+04:00","gmt_modified":"2026-04-28T10:05:07.3359902+04:00"},{"id":35139,"source_id":"18a3eced-7204-4ae6-ace6-9325629f7540","target_id":"6173e746-57a9-469a-ac23-90061ef56ab6","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 18a3eced-7204-4ae6-ace6-9325629f7540 -\u003e 6173e746-57a9-469a-ac23-90061ef56ab6","gmt_create":"2026-04-28T10:05:07.3359902+04:00","gmt_modified":"2026-04-28T10:05:07.3359902+04:00"},{"id":35140,"source_id":"18a3eced-7204-4ae6-ace6-9325629f7540","target_id":"cbe0e279-74ff-4c6f-bc5d-ac88e9bcc6ac","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 18a3eced-7204-4ae6-ace6-9325629f7540 -\u003e cbe0e279-74ff-4c6f-bc5d-ac88e9bcc6ac","gmt_create":"2026-04-28T10:05:07.3369899+04:00","gmt_modified":"2026-04-28T10:05:07.3369899+04:00"},{"id":35141,"source_id":"18a3eced-7204-4ae6-ace6-9325629f7540","target_id":"43b1b513-1146-4508-9929-bfca0e7bc21d","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 18a3eced-7204-4ae6-ace6-9325629f7540 -\u003e 43b1b513-1146-4508-9929-bfca0e7bc21d","gmt_create":"2026-04-28T10:05:07.3369899+04:00","gmt_modified":"2026-04-28T10:05:07.3369899+04:00"},{"id":35142,"source_id":"18a3eced-7204-4ae6-ace6-9325629f7540","target_id":"75c788ef-0d7a-4a59-a02a-d56403aa3459","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 18a3eced-7204-4ae6-ace6-9325629f7540 -\u003e 75c788ef-0d7a-4a59-a02a-d56403aa3459","gmt_create":"2026-04-28T10:05:07.3369899+04:00","gmt_modified":"2026-04-28T10:05:07.3369899+04:00"},{"id":35143,"source_id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","target_id":"9c3a79b2-0a34-4da6-8159-eb775b084846","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: bf24a62f-5138-4dfb-9ecb-9d4133953010 -\u003e 9c3a79b2-0a34-4da6-8159-eb775b084846","gmt_create":"2026-04-28T10:05:07.3369899+04:00","gmt_modified":"2026-04-28T10:05:07.3369899+04:00"},{"id":35144,"source_id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","target_id":"58e53605-3c83-4017-8c41-9a5919796003","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: bf24a62f-5138-4dfb-9ecb-9d4133953010 -\u003e 58e53605-3c83-4017-8c41-9a5919796003","gmt_create":"2026-04-28T10:05:07.3381724+04:00","gmt_modified":"2026-04-28T10:05:07.3381724+04:00"},{"id":35145,"source_id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","target_id":"2d5de852-4c30-4e77-8e31-3963f875ae85","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: bf24a62f-5138-4dfb-9ecb-9d4133953010 -\u003e 2d5de852-4c30-4e77-8e31-3963f875ae85","gmt_create":"2026-04-28T10:05:07.3381724+04:00","gmt_modified":"2026-04-28T10:05:07.3381724+04:00"},{"id":35146,"source_id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","target_id":"e2b16877-3e41-4465-91f2-48e5dacfd2ad","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: bf24a62f-5138-4dfb-9ecb-9d4133953010 -\u003e e2b16877-3e41-4465-91f2-48e5dacfd2ad","gmt_create":"2026-04-28T10:05:07.3381724+04:00","gmt_modified":"2026-04-28T10:05:07.3381724+04:00"},{"id":35147,"source_id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","target_id":"44884ed2-2c27-4d94-af1e-c939b85e2dba","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: bf24a62f-5138-4dfb-9ecb-9d4133953010 -\u003e 44884ed2-2c27-4d94-af1e-c939b85e2dba","gmt_create":"2026-04-28T10:05:07.3381724+04:00","gmt_modified":"2026-04-28T10:05:07.3381724+04:00"},{"id":35436,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"f2c0625ed2d13bb0a3699a45bf25d6cc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-50","gmt_create":"2026-04-28T12:27:40.783289+04:00","gmt_modified":"2026-04-28T12:27:40.783289+04:00"},{"id":35438,"source_id":"bd196d115b6bd3d5310dfa247f1e25b2","target_id":"b49193d53f2bca5e5a84c905c1f90429","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-88","gmt_create":"2026-04-28T12:27:40.783289+04:00","gmt_modified":"2026-04-28T12:27:40.783289+04:00"},{"id":35440,"source_id":"991938d306a547ff48a759fb9bd1c5a4","target_id":"ca74187bf1151ee3b389754df4ce08d1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-52","gmt_create":"2026-04-28T12:27:40.7838156+04:00","gmt_modified":"2026-04-28T12:27:40.7838156+04:00"},{"id":35442,"source_id":"dc43b9a20a2ae22effbe51aecf8ca751","target_id":"60ec378a66a7653324d69f456e1242c4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-52","gmt_create":"2026-04-28T12:27:40.7843455+04:00","gmt_modified":"2026-04-28T12:27:40.7843455+04:00"},{"id":35444,"source_id":"bd196d115b6bd3d5310dfa247f1e25b2","target_id":"93f965590673de9c5fe2a34fbc24af1c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 42-76","gmt_create":"2026-04-28T12:27:40.7848635+04:00","gmt_modified":"2026-04-28T12:27:40.7848635+04:00"},{"id":35446,"source_id":"991938d306a547ff48a759fb9bd1c5a4","target_id":"98907d07c594fe14c157ba203649b596","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 16-52","gmt_create":"2026-04-28T12:27:40.7848635+04:00","gmt_modified":"2026-04-28T12:27:40.7848635+04:00"},{"id":35448,"source_id":"d8e14923de7e4be8f600e264a03ea281","target_id":"aba83bcb80fe7ecb8f1f224f2fca05da","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 30-158","gmt_create":"2026-04-28T12:27:40.7853793+04:00","gmt_modified":"2026-04-28T12:27:40.7853793+04:00"},{"id":35450,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"33fc7efcd171685ef3235423dc636724","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 675-780","gmt_create":"2026-04-28T12:27:40.7853793+04:00","gmt_modified":"2026-04-28T12:27:40.7853793+04:00"},{"id":35452,"source_id":"d8e14923de7e4be8f600e264a03ea281","target_id":"0a6e409382fdf7cc6924a9f918b5a4d8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 37-107","gmt_create":"2026-04-28T12:27:40.7858969+04:00","gmt_modified":"2026-04-28T12:27:40.7858969+04:00"},{"id":35454,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"d419300bbe8000129fd9765417390c9d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 885-987","gmt_create":"2026-04-28T12:27:40.7864124+04:00","gmt_modified":"2026-04-28T12:27:40.7864124+04:00"},{"id":35456,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"c331a645eb7848bda9b3f651866478ec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 789-883","gmt_create":"2026-04-28T12:27:40.7869301+04:00","gmt_modified":"2026-04-28T12:27:40.7869301+04:00"},{"id":35458,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"06ca849a27b79d1edd6a6faaabf7a1fa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1400-1484","gmt_create":"2026-04-28T12:27:40.7869301+04:00","gmt_modified":"2026-04-28T12:27:40.7869301+04:00"},{"id":35460,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"9102ca0b920a6d527e069776bf786565","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1046-1288","gmt_create":"2026-04-28T12:27:40.7874488+04:00","gmt_modified":"2026-04-28T12:27:40.7874488+04:00"},{"id":35462,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"272ad74758e94f746c862c4c91c17496","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1902-2038","gmt_create":"2026-04-28T12:27:40.7874488+04:00","gmt_modified":"2026-04-28T12:27:40.7874488+04:00"},{"id":35464,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"ea6973621046033cdcd6899b7a2bcb0f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1470-1599","gmt_create":"2026-04-28T12:27:40.7879647+04:00","gmt_modified":"2026-04-28T12:27:40.7879647+04:00"},{"id":35466,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"2f7386ded19c5e4ae6581ef6148fdd81","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2473-2510","gmt_create":"2026-04-28T12:27:40.7884803+04:00","gmt_modified":"2026-04-28T12:27:40.7884803+04:00"},{"id":35468,"source_id":"4d5bf798ac6e167d6d0e20a669431373","target_id":"5fcc5bdcf25fd73b110eede565277186","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 247-273","gmt_create":"2026-04-28T12:27:40.7890081+04:00","gmt_modified":"2026-04-28T12:27:40.7890081+04:00"},{"id":35470,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"7c62e5936f0fc7f60f9a5587800a2578","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1418-1436","gmt_create":"2026-04-28T12:27:40.7895307+04:00","gmt_modified":"2026-04-28T12:27:40.7895307+04:00"},{"id":35472,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"983d0185105181e4f539d636abc549c9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 737-743","gmt_create":"2026-04-28T12:27:40.7900433+04:00","gmt_modified":"2026-04-28T12:27:40.7900433+04:00"},{"id":35474,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"82acc28cade2d06dac2a207816ccf888","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1390-1484","gmt_create":"2026-04-28T12:27:40.7900433+04:00","gmt_modified":"2026-04-28T12:27:40.7900433+04:00"},{"id":35476,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"1d3d2cb1a9886c18b5c14d6f4bbbec7c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1440-1449","gmt_create":"2026-04-28T12:27:40.790638+04:00","gmt_modified":"2026-04-28T12:27:40.790638+04:00"},{"id":35478,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"de42aaaef014331a40dc6a645093a785","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 335-551","gmt_create":"2026-04-28T12:27:40.790638+04:00","gmt_modified":"2026-04-28T12:27:40.790638+04:00"},{"id":35480,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"e9427d96ecb45f8e18859dbaff6e2b3a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1326-1376","gmt_create":"2026-04-28T12:27:40.7911415+04:00","gmt_modified":"2026-04-28T12:27:40.7911415+04:00"},{"id":35482,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"ddc5b6727e7907a53519cc63983aabb7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1426-1435","gmt_create":"2026-04-28T12:27:40.7916629+04:00","gmt_modified":"2026-04-28T12:27:40.7916629+04:00"},{"id":35484,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"a54238157d7db2d12e89ea767738f5ea","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 745-750","gmt_create":"2026-04-28T12:27:40.7916629+04:00","gmt_modified":"2026-04-28T12:27:40.7916629+04:00"},{"id":35486,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"8481665c6e787cad6255e9ace82fe2c2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 697-700","gmt_create":"2026-04-28T12:27:40.7921765+04:00","gmt_modified":"2026-04-28T12:27:40.7921765+04:00"},{"id":35488,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"91b6373025370a39784ff61586453267","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2831-2845","gmt_create":"2026-04-28T12:27:40.7921765+04:00","gmt_modified":"2026-04-28T12:27:40.7921765+04:00"},{"id":35490,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"fa6088c57e5552b03a53d22a8b3371cd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1719-1748","gmt_create":"2026-04-28T12:27:40.7927025+04:00","gmt_modified":"2026-04-28T12:27:40.7927025+04:00"},{"id":35492,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"2ef1c85090d0812eda040eb10dbfab00","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1706-1748","gmt_create":"2026-04-28T12:27:40.7927025+04:00","gmt_modified":"2026-04-28T12:27:40.7927025+04:00"},{"id":35494,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"6c4fffc1a4b8a3dccdccf3bc8b6e478c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 490-560","gmt_create":"2026-04-28T12:27:40.7927025+04:00","gmt_modified":"2026-04-28T12:27:40.7927025+04:00"},{"id":35496,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"002084afc1181b400d0ac1f626010b50","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2945-2959","gmt_create":"2026-04-28T12:27:40.7932232+04:00","gmt_modified":"2026-04-28T12:27:40.7932232+04:00"},{"id":35498,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a9ac85c210bf25017dc2f3043429bcff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 441-5201","gmt_create":"2026-04-28T12:27:40.7937357+04:00","gmt_modified":"2026-04-28T12:27:40.7937357+04:00"},{"id":35500,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"4083d0aedccff773e235c49f483acdee","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 542-559","gmt_create":"2026-04-28T12:27:40.7942525+04:00","gmt_modified":"2026-04-28T12:27:40.7942525+04:00"},{"id":35502,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"5b61f5ff88b75190fc4b2051750444dd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2976-3009","gmt_create":"2026-04-28T12:27:40.7942525+04:00","gmt_modified":"2026-04-28T12:27:40.7942525+04:00"},{"id":35504,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"133becde6f5eef3f56c38aa3650af39f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2468-2570","gmt_create":"2026-04-28T12:27:40.7947682+04:00","gmt_modified":"2026-04-28T12:27:40.7947682+04:00"},{"id":35506,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"7c3bbfa78db4dbdd170795e4ff786767","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 689-697","gmt_create":"2026-04-28T12:27:40.7947682+04:00","gmt_modified":"2026-04-28T12:27:40.7947682+04:00"},{"id":35508,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"de95ec3c9606ef77f32c319a1cee9d67","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5241-5274","gmt_create":"2026-04-28T12:27:40.7953867+04:00","gmt_modified":"2026-04-28T12:27:40.7953867+04:00"},{"id":35510,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"4ad076dd99ea06a22ba2703dfdfbef50","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 284-290","gmt_create":"2026-04-28T12:27:40.8035734+04:00","gmt_modified":"2026-04-28T12:27:40.8035734+04:00"},{"id":35512,"source_id":"bd196d115b6bd3d5310dfa247f1e25b2","target_id":"d21d0e6c03944b9bd0683917197d45ec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 86-88","gmt_create":"2026-04-28T12:27:40.8035734+04:00","gmt_modified":"2026-04-28T12:27:40.8035734+04:00"},{"id":35514,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"ce8c1f15287ef92650480373eef95fb8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 735-740","gmt_create":"2026-04-28T12:27:40.8045729+04:00","gmt_modified":"2026-04-28T12:27:40.8045729+04:00"},{"id":35516,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"474f1f81f21526cb90fc2564fdc36457","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1814-1862","gmt_create":"2026-04-28T12:27:40.8045729+04:00","gmt_modified":"2026-04-28T12:27:40.8045729+04:00"},{"id":35518,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"3fcde09e22fa6291183075924ea0ea2a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 772-785","gmt_create":"2026-04-28T12:27:40.8045729+04:00","gmt_modified":"2026-04-28T12:27:40.8045729+04:00"},{"id":35520,"source_id":"4d5bf798ac6e167d6d0e20a669431373","target_id":"25271321cea3b037da1bd49f4a9c73e3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 339-374","gmt_create":"2026-04-28T12:27:40.8045729+04:00","gmt_modified":"2026-04-28T12:27:40.8045729+04:00"},{"id":35522,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"ffac59891823846e038c48de0f7fa754","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 585-649","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35524,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"ce8fa82841cb9ff1bb572dde21cd2dca","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 673-677","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35526,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"31a0e1cdb8f61a661ea048c18a0c31ab","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 744-755","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35528,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"e360b37afb60df02b8a51d89f141bf95","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 165-176","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35530,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"a4b87380168ea56eda2691254c138879","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1587-1596","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35532,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"0442d9dfa5137479a458da6803ce6d7d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1610-1620","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35534,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"5f1666b71febd2ce3545e7b9c05598fa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1812-1877","gmt_create":"2026-04-28T12:27:40.8055727+04:00","gmt_modified":"2026-04-28T12:27:40.8055727+04:00"},{"id":35536,"source_id":"bd196d115b6bd3d5310dfa247f1e25b2","target_id":"f7cc310f8ee11ed3c2ac13ce575146d4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 24-34","gmt_create":"2026-04-28T12:27:40.8070771+04:00","gmt_modified":"2026-04-28T12:27:40.8070771+04:00"},{"id":35538,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"24d1f458412bbfade9d25c0ed1521322","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2598-2680","gmt_create":"2026-04-28T12:27:40.8076056+04:00","gmt_modified":"2026-04-28T12:27:40.8076056+04:00"},{"id":35540,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"985521912575155bfda8b7bce74494df","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 364-432","gmt_create":"2026-04-28T12:27:40.8076056+04:00","gmt_modified":"2026-04-28T12:27:40.8076056+04:00"},{"id":35542,"source_id":"dc43b9a20a2ae22effbe51aecf8ca751","target_id":"299aedc88851dabd155fc2edbbad96ef","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 27-38","gmt_create":"2026-04-28T12:27:40.8076056+04:00","gmt_modified":"2026-04-28T12:27:40.8076056+04:00"},{"id":35544,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"2a76e2c48df7946c93b84c330a1c08ae","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2294-2464","gmt_create":"2026-04-28T12:27:40.8081245+04:00","gmt_modified":"2026-04-28T12:27:40.8081245+04:00"},{"id":35546,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"d7fc3783ff3c58fa7beaff10948417fa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1378-1464","gmt_create":"2026-04-28T12:27:40.8081245+04:00","gmt_modified":"2026-04-28T12:27:40.8081245+04:00"},{"id":35683,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"4238a9561f85e50a38f76813baeadd7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/block_log.hpp","gmt_create":"2026-04-28T12:54:08.0803549+04:00","gmt_modified":"2026-04-28T12:54:08.0803549+04:00"},{"id":35684,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"d2090ff9016be0d896d06e843936e0f4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/block_log.cpp","gmt_create":"2026-04-28T12:54:08.0803549+04:00","gmt_modified":"2026-04-28T12:54:08.0803549+04:00"},{"id":35685,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/dlt_block_log.hpp","gmt_create":"2026-04-28T12:54:08.0803549+04:00","gmt_modified":"2026-04-28T12:54:08.0803549+04:00"},{"id":35686,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-28T12:54:08.0808764+04:00","gmt_modified":"2026-04-28T12:54:08.0808764+04:00"},{"id":35687,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-28T12:54:08.0819174+04:00","gmt_modified":"2026-04-28T12:54:08.0819174+04:00"},{"id":35688,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-28T12:54:08.0819174+04:00","gmt_modified":"2026-04-28T12:54:08.0819174+04:00"},{"id":35689,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-28T12:54:08.0824359+04:00","gmt_modified":"2026-04-28T12:54:08.0824359+04:00"},{"id":35690,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-28T12:54:08.0824359+04:00","gmt_modified":"2026-04-28T12:54:08.0824359+04:00"},{"id":35691,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"d6fb716d9203d54e5aaccd580adc4703","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/block.hpp","gmt_create":"2026-04-28T12:54:08.0824359+04:00","gmt_modified":"2026-04-28T12:54:08.0824359+04:00"},{"id":35692,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"3b0d308523637c64e62fac4d1a2a4a66","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/block_header.hpp","gmt_create":"2026-04-28T12:54:08.0824359+04:00","gmt_modified":"2026-04-28T12:54:08.0824359+04:00"},{"id":35693,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"0dd2a38630da83b11fb3596ad4d60705","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/include/graphene/plugins/witness/witness.hpp","gmt_create":"2026-04-28T12:54:08.0824359+04:00","gmt_modified":"2026-04-28T12:54:08.0824359+04:00"},{"id":35694,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-28T12:54:08.0829598+04:00","gmt_modified":"2026-04-28T12:54:08.0829598+04:00"},{"id":35695,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"cb29035725926be38d36ad8c01792b7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database_exceptions.hpp","gmt_create":"2026-04-28T12:54:08.0829598+04:00","gmt_modified":"2026-04-28T12:54:08.0829598+04:00"},{"id":35696,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"0acaf479cbd8b11ed2774cc96aa68335","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/block_header.hpp#1-43","gmt_create":"2026-04-28T12:54:08.0829598+04:00","gmt_modified":"2026-04-28T12:54:08.0829598+04:00"},{"id":35697,"source_id":"3b0d308523637c64e62fac4d1a2a4a66","target_id":"0acaf479cbd8b11ed2774cc96aa68335","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-43","gmt_create":"2026-04-28T12:54:08.0829598+04:00","gmt_modified":"2026-04-28T12:54:08.0829598+04:00"},{"id":35698,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"65b16a5b2283f9a71e063b5381577bae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/block.hpp#1-19","gmt_create":"2026-04-28T12:54:08.0834764+04:00","gmt_modified":"2026-04-28T12:54:08.0834764+04:00"},{"id":35699,"source_id":"d6fb716d9203d54e5aaccd580adc4703","target_id":"65b16a5b2283f9a71e063b5381577bae","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-19","gmt_create":"2026-04-28T12:54:08.0834764+04:00","gmt_modified":"2026-04-28T12:54:08.0834764+04:00"},{"id":35700,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"54d3aec8b80d5706beb75720a6232861","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#1-561","gmt_create":"2026-04-28T12:54:08.0834764+04:00","gmt_modified":"2026-04-28T12:54:08.0834764+04:00"},{"id":35701,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"54d3aec8b80d5706beb75720a6232861","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-561","gmt_create":"2026-04-28T12:54:08.083993+04:00","gmt_modified":"2026-04-28T12:54:08.083993+04:00"},{"id":35702,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"0b148db227f6081cc819c2f686550d84","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#737-913","gmt_create":"2026-04-28T12:54:08.083993+04:00","gmt_modified":"2026-04-28T12:54:08.083993+04:00"},{"id":35703,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"0b148db227f6081cc819c2f686550d84","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 737-913","gmt_create":"2026-04-28T12:54:08.083993+04:00","gmt_modified":"2026-04-28T12:54:08.083993+04:00"},{"id":35704,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"3409eb023f550b5d11bccc50d317764a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#1-125","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35705,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"3409eb023f550b5d11bccc50d317764a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-125","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35706,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"e29d6a6310d6247eaf38fa2c35b35373","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#33-90","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35707,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"e29d6a6310d6247eaf38fa2c35b35373","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 33-90","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35708,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"2c17018b7a7ecf1dd7b6432e97d0a586","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#1-75","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35709,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"faea01a77c78aa0e75d22b0d5cafa704","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#238-300","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35710,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"faea01a77c78aa0e75d22b0d5cafa704","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 238-300","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35711,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"6ebf29a038d578e8864ca6c9c9366bda","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#1-76","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35712,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"60cef3a5a468a40533451ed7d7e09503","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#162-242","gmt_create":"2026-04-28T12:54:08.0845151+04:00","gmt_modified":"2026-04-28T12:54:08.0845151+04:00"},{"id":35713,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"60cef3a5a468a40533451ed7d7e09503","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 162-242","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35714,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"10f717e60b716201555827209e101cba","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/include/graphene/plugins/witness/witness.hpp#1-70","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35715,"source_id":"0dd2a38630da83b11fb3596ad4d60705","target_id":"10f717e60b716201555827209e101cba","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-70","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35716,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"04c7b01cbc51b1f23ab4704279190eda","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#295-341","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35717,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"04c7b01cbc51b1f23ab4704279190eda","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 295-341","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35718,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"11090a858982a9991f12a111ff65ebec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/block.hpp#9-13","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35719,"source_id":"d6fb716d9203d54e5aaccd580adc4703","target_id":"11090a858982a9991f12a111ff65ebec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 9-13","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35720,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"91b8b7279712d5789fb67e0c06442732","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/block_header.hpp#25-35","gmt_create":"2026-04-28T12:54:08.0855185+04:00","gmt_modified":"2026-04-28T12:54:08.0855185+04:00"},{"id":35721,"source_id":"3b0d308523637c64e62fac4d1a2a4a66","target_id":"91b8b7279712d5789fb67e0c06442732","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 25-35","gmt_create":"2026-04-28T12:54:08.0870693+04:00","gmt_modified":"2026-04-28T12:54:08.0870693+04:00"},{"id":35722,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"5800e8f7ef6353fa3f37c39ea75ff454","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#53-96","gmt_create":"2026-04-28T12:54:08.0870693+04:00","gmt_modified":"2026-04-28T12:54:08.0870693+04:00"},{"id":35723,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"5800e8f7ef6353fa3f37c39ea75ff454","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 53-96","gmt_create":"2026-04-28T12:54:08.0870693+04:00","gmt_modified":"2026-04-28T12:54:08.0870693+04:00"},{"id":35724,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"6da448acd4e86b4234427febab48fbca","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#38-68","gmt_create":"2026-04-28T12:54:08.0870693+04:00","gmt_modified":"2026-04-28T12:54:08.0870693+04:00"},{"id":35725,"source_id":"4238a9561f85e50a38f76813baeadd7e","target_id":"6da448acd4e86b4234427febab48fbca","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-68","gmt_create":"2026-04-28T12:54:08.0870693+04:00","gmt_modified":"2026-04-28T12:54:08.0870693+04:00"},{"id":35726,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"35d8d0314587b8714a154437fe7243af","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#13-33","gmt_create":"2026-04-28T12:54:08.088074+04:00","gmt_modified":"2026-04-28T12:54:08.088074+04:00"},{"id":35727,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"35d8d0314587b8714a154437fe7243af","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 13-33","gmt_create":"2026-04-28T12:54:08.088074+04:00","gmt_modified":"2026-04-28T12:54:08.088074+04:00"},{"id":35728,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"827fa8459b891ae8c31d3194640245da","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#36-287","gmt_create":"2026-04-28T12:54:08.088074+04:00","gmt_modified":"2026-04-28T12:54:08.088074+04:00"},{"id":35729,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"827fa8459b891ae8c31d3194640245da","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 36-287","gmt_create":"2026-04-28T12:54:08.088074+04:00","gmt_modified":"2026-04-28T12:54:08.088074+04:00"},{"id":35730,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"7d0d371be208eb98ecc21b182f56ab1b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/include/graphene/plugins/witness/witness.hpp#20-32","gmt_create":"2026-04-28T12:54:08.088074+04:00","gmt_modified":"2026-04-28T12:54:08.088074+04:00"},{"id":35731,"source_id":"0dd2a38630da83b11fb3596ad4d60705","target_id":"7d0d371be208eb98ecc21b182f56ab1b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 20-32","gmt_create":"2026-04-28T12:54:08.088074+04:00","gmt_modified":"2026-04-28T12:54:08.088074+04:00"},{"id":35732,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"0b84b82025c87f3b725ddf0c3804f197","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#253-257","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35733,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"0b84b82025c87f3b725ddf0c3804f197","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 253-257","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35734,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"01b277ebba52e972b94566a19ab057fd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#336-340","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35735,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"01b277ebba52e972b94566a19ab057fd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 336-340","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35736,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"161ddc3f0d369da41719390d93b735c9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#194-206","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35737,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"161ddc3f0d369da41719390d93b735c9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 194-206","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35738,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"d4957d3a60b33265b5b19edffd627403","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#737-757","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35739,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d4957d3a60b33265b5b19edffd627403","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 737-757","gmt_create":"2026-04-28T12:54:08.0890755+04:00","gmt_modified":"2026-04-28T12:54:08.0890755+04:00"},{"id":35740,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"9025ea64db0b2dfc92ca8bab0394cc08","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3443-3509","gmt_create":"2026-04-28T12:54:08.0900741+04:00","gmt_modified":"2026-04-28T12:54:08.0900741+04:00"},{"id":35741,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"9025ea64db0b2dfc92ca8bab0394cc08","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3443-3509","gmt_create":"2026-04-28T12:54:08.0900741+04:00","gmt_modified":"2026-04-28T12:54:08.0900741+04:00"},{"id":35742,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"fa4bc24d813810d4fe658d78919900b7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#812-825","gmt_create":"2026-04-28T12:54:08.0900741+04:00","gmt_modified":"2026-04-28T12:54:08.0900741+04:00"},{"id":35743,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"fa4bc24d813810d4fe658d78919900b7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 812-825","gmt_create":"2026-04-28T12:54:08.0900741+04:00","gmt_modified":"2026-04-28T12:54:08.0900741+04:00"},{"id":35744,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"423e0e34f575bf3a7563c57bb997739b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#56-73","gmt_create":"2026-04-28T12:54:08.0900741+04:00","gmt_modified":"2026-04-28T12:54:08.0900741+04:00"},{"id":35745,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"423e0e34f575bf3a7563c57bb997739b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 56-73","gmt_create":"2026-04-28T12:54:08.0900741+04:00","gmt_modified":"2026-04-28T12:54:08.0900741+04:00"},{"id":35746,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"c7518bf7e6927e1b54afebcbb8cf3d5a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#168-210","gmt_create":"2026-04-28T12:54:08.0911239+04:00","gmt_modified":"2026-04-28T12:54:08.0911239+04:00"},{"id":35747,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"c7518bf7e6927e1b54afebcbb8cf3d5a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 168-210","gmt_create":"2026-04-28T12:54:08.0911239+04:00","gmt_modified":"2026-04-28T12:54:08.0911239+04:00"},{"id":35748,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"f8faa71211c346e22bdea2dbbd1cc994","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#134-193","gmt_create":"2026-04-28T12:54:08.0911239+04:00","gmt_modified":"2026-04-28T12:54:08.0911239+04:00"},{"id":35749,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"f8faa71211c346e22bdea2dbbd1cc994","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 134-193","gmt_create":"2026-04-28T12:54:08.0911239+04:00","gmt_modified":"2026-04-28T12:54:08.0911239+04:00"},{"id":35750,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"fd6ccb853bf752497d776a99eba9c7e2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#115-132","gmt_create":"2026-04-28T12:54:08.0911239+04:00","gmt_modified":"2026-04-28T12:54:08.0911239+04:00"},{"id":35751,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"fd6ccb853bf752497d776a99eba9c7e2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 115-132","gmt_create":"2026-04-28T12:54:08.0921227+04:00","gmt_modified":"2026-04-28T12:54:08.0921227+04:00"},{"id":35752,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"6d71835687e91bd31fc0bba06adc43a1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#195-219","gmt_create":"2026-04-28T12:54:08.0921227+04:00","gmt_modified":"2026-04-28T12:54:08.0921227+04:00"},{"id":35753,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"6d71835687e91bd31fc0bba06adc43a1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 195-219","gmt_create":"2026-04-28T12:54:08.0921227+04:00","gmt_modified":"2026-04-28T12:54:08.0921227+04:00"},{"id":35754,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"21313db2130a5ad3661163e46e6eb28d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#13-36","gmt_create":"2026-04-28T12:54:08.0921227+04:00","gmt_modified":"2026-04-28T12:54:08.0921227+04:00"},{"id":35755,"source_id":"4238a9561f85e50a38f76813baeadd7e","target_id":"21313db2130a5ad3661163e46e6eb28d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 13-36","gmt_create":"2026-04-28T12:54:08.0921227+04:00","gmt_modified":"2026-04-28T12:54:08.0921227+04:00"},{"id":35756,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"1cd86ca915ed082dac6b3930d1822c8d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#846-913","gmt_create":"2026-04-28T12:54:08.0931234+04:00","gmt_modified":"2026-04-28T12:54:08.0931234+04:00"},{"id":35757,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1cd86ca915ed082dac6b3930d1822c8d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 846-913","gmt_create":"2026-04-28T12:54:08.0931234+04:00","gmt_modified":"2026-04-28T12:54:08.0931234+04:00"},{"id":35758,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"180a1810a21b0ec9cfa9bf2239b68849","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#47-71","gmt_create":"2026-04-28T12:54:08.0931234+04:00","gmt_modified":"2026-04-28T12:54:08.0931234+04:00"},{"id":35759,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"180a1810a21b0ec9cfa9bf2239b68849","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 47-71","gmt_create":"2026-04-28T12:54:08.0931234+04:00","gmt_modified":"2026-04-28T12:54:08.0931234+04:00"},{"id":35760,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"3e995e6e2441ab172c06a81ab175d467","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#79-90","gmt_create":"2026-04-28T12:54:08.1006305+04:00","gmt_modified":"2026-04-28T12:54:08.1006305+04:00"},{"id":35761,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"3e995e6e2441ab172c06a81ab175d467","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-90","gmt_create":"2026-04-28T12:54:08.1006305+04:00","gmt_modified":"2026-04-28T12:54:08.1006305+04:00"},{"id":35762,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"742afd64fbe28a0b4b264ad2c80c3f13","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/include/graphene/plugins/witness/witness.hpp#34-65","gmt_create":"2026-04-28T12:54:08.1006305+04:00","gmt_modified":"2026-04-28T12:54:08.1006305+04:00"},{"id":35763,"source_id":"0dd2a38630da83b11fb3596ad4d60705","target_id":"742afd64fbe28a0b4b264ad2c80c3f13","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 34-65","gmt_create":"2026-04-28T12:54:08.1016326+04:00","gmt_modified":"2026-04-28T12:54:08.1016326+04:00"},{"id":35764,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"8982624b20d4012f524d7608d0e898df","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#214-226","gmt_create":"2026-04-28T12:54:08.1016326+04:00","gmt_modified":"2026-04-28T12:54:08.1016326+04:00"},{"id":35765,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"8982624b20d4012f524d7608d0e898df","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 214-226","gmt_create":"2026-04-28T12:54:08.1016326+04:00","gmt_modified":"2026-04-28T12:54:08.1016326+04:00"},{"id":35766,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"ed8569143dcfa15ac6f3519b0aa6b31b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#423-428","gmt_create":"2026-04-28T12:54:08.1026317+04:00","gmt_modified":"2026-04-28T12:54:08.1026317+04:00"},{"id":35767,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"ed8569143dcfa15ac6f3519b0aa6b31b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 423-428","gmt_create":"2026-04-28T12:54:08.1026317+04:00","gmt_modified":"2026-04-28T12:54:08.1026317+04:00"},{"id":35768,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"dea1056d264deb0399cea4b15ce31eff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#38-44","gmt_create":"2026-04-28T12:54:08.1036312+04:00","gmt_modified":"2026-04-28T12:54:08.1036312+04:00"},{"id":35769,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"dea1056d264deb0399cea4b15ce31eff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-44","gmt_create":"2026-04-28T12:54:08.1036312+04:00","gmt_modified":"2026-04-28T12:54:08.1036312+04:00"},{"id":35770,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"9050957c1241bdff17fa2cda2294cc02","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database_exceptions.hpp#83-83","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35771,"source_id":"cb29035725926be38d36ad8c01792b7e","target_id":"9050957c1241bdff17fa2cda2294cc02","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 83-83","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35772,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"69496892894b525caf293d463cec60ac","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#163-193","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35773,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"69496892894b525caf293d463cec60ac","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 163-193","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35774,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"7a0064bf94d855137c23263ebefc4086","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#305-307","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35775,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"7a0064bf94d855137c23263ebefc4086","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 305-307","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35776,"source_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","target_id":"4f05f855970de061eacdd41742b1c8f6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5395-5419","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35777,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"4f05f855970de061eacdd41742b1c8f6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5395-5419","gmt_create":"2026-04-28T12:54:08.1046315+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"id":35789,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"620825701e1a1b114e822bbda1ceb234","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-454","gmt_create":"2026-04-28T12:55:56.0979984+04:00","gmt_modified":"2026-04-28T12:55:56.0979984+04:00"},{"id":35792,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"233a2d8be5340ec151c42900fcd58a96","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 220-271","gmt_create":"2026-04-28T12:55:56.0979984+04:00","gmt_modified":"2026-04-28T12:55:56.0979984+04:00"},{"id":35794,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"02b88f06fab1f8f2b384ec38dfea95a8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-258","gmt_create":"2026-04-28T12:55:56.0989987+04:00","gmt_modified":"2026-04-28T12:55:56.0989987+04:00"},{"id":35796,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"c6c440b24137364c19bc7fe7a800ce3e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 320-330","gmt_create":"2026-04-28T12:55:56.0989987+04:00","gmt_modified":"2026-04-28T12:55:56.0989987+04:00"},{"id":35798,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"7c7114ff694eca47458deb9031af3874","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1960-2039","gmt_create":"2026-04-28T12:55:56.0989987+04:00","gmt_modified":"2026-04-28T12:55:56.0989987+04:00"},{"id":35800,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"5c9a153931730742e72d3183f4f76128","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 255-286","gmt_create":"2026-04-28T12:55:56.0989987+04:00","gmt_modified":"2026-04-28T12:55:56.0989987+04:00"},{"id":35802,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"77740b8f659f6e4296624292e035f0b6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 515-516","gmt_create":"2026-04-28T12:55:56.0999988+04:00","gmt_modified":"2026-04-28T12:55:56.0999988+04:00"},{"id":35805,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"44c647e6bdaeba8176c9fd58d7bf204f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 18-278","gmt_create":"2026-04-28T12:55:56.1009972+04:00","gmt_modified":"2026-04-28T12:55:56.1009972+04:00"},{"id":35807,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1fec0c281b15b8cb253758a81bda9d53","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 230-231","gmt_create":"2026-04-28T12:55:56.1009972+04:00","gmt_modified":"2026-04-28T12:55:56.1009972+04:00"},{"id":35809,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"20be4db8cc88d28b2576cc8c6640d89d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 24-28","gmt_create":"2026-04-28T12:55:56.1009972+04:00","gmt_modified":"2026-04-28T12:55:56.1009972+04:00"},{"id":35811,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"4aaf8248e16bc831caf0d6215227a347","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 327-329","gmt_create":"2026-04-28T12:55:56.101998+04:00","gmt_modified":"2026-04-28T12:55:56.101998+04:00"},{"id":35813,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"2496b05edb9e2cce32ff03d614866f80","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1968-1970","gmt_create":"2026-04-28T12:55:56.101998+04:00","gmt_modified":"2026-04-28T12:55:56.101998+04:00"},{"id":35815,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"bd936d7f114a4d7505d38477591a432b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 265-272","gmt_create":"2026-04-28T12:55:56.101998+04:00","gmt_modified":"2026-04-28T12:55:56.101998+04:00"},{"id":35817,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"09e49fa0e3657a309d205da532bae0d4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1414-1500","gmt_create":"2026-04-28T12:55:56.101998+04:00","gmt_modified":"2026-04-28T12:55:56.101998+04:00"},{"id":35819,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"89fd5aa6e93582e3684130e9c0920e33","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 438-544","gmt_create":"2026-04-28T12:55:56.101998+04:00","gmt_modified":"2026-04-28T12:55:56.101998+04:00"},{"id":35821,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1e55d2aee2f6d3f5d6247eb14ca9350a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 230-268","gmt_create":"2026-04-28T12:55:56.1030316+04:00","gmt_modified":"2026-04-28T12:55:56.1030316+04:00"},{"id":35823,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"05c7b54032266aa804fecdd672849759","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 560-627","gmt_create":"2026-04-28T12:55:56.1030316+04:00","gmt_modified":"2026-04-28T12:55:56.1030316+04:00"},{"id":35825,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"d00cc75cac98bab547f7549076e3504d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 238-241","gmt_create":"2026-04-28T12:55:56.1030316+04:00","gmt_modified":"2026-04-28T12:55:56.1030316+04:00"},{"id":35827,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"3f58d42a32421c25e81f24320b1d1f20","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 313-328","gmt_create":"2026-04-28T12:55:56.1030316+04:00","gmt_modified":"2026-04-28T12:55:56.1030316+04:00"},{"id":35829,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"7a9e26fca49ab5e068040d1db9fed41f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 259-286","gmt_create":"2026-04-28T12:55:56.1040322+04:00","gmt_modified":"2026-04-28T12:55:56.1040322+04:00"},{"id":35831,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"d059ab60b812d6e824e8fef796efa497","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 161-209","gmt_create":"2026-04-28T12:55:56.1040322+04:00","gmt_modified":"2026-04-28T12:55:56.1040322+04:00"},{"id":35833,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"343b12b915179b0ceab0474a3260def1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 125-159","gmt_create":"2026-04-28T12:55:56.1050323+04:00","gmt_modified":"2026-04-28T12:55:56.1050323+04:00"},{"id":35835,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"edc66ae920408eb6412dad9e65c69f63","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 211-268","gmt_create":"2026-04-28T12:55:56.1050323+04:00","gmt_modified":"2026-04-28T12:55:56.1050323+04:00"},{"id":35837,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"c87f750598b8e3aeb391ebd49f3c8730","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 356-411","gmt_create":"2026-04-28T12:55:56.1060321+04:00","gmt_modified":"2026-04-28T12:55:56.1060321+04:00"},{"id":35839,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"85e42052e68826778bf6fc0a5c348061","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 266-292","gmt_create":"2026-04-28T12:55:56.1060321+04:00","gmt_modified":"2026-04-28T12:55:56.1060321+04:00"},{"id":35841,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"44fcac467d4197ec65011a88dcc9b983","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 942-1054","gmt_create":"2026-04-28T12:55:56.1070331+04:00","gmt_modified":"2026-04-28T12:55:56.1070331+04:00"},{"id":35843,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"d866d0710f48a708f384cfe833d19819","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2790-2791","gmt_create":"2026-04-28T12:55:56.1070331+04:00","gmt_modified":"2026-04-28T12:55:56.1070331+04:00"},{"id":35845,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"cc2057fd8aeaa6b63ca70e0d4192d8f6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 542-555","gmt_create":"2026-04-28T12:55:56.108034+04:00","gmt_modified":"2026-04-28T12:55:56.108034+04:00"},{"id":35847,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"01a99d91c53d13ef4ba094c44e59df35","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 44-65","gmt_create":"2026-04-28T12:55:56.108034+04:00","gmt_modified":"2026-04-28T12:55:56.108034+04:00"},{"id":35849,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"93d04b438bd28d7bb695e44a81c65fad","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 146-159","gmt_create":"2026-04-28T12:55:56.108034+04:00","gmt_modified":"2026-04-28T12:55:56.108034+04:00"},{"id":35851,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"bdb42378c1617c8167b38c65ff9277f8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 253-297","gmt_create":"2026-04-28T12:55:56.108034+04:00","gmt_modified":"2026-04-28T12:55:56.108034+04:00"},{"id":35853,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"5985a883b7cd6e373fa45e06af549458","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 172-202","gmt_create":"2026-04-28T12:55:56.1090334+04:00","gmt_modified":"2026-04-28T12:55:56.1090334+04:00"},{"id":35855,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"27ad3a44022480b087c9dee522cf8f53","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 432-444","gmt_create":"2026-04-28T12:55:56.1090334+04:00","gmt_modified":"2026-04-28T12:55:56.1090334+04:00"},{"id":35857,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5df133f8f7e5465f2256280233f7492a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4005-4036","gmt_create":"2026-04-28T12:55:56.1100329+04:00","gmt_modified":"2026-04-28T12:55:56.1100329+04:00"},{"id":35859,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"7ed16e639125b23aeb82bbb3768334fb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4170-4172","gmt_create":"2026-04-28T12:55:56.1100329+04:00","gmt_modified":"2026-04-28T12:55:56.1100329+04:00"},{"id":35861,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ed64be85dbceaa49c4d38b1fd7b0537f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4392-4394","gmt_create":"2026-04-28T12:55:56.1175534+04:00","gmt_modified":"2026-04-28T12:55:56.1175534+04:00"},{"id":35863,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"770f933f07c1261bce810845c6711ec4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4043-4047","gmt_create":"2026-04-28T12:55:56.1175534+04:00","gmt_modified":"2026-04-28T12:55:56.1175534+04:00"},{"id":35865,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5988d2bd7b0d5bec2b97a885d1928110","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4189-4192","gmt_create":"2026-04-28T12:55:56.1185519+04:00","gmt_modified":"2026-04-28T12:55:56.1185519+04:00"},{"id":35867,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a9078dfa0e86bfbf3c1bef4a473eb117","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4419-4421","gmt_create":"2026-04-28T12:55:56.1185519+04:00","gmt_modified":"2026-04-28T12:55:56.1185519+04:00"},{"id":35869,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"4af3002393266cb7f00332d69f2019ca","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 233-236","gmt_create":"2026-04-28T12:55:56.1185519+04:00","gmt_modified":"2026-04-28T12:55:56.1185519+04:00"},{"id":35871,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"36faa15d1b9669c10ef981b57dd230bc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 326-329","gmt_create":"2026-04-28T12:55:56.1195517+04:00","gmt_modified":"2026-04-28T12:55:56.1195517+04:00"},{"id":35873,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"69112cf16468224f02ecd1fbd49ea64f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-10","gmt_create":"2026-04-28T12:55:56.1195517+04:00","gmt_modified":"2026-04-28T12:55:56.1195517+04:00"},{"id":35875,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"6456782ce4d45cbdc58f90d5677ca2c2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-7","gmt_create":"2026-04-28T12:55:56.1195517+04:00","gmt_modified":"2026-04-28T12:55:56.1195517+04:00"},{"id":35877,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"fce2bc849f6a01aeb1da861120260037","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6","gmt_create":"2026-04-28T12:55:56.1195517+04:00","gmt_modified":"2026-04-28T12:55:56.1195517+04:00"},{"id":35879,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ab61b06105c0618ce476f62fc42d04c5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-10","gmt_create":"2026-04-28T12:55:56.1195517+04:00","gmt_modified":"2026-04-28T12:55:56.1195517+04:00"},{"id":35881,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"cf34d537814cc823a0786b3fa43ffca0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6","gmt_create":"2026-04-28T12:55:56.1205515+04:00","gmt_modified":"2026-04-28T12:55:56.1205515+04:00"},{"id":35883,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"7bd018a7b3dab28cc6792e89ffa39efd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-10","gmt_create":"2026-04-28T12:55:56.1205515+04:00","gmt_modified":"2026-04-28T12:55:56.1205515+04:00"},{"id":35885,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"96e5aca4a37cf627dff138e440efffa2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-10","gmt_create":"2026-04-28T12:55:56.1205515+04:00","gmt_modified":"2026-04-28T12:55:56.1205515+04:00"},{"id":35887,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"8e436d067efeeee9365369d51dc3fd02","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 250-271","gmt_create":"2026-04-28T12:55:56.121552+04:00","gmt_modified":"2026-04-28T12:55:56.121552+04:00"},{"id":35889,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2fb2b5eefe1dda70f9332bca41575f3d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 259-268","gmt_create":"2026-04-28T12:55:56.121552+04:00","gmt_modified":"2026-04-28T12:55:56.121552+04:00"},{"id":35891,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5864019a1f5c741338aa5810a4a9a2a4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 262-267","gmt_create":"2026-04-28T12:55:56.122552+04:00","gmt_modified":"2026-04-28T12:55:56.122552+04:00"},{"id":35893,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ac84b774b43ade0dc3ca1ecbd3b60ea5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 576-580","gmt_create":"2026-04-28T12:55:56.122552+04:00","gmt_modified":"2026-04-28T12:55:56.122552+04:00"},{"id":35895,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"29ef92399939d0e10862168a6d547c18","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 609-613","gmt_create":"2026-04-28T12:55:56.122552+04:00","gmt_modified":"2026-04-28T12:55:56.122552+04:00"},{"id":35897,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"50f4656fe0d323b661f41cd93f59398f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 599-621","gmt_create":"2026-04-28T12:55:56.1235737+04:00","gmt_modified":"2026-04-28T12:55:56.1235737+04:00"},{"id":35899,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"f1468b7365f4d06080c1395db0eb5819","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 623-640","gmt_create":"2026-04-28T12:55:56.1235737+04:00","gmt_modified":"2026-04-28T12:55:56.1235737+04:00"},{"id":35901,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"06b382c82ee3f42c24d858503dcde41b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 241-249","gmt_create":"2026-04-28T12:55:56.1235737+04:00","gmt_modified":"2026-04-28T12:55:56.1235737+04:00"},{"id":35903,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"ce3f0826aff2367e46f4cd37417c2f67","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 320-325","gmt_create":"2026-04-28T12:55:56.1235737+04:00","gmt_modified":"2026-04-28T12:55:56.1235737+04:00"},{"id":35905,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"55755f9121d991a7682722b9a8b3ab80","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 560-595","gmt_create":"2026-04-28T12:55:56.1245736+04:00","gmt_modified":"2026-04-28T12:55:56.1245736+04:00"},{"id":35907,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"86ba814ebeaa92690f62613e16aa5e7d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 656-697","gmt_create":"2026-04-28T12:55:56.1255737+04:00","gmt_modified":"2026-04-28T12:55:56.1255737+04:00"},{"id":35909,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"ce99ae12d13aaa7b2653e47db773bcce","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1435-1500","gmt_create":"2026-04-28T12:55:56.1255737+04:00","gmt_modified":"2026-04-28T12:55:56.1255737+04:00"},{"id":35911,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"a370301bb49e9efcf0ef5d6724ac9dd2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2691-2696","gmt_create":"2026-04-28T12:55:56.1255737+04:00","gmt_modified":"2026-04-28T12:55:56.1255737+04:00"},{"id":35913,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"09819e68e897288cd817074594e4f548","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2863-2866","gmt_create":"2026-04-28T12:55:56.1265735+04:00","gmt_modified":"2026-04-28T12:55:56.1265735+04:00"},{"id":35915,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"b44834f88165b1116477f30dc5f88a3e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4581-4608","gmt_create":"2026-04-28T12:55:56.1265735+04:00","gmt_modified":"2026-04-28T12:55:56.1265735+04:00"},{"id":35917,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"c6612598fe076c0f7d5ca15ca94cf26b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 627-627","gmt_create":"2026-04-28T12:55:56.1265735+04:00","gmt_modified":"2026-04-28T12:55:56.1265735+04:00"},{"id":35919,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"62962be4cb55c4466a47fba7a814b326","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1473-1476","gmt_create":"2026-04-28T12:55:56.1275762+04:00","gmt_modified":"2026-04-28T12:55:56.1275762+04:00"},{"id":35921,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"798b9ae312576643f8fefd434b40bddc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 626-632","gmt_create":"2026-04-28T12:55:56.1275762+04:00","gmt_modified":"2026-04-28T12:55:56.1275762+04:00"},{"id":35923,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"02c2555684691e747182b4078de2ed9b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1472-1477","gmt_create":"2026-04-28T12:55:56.1275762+04:00","gmt_modified":"2026-04-28T12:55:56.1275762+04:00"},{"id":35944,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"acad56e778bef31e851bbfe9812907e3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-655","gmt_create":"2026-04-28T13:00:52.0416758+04:00","gmt_modified":"2026-04-28T13:00:52.0416758+04:00"},{"id":35946,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e9ccdfc3f5a6e39a50944bf264e9f186","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6539","gmt_create":"2026-04-28T13:00:52.0416758+04:00","gmt_modified":"2026-04-28T13:00:52.0416758+04:00"},{"id":35954,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"590d1dfeeb49a4029c52d3c49b2f0362","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-144","gmt_create":"2026-04-28T13:00:52.042675+04:00","gmt_modified":"2026-04-28T13:00:52.042675+04:00"},{"id":35956,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"d599d8eb734647e07f075785246b447f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-278","gmt_create":"2026-04-28T13:00:52.042675+04:00","gmt_modified":"2026-04-28T13:00:52.042675+04:00"},{"id":35971,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"268918c988500bd0972ad80cef38bdd3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 53-144","gmt_create":"2026-04-28T13:00:52.0466739+04:00","gmt_modified":"2026-04-28T13:00:52.0466739+04:00"},{"id":35989,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"f79e33b8389fbdcd06f18bdcc93dc600","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5399-5417","gmt_create":"2026-04-28T13:00:52.0601814+04:00","gmt_modified":"2026-04-28T13:00:52.0601814+04:00"},{"id":35992,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"b34a0ea07e8be722ec91b23e3790def6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 805-824","gmt_create":"2026-04-28T13:00:52.0601814+04:00","gmt_modified":"2026-04-28T13:00:52.0601814+04:00"},{"id":35994,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5dbf7e59d79e02ad4264f7fbf7bdd28f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 835-856","gmt_create":"2026-04-28T13:00:52.0601814+04:00","gmt_modified":"2026-04-28T13:00:52.0601814+04:00"},{"id":35996,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"b6f94c70f9336700bbf26e4d787f9105","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 859-875","gmt_create":"2026-04-28T13:00:52.0616858+04:00","gmt_modified":"2026-04-28T13:00:52.0616858+04:00"},{"id":35998,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"f8046595bff9cdc6c60e330d85bd886a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5080-5105","gmt_create":"2026-04-28T13:00:52.0616858+04:00","gmt_modified":"2026-04-28T13:00:52.0616858+04:00"},{"id":36000,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"7b1db3050efae2995a8447733afa6894","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5442-5456","gmt_create":"2026-04-28T13:00:52.0616858+04:00","gmt_modified":"2026-04-28T13:00:52.0616858+04:00"},{"id":36046,"source_id":"9e5f8d60-bebb-4161-a630-0880704f9f81","target_id":"885ce864-d616-4f9f-8a3e-6198a88feda6","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 9e5f8d60-bebb-4161-a630-0880704f9f81 -\u003e 885ce864-d616-4f9f-8a3e-6198a88feda6","gmt_create":"2026-04-28T13:00:52.7021163+04:00","gmt_modified":"2026-04-28T13:00:52.7021163+04:00"},{"id":36083,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"67775c086bbf192f443fef2bdac726ee","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 290-325","gmt_create":"2026-04-28T14:54:15.7093879+04:00","gmt_modified":"2026-04-28T14:54:15.7093879+04:00"},{"id":36085,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"24e53b6bba24767d436a386f584d39b1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 330-364","gmt_create":"2026-04-28T14:54:15.7093879+04:00","gmt_modified":"2026-04-28T14:54:15.7093879+04:00"},{"id":36087,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"e24259987d3c50dee1a87e13d63ccf03","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 57-78","gmt_create":"2026-04-28T14:54:15.7093879+04:00","gmt_modified":"2026-04-28T14:54:15.7093879+04:00"},{"id":36090,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"163647b506865a0b9039e6ab0e61b35b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 290-364","gmt_create":"2026-04-28T14:54:15.7098902+04:00","gmt_modified":"2026-04-28T14:54:15.7098902+04:00"},{"id":36092,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"909f520b94e82193bac04c745bf8f67c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 614-650","gmt_create":"2026-04-28T14:54:15.7098902+04:00","gmt_modified":"2026-04-28T14:54:15.7098902+04:00"},{"id":36094,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"88e819e823c1dfa5a1120f734b8b99e4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 342-350","gmt_create":"2026-04-28T14:54:15.7105432+04:00","gmt_modified":"2026-04-28T14:54:15.7105432+04:00"},{"id":36096,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"de6b88d0d6cc0f704dd82fdef474a555","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 660-724","gmt_create":"2026-04-28T14:54:15.7105432+04:00","gmt_modified":"2026-04-28T14:54:15.7105432+04:00"},{"id":36098,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"e1e9fa05835bc27c4c0c558c27e51829","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 297-323","gmt_create":"2026-04-28T14:54:15.7110456+04:00","gmt_modified":"2026-04-28T14:54:15.7110456+04:00"},{"id":36100,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"bc6ec56aa2075f5d4f63b9761791d35b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 480-487","gmt_create":"2026-04-28T14:54:15.7111308+04:00","gmt_modified":"2026-04-28T14:54:15.7111308+04:00"},{"id":36102,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"c45e7282b5fb1668e8ae6f5a8da708ea","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 151-208","gmt_create":"2026-04-28T14:54:15.7111308+04:00","gmt_modified":"2026-04-28T14:54:15.7111308+04:00"},{"id":36104,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"cf6fe4716897ebef51503d6e48f458c2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 566-644","gmt_create":"2026-04-28T14:54:15.7111308+04:00","gmt_modified":"2026-04-28T14:54:15.7111308+04:00"},{"id":36106,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"9a05d082478a1b4a7c5d0f3aae5e5d61","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 336-350","gmt_create":"2026-04-28T14:54:15.7116333+04:00","gmt_modified":"2026-04-28T14:54:15.7116333+04:00"},{"id":36108,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"29d1adb11bc96c1e7216be1c31cb57f7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 173-204","gmt_create":"2026-04-28T14:54:15.7122299+04:00","gmt_modified":"2026-04-28T14:54:15.7122299+04:00"},{"id":36110,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"a53e81cce3125afb7be85b22de187020","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 951-1020","gmt_create":"2026-04-28T14:54:15.7122299+04:00","gmt_modified":"2026-04-28T14:54:15.7122299+04:00"},{"id":36139,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"b1416c15172aac5cdff13f12c0d385b6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 180-355","gmt_create":"2026-04-28T14:54:34.9634137+04:00","gmt_modified":"2026-04-28T14:54:34.9634137+04:00"},{"id":36141,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b2e1954ed604c23c3fe69090870838ec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 869-905","gmt_create":"2026-04-28T14:54:34.9634137+04:00","gmt_modified":"2026-04-28T14:54:34.9634137+04:00"},{"id":36144,"source_id":"3a8d8a10556a0b6501e25aa43e91f913","target_id":"7b85de17d00c3c33f3e6ce72493cea24","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 104-134","gmt_create":"2026-04-28T14:54:34.9644249+04:00","gmt_modified":"2026-04-28T14:54:34.9644249+04:00"},{"id":36146,"source_id":"b4467ca30cb6f6d587fc200900ee9ec9","target_id":"4a47af89ac294fea1a93e8ab87bc62a8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 42-114","gmt_create":"2026-04-28T14:54:34.9722109+04:00","gmt_modified":"2026-04-28T14:54:34.9722109+04:00"},{"id":36148,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"fcf5471e2941c73aa4796f0d15ca9d4f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 111-120","gmt_create":"2026-04-28T14:54:34.972721+04:00","gmt_modified":"2026-04-28T14:54:34.972721+04:00"},{"id":36151,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"7d072be9a3f767f13a99da4cd6df4783","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 110-123","gmt_create":"2026-04-28T14:54:34.973233+04:00","gmt_modified":"2026-04-28T14:54:34.973233+04:00"},{"id":36153,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"4f55b43e070bade6f117e02d6faaaac2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 368-379","gmt_create":"2026-04-28T14:54:34.973233+04:00","gmt_modified":"2026-04-28T14:54:34.973233+04:00"},{"id":36155,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"5b2dac9b59c644b7b47e991af481e627","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 330-360","gmt_create":"2026-04-28T14:54:34.973233+04:00","gmt_modified":"2026-04-28T14:54:34.973233+04:00"},{"id":36157,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"d9910f1ea9014fede12810a52c49c4a4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 952-1047","gmt_create":"2026-04-28T14:54:34.9747815+04:00","gmt_modified":"2026-04-28T14:54:34.9747815+04:00"},{"id":36159,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"2d0f35d8035d544c59943ce9488d042e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1623-1654","gmt_create":"2026-04-28T14:54:34.9747815+04:00","gmt_modified":"2026-04-28T14:54:34.9747815+04:00"},{"id":36161,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4cb233e3e08980c2c2c76e7762ad457b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2282-2350","gmt_create":"2026-04-28T14:54:34.975299+04:00","gmt_modified":"2026-04-28T14:54:34.975299+04:00"},{"id":36163,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"465b6e48aad6641d48e75039b1ba6cf7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 869-931","gmt_create":"2026-04-28T14:54:34.975299+04:00","gmt_modified":"2026-04-28T14:54:34.975299+04:00"},{"id":36165,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"64eac8af7d3939075658361c76c2a7f4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2029-2230","gmt_create":"2026-04-28T14:54:34.9758158+04:00","gmt_modified":"2026-04-28T14:54:34.9758158+04:00"},{"id":36167,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"63a72fb69b4b837d5841f87d654e6835","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2232-2250","gmt_create":"2026-04-28T14:54:34.9764386+04:00","gmt_modified":"2026-04-28T14:54:34.9764386+04:00"},{"id":36169,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"7d1df83c34f12b0c857fb327e688236b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1400-1621","gmt_create":"2026-04-28T14:54:34.9769418+04:00","gmt_modified":"2026-04-28T14:54:34.9769418+04:00"},{"id":36171,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"3497c413bfbe054ece4588e4c9764e38","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-80","gmt_create":"2026-04-28T14:54:34.9774578+04:00","gmt_modified":"2026-04-28T14:54:34.9774578+04:00"},{"id":36173,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"832f176e5f919dc138e93f3d848899bb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3117-3199","gmt_create":"2026-04-28T14:54:34.9774578+04:00","gmt_modified":"2026-04-28T14:54:34.9774578+04:00"},{"id":36175,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"d93f64e1090b7fb0448056ce453003b2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 200-294","gmt_create":"2026-04-28T14:54:34.9779776+04:00","gmt_modified":"2026-04-28T14:54:34.9779776+04:00"},{"id":36177,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"8422adca0104a424dd0293e0e0b74399","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 933-950","gmt_create":"2026-04-28T14:54:34.9779776+04:00","gmt_modified":"2026-04-28T14:54:34.9779776+04:00"},{"id":36179,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"f88975c9b8df22394830934bbd2b8fec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1686-1713","gmt_create":"2026-04-28T14:54:34.9784903+04:00","gmt_modified":"2026-04-28T14:54:34.9784903+04:00"},{"id":36181,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"140ecf3fedde255af2f0a4ce07a0edaa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 211-296","gmt_create":"2026-04-28T14:54:34.9784903+04:00","gmt_modified":"2026-04-28T14:54:34.9784903+04:00"},{"id":36183,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"2a546861a7d7c1c0f13a5e81a60a2663","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1788-1841","gmt_create":"2026-04-28T14:54:34.9784903+04:00","gmt_modified":"2026-04-28T14:54:34.9784903+04:00"},{"id":36185,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"e9dd01a70c4ccc4eff5ea6c67ea751d4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1326-1398","gmt_create":"2026-04-28T14:54:34.9789986+04:00","gmt_modified":"2026-04-28T14:54:34.9789986+04:00"},{"id":36187,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"e396600c03187cea5577399428a6bc7a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2830-2892","gmt_create":"2026-04-28T14:54:34.9795086+04:00","gmt_modified":"2026-04-28T14:54:34.9795086+04:00"},{"id":36189,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4d84381505174d8f3190a51bf7d50f11","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 111-217","gmt_create":"2026-04-28T14:54:34.9795086+04:00","gmt_modified":"2026-04-28T14:54:34.9795086+04:00"},{"id":36191,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"7b14802356f080ebee34dc774e07624c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3574-3629","gmt_create":"2026-04-28T14:54:34.980024+04:00","gmt_modified":"2026-04-28T14:54:34.980024+04:00"},{"id":36193,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"50f62c7fc3da12388428d618747f9efb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3436-3458","gmt_create":"2026-04-28T14:54:34.9806723+04:00","gmt_modified":"2026-04-28T14:54:34.9806723+04:00"},{"id":36195,"source_id":"3948eb588d15d01acf21ffd439ec508c","target_id":"0945d0202cc0f557ece19e5847d25784","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 45","gmt_create":"2026-04-28T14:54:34.9806723+04:00","gmt_modified":"2026-04-28T14:54:34.9806723+04:00"},{"id":36197,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"a3bdee51f93df3b62a4e4fdb0a7dedb4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3444-3458","gmt_create":"2026-04-28T14:54:34.9811753+04:00","gmt_modified":"2026-04-28T14:54:34.9811753+04:00"},{"id":36199,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"bbde504b5dfd07112221bccec888d422","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3574-3595","gmt_create":"2026-04-28T14:54:34.9816905+04:00","gmt_modified":"2026-04-28T14:54:34.9816905+04:00"},{"id":36201,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"8ad4beaaf873dcab9dbc1423c81a1be3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3436-3449","gmt_create":"2026-04-28T14:54:34.9816905+04:00","gmt_modified":"2026-04-28T14:54:34.9816905+04:00"},{"id":36203,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"6df9093f4da150f4dc814ac83c7e8e90","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3428-3449","gmt_create":"2026-04-28T14:54:34.9816905+04:00","gmt_modified":"2026-04-28T14:54:34.9816905+04:00"},{"id":36207,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"b8b4ec7ad2e061ee8af94b012e12e61f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 479-489","gmt_create":"2026-04-28T14:54:34.9832283+04:00","gmt_modified":"2026-04-28T14:54:34.9832283+04:00"},{"id":36209,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"61c8442a024b1f3b64d9a00c6fd078be","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5015-5030","gmt_create":"2026-04-28T14:54:34.9832283+04:00","gmt_modified":"2026-04-28T14:54:34.9832283+04:00"},{"id":36211,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"fb7100979bb6f3fc24f7989f7d731054","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5042-5050","gmt_create":"2026-04-28T14:54:34.9832283+04:00","gmt_modified":"2026-04-28T14:54:34.9832283+04:00"},{"id":36213,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4d5886271588a3ca7dcd269511761120","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2840-2847","gmt_create":"2026-04-28T14:54:34.9837462+04:00","gmt_modified":"2026-04-28T14:54:34.9837462+04:00"},{"id":36215,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"069b9408b99542da15a9163e6c66de5c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2848-2873","gmt_create":"2026-04-28T14:54:34.9837462+04:00","gmt_modified":"2026-04-28T14:54:34.9837462+04:00"},{"id":36217,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b76d7561641e0b9544f923bd5aa66e1e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2395-2500","gmt_create":"2026-04-28T14:54:34.9842585+04:00","gmt_modified":"2026-04-28T14:54:34.9842585+04:00"},{"id":36219,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"5c39e132de390652b9995c75c9dd7b40","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2572-2592","gmt_create":"2026-04-28T14:54:34.9842585+04:00","gmt_modified":"2026-04-28T14:54:34.9842585+04:00"},{"id":36221,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"d9e91362d84f2e5c27205bacead0d8e5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2251-2280","gmt_create":"2026-04-28T14:54:34.9852894+04:00","gmt_modified":"2026-04-28T14:54:34.9852894+04:00"},{"id":36223,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b0c24aab481fd56f8134b091b5bf0525","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2137-2168","gmt_create":"2026-04-28T14:54:34.9858051+04:00","gmt_modified":"2026-04-28T14:54:34.9858051+04:00"},{"id":36225,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"731063774f04d46e1f2c0d64963dfe33","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4455-4460","gmt_create":"2026-04-28T14:54:34.9863217+04:00","gmt_modified":"2026-04-28T14:54:34.9863217+04:00"},{"id":36254,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"733f077750798ad212e3907d2e15c841","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 835-858","gmt_create":"2026-04-28T14:55:54.6734992+04:00","gmt_modified":"2026-04-28T14:55:54.6734992+04:00"},{"id":36308,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"c31e2a585be22edd01d86dba51c51e79","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 294-302","gmt_create":"2026-04-28T14:55:54.6997652+04:00","gmt_modified":"2026-04-28T14:55:54.6997652+04:00"},{"id":36310,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"6ef04fbc3dcdfebcf2580c7e7146cbc3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 317-323","gmt_create":"2026-04-28T14:55:54.6997652+04:00","gmt_modified":"2026-04-28T14:55:54.6997652+04:00"},{"id":36312,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5e4bd94274850728b00115526a2479ee","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 860-882","gmt_create":"2026-04-28T14:55:54.7007652+04:00","gmt_modified":"2026-04-28T14:55:54.7007652+04:00"},{"id":36314,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"c53a6db24cea4db25b30783459c40125","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 884-901","gmt_create":"2026-04-28T14:55:54.7007652+04:00","gmt_modified":"2026-04-28T14:55:54.7007652+04:00"},{"id":36317,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"88d1d1609201e580c6cdf488fcd22fad","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 370-489","gmt_create":"2026-04-28T14:55:54.7017655+04:00","gmt_modified":"2026-04-28T14:55:54.7017655+04:00"},{"id":36378,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"57113fdc395a5c98d34b2e59cf16018d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5420-5444","gmt_create":"2026-04-28T15:02:14.7764007+04:00","gmt_modified":"2026-04-28T15:02:14.7764007+04:00"},{"id":36381,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"929cc0269a5814a951b45c3300f44597","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 789-827","gmt_create":"2026-04-28T15:02:14.7774004+04:00","gmt_modified":"2026-04-28T15:02:14.7774004+04:00"},{"id":36385,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"6abfbb22aee54a4b386173dfc74da72b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5452-5482","gmt_create":"2026-04-28T15:02:14.7784002+04:00","gmt_modified":"2026-04-28T15:02:14.7784002+04:00"},{"id":36387,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2ba1e391be7a79f5151c8abeb655315d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5467-5480","gmt_create":"2026-04-28T15:02:14.7784002+04:00","gmt_modified":"2026-04-28T15:02:14.7784002+04:00"},{"id":36470,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"55ed0cee7e5f683b8caac133899cb8cd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 295-340","gmt_create":"2026-04-28T17:51:11.5952731+04:00","gmt_modified":"2026-04-28T17:51:11.5952731+04:00"},{"id":36472,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"0dc933c238ae7ab73de36f0171dfb48b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 371-405","gmt_create":"2026-04-28T17:51:11.5952731+04:00","gmt_modified":"2026-04-28T17:51:11.5952731+04:00"},{"id":36476,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"5a09c4b30cf4ad7363c8ddc12bc7c8db","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 290-405","gmt_create":"2026-04-28T17:51:11.5957891+04:00","gmt_modified":"2026-04-28T17:51:11.5957891+04:00"},{"id":36478,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"d8698088d1f1c1cd1343a5552104c443","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 295-405","gmt_create":"2026-04-28T17:51:11.5968267+04:00","gmt_modified":"2026-04-28T17:51:11.5968267+04:00"},{"id":36480,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"bccfbcb90cc33fcb1bad654d203eab3a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 308-340","gmt_create":"2026-04-28T17:51:11.5968267+04:00","gmt_modified":"2026-04-28T17:51:11.5968267+04:00"},{"id":36482,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"3e4f7ec74d72ee9c1f4f5cf2a0f86844","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 298-302","gmt_create":"2026-04-28T17:51:11.5973383+04:00","gmt_modified":"2026-04-28T17:51:11.5973383+04:00"},{"id":36484,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"e13810e67e91d0b4940124f48b98095c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 335-338","gmt_create":"2026-04-28T17:51:11.5973383+04:00","gmt_modified":"2026-04-28T17:51:11.5973383+04:00"},{"id":36487,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"30fcbbca25b602851122e4d2b5ae4754","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 701-765","gmt_create":"2026-04-28T17:51:11.5978575+04:00","gmt_modified":"2026-04-28T17:51:11.5978575+04:00"},{"id":36489,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"2d0ff08ccbd1994ff4f46985361fc3d4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 355-364","gmt_create":"2026-04-28T17:51:11.600892+04:00","gmt_modified":"2026-04-28T17:51:11.600892+04:00"},{"id":36491,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"f195c24b5ab419ffbcc0c59a70fc3b0b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 520-528","gmt_create":"2026-04-28T17:51:11.600892+04:00","gmt_modified":"2026-04-28T17:51:11.600892+04:00"},{"id":36495,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"1638be790588edb6a3913a76064f24e0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 992-1061","gmt_create":"2026-04-28T17:51:11.6035214+04:00","gmt_modified":"2026-04-28T17:51:11.6035214+04:00"},{"id":36504,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"cfa97ba993c799f350895b1deb5bfa1a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 596-699","gmt_create":"2026-04-28T17:51:11.6115462+04:00","gmt_modified":"2026-04-28T17:51:11.6115462+04:00"},{"id":36561,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"87dbe393410d68cf0b46ab22827e33fe","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3252-3290","gmt_create":"2026-04-28T17:52:33.4726022+04:00","gmt_modified":"2026-04-28T17:52:33.4726022+04:00"},{"id":36563,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"98c57a9cc27d3a9e444c74d23a63bf9e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4945-4947","gmt_create":"2026-04-28T17:52:33.4726876+04:00","gmt_modified":"2026-04-28T17:52:33.4726876+04:00"},{"id":36565,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2d706a9e6bc734297fc6693a4cec7176","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5139-5140","gmt_create":"2026-04-28T17:52:33.4736196+04:00","gmt_modified":"2026-04-28T17:52:33.4736196+04:00"},{"id":36567,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"95d10140a46b090a8cea978c1c73a9bb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 337-338","gmt_create":"2026-04-28T17:52:33.4742694+04:00","gmt_modified":"2026-04-28T17:52:33.4742694+04:00"},{"id":36611,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"95da2daaa0065c2230410221cb5b5dbd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-670","gmt_create":"2026-04-28T17:55:35.3910266+04:00","gmt_modified":"2026-04-28T17:55:35.3910266+04:00"},{"id":36613,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e381e37084462c877d129f82d20ff00a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6623","gmt_create":"2026-04-28T17:55:35.3915293+04:00","gmt_modified":"2026-04-28T17:55:35.3915293+04:00"},{"id":36619,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"86991432e69878d08cc76b9d386b7d8f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-80","gmt_create":"2026-04-28T17:55:35.3939384+04:00","gmt_modified":"2026-04-28T17:55:35.3939384+04:00"},{"id":36621,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"cdc31aad39121f507723919db7d70dee","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-476","gmt_create":"2026-04-28T17:55:35.3944414+04:00","gmt_modified":"2026-04-28T17:55:35.3944414+04:00"},{"id":36749,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"7f7ad64f5cbf50ccfc4c9382214c8ec2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5113-5150","gmt_create":"2026-04-28T17:55:59.5724875+04:00","gmt_modified":"2026-04-28T17:55:59.5724875+04:00"},{"id":36751,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"ad3b81ed34c5384299f6f2e32cca10ed","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5152-5207","gmt_create":"2026-04-28T17:55:59.5734892+04:00","gmt_modified":"2026-04-28T17:55:59.5734892+04:00"},{"id":36753,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"5f552c130950ae2424391d5588a6d221","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2616-2663","gmt_create":"2026-04-28T17:55:59.5734892+04:00","gmt_modified":"2026-04-28T17:55:59.5734892+04:00"},{"id":36755,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"283622d9bcc1bce9d94e8923c2f25f8d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2649-2663","gmt_create":"2026-04-28T17:55:59.5744885+04:00","gmt_modified":"2026-04-28T17:55:59.5744885+04:00"},{"id":36757,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"53f0c930ae76bfa54d695357483cfd14","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1574-1590","gmt_create":"2026-04-28T17:55:59.5744885+04:00","gmt_modified":"2026-04-28T17:55:59.5744885+04:00"},{"id":36759,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"3ee025d1596a00346251c53e81140245","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1600-1603","gmt_create":"2026-04-28T17:55:59.5754874+04:00","gmt_modified":"2026-04-28T17:55:59.5754874+04:00"},{"id":36761,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"cd28c0d27d7ee3f81690b82569787afd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1615-1618","gmt_create":"2026-04-28T17:55:59.5754874+04:00","gmt_modified":"2026-04-28T17:55:59.5754874+04:00"},{"id":36763,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"50e65d04bb14ac9f5de0d5e24fb1bc95","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5234-5312","gmt_create":"2026-04-28T17:55:59.5754874+04:00","gmt_modified":"2026-04-28T17:55:59.5754874+04:00"},{"id":36765,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"782b21b9fe9078ddf841f9c5edb30a5c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5134-5150","gmt_create":"2026-04-28T17:55:59.5764874+04:00","gmt_modified":"2026-04-28T17:55:59.5764874+04:00"},{"id":36767,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"c440df88a010dbe878f2183c720c9bd4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5073-5111","gmt_create":"2026-04-28T17:55:59.5764874+04:00","gmt_modified":"2026-04-28T17:55:59.5764874+04:00"},{"id":36769,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"d40711c4a4526e9360e24999d3a60c4c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1561-1561","gmt_create":"2026-04-28T17:55:59.5774877+04:00","gmt_modified":"2026-04-28T17:55:59.5774877+04:00"},{"id":36771,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"ab3013cc4873cf8f4df651d34931505a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2890-2901","gmt_create":"2026-04-28T17:55:59.5774877+04:00","gmt_modified":"2026-04-28T17:55:59.5774877+04:00"},{"id":36773,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"a867692b466336c352cc15b0f4f2df75","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3034-3038","gmt_create":"2026-04-28T17:55:59.5774877+04:00","gmt_modified":"2026-04-28T17:55:59.5774877+04:00"},{"id":36775,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"dfd73892c200f1d97774665580c6e71f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2951-2956","gmt_create":"2026-04-28T17:55:59.5784874+04:00","gmt_modified":"2026-04-28T17:55:59.5784874+04:00"},{"id":36777,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"01940ab830f5b568738f999638e911f5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3052-3077","gmt_create":"2026-04-28T17:55:59.5784874+04:00","gmt_modified":"2026-04-28T17:55:59.5784874+04:00"},{"id":36779,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"3c85ac88e286b8cc43c883b8df381dc4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2903-2910","gmt_create":"2026-04-28T17:55:59.5784874+04:00","gmt_modified":"2026-04-28T17:55:59.5784874+04:00"},{"id":36781,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"6c44a74a07d60a21476756afc2a21751","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2906-2908","gmt_create":"2026-04-28T17:55:59.579991+04:00","gmt_modified":"2026-04-28T17:55:59.579991+04:00"},{"id":36783,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"f93b14aeceeb7a9cd9ffe6f5eb64aa31","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5119-5127","gmt_create":"2026-04-28T17:55:59.579991+04:00","gmt_modified":"2026-04-28T17:55:59.579991+04:00"},{"id":36785,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"3139928b0a5ec8f18b756ec5a2002d53","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5116-5118","gmt_create":"2026-04-28T17:55:59.5809967+04:00","gmt_modified":"2026-04-28T17:55:59.5809967+04:00"},{"id":36787,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"e3459f7af1223806827d89c0dc203eea","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5135-5139","gmt_create":"2026-04-28T17:55:59.5819951+04:00","gmt_modified":"2026-04-28T17:55:59.5819951+04:00"},{"id":36789,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"41f0eb3c878b82cdb92e49b6aaeb78a7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2517-2522","gmt_create":"2026-04-28T17:55:59.5911087+04:00","gmt_modified":"2026-04-28T17:55:59.5911087+04:00"},{"id":36822,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"31f6fa48f80c0c6d8c5b09074b2f3f45","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4910-5150","gmt_create":"2026-04-28T17:57:38.4551102+04:00","gmt_modified":"2026-04-28T17:57:38.4551102+04:00"},{"id":36833,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"850a6c0029d8ecca44d93e902433bd6e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 453-473","gmt_create":"2026-04-28T17:57:38.4571105+04:00","gmt_modified":"2026-04-28T17:57:38.4571105+04:00"},{"id":36838,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"7f9a564b8011c536e8ff555516dffbae","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3254","gmt_create":"2026-04-28T17:57:38.4591249+04:00","gmt_modified":"2026-04-28T17:57:38.4591249+04:00"},{"id":36876,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"17512b930411cc392fa7b28fa3ad2b84","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 332-338","gmt_create":"2026-04-28T17:57:38.4691427+04:00","gmt_modified":"2026-04-28T17:57:38.4691427+04:00"},{"id":36906,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"930f47bc2c95f0cac4086eeca5749e2c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 190-304","gmt_create":"2026-04-28T18:50:34.4094415+04:00","gmt_modified":"2026-04-28T18:50:34.4094415+04:00"},{"id":36908,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"9f8690911ef66c966743475f294ffb7e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-351","gmt_create":"2026-04-28T18:50:34.4094415+04:00","gmt_modified":"2026-04-28T18:50:34.4094415+04:00"},{"id":36910,"source_id":"b4467ca30cb6f6d587fc200900ee9ec9","target_id":"a480bfeb1604076e7d1db7746687a3b4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 42-106","gmt_create":"2026-04-28T18:50:34.4105267+04:00","gmt_modified":"2026-04-28T18:50:34.4105267+04:00"},{"id":36912,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"c54e54629fd9cd255341b311da9b6be1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 72-573","gmt_create":"2026-04-28T18:50:34.4105267+04:00","gmt_modified":"2026-04-28T18:50:34.4105267+04:00"},{"id":36914,"source_id":"398d9d4b02b6383c0b752cb0196a0475","target_id":"4e8bc2cdd3d4fc68b2f7138c54288174","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 37-93","gmt_create":"2026-04-28T18:50:34.4126371+04:00","gmt_modified":"2026-04-28T18:50:34.4126371+04:00"},{"id":36917,"source_id":"6e587b97bf4080c7754c5ed73736fca7","target_id":"ff11a77da594972e0b8a78df4289ada5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 45-79","gmt_create":"2026-04-28T18:50:34.4135396+04:00","gmt_modified":"2026-04-28T18:50:34.4135396+04:00"},{"id":36919,"source_id":"c82262dc5275e094efc9474032d15922","target_id":"d614fc51360bb61b8f521167a6db1e11","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-106","gmt_create":"2026-04-28T18:50:34.414043+04:00","gmt_modified":"2026-04-28T18:50:34.414043+04:00"},{"id":36921,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"411a87e4e085e019827e9bd8085b8328","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 500-560","gmt_create":"2026-04-28T18:50:34.414043+04:00","gmt_modified":"2026-04-28T18:50:34.414043+04:00"},{"id":36923,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"6a050f797e4d75bb2867072fae629c2a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5281-5286","gmt_create":"2026-04-28T18:50:34.4151271+04:00","gmt_modified":"2026-04-28T18:50:34.4151271+04:00"},{"id":36925,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"448218400a050ac2d3a0f1ee290faf50","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 346-347","gmt_create":"2026-04-28T18:50:34.4151271+04:00","gmt_modified":"2026-04-28T18:50:34.4151271+04:00"},{"id":36927,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"34550b8d6e01a13e99a0ed70072db86b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-355","gmt_create":"2026-04-28T18:50:34.4162025+04:00","gmt_modified":"2026-04-28T18:50:34.4162025+04:00"},{"id":36929,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"9847129d410beb4256be18f2e7489d18","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-383","gmt_create":"2026-04-28T18:50:34.4162025+04:00","gmt_modified":"2026-04-28T18:50:34.4162025+04:00"},{"id":36931,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"a3e6f8b4e51c838c44eaef16bb205031","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-573","gmt_create":"2026-04-28T18:50:34.4162025+04:00","gmt_modified":"2026-04-28T18:50:34.4162025+04:00"},{"id":36933,"source_id":"398d9d4b02b6383c0b752cb0196a0475","target_id":"53a676bf1df5198ba31b6ebe848a29bd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-99","gmt_create":"2026-04-28T18:50:34.4172058+04:00","gmt_modified":"2026-04-28T18:50:34.4172058+04:00"},{"id":36935,"source_id":"3a8d8a10556a0b6501e25aa43e91f913","target_id":"f14b0ce0c1ba142fb55271fdc6c2ee9f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-141","gmt_create":"2026-04-28T18:50:34.4172058+04:00","gmt_modified":"2026-04-28T18:50:34.4172058+04:00"},{"id":36937,"source_id":"b4467ca30cb6f6d587fc200900ee9ec9","target_id":"f59f370133646ba0c40ebdd11ee6d697","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-114","gmt_create":"2026-04-28T18:50:34.4172058+04:00","gmt_modified":"2026-04-28T18:50:34.4172058+04:00"},{"id":36939,"source_id":"6e587b97bf4080c7754c5ed73736fca7","target_id":"762c4b81ed454ad789c0b3b3cb00cc88","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-85","gmt_create":"2026-04-28T18:50:34.4182946+04:00","gmt_modified":"2026-04-28T18:50:34.4182946+04:00"},{"id":36941,"source_id":"c82262dc5275e094efc9474032d15922","target_id":"bc9b507990af077f95d7bbe7203d51f0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-106","gmt_create":"2026-04-28T18:50:34.4182946+04:00","gmt_modified":"2026-04-28T18:50:34.4182946+04:00"},{"id":36943,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"b54da9d3dceb59e39a6cd88a5a99b593","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-742","gmt_create":"2026-04-28T18:50:34.4193737+04:00","gmt_modified":"2026-04-28T18:50:34.4193737+04:00"},{"id":36945,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"54ba2e42561f4bf9913449b2a78838bb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 182-304","gmt_create":"2026-04-28T18:50:34.4193737+04:00","gmt_modified":"2026-04-28T18:50:34.4193737+04:00"},{"id":36947,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"94c69e0f6c696d5856c9a52b4dbcda00","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 780-790","gmt_create":"2026-04-28T18:50:34.4220565+04:00","gmt_modified":"2026-04-28T18:50:34.4220565+04:00"},{"id":36949,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"d6f1f5c1ab7e33b9a558f648113330fb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 208-242","gmt_create":"2026-04-28T18:50:34.4221458+04:00","gmt_modified":"2026-04-28T18:50:34.4221458+04:00"},{"id":36951,"source_id":"01e57c63d684a56829f909c03b8ea162","target_id":"db2f58e50720e139a95c598355919158","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 69-72","gmt_create":"2026-04-28T18:50:34.4221458+04:00","gmt_modified":"2026-04-28T18:50:34.4221458+04:00"},{"id":36953,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"a6b2b3362f68c7523a4e0d06a2a4352f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 424-799","gmt_create":"2026-04-28T18:50:34.4448355+04:00","gmt_modified":"2026-04-28T18:50:34.4448355+04:00"},{"id":36956,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"20359a9271432a216b6faf9dc97fa5c6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 169-206","gmt_create":"2026-04-28T18:50:34.4458606+04:00","gmt_modified":"2026-04-28T18:50:34.4458606+04:00"},{"id":36958,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"fee43b750e5fa7d56e56ac6251e6b5bb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 41-66","gmt_create":"2026-04-28T18:50:34.4458606+04:00","gmt_modified":"2026-04-28T18:50:34.4458606+04:00"},{"id":36960,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"25c0bdec5e0fb6c8d410577e1478ea78","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 310-354","gmt_create":"2026-04-28T18:50:34.4463761+04:00","gmt_modified":"2026-04-28T18:50:34.4463761+04:00"},{"id":36962,"source_id":"2c99501f0c511d0792a2e5a56e4debfa","target_id":"e81b900c80dc9c266a93a66cc60486de","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 30-49","gmt_create":"2026-04-28T18:50:34.4463761+04:00","gmt_modified":"2026-04-28T18:50:34.4463761+04:00"},{"id":36964,"source_id":"01e57c63d684a56829f909c03b8ea162","target_id":"0b26d723a87937f5a3f58770c522d067","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 49-72","gmt_create":"2026-04-28T18:50:34.4472155+04:00","gmt_modified":"2026-04-28T18:50:34.4472155+04:00"},{"id":36966,"source_id":"01e57c63d684a56829f909c03b8ea162","target_id":"d4b4e356cc64c1eac490f25138ad8337","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 132-177","gmt_create":"2026-04-28T18:50:34.4472155+04:00","gmt_modified":"2026-04-28T18:50:34.4472155+04:00"},{"id":36968,"source_id":"01e57c63d684a56829f909c03b8ea162","target_id":"b43af1b0b5dbbe32011b0ab877770800","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 49-177","gmt_create":"2026-04-28T18:50:34.4477185+04:00","gmt_modified":"2026-04-28T18:50:34.4477185+04:00"},{"id":36970,"source_id":"c70caf63ce77b078dcf89380b228a71a","target_id":"fd44e8ec8ed78651240bac55efcc1f20","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 41-82","gmt_create":"2026-04-28T18:50:34.4477185+04:00","gmt_modified":"2026-04-28T18:50:34.4477185+04:00"},{"id":36972,"source_id":"c70caf63ce77b078dcf89380b228a71a","target_id":"ee53a7a714337d597cc0a626fceec27f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 100-174","gmt_create":"2026-04-28T18:50:34.4487235+04:00","gmt_modified":"2026-04-28T18:50:34.4487235+04:00"},{"id":36974,"source_id":"b4467ca30cb6f6d587fc200900ee9ec9","target_id":"aae2609ed41db2a866f07ce766cbb938","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 70-105","gmt_create":"2026-04-28T18:50:34.4487235+04:00","gmt_modified":"2026-04-28T18:50:34.4487235+04:00"},{"id":36976,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"023d67279bae7acc551e276826dc0ff1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3710-3723","gmt_create":"2026-04-28T18:50:34.4497233+04:00","gmt_modified":"2026-04-28T18:50:34.4497233+04:00"},{"id":36978,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b7e9099b5e14dfe6396c0a1c5c308eb7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 312-381","gmt_create":"2026-04-28T18:50:34.4497233+04:00","gmt_modified":"2026-04-28T18:50:34.4497233+04:00"},{"id":36980,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"26b49296ed2024fd0dde20e6f7e40b88","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 383-420","gmt_create":"2026-04-28T18:50:34.4507268+04:00","gmt_modified":"2026-04-28T18:50:34.4507268+04:00"},{"id":36982,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"41d27526c610abf125f7a7d008632585","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 173-179","gmt_create":"2026-04-28T18:50:34.4507268+04:00","gmt_modified":"2026-04-28T18:50:34.4507268+04:00"},{"id":36984,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"868fa39acf5aeee17dc7a8161740807d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4920-4970","gmt_create":"2026-04-28T18:50:34.4517267+04:00","gmt_modified":"2026-04-28T18:50:34.4517267+04:00"},{"id":36986,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"9814b4d33f73344843377eb1e1b063d8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5128-5131","gmt_create":"2026-04-28T18:50:34.4524989+04:00","gmt_modified":"2026-04-28T18:50:34.4524989+04:00"},{"id":36988,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"380d27e76ccd3471d37efe9e980c19ae","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 322-346","gmt_create":"2026-04-28T18:50:34.4530856+04:00","gmt_modified":"2026-04-28T18:50:34.4530856+04:00"},{"id":36990,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"569091d2a8631dcfd98eaa50ae2bfc44","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 428-448","gmt_create":"2026-04-28T18:50:34.4536651+04:00","gmt_modified":"2026-04-28T18:50:34.4536651+04:00"},{"id":36992,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"1ed9d91d6dd0223209ee65abf9542242","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4900-4970","gmt_create":"2026-04-28T18:50:34.4543112+04:00","gmt_modified":"2026-04-28T18:50:34.4543112+04:00"},{"id":36994,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b8d925dd432a091aeeb11648caa2eb44","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4164-4168","gmt_create":"2026-04-28T18:50:34.4560148+04:00","gmt_modified":"2026-04-28T18:50:34.4560148+04:00"},{"id":36996,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"bf71a71910c5567123121789e437c062","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 298-304","gmt_create":"2026-04-28T18:50:34.4565303+04:00","gmt_modified":"2026-04-28T18:50:34.4565303+04:00"},{"id":36998,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"690241d99c53c94e7cfd1435215c90f7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 616-618","gmt_create":"2026-04-28T18:50:34.4565303+04:00","gmt_modified":"2026-04-28T18:50:34.4565303+04:00"},{"id":37000,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"8df3a4b9e7bead2487cc1ffc6cd49cae","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 16-19","gmt_create":"2026-04-28T18:50:34.4575335+04:00","gmt_modified":"2026-04-28T18:50:34.4575335+04:00"},{"id":37002,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"84d9f61aa92a7de46576a47f269a5dcd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 169-171","gmt_create":"2026-04-28T18:50:34.4575335+04:00","gmt_modified":"2026-04-28T18:50:34.4575335+04:00"},{"id":37004,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"0be17f1ab260a4b3bcead7596649bfc6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 81-81","gmt_create":"2026-04-28T18:50:34.4575335+04:00","gmt_modified":"2026-04-28T18:50:34.4575335+04:00"},{"id":37006,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"271449375aea639d1f03a23e291eaea3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1187-1194","gmt_create":"2026-04-28T18:50:34.4585336+04:00","gmt_modified":"2026-04-28T18:50:34.4585336+04:00"},{"id":37008,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"14c432cf8aa5e9ce797534c3d40b187f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1200-1202","gmt_create":"2026-04-28T18:50:34.4585336+04:00","gmt_modified":"2026-04-28T18:50:34.4585336+04:00"},{"id":37010,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"c63bedc7eec5da82c3e0783176a7a564","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2651-2663","gmt_create":"2026-04-28T18:50:34.4585336+04:00","gmt_modified":"2026-04-28T18:50:34.4585336+04:00"},{"id":37012,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"95b247631c8b269b2a98c7e385bac407","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2772-2779","gmt_create":"2026-04-28T18:50:34.4595337+04:00","gmt_modified":"2026-04-28T18:50:34.4595337+04:00"},{"id":37014,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"e5fedb1d60d7f5a29bddbc403ff3d857","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2790-2796","gmt_create":"2026-04-28T18:50:34.4595337+04:00","gmt_modified":"2026-04-28T18:50:34.4595337+04:00"},{"id":37016,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"b30577f67d2d7eafe760daf3f3f386b0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-28","gmt_create":"2026-04-28T18:50:34.4605335+04:00","gmt_modified":"2026-04-28T18:50:34.4605335+04:00"},{"id":37018,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"8d12769b91a5f0a7c02fe172ecffd5ef","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-29","gmt_create":"2026-04-28T18:50:34.4610774+04:00","gmt_modified":"2026-04-28T18:50:34.4610774+04:00"},{"id":37020,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"b4c2646d470a9f4262288b71e7b10e89","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-28","gmt_create":"2026-04-28T18:50:34.4616314+04:00","gmt_modified":"2026-04-28T18:50:34.4616314+04:00"},{"id":37022,"source_id":"398d9d4b02b6383c0b752cb0196a0475","target_id":"055bd4ac99dff0f7dad3d2d929f83b6a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-28","gmt_create":"2026-04-28T18:50:34.4622131+04:00","gmt_modified":"2026-04-28T18:50:34.4622131+04:00"},{"id":37024,"source_id":"6e587b97bf4080c7754c5ed73736fca7","target_id":"eb6ddb4ecbe9eb87c395f2865107ea0e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-27","gmt_create":"2026-04-28T18:50:34.4627918+04:00","gmt_modified":"2026-04-28T18:50:34.4627918+04:00"},{"id":37026,"source_id":"3a8d8a10556a0b6501e25aa43e91f913","target_id":"660390a5b68b3f21fd886800f24fcc78","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 39-45","gmt_create":"2026-04-28T18:50:34.4644202+04:00","gmt_modified":"2026-04-28T18:50:34.4644202+04:00"},{"id":37028,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"688ff5b9bf8ff49c734540b2a73a1251","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 288-298","gmt_create":"2026-04-28T18:50:34.4649279+04:00","gmt_modified":"2026-04-28T18:50:34.4649279+04:00"},{"id":37030,"source_id":"b4467ca30cb6f6d587fc200900ee9ec9","target_id":"34e2f989664fd5eff332f3ac19b8b16e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 85-105","gmt_create":"2026-04-28T18:50:34.4655215+04:00","gmt_modified":"2026-04-28T18:50:34.4655215+04:00"},{"id":37099,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"16865a1a8de25574f1f934ab4b21167f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1595-1624","gmt_create":"2026-04-28T18:53:18.2656376+04:00","gmt_modified":"2026-04-28T18:53:18.2656376+04:00"},{"id":37195,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"9a668be3a6e1e9f3afa6fcd90677d0b3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5482-5499","gmt_create":"2026-04-28T18:54:23.698664+04:00","gmt_modified":"2026-04-28T18:54:23.698664+04:00"},{"id":37345,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"e2711f14cc959993e82e19c033761fff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 16-21","gmt_create":"2026-04-28T19:27:53.6199688+04:00","gmt_modified":"2026-04-28T19:27:53.6199688+04:00"},{"id":37348,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"c66e1ad4dc89d485e77e61d3776d5e80","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 299-301","gmt_create":"2026-04-28T19:27:53.620969+04:00","gmt_modified":"2026-04-28T19:27:53.620969+04:00"},{"id":37350,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"d6af3938508b615a82ba5a3820d850c0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 522-528","gmt_create":"2026-04-28T19:27:53.620969+04:00","gmt_modified":"2026-04-28T19:27:53.620969+04:00"},{"id":37352,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"10e2e9b3ec934c86877d6a4b57e21d34","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 321-327","gmt_create":"2026-04-28T19:27:53.6219687+04:00","gmt_modified":"2026-04-28T19:27:53.6219687+04:00"},{"id":37354,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"5cdb837c5a6e5f183ed9a818f50b2abf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 336-338","gmt_create":"2026-04-28T19:27:53.6219687+04:00","gmt_modified":"2026-04-28T19:27:53.6219687+04:00"},{"id":37356,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"4a61426778eec404639d3aa8059fbe1a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 357-364","gmt_create":"2026-04-28T19:27:53.6219687+04:00","gmt_modified":"2026-04-28T19:27:53.6219687+04:00"},{"id":37461,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"3f9fffa35f3712fd1f90ebc3c0593373","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: documentation/debug_node_plugin.md","gmt_create":"2026-04-28T19:48:38.8756263+04:00","gmt_modified":"2026-04-28T19:48:38.8756263+04:00"},{"id":37462,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"85e109c379780f5df7bb2695b2862fba","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp","gmt_create":"2026-04-28T19:48:38.8766264+04:00","gmt_modified":"2026-04-28T19:48:38.8766264+04:00"},{"id":37463,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"796d893189111ab0df5e606d82fea700","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/debug_node/plugin.cpp","gmt_create":"2026-04-28T19:48:38.8766264+04:00","gmt_modified":"2026-04-28T19:48:38.8766264+04:00"},{"id":37464,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"fb3a9999324017fb4d2fad7f61f2660e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp","gmt_create":"2026-04-28T19:48:38.8766264+04:00","gmt_modified":"2026-04-28T19:48:38.8766264+04:00"},{"id":37465,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"2b777822e4399aff7d4e5ea26fcbe8b9","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config_debug.ini","gmt_create":"2026-04-28T19:48:38.8766264+04:00","gmt_modified":"2026-04-28T19:48:38.8766264+04:00"},{"id":37466,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"89eb7bdd3591c729dfa6f00de5cdfa1c","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: programs/util/sign_transaction.cpp","gmt_create":"2026-04-28T19:48:38.8766264+04:00","gmt_modified":"2026-04-28T19:48:38.8766264+04:00"},{"id":37467,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"2819790574404236d7b1bca0b253c524","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: programs/util/sign_digest.cpp","gmt_create":"2026-04-28T19:48:38.8777272+04:00","gmt_modified":"2026-04-28T19:48:38.8777272+04:00"},{"id":37468,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-28T19:48:38.8777272+04:00","gmt_modified":"2026-04-28T19:48:38.8777272+04:00"},{"id":37469,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"f7dedf31e491c7adbaf05e957360c531","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-28T19:48:38.8777272+04:00","gmt_modified":"2026-04-28T19:48:38.8777272+04:00"},{"id":37470,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"d72b348a2c3c7943e4a7abb7dbdaa751","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_connection.hpp","gmt_create":"2026-04-28T19:48:38.8782298+04:00","gmt_modified":"2026-04-28T19:48:38.8782298+04:00"},{"id":37471,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-28T19:48:38.8783137+04:00","gmt_modified":"2026-04-28T19:48:38.8783137+04:00"},{"id":37472,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"f9ff6c6bb4dec49a31a1b770a5d8e89f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#38-108","gmt_create":"2026-04-28T19:48:38.8783137+04:00","gmt_modified":"2026-04-28T19:48:38.8783137+04:00"},{"id":37473,"source_id":"85e109c379780f5df7bb2695b2862fba","target_id":"f9ff6c6bb4dec49a31a1b770a5d8e89f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-108","gmt_create":"2026-04-28T19:48:38.8789249+04:00","gmt_modified":"2026-04-28T19:48:38.8789249+04:00"},{"id":37474,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"c914b1a14377d172e66d3320eee0a043","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#25-94","gmt_create":"2026-04-28T19:48:38.8789249+04:00","gmt_modified":"2026-04-28T19:48:38.8789249+04:00"},{"id":37475,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"c914b1a14377d172e66d3320eee0a043","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 25-94","gmt_create":"2026-04-28T19:48:38.8789249+04:00","gmt_modified":"2026-04-28T19:48:38.8789249+04:00"},{"id":37476,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"f3cc232f0f51afc8747a00f6b9906c3d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp#1-108","gmt_create":"2026-04-28T19:48:38.8795322+04:00","gmt_modified":"2026-04-28T19:48:38.8795322+04:00"},{"id":37477,"source_id":"fb3a9999324017fb4d2fad7f61f2660e","target_id":"f3cc232f0f51afc8747a00f6b9906c3d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-108","gmt_create":"2026-04-28T19:48:38.8795322+04:00","gmt_modified":"2026-04-28T19:48:38.8795322+04:00"},{"id":37478,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"b9491b3f2a4f089e194b8c382d40e3ed","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/util/sign_transaction.cpp#12-26","gmt_create":"2026-04-28T19:48:38.8800347+04:00","gmt_modified":"2026-04-28T19:48:38.8800347+04:00"},{"id":37479,"source_id":"89eb7bdd3591c729dfa6f00de5cdfa1c","target_id":"b9491b3f2a4f089e194b8c382d40e3ed","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 12-26","gmt_create":"2026-04-28T19:48:38.8801072+04:00","gmt_modified":"2026-04-28T19:48:38.8801072+04:00"},{"id":37480,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"80d96f5a237fbc996d591a3a6738455e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/util/sign_digest.cpp#12-24","gmt_create":"2026-04-28T19:48:38.8801072+04:00","gmt_modified":"2026-04-28T19:48:38.8801072+04:00"},{"id":37481,"source_id":"2819790574404236d7b1bca0b253c524","target_id":"80d96f5a237fbc996d591a3a6738455e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 12-24","gmt_create":"2026-04-28T19:48:38.8801072+04:00","gmt_modified":"2026-04-28T19:48:38.8801072+04:00"},{"id":37482,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"3aa63104c48be9af56ac2394a0ff0465","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#1-200","gmt_create":"2026-04-28T19:48:38.8806797+04:00","gmt_modified":"2026-04-28T19:48:38.8806797+04:00"},{"id":37483,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"3aa63104c48be9af56ac2394a0ff0465","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-200","gmt_create":"2026-04-28T19:48:38.8806797+04:00","gmt_modified":"2026-04-28T19:48:38.8806797+04:00"},{"id":37484,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"035cfc0826dd0b5915c1d3c58d1ef13d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#190-200","gmt_create":"2026-04-28T19:48:38.8811822+04:00","gmt_modified":"2026-04-28T19:48:38.8811822+04:00"},{"id":37485,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"035cfc0826dd0b5915c1d3c58d1ef13d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 190-200","gmt_create":"2026-04-28T19:48:38.8811822+04:00","gmt_modified":"2026-04-28T19:48:38.8811822+04:00"},{"id":37486,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"775f3dad042fb99fc87d2a984fb10026","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#79-200","gmt_create":"2026-04-28T19:48:38.8811822+04:00","gmt_modified":"2026-04-28T19:48:38.8811822+04:00"},{"id":37487,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"775f3dad042fb99fc87d2a984fb10026","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-200","gmt_create":"2026-04-28T19:48:38.8811822+04:00","gmt_modified":"2026-04-28T19:48:38.8811822+04:00"},{"id":37488,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"ab666734d7a7b8fd90bcfb1f1a312813","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config_debug.ini#1-126","gmt_create":"2026-04-28T19:48:38.8811822+04:00","gmt_modified":"2026-04-28T19:48:38.8811822+04:00"},{"id":37489,"source_id":"2b777822e4399aff7d4e5ea26fcbe8b9","target_id":"ab666734d7a7b8fd90bcfb1f1a312813","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-126","gmt_create":"2026-04-28T19:48:38.8811822+04:00","gmt_modified":"2026-04-28T19:48:38.8811822+04:00"},{"id":37490,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"6fca2c5700dd39ca35022a36a9d989cf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#1-111","gmt_create":"2026-04-28T19:48:38.8824615+04:00","gmt_modified":"2026-04-28T19:48:38.8824615+04:00"},{"id":37491,"source_id":"85e109c379780f5df7bb2695b2862fba","target_id":"6fca2c5700dd39ca35022a36a9d989cf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-111","gmt_create":"2026-04-28T19:48:38.8824615+04:00","gmt_modified":"2026-04-28T19:48:38.8824615+04:00"},{"id":37492,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"12ee4287ffb75b424c50950e8f5b2df5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#1-668","gmt_create":"2026-04-28T19:48:38.8824615+04:00","gmt_modified":"2026-04-28T19:48:38.8824615+04:00"},{"id":37493,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"12ee4287ffb75b424c50950e8f5b2df5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-668","gmt_create":"2026-04-28T19:48:38.8829645+04:00","gmt_modified":"2026-04-28T19:48:38.8829645+04:00"},{"id":37494,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"1bbdcbd551f6b6db613208a506ed663b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/util/sign_transaction.cpp#1-54","gmt_create":"2026-04-28T19:48:38.8829645+04:00","gmt_modified":"2026-04-28T19:48:38.8829645+04:00"},{"id":37495,"source_id":"89eb7bdd3591c729dfa6f00de5cdfa1c","target_id":"1bbdcbd551f6b6db613208a506ed663b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-54","gmt_create":"2026-04-28T19:48:38.8829645+04:00","gmt_modified":"2026-04-28T19:48:38.8829645+04:00"},{"id":37496,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"fca351b915d815422a74e940fcde8cdb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/util/sign_digest.cpp#1-49","gmt_create":"2026-04-28T19:48:38.8829645+04:00","gmt_modified":"2026-04-28T19:48:38.8829645+04:00"},{"id":37497,"source_id":"2819790574404236d7b1bca0b253c524","target_id":"fca351b915d815422a74e940fcde8cdb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-49","gmt_create":"2026-04-28T19:48:38.8829645+04:00","gmt_modified":"2026-04-28T19:48:38.8829645+04:00"},{"id":37498,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"07bbb44ee7833965e9eb8064c7dca3a5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#1-200","gmt_create":"2026-04-28T19:48:38.8829645+04:00","gmt_modified":"2026-04-28T19:48:38.8829645+04:00"},{"id":37499,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"07bbb44ee7833965e9eb8064c7dca3a5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-200","gmt_create":"2026-04-28T19:48:38.8844722+04:00","gmt_modified":"2026-04-28T19:48:38.8844722+04:00"},{"id":37500,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"5dbe534429ba35b6a93bafccda79ab2b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#1-200","gmt_create":"2026-04-28T19:48:38.8844722+04:00","gmt_modified":"2026-04-28T19:48:38.8844722+04:00"},{"id":37501,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"5dbe534429ba35b6a93bafccda79ab2b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-200","gmt_create":"2026-04-28T19:48:38.8844722+04:00","gmt_modified":"2026-04-28T19:48:38.8844722+04:00"},{"id":37502,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"e4d3b0996d354976a0f8d7be27bba43f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#222-288","gmt_create":"2026-04-28T19:48:38.8844722+04:00","gmt_modified":"2026-04-28T19:48:38.8844722+04:00"},{"id":37503,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"e4d3b0996d354976a0f8d7be27bba43f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 222-288","gmt_create":"2026-04-28T19:48:38.8854751+04:00","gmt_modified":"2026-04-28T19:48:38.8854751+04:00"},{"id":37504,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"073296e0c0907087372d72bd8ed5a30d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#321-420","gmt_create":"2026-04-28T19:48:38.887196+04:00","gmt_modified":"2026-04-28T19:48:38.887196+04:00"},{"id":37505,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"073296e0c0907087372d72bd8ed5a30d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 321-420","gmt_create":"2026-04-28T19:48:38.887196+04:00","gmt_modified":"2026-04-28T19:48:38.887196+04:00"},{"id":37506,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"8a25fc8080abd566cbef2d584b42c23b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#489-555","gmt_create":"2026-04-28T19:48:38.887196+04:00","gmt_modified":"2026-04-28T19:48:38.887196+04:00"},{"id":37507,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"8a25fc8080abd566cbef2d584b42c23b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 489-555","gmt_create":"2026-04-28T19:48:38.887196+04:00","gmt_modified":"2026-04-28T19:48:38.887196+04:00"},{"id":37508,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"06bbb8421071aa4e70ada689b0e8d0de","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#118-170","gmt_create":"2026-04-28T19:48:38.8877895+04:00","gmt_modified":"2026-04-28T19:48:38.8877895+04:00"},{"id":37509,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"06bbb8421071aa4e70ada689b0e8d0de","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 118-170","gmt_create":"2026-04-28T19:48:38.8877895+04:00","gmt_modified":"2026-04-28T19:48:38.8877895+04:00"},{"id":37510,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"e2711f14cc959993e82e19c033761fff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#16-21","gmt_create":"2026-04-28T19:48:38.8877895+04:00","gmt_modified":"2026-04-28T19:48:38.8877895+04:00"},{"id":37511,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"be7f7c455b691c95a3dd6a2789b92a2a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#489-511","gmt_create":"2026-04-28T19:48:38.8883968+04:00","gmt_modified":"2026-04-28T19:48:38.8883968+04:00"},{"id":37512,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"be7f7c455b691c95a3dd6a2789b92a2a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 489-511","gmt_create":"2026-04-28T19:48:38.8883968+04:00","gmt_modified":"2026-04-28T19:48:38.8883968+04:00"},{"id":37513,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"3e4f072495a91bf2b13739f1f9d95a9c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#321-372","gmt_create":"2026-04-28T19:48:38.8888994+04:00","gmt_modified":"2026-04-28T19:48:38.8888994+04:00"},{"id":37514,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"3e4f072495a91bf2b13739f1f9d95a9c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 321-372","gmt_create":"2026-04-28T19:48:38.8889786+04:00","gmt_modified":"2026-04-28T19:48:38.8889786+04:00"},{"id":37515,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"a31a8b940a678fa2f3d45d33ed52e2a8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#222-555","gmt_create":"2026-04-28T19:48:38.8895524+04:00","gmt_modified":"2026-04-28T19:48:38.8895524+04:00"},{"id":37516,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"a31a8b940a678fa2f3d45d33ed52e2a8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 222-555","gmt_create":"2026-04-28T19:48:38.8895524+04:00","gmt_modified":"2026-04-28T19:48:38.8895524+04:00"},{"id":37517,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"b7b962f2d1cc506c60851cf76e545c68","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#441-454","gmt_create":"2026-04-28T19:48:38.8901466+04:00","gmt_modified":"2026-04-28T19:48:38.8901466+04:00"},{"id":37518,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"b7b962f2d1cc506c60851cf76e545c68","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 441-454","gmt_create":"2026-04-28T19:48:38.8906491+04:00","gmt_modified":"2026-04-28T19:48:38.8906491+04:00"},{"id":37519,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"5ad89d11099370a27a89a9f7d0346730","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#422-430","gmt_create":"2026-04-28T19:48:38.8906491+04:00","gmt_modified":"2026-04-28T19:48:38.8906491+04:00"},{"id":37520,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"5ad89d11099370a27a89a9f7d0346730","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 422-430","gmt_create":"2026-04-28T19:48:38.8906491+04:00","gmt_modified":"2026-04-28T19:48:38.8906491+04:00"},{"id":37521,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"20bc4d17331e9bb80414df521e933386","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#117-136","gmt_create":"2026-04-28T19:48:38.8906491+04:00","gmt_modified":"2026-04-28T19:48:38.8906491+04:00"},{"id":37522,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"20bc4d17331e9bb80414df521e933386","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 117-136","gmt_create":"2026-04-28T19:48:38.8906491+04:00","gmt_modified":"2026-04-28T19:48:38.8906491+04:00"},{"id":37523,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"1a03b72ef4884dc486a6cf695bdec9e1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/util/sign_transaction.cpp#28-53","gmt_create":"2026-04-28T19:48:38.8906491+04:00","gmt_modified":"2026-04-28T19:48:38.8906491+04:00"},{"id":37524,"source_id":"89eb7bdd3591c729dfa6f00de5cdfa1c","target_id":"1a03b72ef4884dc486a6cf695bdec9e1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 28-53","gmt_create":"2026-04-28T19:48:38.8916518+04:00","gmt_modified":"2026-04-28T19:48:38.8916518+04:00"},{"id":37525,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"ececdaf0999d8a83addb93f95d0039d3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/util/sign_digest.cpp#26-48","gmt_create":"2026-04-28T19:48:38.8916518+04:00","gmt_modified":"2026-04-28T19:48:38.8916518+04:00"},{"id":37526,"source_id":"2819790574404236d7b1bca0b253c524","target_id":"ececdaf0999d8a83addb93f95d0039d3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-48","gmt_create":"2026-04-28T19:48:38.8916518+04:00","gmt_modified":"2026-04-28T19:48:38.8916518+04:00"},{"id":37527,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"8e15df455552a1b9105a9a8b25797f1e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#169-172","gmt_create":"2026-04-28T19:48:38.892652+04:00","gmt_modified":"2026-04-28T19:48:38.892652+04:00"},{"id":37528,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"8e15df455552a1b9105a9a8b25797f1e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 169-172","gmt_create":"2026-04-28T19:48:38.892652+04:00","gmt_modified":"2026-04-28T19:48:38.892652+04:00"},{"id":37529,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"9be51c5b9724dc743dc15a2758b6e4b2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#605-652","gmt_create":"2026-04-28T19:48:38.892652+04:00","gmt_modified":"2026-04-28T19:48:38.892652+04:00"},{"id":37530,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"9be51c5b9724dc743dc15a2758b6e4b2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 605-652","gmt_create":"2026-04-28T19:48:38.892652+04:00","gmt_modified":"2026-04-28T19:48:38.892652+04:00"},{"id":37531,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"7232f04ac5a3f960659a84db72e3c8c6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#79-83","gmt_create":"2026-04-28T19:48:38.8936519+04:00","gmt_modified":"2026-04-28T19:48:38.8936519+04:00"},{"id":37532,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"7232f04ac5a3f960659a84db72e3c8c6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-83","gmt_create":"2026-04-28T19:48:38.8936519+04:00","gmt_modified":"2026-04-28T19:48:38.8936519+04:00"},{"id":37533,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"86b86c367842460e75e043fb87b0f88f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5091-5108","gmt_create":"2026-04-28T19:48:38.8936519+04:00","gmt_modified":"2026-04-28T19:48:38.8936519+04:00"},{"id":37534,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"86b86c367842460e75e043fb87b0f88f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5091-5108","gmt_create":"2026-04-28T19:48:38.8936519+04:00","gmt_modified":"2026-04-28T19:48:38.8936519+04:00"},{"id":37535,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"d46277833fc133df8624346cd33cb6f3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#298-365","gmt_create":"2026-04-28T19:48:38.894752+04:00","gmt_modified":"2026-04-28T19:48:38.894752+04:00"},{"id":37536,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"d46277833fc133df8624346cd33cb6f3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 298-365","gmt_create":"2026-04-28T19:48:38.894752+04:00","gmt_modified":"2026-04-28T19:48:38.894752+04:00"},{"id":37537,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"9178051ee4765210ad69eba44f39c87a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#521-530","gmt_create":"2026-04-28T19:48:38.8952547+04:00","gmt_modified":"2026-04-28T19:48:38.8952547+04:00"},{"id":37538,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"9178051ee4765210ad69eba44f39c87a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 521-530","gmt_create":"2026-04-28T19:48:38.8953242+04:00","gmt_modified":"2026-04-28T19:48:38.8953242+04:00"},{"id":37539,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"cd9f1eabe9bd89866fea8372b8de3fb2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#605-686","gmt_create":"2026-04-28T19:48:38.8953242+04:00","gmt_modified":"2026-04-28T19:48:38.8953242+04:00"},{"id":37540,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"cd9f1eabe9bd89866fea8372b8de3fb2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 605-686","gmt_create":"2026-04-28T19:48:38.9043108+04:00","gmt_modified":"2026-04-28T19:48:38.9043108+04:00"},{"id":37541,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"b988e082f7d48dc4995d1e872598437c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#40-41","gmt_create":"2026-04-28T19:48:38.905334+04:00","gmt_modified":"2026-04-28T19:48:38.905334+04:00"},{"id":37542,"source_id":"85e109c379780f5df7bb2695b2862fba","target_id":"b988e082f7d48dc4995d1e872598437c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 40-41","gmt_create":"2026-04-28T19:48:38.905334+04:00","gmt_modified":"2026-04-28T19:48:38.905334+04:00"},{"id":37543,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"870cbffea14b19ef183293221413ec5c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config_debug.ini#36-47","gmt_create":"2026-04-28T19:48:38.9063824+04:00","gmt_modified":"2026-04-28T19:48:38.9063824+04:00"},{"id":37544,"source_id":"2b777822e4399aff7d4e5ea26fcbe8b9","target_id":"870cbffea14b19ef183293221413ec5c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 36-47","gmt_create":"2026-04-28T19:48:38.9063824+04:00","gmt_modified":"2026-04-28T19:48:38.9063824+04:00"},{"id":37545,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"1448971e80d721aa1c85a46aea3da577","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config_debug.ini#49-67","gmt_create":"2026-04-28T19:48:38.9069109+04:00","gmt_modified":"2026-04-28T19:48:38.9069109+04:00"},{"id":37546,"source_id":"2b777822e4399aff7d4e5ea26fcbe8b9","target_id":"1448971e80d721aa1c85a46aea3da577","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 49-67","gmt_create":"2026-04-28T19:48:38.9069109+04:00","gmt_modified":"2026-04-28T19:48:38.9069109+04:00"},{"id":37547,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"d64fee9bc095b55a1d7636f437075323","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#244-248","gmt_create":"2026-04-28T19:48:38.9085032+04:00","gmt_modified":"2026-04-28T19:48:38.9085032+04:00"},{"id":37548,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"d64fee9bc095b55a1d7636f437075323","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 244-248","gmt_create":"2026-04-28T19:48:38.9085032+04:00","gmt_modified":"2026-04-28T19:48:38.9085032+04:00"},{"id":37549,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"076badf84cd673f3b9244504dca95861","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#363-366","gmt_create":"2026-04-28T19:48:38.9085032+04:00","gmt_modified":"2026-04-28T19:48:38.9085032+04:00"},{"id":37550,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"076badf84cd673f3b9244504dca95861","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 363-366","gmt_create":"2026-04-28T19:48:38.9085032+04:00","gmt_modified":"2026-04-28T19:48:38.9085032+04:00"},{"id":37551,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"a9154fa6ace3118ba37f67a132a8951a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#124-133","gmt_create":"2026-04-28T19:48:38.9085032+04:00","gmt_modified":"2026-04-28T19:48:38.9085032+04:00"},{"id":37552,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"a9154fa6ace3118ba37f67a132a8951a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 124-133","gmt_create":"2026-04-28T19:48:38.9090174+04:00","gmt_modified":"2026-04-28T19:48:38.9090174+04:00"},{"id":37553,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"d7d14eee16cef3e1cf0039ec5f52febf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config_debug.ini#107-126","gmt_create":"2026-04-28T19:48:38.9090174+04:00","gmt_modified":"2026-04-28T19:48:38.9090174+04:00"},{"id":37554,"source_id":"2b777822e4399aff7d4e5ea26fcbe8b9","target_id":"d7d14eee16cef3e1cf0039ec5f52febf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 107-126","gmt_create":"2026-04-28T19:48:38.9090174+04:00","gmt_modified":"2026-04-28T19:48:38.9090174+04:00"},{"id":37555,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"154f8101227c15750c6aa53260e9b84f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/plugin.cpp#374-420","gmt_create":"2026-04-28T19:48:38.909541+04:00","gmt_modified":"2026-04-28T19:48:38.909541+04:00"},{"id":37556,"source_id":"796d893189111ab0df5e606d82fea700","target_id":"154f8101227c15750c6aa53260e9b84f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 374-420","gmt_create":"2026-04-28T19:48:38.909541+04:00","gmt_modified":"2026-04-28T19:48:38.909541+04:00"},{"id":37557,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"020b183f2967c754e5327defd9a63415","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: documentation/debug_node_plugin.md#50-134","gmt_create":"2026-04-28T19:48:38.909541+04:00","gmt_modified":"2026-04-28T19:48:38.909541+04:00"},{"id":37558,"source_id":"3f9fffa35f3712fd1f90ebc3c0593373","target_id":"020b183f2967c754e5327defd9a63415","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 50-134","gmt_create":"2026-04-28T19:48:38.9100511+04:00","gmt_modified":"2026-04-28T19:48:38.9100511+04:00"},{"id":37559,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"f7356b2eafc5252755db0df36458992f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp#62-90","gmt_create":"2026-04-28T19:48:38.9105723+04:00","gmt_modified":"2026-04-28T19:48:38.9105723+04:00"},{"id":37560,"source_id":"85e109c379780f5df7bb2695b2862fba","target_id":"f7356b2eafc5252755db0df36458992f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 62-90","gmt_create":"2026-04-28T19:48:38.9105723+04:00","gmt_modified":"2026-04-28T19:48:38.9105723+04:00"},{"id":37710,"source_id":"4493b728-953f-4c7d-9b30-89aa33255a3b","target_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4493b728-953f-4c7d-9b30-89aa33255a3b -\u003e 3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","gmt_create":"2026-04-28T19:53:06.3555351+04:00","gmt_modified":"2026-04-28T19:53:06.3555351+04:00"},{"id":37711,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"589f3f8e-0048-4b0d-befe-693f063b0ce4","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 3db3b667-c9b0-4ef0-bdbd-1e4638995b5c -\u003e 589f3f8e-0048-4b0d-befe-693f063b0ce4","gmt_create":"2026-04-28T19:53:06.3588937+04:00","gmt_modified":"2026-04-28T19:53:06.3588937+04:00"},{"id":37712,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"3c6c03d8-c73c-48e4-8076-4deaff61fc83","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 3db3b667-c9b0-4ef0-bdbd-1e4638995b5c -\u003e 3c6c03d8-c73c-48e4-8076-4deaff61fc83","gmt_create":"2026-04-28T19:53:06.3588937+04:00","gmt_modified":"2026-04-28T19:53:06.3588937+04:00"},{"id":37713,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"7e6094b6-9310-43af-993d-e3bebf0c7ee7","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 3db3b667-c9b0-4ef0-bdbd-1e4638995b5c -\u003e 7e6094b6-9310-43af-993d-e3bebf0c7ee7","gmt_create":"2026-04-28T19:53:06.3594842+04:00","gmt_modified":"2026-04-28T19:53:06.3594842+04:00"},{"id":37714,"source_id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","target_id":"40c4f036-b025-4e82-846a-a71cf89d5e5a","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 3db3b667-c9b0-4ef0-bdbd-1e4638995b5c -\u003e 40c4f036-b025-4e82-846a-a71cf89d5e5a","gmt_create":"2026-04-28T19:53:06.3594842+04:00","gmt_modified":"2026-04-28T19:53:06.3594842+04:00"},{"id":37742,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"82787c8d2e7394cf00fa87735394c3d5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 72-95","gmt_create":"2026-04-28T20:32:44.2192103+04:00","gmt_modified":"2026-04-28T20:32:44.2192103+04:00"},{"id":37747,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"ab73f6cda0f4389d2bef214161afef81","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 593-601","gmt_create":"2026-04-28T20:32:44.2202162+04:00","gmt_modified":"2026-04-28T20:32:44.2202162+04:00"},{"id":37749,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"8caf3345fc407dc61382d80fab63bc93","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5240-5274","gmt_create":"2026-04-28T20:32:44.221214+04:00","gmt_modified":"2026-04-28T20:32:44.221214+04:00"},{"id":37752,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"8e54da669dc427d415acec15fb6a804a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3039-3045","gmt_create":"2026-04-28T20:32:44.2222136+04:00","gmt_modified":"2026-04-28T20:32:44.2222136+04:00"},{"id":37754,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e6da7f673c730dddfe0373c2e796f71a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1215-1246","gmt_create":"2026-04-28T20:32:44.2222136+04:00","gmt_modified":"2026-04-28T20:32:44.2222136+04:00"},{"id":37757,"source_id":"3948eb588d15d01acf21ffd439ec508c","target_id":"f02c5e6d0090d5de89e01b0a1d478c5c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 33-45","gmt_create":"2026-04-28T20:32:44.2232056+04:00","gmt_modified":"2026-04-28T20:32:44.2232056+04:00"},{"id":37759,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"f5f7d5764818ed749bc9b829f7aea2ff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-386","gmt_create":"2026-04-28T20:32:44.2242947+04:00","gmt_modified":"2026-04-28T20:32:44.2242947+04:00"},{"id":37764,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"56968eec4c8adc4f9edd153c6ce9e231","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-374","gmt_create":"2026-04-28T20:32:44.2260949+04:00","gmt_modified":"2026-04-28T20:32:44.2260949+04:00"},{"id":37768,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"8923fae736f09b1cb255636af9a52069","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6389","gmt_create":"2026-04-28T20:32:44.2274126+04:00","gmt_modified":"2026-04-28T20:32:44.2274126+04:00"},{"id":37771,"source_id":"3948eb588d15d01acf21ffd439ec508c","target_id":"18382ca1b0f5cd7c4280e057d0242410","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-49","gmt_create":"2026-04-28T20:32:44.2280338+04:00","gmt_modified":"2026-04-28T20:32:44.2280338+04:00"},{"id":37773,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"a41d1e5edd6d2706424bcd3b39295264","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 68-162","gmt_create":"2026-04-28T20:32:44.2291474+04:00","gmt_modified":"2026-04-28T20:32:44.2291474+04:00"},{"id":37775,"source_id":"69291a4b8d9de900b397578829d3e0d9","target_id":"3de0360b8d19b4e6a521e737d44e1eec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 128-140","gmt_create":"2026-04-28T20:32:44.2291474+04:00","gmt_modified":"2026-04-28T20:32:44.2291474+04:00"},{"id":37778,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"df02d50a1fa9064e2cfe4b033b6ecfc8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 233-306","gmt_create":"2026-04-28T20:32:44.2306744+04:00","gmt_modified":"2026-04-28T20:32:44.2306744+04:00"},{"id":37783,"source_id":"69291a4b8d9de900b397578829d3e0d9","target_id":"c32e26ac2223fcbd9ec8280f135d338c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 135-140","gmt_create":"2026-04-28T20:32:44.2334797+04:00","gmt_modified":"2026-04-28T20:32:44.2334797+04:00"},{"id":37786,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"0543ad31da0059b627fc7cfd5bac0cad","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 233-272","gmt_create":"2026-04-28T20:32:44.2339834+04:00","gmt_modified":"2026-04-28T20:32:44.2339834+04:00"},{"id":37788,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"fa47faf491ab252556d3fc50f00c6a8d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 662-718","gmt_create":"2026-04-28T20:32:44.2339834+04:00","gmt_modified":"2026-04-28T20:32:44.2339834+04:00"},{"id":37791,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"09cde54d90657149861df59444b291ba","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 244-338","gmt_create":"2026-04-28T20:32:44.2359867+04:00","gmt_modified":"2026-04-28T20:32:44.2359867+04:00"},{"id":37793,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"e3f78790c3dfc7c45569ce78697bf9ef","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 240-278","gmt_create":"2026-04-28T20:32:44.2359867+04:00","gmt_modified":"2026-04-28T20:32:44.2359867+04:00"},{"id":37795,"source_id":"69291a4b8d9de900b397578829d3e0d9","target_id":"5212be0912badd41a65263c4ef5ab6fc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 237-283","gmt_create":"2026-04-28T20:32:44.2359867+04:00","gmt_modified":"2026-04-28T20:32:44.2359867+04:00"},{"id":37797,"source_id":"69291a4b8d9de900b397578829d3e0d9","target_id":"8743a349115da7520b9567a7d855ca18","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 148-235","gmt_create":"2026-04-28T20:32:44.2376163+04:00","gmt_modified":"2026-04-28T20:32:44.2376163+04:00"},{"id":37801,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"8c4475690fa2dd5b99756637ea5c7358","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 356-369","gmt_create":"2026-04-28T20:32:44.2411885+04:00","gmt_modified":"2026-04-28T20:32:44.2411885+04:00"},{"id":37803,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"c71708bd330ca79e1ba386a1763f5154","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 718-740","gmt_create":"2026-04-28T20:32:44.2418022+04:00","gmt_modified":"2026-04-28T20:32:44.2418022+04:00"},{"id":37805,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"9aacd835d38b395734ce6c87619ff59b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5272-5274","gmt_create":"2026-04-28T20:32:44.2423052+04:00","gmt_modified":"2026-04-28T20:32:44.2423052+04:00"},{"id":37807,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"a1b4ebbf3bdbfda373c476ae64175283","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 169-242","gmt_create":"2026-04-28T20:32:44.2423052+04:00","gmt_modified":"2026-04-28T20:32:44.2423052+04:00"},{"id":37809,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"179c82a5b5dc0ef88ef0f3d348aeabf7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 310-338","gmt_create":"2026-04-28T20:32:44.2433084+04:00","gmt_modified":"2026-04-28T20:32:44.2433084+04:00"},{"id":37811,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"50fe97d449649d053646df017c7d5108","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 255-308","gmt_create":"2026-04-28T20:32:44.2440526+04:00","gmt_modified":"2026-04-28T20:32:44.2440526+04:00"},{"id":37813,"source_id":"c82262dc5275e094efc9474032d15922","target_id":"707909d344d5db986aaec2161fe7ec64","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 58-58","gmt_create":"2026-04-28T20:32:44.2446234+04:00","gmt_modified":"2026-04-28T20:32:44.2446234+04:00"},{"id":37815,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"b07239f8068fdc811cbe0313fbee8a56","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 175-279","gmt_create":"2026-04-28T20:32:44.2463141+04:00","gmt_modified":"2026-04-28T20:32:44.2463141+04:00"},{"id":37817,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"89fa671fe61671f5b5ab01a94646d7a3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 428-480","gmt_create":"2026-04-28T20:32:44.2464113+04:00","gmt_modified":"2026-04-28T20:32:44.2464113+04:00"},{"id":37819,"source_id":"3a8d8a10556a0b6501e25aa43e91f913","target_id":"f57a019298b7b9496ef4f4a85f921f75","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 47-71","gmt_create":"2026-04-28T20:32:44.2469161+04:00","gmt_modified":"2026-04-28T20:32:44.2469161+04:00"},{"id":37821,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"228fc59a5268956db45d2f696afd5586","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 518-526","gmt_create":"2026-04-28T20:32:44.2469161+04:00","gmt_modified":"2026-04-28T20:32:44.2469161+04:00"},{"id":37823,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"8f4a83adbe444c72194effc3001fe32c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5265-5274","gmt_create":"2026-04-28T20:32:44.2480151+04:00","gmt_modified":"2026-04-28T20:32:44.2480151+04:00"},{"id":37825,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4488509fca59b45f940f83b5db6daa2a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3874-3908","gmt_create":"2026-04-28T20:32:44.2485196+04:00","gmt_modified":"2026-04-28T20:32:44.2485196+04:00"},{"id":37827,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"313eca37529d6d0a87af6db00b3cae76","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3598-3626","gmt_create":"2026-04-28T20:32:44.2485196+04:00","gmt_modified":"2026-04-28T20:32:44.2485196+04:00"},{"id":37829,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"88cfd8edcbcced125d9358898750ad6a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 172-182","gmt_create":"2026-04-28T20:32:44.2495238+04:00","gmt_modified":"2026-04-28T20:32:44.2495238+04:00"},{"id":37831,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"3442c112ec40ff3a7ce211827dd731fb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 599-600","gmt_create":"2026-04-28T20:32:44.2495238+04:00","gmt_modified":"2026-04-28T20:32:44.2495238+04:00"},{"id":37833,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"d2465d6814378cac309bb4813312e463","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4472-4479","gmt_create":"2026-04-28T20:32:44.2514671+04:00","gmt_modified":"2026-04-28T20:32:44.2514671+04:00"},{"id":37835,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"028e9730d5a18007052fca00e208de6c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5016-5021","gmt_create":"2026-04-28T20:32:44.252754+04:00","gmt_modified":"2026-04-28T20:32:44.252754+04:00"},{"id":37837,"source_id":"c70caf63ce77b078dcf89380b228a71a","target_id":"22da7c51b4640e33df6b1774de67e615","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 120-137","gmt_create":"2026-04-28T20:32:44.2533932+04:00","gmt_modified":"2026-04-28T20:32:44.2533932+04:00"},{"id":37839,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"0ff96f9f66fe11cb2bba96b2257c56cc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5013-5014","gmt_create":"2026-04-28T20:32:44.2539907+04:00","gmt_modified":"2026-04-28T20:32:44.2539907+04:00"},{"id":37841,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"f6e4014b53decf8540f03050dcb248a7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3061-3062","gmt_create":"2026-04-28T20:32:44.2546123+04:00","gmt_modified":"2026-04-28T20:32:44.2546123+04:00"},{"id":37843,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"3a3515773dce3d23274f19f11b24b8a1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 279-283","gmt_create":"2026-04-28T20:32:44.2551934+04:00","gmt_modified":"2026-04-28T20:32:44.2551934+04:00"},{"id":37845,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"bced6dc0057c5231cbd868d55b0f5331","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 340-354","gmt_create":"2026-04-28T20:32:44.2564626+04:00","gmt_modified":"2026-04-28T20:32:44.2564626+04:00"},{"id":37847,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"1496fd024a306e65ca11a1074f83e158","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 371-399","gmt_create":"2026-04-28T20:32:44.2569663+04:00","gmt_modified":"2026-04-28T20:32:44.2569663+04:00"},{"id":37849,"source_id":"18555f254f50536a15d8591acf982406","target_id":"e196c8cfa8e7dbe5852841c2e099bad5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 96-101","gmt_create":"2026-04-28T20:32:44.2579708+04:00","gmt_modified":"2026-04-28T20:32:44.2579708+04:00"},{"id":37851,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"c1db40762caab4c9462bb6bc70748152","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-45","gmt_create":"2026-04-28T20:32:44.2614959+04:00","gmt_modified":"2026-04-28T20:32:44.2614959+04:00"},{"id":37853,"source_id":"6e587b97bf4080c7754c5ed73736fca7","target_id":"973153247aad2260b57a9e4842ddfeb7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-28","gmt_create":"2026-04-28T20:32:44.2620208+04:00","gmt_modified":"2026-04-28T20:32:44.2620208+04:00"},{"id":37856,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"37d9a1a0250d6f2b30d0b5acfef7b606","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-35","gmt_create":"2026-04-28T20:32:44.2630248+04:00","gmt_modified":"2026-04-28T20:32:44.2630248+04:00"},{"id":37858,"source_id":"f7dedf31e491c7adbaf05e957360c531","target_id":"9fbb8ea1084712b6e00c9f6b9ad60c72","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-31","gmt_create":"2026-04-28T20:32:44.2635279+04:00","gmt_modified":"2026-04-28T20:32:44.2635279+04:00"},{"id":37860,"source_id":"3a8d8a10556a0b6501e25aa43e91f913","target_id":"63f471b93ce70266253197686f8d7df6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-35","gmt_create":"2026-04-28T20:32:44.2857552+04:00","gmt_modified":"2026-04-28T20:32:44.2857552+04:00"},{"id":37862,"source_id":"b4467ca30cb6f6d587fc200900ee9ec9","target_id":"f18f4bdd5e9956c9abb9a7a52882581a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 26-31","gmt_create":"2026-04-28T20:32:44.2863601+04:00","gmt_modified":"2026-04-28T20:32:44.2863601+04:00"},{"id":37864,"source_id":"3a6c30f4bb3b265155c881ccfafa980e","target_id":"03beec6b648d43a165d35e195b3eb81e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 285-306","gmt_create":"2026-04-28T20:32:44.2886911+04:00","gmt_modified":"2026-04-28T20:32:44.2886911+04:00"},{"id":37866,"source_id":"c82262dc5275e094efc9474032d15922","target_id":"8620e966ce1a76d2ab405f5a49743457","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 48-50","gmt_create":"2026-04-28T20:32:44.2886911+04:00","gmt_modified":"2026-04-28T20:32:44.2886911+04:00"},{"id":37868,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"d6ed83bad3a3124e13f09c4c1191b96a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 314-325","gmt_create":"2026-04-28T20:32:44.2898464+04:00","gmt_modified":"2026-04-28T20:32:44.2898464+04:00"},{"id":37870,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"fcdabf2022a050e8a2414fb979f5c419","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3448-3470","gmt_create":"2026-04-28T20:32:44.2903506+04:00","gmt_modified":"2026-04-28T20:32:44.2903506+04:00"},{"id":37873,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"70f9beafdbca4132e0349e28e51fcd6a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1239-1241","gmt_create":"2026-04-28T20:32:44.2920603+04:00","gmt_modified":"2026-04-28T20:32:44.2920603+04:00"},{"id":37875,"source_id":"cb29035725926be38d36ad8c01792b7e","target_id":"e4313416f9a67f60a9a17f977b2367f6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 86-86","gmt_create":"2026-04-28T20:32:44.2925879+04:00","gmt_modified":"2026-04-28T20:32:44.2925879+04:00"},{"id":37877,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b0498790fa704db3f0e0545f4299a7ba","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 79-82","gmt_create":"2026-04-28T20:32:44.2935951+04:00","gmt_modified":"2026-04-28T20:32:44.2935951+04:00"},{"id":37879,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"34cf64d4d37824f3883c4c577708cd70","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3278-3281","gmt_create":"2026-04-28T20:32:44.3000122+04:00","gmt_modified":"2026-04-28T20:32:44.3000122+04:00"},{"id":37881,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"9619a9ec94b261a592033e0c0b3be117","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3633-3636","gmt_create":"2026-04-28T20:32:44.3010127+04:00","gmt_modified":"2026-04-28T20:32:44.3010127+04:00"},{"id":37883,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"99d4fcb1177774e0710d2bd69e6b48d3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3653-3656","gmt_create":"2026-04-28T20:32:44.3010127+04:00","gmt_modified":"2026-04-28T20:32:44.3010127+04:00"},{"id":37885,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"f652bd37bb37781168ae6561fb03f9b4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3671-3674","gmt_create":"2026-04-28T20:32:44.3020116+04:00","gmt_modified":"2026-04-28T20:32:44.3020116+04:00"},{"id":37937,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"c6554da5c3030abf996fb5a45f613846","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3874-3909","gmt_create":"2026-04-28T20:33:17.151438+04:00","gmt_modified":"2026-04-28T20:33:17.151438+04:00"},{"id":37939,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"bb2f9b88565dffaafac5243508216aad","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3883","gmt_create":"2026-04-28T20:33:17.1524413+04:00","gmt_modified":"2026-04-28T20:33:17.1524413+04:00"},{"id":37941,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"2b7b8d880d43c3e7cdf61f5c181897fa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3895","gmt_create":"2026-04-28T20:33:17.1524413+04:00","gmt_modified":"2026-04-28T20:33:17.1524413+04:00"},{"id":37943,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"49a1105e8b0a1091759ed0dd8456fc18","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3717-3739","gmt_create":"2026-04-28T20:33:17.1534413+04:00","gmt_modified":"2026-04-28T20:33:17.1534413+04:00"},{"id":37945,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"82fada5a3bea92890319c59908b5d6af","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 595-603","gmt_create":"2026-04-28T20:33:17.1544449+04:00","gmt_modified":"2026-04-28T20:33:17.1544449+04:00"},{"id":37947,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"9f97ef4b79b69efdb12cdc50f9ef0041","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 595-598","gmt_create":"2026-04-28T20:33:17.1544449+04:00","gmt_modified":"2026-04-28T20:33:17.1544449+04:00"},{"id":37949,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"370a3f3d23b4e8b36669b9d077f388d6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5493-5498","gmt_create":"2026-04-28T20:33:17.155442+04:00","gmt_modified":"2026-04-28T20:33:17.155442+04:00"},{"id":37951,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"81b23e3b2e8218a8eb6600e8f2e88066","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 601-602","gmt_create":"2026-04-28T20:33:17.155442+04:00","gmt_modified":"2026-04-28T20:33:17.155442+04:00"},{"id":37953,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"9a4c9be971e9ec84202ea73657bbdd6e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3523-3533","gmt_create":"2026-04-28T20:33:17.1564433+04:00","gmt_modified":"2026-04-28T20:33:17.1564433+04:00"},{"id":37955,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"abeeffae57c62e28daad08e42b7697e9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3892-3895","gmt_create":"2026-04-28T20:33:17.1564433+04:00","gmt_modified":"2026-04-28T20:33:17.1564433+04:00"},{"id":37963,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"a2e60c6967300f8a131741ffe433fb3c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3885-3895","gmt_create":"2026-04-28T20:33:17.1588804+04:00","gmt_modified":"2026-04-28T20:33:17.1588804+04:00"},{"id":38115,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"1fc0915e82dd4c943724dcd2e29106cb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 159-175","gmt_create":"2026-04-28T20:38:58.1779414+04:00","gmt_modified":"2026-04-28T20:38:58.1779414+04:00"},{"id":38124,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"b89fc00b26b800a29e1692b23ada0d56","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 168-172","gmt_create":"2026-04-28T20:38:58.1805172+04:00","gmt_modified":"2026-04-28T20:38:58.1805172+04:00"},{"id":38215,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"4c7480a472e5a837a77edfce9accd782","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1360-1380","gmt_create":"2026-04-28T20:42:58.7586084+04:00","gmt_modified":"2026-04-28T20:42:58.7586084+04:00"},{"id":38369,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"509be78f5e90ad720bc5cb62f8252773","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1204-1270","gmt_create":"2026-04-28T21:02:01.9680597+04:00","gmt_modified":"2026-04-28T21:02:01.9680597+04:00"},{"id":38371,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"2f3627b50fb7b7e496ab7f3d82bb011c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 521-544","gmt_create":"2026-04-28T21:02:01.9680597+04:00","gmt_modified":"2026-04-28T21:02:01.9680597+04:00"},{"id":38375,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"2426f410a167c0334267f66a00c3b706","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-200","gmt_create":"2026-04-28T21:02:01.9690597+04:00","gmt_modified":"2026-04-28T21:02:01.9690597+04:00"},{"id":38380,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"13ad0bfb014506790d3eea0ac1e240eb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-697","gmt_create":"2026-04-28T21:02:01.9813722+04:00","gmt_modified":"2026-04-28T21:02:01.9813722+04:00"},{"id":38382,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"adbfa85db79875fa5ba5450da427c626","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 110-124","gmt_create":"2026-04-28T21:02:01.9813722+04:00","gmt_modified":"2026-04-28T21:02:01.9813722+04:00"},{"id":38384,"source_id":"8ede002b6c76d0a07d75e34f812e8305","target_id":"9f5944a7feb01c0c201e4a490c1c6d47","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-7","gmt_create":"2026-04-28T21:02:01.9818898+04:00","gmt_modified":"2026-04-28T21:02:01.9818898+04:00"},{"id":38387,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"3697c4125ef62ecb76b9032b46a67ad2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 33-92","gmt_create":"2026-04-28T21:02:01.9824071+04:00","gmt_modified":"2026-04-28T21:02:01.9824071+04:00"},{"id":38392,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"3ae8431619379889035afee8a7f98a96","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1300-1399","gmt_create":"2026-04-28T21:02:01.9834512+04:00","gmt_modified":"2026-04-28T21:02:01.9834512+04:00"},{"id":38394,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"4dbf49507663ef2d477a4e56e7ad4113","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 48-84","gmt_create":"2026-04-28T21:02:01.9834512+04:00","gmt_modified":"2026-04-28T21:02:01.9834512+04:00"},{"id":38396,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"a0f13c35d9ba18928ea0803560d59338","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 48-55","gmt_create":"2026-04-28T21:02:01.9834512+04:00","gmt_modified":"2026-04-28T21:02:01.9834512+04:00"},{"id":38398,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"b241a3148f38f58dc134c0db61057fbe","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 20-144","gmt_create":"2026-04-28T21:02:01.9845602+04:00","gmt_modified":"2026-04-28T21:02:01.9845602+04:00"},{"id":38400,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"78b7d2f924e066f18e5bf4ed42de6954","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 33-278","gmt_create":"2026-04-28T21:02:01.9853589+04:00","gmt_modified":"2026-04-28T21:02:01.9853589+04:00"},{"id":38402,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"4166755ed54cfd6a56e5d4acb8144b2e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 111-144","gmt_create":"2026-04-28T21:02:01.9859735+04:00","gmt_modified":"2026-04-28T21:02:01.9859735+04:00"},{"id":38405,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"f32c5cb0846168684a2574ccb17d2cd7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 189-231","gmt_create":"2026-04-28T21:02:01.9865516+04:00","gmt_modified":"2026-04-28T21:02:01.9865516+04:00"},{"id":38407,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"7066405016bb2acea5a040cdac13b646","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1037-1177","gmt_create":"2026-04-28T21:02:01.9871318+04:00","gmt_modified":"2026-04-28T21:02:01.9871318+04:00"},{"id":38409,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1c914e70d8a846e0ed416af007bd2b78","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 259-294","gmt_create":"2026-04-28T21:02:01.9877186+04:00","gmt_modified":"2026-04-28T21:02:01.9877186+04:00"},{"id":38412,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d2b082cb10acf9968b8eb975d61abc92","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4444-4533","gmt_create":"2026-04-28T21:02:01.9889113+04:00","gmt_modified":"2026-04-28T21:02:01.9889113+04:00"},{"id":38415,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"da4792d808b920542e8e3f85c533f97d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 118-164","gmt_create":"2026-04-28T21:02:01.9894139+04:00","gmt_modified":"2026-04-28T21:02:01.9894139+04:00"},{"id":38417,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"fb711bedba071dd61482af3dd825bb98","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 115-128","gmt_create":"2026-04-28T21:02:01.9904171+04:00","gmt_modified":"2026-04-28T21:02:01.9904171+04:00"},{"id":38419,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"83d7c3cd276c8b6c3a77f6379e8968e4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 561-580","gmt_create":"2026-04-28T21:02:01.9904171+04:00","gmt_modified":"2026-04-28T21:02:01.9904171+04:00"},{"id":38421,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"420ba4d931c9392dd8824c0faaf3b99b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 738-792","gmt_create":"2026-04-28T21:02:01.9904171+04:00","gmt_modified":"2026-04-28T21:02:01.9904171+04:00"},{"id":38423,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"fc699eb749d790d44345a92dd356956e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 206-230","gmt_create":"2026-04-28T21:02:01.9917637+04:00","gmt_modified":"2026-04-28T21:02:01.9917637+04:00"},{"id":38425,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"217f231a3e1f1591acfb14f4b40fb2b3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 476-515","gmt_create":"2026-04-28T21:02:01.9922668+04:00","gmt_modified":"2026-04-28T21:02:01.9922668+04:00"},{"id":38427,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"b25bd7f79844225b0a1356e5a6dbee8b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 92-103","gmt_create":"2026-04-28T21:02:01.9937452+04:00","gmt_modified":"2026-04-28T21:02:01.9937452+04:00"},{"id":38429,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"447323529866a4410bcb34a227ea5b5a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1075-1087","gmt_create":"2026-04-28T21:02:01.9942487+04:00","gmt_modified":"2026-04-28T21:02:01.9942487+04:00"},{"id":38431,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d59a4116db64f54b5d2ce9c64e1c2c50","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4581-4594","gmt_create":"2026-04-28T21:02:01.9942487+04:00","gmt_modified":"2026-04-28T21:02:01.9942487+04:00"},{"id":38434,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"aa98d4cf2cd33ba5c1e2a0fee44c3645","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 597-612","gmt_create":"2026-04-28T21:02:01.9952524+04:00","gmt_modified":"2026-04-28T21:02:01.9952524+04:00"},{"id":38436,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a8acea32ccb60d16cf2952aa7df51926","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4334-4438","gmt_create":"2026-04-28T21:02:01.9962521+04:00","gmt_modified":"2026-04-28T21:02:01.9962521+04:00"},{"id":38438,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"cda8d76def01f28cd58e347939faf10a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4420-4438","gmt_create":"2026-04-28T21:02:01.9962521+04:00","gmt_modified":"2026-04-28T21:02:01.9962521+04:00"},{"id":38440,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"119ec7c643a34aa808f9d5b76965a662","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4444-4450","gmt_create":"2026-04-28T21:02:01.9962521+04:00","gmt_modified":"2026-04-28T21:02:01.9962521+04:00"},{"id":38443,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"9d352185df5c8af06a8101e30a248ef8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4360-4398","gmt_create":"2026-04-28T21:02:01.997252+04:00","gmt_modified":"2026-04-28T21:02:01.997252+04:00"},{"id":38445,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"315cdb5412a417207b9932317c120c97","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4400-4419","gmt_create":"2026-04-28T21:02:01.997252+04:00","gmt_modified":"2026-04-28T21:02:01.997252+04:00"},{"id":38447,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"450f29f1b3febed1e121639037851576","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 521-526","gmt_create":"2026-04-28T21:02:01.9982523+04:00","gmt_modified":"2026-04-28T21:02:01.9982523+04:00"},{"id":38449,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"17c7b337a56de7a5cd94cd10c3d44dac","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4428-4430","gmt_create":"2026-04-28T21:02:01.9992273+04:00","gmt_modified":"2026-04-28T21:02:01.9992273+04:00"},{"id":38451,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"48593fdf91cfb9e4763322dc0efe26f4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 565-656","gmt_create":"2026-04-28T21:02:01.9997631+04:00","gmt_modified":"2026-04-28T21:02:01.9997631+04:00"},{"id":38453,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"b71278e4b4266bb8a15b6aba152b2962","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 121","gmt_create":"2026-04-28T21:02:02.0004254+04:00","gmt_modified":"2026-04-28T21:02:02.0004254+04:00"},{"id":38455,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"065d68f5f096b48e88dfb2982a30bcb6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 114-146","gmt_create":"2026-04-28T21:02:02.0010139+04:00","gmt_modified":"2026-04-28T21:02:02.0010139+04:00"},{"id":38458,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"c2ea6047c89205fb8308a32ce5248eee","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 48-103","gmt_create":"2026-04-28T21:02:02.0044444+04:00","gmt_modified":"2026-04-28T21:02:02.0044444+04:00"},{"id":38460,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"811ab9cb3d00924a080202c009434d6e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1254-1298","gmt_create":"2026-04-28T21:02:02.0061844+04:00","gmt_modified":"2026-04-28T21:02:02.0061844+04:00"},{"id":38462,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"33f7190f97ab910175bbc25c0d3288cc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-46","gmt_create":"2026-04-28T21:02:02.0078591+04:00","gmt_modified":"2026-04-28T21:02:02.0078591+04:00"},{"id":38464,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"536ce91c592f598c4853f295a9a28e59","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 59-75","gmt_create":"2026-04-28T21:02:02.0084982+04:00","gmt_modified":"2026-04-28T21:02:02.0084982+04:00"},{"id":38466,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"49ed36643f75c3d722e6b1a2ff17d7e0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1390-1465","gmt_create":"2026-04-28T21:02:02.0090723+04:00","gmt_modified":"2026-04-28T21:02:02.0090723+04:00"},{"id":38468,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"58d38a217f74656ded3587bd3c6f7dc1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 614-646","gmt_create":"2026-04-28T21:02:02.0097348+04:00","gmt_modified":"2026-04-28T21:02:02.0097348+04:00"},{"id":38469,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"4238a9561f85e50a38f76813baeadd7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/block_log.hpp","gmt_create":"2026-04-28T21:03:48.5527991+04:00","gmt_modified":"2026-04-28T21:03:48.5527991+04:00"},{"id":38470,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"d2090ff9016be0d896d06e843936e0f4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/block_log.cpp","gmt_create":"2026-04-28T21:03:48.5533394+04:00","gmt_modified":"2026-04-28T21:03:48.5533394+04:00"},{"id":38471,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"01456fc1d03088da2d9080a7ba380f5f","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/block_summary_object.hpp","gmt_create":"2026-04-28T21:03:48.5533394+04:00","gmt_modified":"2026-04-28T21:03:48.5533394+04:00"},{"id":38472,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-28T21:03:48.5539209+04:00","gmt_modified":"2026-04-28T21:03:48.5539209+04:00"},{"id":38473,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-28T21:03:48.5539209+04:00","gmt_modified":"2026-04-28T21:03:48.5539209+04:00"},{"id":38474,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-28T21:03:48.5539209+04:00","gmt_modified":"2026-04-28T21:03:48.5539209+04:00"},{"id":38475,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-28T21:03:48.5545273+04:00","gmt_modified":"2026-04-28T21:03:48.5545273+04:00"},{"id":38476,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-28T21:03:48.5545273+04:00","gmt_modified":"2026-04-28T21:03:48.5545273+04:00"},{"id":38477,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"6c94b84fdfd5c7016b5eeadf8099133e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/plugin.cpp","gmt_create":"2026-04-28T21:03:48.5545273+04:00","gmt_modified":"2026-04-28T21:03:48.5545273+04:00"},{"id":38478,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-28T21:03:48.5551092+04:00","gmt_modified":"2026-04-28T21:03:48.5551092+04:00"},{"id":38479,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"73975f89dd3f307861db3a250bbdd78c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#36-200","gmt_create":"2026-04-28T21:03:48.5551092+04:00","gmt_modified":"2026-04-28T21:03:48.5551092+04:00"},{"id":38480,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"73975f89dd3f307861db3a250bbdd78c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 36-200","gmt_create":"2026-04-28T21:03:48.5556117+04:00","gmt_modified":"2026-04-28T21:03:48.5556117+04:00"},{"id":38481,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"7624493910e6464a6ff493726bdeee9d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#53-122","gmt_create":"2026-04-28T21:03:48.5556905+04:00","gmt_modified":"2026-04-28T21:03:48.5556905+04:00"},{"id":38482,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"7624493910e6464a6ff493726bdeee9d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 53-122","gmt_create":"2026-04-28T21:03:48.5556905+04:00","gmt_modified":"2026-04-28T21:03:48.5556905+04:00"},{"id":38483,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"945c89d3db9c4818f2b0a69c5686f53c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#38-71","gmt_create":"2026-04-28T21:03:48.5562744+04:00","gmt_modified":"2026-04-28T21:03:48.5562744+04:00"},{"id":38484,"source_id":"4238a9561f85e50a38f76813baeadd7e","target_id":"945c89d3db9c4818f2b0a69c5686f53c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 38-71","gmt_create":"2026-04-28T21:03:48.5562744+04:00","gmt_modified":"2026-04-28T21:03:48.5562744+04:00"},{"id":38485,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"6e1a4f8dfd8b1b6545342d2b70d91400","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_summary_object.hpp#19-42","gmt_create":"2026-04-28T21:03:48.5567771+04:00","gmt_modified":"2026-04-28T21:03:48.5567771+04:00"},{"id":38486,"source_id":"01456fc1d03088da2d9080a7ba380f5f","target_id":"6e1a4f8dfd8b1b6545342d2b70d91400","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 19-42","gmt_create":"2026-04-28T21:03:48.556929+04:00","gmt_modified":"2026-04-28T21:03:48.556929+04:00"},{"id":38487,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"8fef9b15033051e7fdc3504d87a3d365","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3354-3366","gmt_create":"2026-04-28T21:03:48.556929+04:00","gmt_modified":"2026-04-28T21:03:48.556929+04:00"},{"id":38488,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"8fef9b15033051e7fdc3504d87a3d365","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3354-3366","gmt_create":"2026-04-28T21:03:48.5574316+04:00","gmt_modified":"2026-04-28T21:03:48.5574316+04:00"},{"id":38489,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"eb846b95ed45c45ecbc3ee5ab1ebd24f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#152-157","gmt_create":"2026-04-28T21:03:48.5574316+04:00","gmt_modified":"2026-04-28T21:03:48.5574316+04:00"},{"id":38490,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"eb846b95ed45c45ecbc3ee5ab1ebd24f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 152-157","gmt_create":"2026-04-28T21:03:48.5574316+04:00","gmt_modified":"2026-04-28T21:03:48.5574316+04:00"},{"id":38491,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"5b3217d2dd702e5291fda9a14af9defe","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#104-121","gmt_create":"2026-04-28T21:03:48.5574316+04:00","gmt_modified":"2026-04-28T21:03:48.5574316+04:00"},{"id":38492,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"5b3217d2dd702e5291fda9a14af9defe","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 104-121","gmt_create":"2026-04-28T21:03:48.5574316+04:00","gmt_modified":"2026-04-28T21:03:48.5574316+04:00"},{"id":38493,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"f8faa71211c346e22bdea2dbbd1cc994","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#134-193","gmt_create":"2026-04-28T21:03:48.5670475+04:00","gmt_modified":"2026-04-28T21:03:48.5670475+04:00"},{"id":38494,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"e29d6a6310d6247eaf38fa2c35b35373","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#33-90","gmt_create":"2026-04-28T21:03:48.5684052+04:00","gmt_modified":"2026-04-28T21:03:48.5684052+04:00"},{"id":38495,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"b18325dfe4c9f546850aa53aae935765","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#193-196","gmt_create":"2026-04-28T21:03:48.5684052+04:00","gmt_modified":"2026-04-28T21:03:48.5684052+04:00"},{"id":38496,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"b18325dfe4c9f546850aa53aae935765","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 193-196","gmt_create":"2026-04-28T21:03:48.5684052+04:00","gmt_modified":"2026-04-28T21:03:48.5684052+04:00"},{"id":38497,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"7f585b7ee9cce4a8ab1f33b5b34f7f81","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#737-792","gmt_create":"2026-04-28T21:03:48.5689197+04:00","gmt_modified":"2026-04-28T21:03:48.5689197+04:00"},{"id":38498,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"7f585b7ee9cce4a8ab1f33b5b34f7f81","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 737-792","gmt_create":"2026-04-28T21:03:48.5689197+04:00","gmt_modified":"2026-04-28T21:03:48.5689197+04:00"},{"id":38499,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"a2dca3272c082a5aafb990a745f3fd75","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#142-174","gmt_create":"2026-04-28T21:03:48.5689197+04:00","gmt_modified":"2026-04-28T21:03:48.5689197+04:00"},{"id":38500,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"a2dca3272c082a5aafb990a745f3fd75","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 142-174","gmt_create":"2026-04-28T21:03:48.5694321+04:00","gmt_modified":"2026-04-28T21:03:48.5694321+04:00"},{"id":38501,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"5256db9d4dcb3c7fd627ebb8f8547f74","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#103-142","gmt_create":"2026-04-28T21:03:48.5694321+04:00","gmt_modified":"2026-04-28T21:03:48.5694321+04:00"},{"id":38502,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"5256db9d4dcb3c7fd627ebb8f8547f74","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 103-142","gmt_create":"2026-04-28T21:03:48.5694321+04:00","gmt_modified":"2026-04-28T21:03:48.5694321+04:00"},{"id":38503,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"6e54a9c0d543d98384d205535ead2c39","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#800-925","gmt_create":"2026-04-28T21:03:48.5694321+04:00","gmt_modified":"2026-04-28T21:03:48.5694321+04:00"},{"id":38504,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"6e54a9c0d543d98384d205535ead2c39","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 800-925","gmt_create":"2026-04-28T21:03:48.5694321+04:00","gmt_modified":"2026-04-28T21:03:48.5694321+04:00"},{"id":38505,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"0b84b82025c87f3b725ddf0c3804f197","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#253-257","gmt_create":"2026-04-28T21:03:48.5699406+04:00","gmt_modified":"2026-04-28T21:03:48.5699406+04:00"},{"id":38506,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"d2efa60ce77e110221f81af1427289e6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#195-226","gmt_create":"2026-04-28T21:03:48.5699406+04:00","gmt_modified":"2026-04-28T21:03:48.5699406+04:00"},{"id":38507,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"d2efa60ce77e110221f81af1427289e6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 195-226","gmt_create":"2026-04-28T21:03:48.5704486+04:00","gmt_modified":"2026-04-28T21:03:48.5704486+04:00"},{"id":38508,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"fdfd8e0c768f197ad065fbd7ffc0d0c1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#263-299","gmt_create":"2026-04-28T21:03:48.5704486+04:00","gmt_modified":"2026-04-28T21:03:48.5704486+04:00"},{"id":38509,"source_id":"d2090ff9016be0d896d06e843936e0f4","target_id":"fdfd8e0c768f197ad065fbd7ffc0d0c1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 263-299","gmt_create":"2026-04-28T21:03:48.5704486+04:00","gmt_modified":"2026-04-28T21:03:48.5704486+04:00"},{"id":38510,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"5573208de1a8f72a275c35db65fc7bd8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#847-925","gmt_create":"2026-04-28T21:03:48.5720571+04:00","gmt_modified":"2026-04-28T21:03:48.5720571+04:00"},{"id":38511,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5573208de1a8f72a275c35db65fc7bd8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 847-925","gmt_create":"2026-04-28T21:03:48.5720571+04:00","gmt_modified":"2026-04-28T21:03:48.5720571+04:00"},{"id":38512,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"2f1395bd486711cb5d7bc0cd80794c96","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3443-3500","gmt_create":"2026-04-28T21:03:48.5720571+04:00","gmt_modified":"2026-04-28T21:03:48.5720571+04:00"},{"id":38513,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2f1395bd486711cb5d7bc0cd80794c96","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3443-3500","gmt_create":"2026-04-28T21:03:48.5720571+04:00","gmt_modified":"2026-04-28T21:03:48.5720571+04:00"},{"id":38514,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"1c4d9b7ff7c2fd8f72e2d64d37f89138","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3723-3748","gmt_create":"2026-04-28T21:03:48.5720571+04:00","gmt_modified":"2026-04-28T21:03:48.5720571+04:00"},{"id":38515,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1c4d9b7ff7c2fd8f72e2d64d37f89138","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3723-3748","gmt_create":"2026-04-28T21:03:48.572559+04:00","gmt_modified":"2026-04-28T21:03:48.572559+04:00"},{"id":38516,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"a5660f23ac4e94b12f678a1bbc729490","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3750-3757","gmt_create":"2026-04-28T21:03:48.572559+04:00","gmt_modified":"2026-04-28T21:03:48.572559+04:00"},{"id":38517,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a5660f23ac4e94b12f678a1bbc729490","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3750-3757","gmt_create":"2026-04-28T21:03:48.572559+04:00","gmt_modified":"2026-04-28T21:03:48.572559+04:00"},{"id":38518,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"54e518a8354b5d7fb8af8a31a68d3a41","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3759-3873","gmt_create":"2026-04-28T21:03:48.572559+04:00","gmt_modified":"2026-04-28T21:03:48.572559+04:00"},{"id":38519,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"54e518a8354b5d7fb8af8a31a68d3a41","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3759-3873","gmt_create":"2026-04-28T21:03:48.572559+04:00","gmt_modified":"2026-04-28T21:03:48.572559+04:00"},{"id":38520,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"6e4547d3d8a2b1ce7fb2eb8442ba9631","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1294-1311","gmt_create":"2026-04-28T21:03:48.573074+04:00","gmt_modified":"2026-04-28T21:03:48.573074+04:00"},{"id":38521,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"bbb78af6252229f84570f752f6e862a7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2824-2837","gmt_create":"2026-04-28T21:03:48.573074+04:00","gmt_modified":"2026-04-28T21:03:48.573074+04:00"},{"id":38522,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"bbb78af6252229f84570f752f6e862a7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2824-2837","gmt_create":"2026-04-28T21:03:48.5736068+04:00","gmt_modified":"2026-04-28T21:03:48.5736068+04:00"},{"id":38523,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"a18ffbd92c9d4b692fc557c3652088aa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2871-2884","gmt_create":"2026-04-28T21:03:48.5736068+04:00","gmt_modified":"2026-04-28T21:03:48.5736068+04:00"},{"id":38524,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a18ffbd92c9d4b692fc557c3652088aa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2871-2884","gmt_create":"2026-04-28T21:03:48.5736068+04:00","gmt_modified":"2026-04-28T21:03:48.5736068+04:00"},{"id":38525,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"fce8b0371886adfbccd297ca25bedd14","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#185-187","gmt_create":"2026-04-28T21:03:48.5752824+04:00","gmt_modified":"2026-04-28T21:03:48.5752824+04:00"},{"id":38526,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"fce8b0371886adfbccd297ca25bedd14","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 185-187","gmt_create":"2026-04-28T21:03:48.5752824+04:00","gmt_modified":"2026-04-28T21:03:48.5752824+04:00"},{"id":38527,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"6f9d8d9e0dc35b4525248327639277c9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3724-3748","gmt_create":"2026-04-28T21:03:48.5757852+04:00","gmt_modified":"2026-04-28T21:03:48.5757852+04:00"},{"id":38528,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"6f9d8d9e0dc35b4525248327639277c9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3724-3748","gmt_create":"2026-04-28T21:03:48.5757852+04:00","gmt_modified":"2026-04-28T21:03:48.5757852+04:00"},{"id":38529,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"601b79126e6723e1b2405eb7e5f8be93","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#270-300","gmt_create":"2026-04-28T21:03:48.5767884+04:00","gmt_modified":"2026-04-28T21:03:48.5767884+04:00"},{"id":38530,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"601b79126e6723e1b2405eb7e5f8be93","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 270-300","gmt_create":"2026-04-28T21:03:48.5767884+04:00","gmt_modified":"2026-04-28T21:03:48.5767884+04:00"},{"id":38531,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"5f0e48d3f7fad098a67eed7a88dc6134","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#250-257","gmt_create":"2026-04-28T21:03:48.5767884+04:00","gmt_modified":"2026-04-28T21:03:48.5767884+04:00"},{"id":38532,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5f0e48d3f7fad098a67eed7a88dc6134","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 250-257","gmt_create":"2026-04-28T21:03:48.5767884+04:00","gmt_modified":"2026-04-28T21:03:48.5767884+04:00"},{"id":38533,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"8df3a4b9e7bead2487cc1ffc6cd49cae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#16-19","gmt_create":"2026-04-28T21:03:48.5789025+04:00","gmt_modified":"2026-04-28T21:03:48.5789025+04:00"},{"id":38534,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"a0d3e919d8c2dcef090c5eb962daae1a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#3-8","gmt_create":"2026-04-28T21:03:48.5789025+04:00","gmt_modified":"2026-04-28T21:03:48.5789025+04:00"},{"id":38535,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"a0d3e919d8c2dcef090c5eb962daae1a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3-8","gmt_create":"2026-04-28T21:03:48.5789025+04:00","gmt_modified":"2026-04-28T21:03:48.5789025+04:00"},{"id":38536,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"54b31fbe33ef1eb12566c9f84fcf779d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#3-18","gmt_create":"2026-04-28T21:03:48.5794808+04:00","gmt_modified":"2026-04-28T21:03:48.5794808+04:00"},{"id":38537,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"54b31fbe33ef1eb12566c9f84fcf779d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3-18","gmt_create":"2026-04-28T21:03:48.5794808+04:00","gmt_modified":"2026-04-28T21:03:48.5794808+04:00"},{"id":38538,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"3503ce5765018277af90477a590846e3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#3-9","gmt_create":"2026-04-28T21:03:48.5794808+04:00","gmt_modified":"2026-04-28T21:03:48.5794808+04:00"},{"id":38539,"source_id":"4238a9561f85e50a38f76813baeadd7e","target_id":"3503ce5765018277af90477a590846e3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3-9","gmt_create":"2026-04-28T21:03:48.5801244+04:00","gmt_modified":"2026-04-28T21:03:48.5801244+04:00"},{"id":38540,"source_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","target_id":"24fdd3fcf0b1451195e987e21e994f65","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#804-823","gmt_create":"2026-04-28T21:03:48.5818075+04:00","gmt_modified":"2026-04-28T21:03:48.5818075+04:00"},{"id":38541,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"24fdd3fcf0b1451195e987e21e994f65","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 804-823","gmt_create":"2026-04-28T21:03:48.582811+04:00","gmt_modified":"2026-04-28T21:03:48.582811+04:00"},{"id":38563,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"01108e5a8e624eede7c7c1ba5eabf3ed","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6643","gmt_create":"2026-04-28T21:05:29.6024784+04:00","gmt_modified":"2026-04-28T21:05:29.6024784+04:00"},{"id":38656,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"868816de-43fc-4fe0-9cf9-0e89f661447c","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e 868816de-43fc-4fe0-9cf9-0e89f661447c","gmt_create":"2026-04-28T21:05:34.7784809+04:00","gmt_modified":"2026-04-28T21:05:34.7784809+04:00"},{"id":38659,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"f7dedf31e491c7adbaf05e957360c531","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-28T21:31:00.4452984+04:00","gmt_modified":"2026-04-28T21:31:00.4452984+04:00"},{"id":38660,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38661,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"d72b348a2c3c7943e4a7abb7dbdaa751","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_connection.hpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38662,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/peer_connection.cpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38663,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"3a6c30f4bb3b265155c881ccfafa980e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/core_messages.hpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38664,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"2c99501f0c511d0792a2e5a56e4debfa","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/core_messages.cpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38665,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"398d9d4b02b6383c0b752cb0196a0475","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/stcp_socket.hpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38666,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"01e57c63d684a56829f909c03b8ea162","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/stcp_socket.cpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38667,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"3a8d8a10556a0b6501e25aa43e91f913","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_database.hpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38668,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"c70caf63ce77b078dcf89380b228a71a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/peer_database.cpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38669,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b4467ca30cb6f6d587fc200900ee9ec9","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message.hpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38670,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"6e587b97bf4080c7754c5ed73736fca7","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message_oriented_connection.hpp","gmt_create":"2026-04-28T21:31:00.4462984+04:00","gmt_modified":"2026-04-28T21:31:00.4462984+04:00"},{"id":38671,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"c82262dc5275e094efc9474032d15922","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/config.hpp","gmt_create":"2026-04-28T21:31:00.4478031+04:00","gmt_modified":"2026-04-28T21:31:00.4478031+04:00"},{"id":38672,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-28T21:31:00.4479719+04:00","gmt_modified":"2026-04-28T21:31:00.4479719+04:00"},{"id":38673,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"930f47bc2c95f0cac4086eeca5749e2c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#190-304","gmt_create":"2026-04-28T21:31:00.4479719+04:00","gmt_modified":"2026-04-28T21:31:00.4479719+04:00"},{"id":38674,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"9f8690911ef66c966743475f294ffb7e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#79-351","gmt_create":"2026-04-28T21:31:00.4479719+04:00","gmt_modified":"2026-04-28T21:31:00.4479719+04:00"},{"id":38675,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"a480bfeb1604076e7d1db7746687a3b4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#42-106","gmt_create":"2026-04-28T21:31:00.4479719+04:00","gmt_modified":"2026-04-28T21:31:00.4479719+04:00"},{"id":38676,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"c54e54629fd9cd255341b311da9b6be1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#72-573","gmt_create":"2026-04-28T21:31:00.4479719+04:00","gmt_modified":"2026-04-28T21:31:00.4479719+04:00"},{"id":38677,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"4e8bc2cdd3d4fc68b2f7138c54288174","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/stcp_socket.hpp#37-93","gmt_create":"2026-04-28T21:31:00.4488068+04:00","gmt_modified":"2026-04-28T21:31:00.4488068+04:00"},{"id":38678,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"7b85de17d00c3c33f3e6ce72493cea24","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#104-134","gmt_create":"2026-04-28T21:31:00.4488068+04:00","gmt_modified":"2026-04-28T21:31:00.4488068+04:00"},{"id":38679,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"ff11a77da594972e0b8a78df4289ada5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message_oriented_connection.hpp#45-79","gmt_create":"2026-04-28T21:31:00.4488068+04:00","gmt_modified":"2026-04-28T21:31:00.4488068+04:00"},{"id":38680,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"d614fc51360bb61b8f521167a6db1e11","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/config.hpp#26-106","gmt_create":"2026-04-28T21:31:00.4488068+04:00","gmt_modified":"2026-04-28T21:31:00.4488068+04:00"},{"id":38681,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"411a87e4e085e019827e9bd8085b8328","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#500-560","gmt_create":"2026-04-28T21:31:00.4488068+04:00","gmt_modified":"2026-04-28T21:31:00.4488068+04:00"},{"id":38682,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"6a050f797e4d75bb2867072fae629c2a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5281-5286","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38683,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"448218400a050ac2d3a0f1ee290faf50","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#346-347","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38684,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"34550b8d6e01a13e99a0ed70072db86b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#1-355","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38685,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"9847129d410beb4256be18f2e7489d18","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#1-383","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38686,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"a3e6f8b4e51c838c44eaef16bb205031","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#1-573","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38687,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"53a676bf1df5198ba31b6ebe848a29bd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/stcp_socket.hpp#1-99","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38688,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"f14b0ce0c1ba142fb55271fdc6c2ee9f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#1-141","gmt_create":"2026-04-28T21:31:00.4498081+04:00","gmt_modified":"2026-04-28T21:31:00.4498081+04:00"},{"id":38689,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"f59f370133646ba0c40ebdd11ee6d697","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#1-114","gmt_create":"2026-04-28T21:31:00.4508077+04:00","gmt_modified":"2026-04-28T21:31:00.4508077+04:00"},{"id":38690,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"762c4b81ed454ad789c0b3b3cb00cc88","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message_oriented_connection.hpp#1-85","gmt_create":"2026-04-28T21:31:00.4508077+04:00","gmt_modified":"2026-04-28T21:31:00.4508077+04:00"},{"id":38691,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"bc9b507990af077f95d7bbe7203d51f0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/config.hpp#1-106","gmt_create":"2026-04-28T21:31:00.4508077+04:00","gmt_modified":"2026-04-28T21:31:00.4508077+04:00"},{"id":38692,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b54da9d3dceb59e39a6cd88a5a99b593","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#1-742","gmt_create":"2026-04-28T21:31:00.4508077+04:00","gmt_modified":"2026-04-28T21:31:00.4508077+04:00"},{"id":38693,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"54ba2e42561f4bf9913449b2a78838bb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#182-304","gmt_create":"2026-04-28T21:31:00.4508077+04:00","gmt_modified":"2026-04-28T21:31:00.4508077+04:00"},{"id":38694,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"94c69e0f6c696d5856c9a52b4dbcda00","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#780-790","gmt_create":"2026-04-28T21:31:00.4518063+04:00","gmt_modified":"2026-04-28T21:31:00.4518063+04:00"},{"id":38695,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"d6f1f5c1ab7e33b9a558f648113330fb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#208-242","gmt_create":"2026-04-28T21:31:00.4518063+04:00","gmt_modified":"2026-04-28T21:31:00.4518063+04:00"},{"id":38696,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"db2f58e50720e139a95c598355919158","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#69-72","gmt_create":"2026-04-28T21:31:00.4518063+04:00","gmt_modified":"2026-04-28T21:31:00.4518063+04:00"},{"id":38697,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"a6b2b3362f68c7523a4e0d06a2a4352f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#424-799","gmt_create":"2026-04-28T21:31:00.4528077+04:00","gmt_modified":"2026-04-28T21:31:00.4528077+04:00"},{"id":38698,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"cd0a62c9a78bb77d3b59a9d5872577f4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#82-106","gmt_create":"2026-04-28T21:31:00.4528077+04:00","gmt_modified":"2026-04-28T21:31:00.4528077+04:00"},{"id":38699,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"20359a9271432a216b6faf9dc97fa5c6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#169-206","gmt_create":"2026-04-28T21:31:00.4528077+04:00","gmt_modified":"2026-04-28T21:31:00.4528077+04:00"},{"id":38700,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"fee43b750e5fa7d56e56ac6251e6b5bb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#41-66","gmt_create":"2026-04-28T21:31:00.4528077+04:00","gmt_modified":"2026-04-28T21:31:00.4528077+04:00"},{"id":38701,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"25c0bdec5e0fb6c8d410577e1478ea78","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#310-354","gmt_create":"2026-04-28T21:31:00.4538064+04:00","gmt_modified":"2026-04-28T21:31:00.4538064+04:00"},{"id":38702,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"e81b900c80dc9c266a93a66cc60486de","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/core_messages.cpp#30-49","gmt_create":"2026-04-28T21:31:00.4538064+04:00","gmt_modified":"2026-04-28T21:31:00.4538064+04:00"},{"id":38703,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"0b26d723a87937f5a3f58770c522d067","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#49-72","gmt_create":"2026-04-28T21:31:00.4538064+04:00","gmt_modified":"2026-04-28T21:31:00.4538064+04:00"},{"id":38704,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"d4b4e356cc64c1eac490f25138ad8337","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#132-177","gmt_create":"2026-04-28T21:31:00.4549933+04:00","gmt_modified":"2026-04-28T21:31:00.4549933+04:00"},{"id":38705,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b43af1b0b5dbbe32011b0ab877770800","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#49-177","gmt_create":"2026-04-28T21:31:00.4549933+04:00","gmt_modified":"2026-04-28T21:31:00.4549933+04:00"},{"id":38706,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"fd44e8ec8ed78651240bac55efcc1f20","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_database.cpp#41-82","gmt_create":"2026-04-28T21:31:00.4549933+04:00","gmt_modified":"2026-04-28T21:31:00.4549933+04:00"},{"id":38707,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"ee53a7a714337d597cc0a626fceec27f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_database.cpp#100-174","gmt_create":"2026-04-28T21:31:00.4549933+04:00","gmt_modified":"2026-04-28T21:31:00.4549933+04:00"},{"id":38708,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"aae2609ed41db2a866f07ce766cbb938","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#70-105","gmt_create":"2026-04-28T21:31:00.4559944+04:00","gmt_modified":"2026-04-28T21:31:00.4559944+04:00"},{"id":38709,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"023d67279bae7acc551e276826dc0ff1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3710-3723","gmt_create":"2026-04-28T21:31:00.4559944+04:00","gmt_modified":"2026-04-28T21:31:00.4559944+04:00"},{"id":38710,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b7e9099b5e14dfe6396c0a1c5c308eb7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#312-381","gmt_create":"2026-04-28T21:31:00.4559944+04:00","gmt_modified":"2026-04-28T21:31:00.4559944+04:00"},{"id":38711,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"26b49296ed2024fd0dde20e6f7e40b88","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#383-420","gmt_create":"2026-04-28T21:31:00.4559944+04:00","gmt_modified":"2026-04-28T21:31:00.4559944+04:00"},{"id":38712,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"41d27526c610abf125f7a7d008632585","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#173-179","gmt_create":"2026-04-28T21:31:00.4559944+04:00","gmt_modified":"2026-04-28T21:31:00.4559944+04:00"},{"id":38713,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"868fa39acf5aeee17dc7a8161740807d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#4920-4970","gmt_create":"2026-04-28T21:31:00.4559944+04:00","gmt_modified":"2026-04-28T21:31:00.4559944+04:00"},{"id":38714,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"9814b4d33f73344843377eb1e1b063d8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5128-5131","gmt_create":"2026-04-28T21:31:00.4569941+04:00","gmt_modified":"2026-04-28T21:31:00.4569941+04:00"},{"id":38715,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"380d27e76ccd3471d37efe9e980c19ae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#322-346","gmt_create":"2026-04-28T21:31:00.4569941+04:00","gmt_modified":"2026-04-28T21:31:00.4569941+04:00"},{"id":38716,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"569091d2a8631dcfd98eaa50ae2bfc44","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#428-448","gmt_create":"2026-04-28T21:31:00.4569941+04:00","gmt_modified":"2026-04-28T21:31:00.4569941+04:00"},{"id":38717,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"1ed9d91d6dd0223209ee65abf9542242","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#4900-4970","gmt_create":"2026-04-28T21:31:00.4569941+04:00","gmt_modified":"2026-04-28T21:31:00.4569941+04:00"},{"id":38718,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b8d925dd432a091aeeb11648caa2eb44","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#4164-4168","gmt_create":"2026-04-28T21:31:00.4579934+04:00","gmt_modified":"2026-04-28T21:31:00.4579934+04:00"},{"id":38719,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"bf71a71910c5567123121789e437c062","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#298-304","gmt_create":"2026-04-28T21:31:00.4579934+04:00","gmt_modified":"2026-04-28T21:31:00.4579934+04:00"},{"id":38720,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"690241d99c53c94e7cfd1435215c90f7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#616-618","gmt_create":"2026-04-28T21:31:00.4584972+04:00","gmt_modified":"2026-04-28T21:31:00.4584972+04:00"},{"id":38721,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"8df3a4b9e7bead2487cc1ffc6cd49cae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#16-19","gmt_create":"2026-04-28T21:31:00.4584972+04:00","gmt_modified":"2026-04-28T21:31:00.4584972+04:00"},{"id":38722,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"84d9f61aa92a7de46576a47f269a5dcd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#169-171","gmt_create":"2026-04-28T21:31:00.4584972+04:00","gmt_modified":"2026-04-28T21:31:00.4584972+04:00"},{"id":38723,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"0be17f1ab260a4b3bcead7596649bfc6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#81-81","gmt_create":"2026-04-28T21:31:00.4584972+04:00","gmt_modified":"2026-04-28T21:31:00.4584972+04:00"},{"id":38724,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"271449375aea639d1f03a23e291eaea3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1187-1194","gmt_create":"2026-04-28T21:31:00.4584972+04:00","gmt_modified":"2026-04-28T21:31:00.4584972+04:00"},{"id":38725,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"14c432cf8aa5e9ce797534c3d40b187f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1200-1202","gmt_create":"2026-04-28T21:31:00.4595008+04:00","gmt_modified":"2026-04-28T21:31:00.4595008+04:00"},{"id":38726,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"c63bedc7eec5da82c3e0783176a7a564","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2651-2663","gmt_create":"2026-04-28T21:31:00.4596137+04:00","gmt_modified":"2026-04-28T21:31:00.4596137+04:00"},{"id":38727,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"95b247631c8b269b2a98c7e385bac407","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2772-2779","gmt_create":"2026-04-28T21:31:00.4596137+04:00","gmt_modified":"2026-04-28T21:31:00.4596137+04:00"},{"id":38728,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"e5fedb1d60d7f5a29bddbc403ff3d857","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2790-2796","gmt_create":"2026-04-28T21:31:00.4596137+04:00","gmt_modified":"2026-04-28T21:31:00.4596137+04:00"},{"id":38729,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b30577f67d2d7eafe760daf3f3f386b0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#26-28","gmt_create":"2026-04-28T21:31:00.4596137+04:00","gmt_modified":"2026-04-28T21:31:00.4596137+04:00"},{"id":38730,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"8d12769b91a5f0a7c02fe172ecffd5ef","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#26-29","gmt_create":"2026-04-28T21:31:00.4596137+04:00","gmt_modified":"2026-04-28T21:31:00.4596137+04:00"},{"id":38731,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"b4c2646d470a9f4262288b71e7b10e89","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#26-28","gmt_create":"2026-04-28T21:31:00.4605058+04:00","gmt_modified":"2026-04-28T21:31:00.4605058+04:00"},{"id":38732,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"055bd4ac99dff0f7dad3d2d929f83b6a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/stcp_socket.hpp#26-28","gmt_create":"2026-04-28T21:31:00.4605058+04:00","gmt_modified":"2026-04-28T21:31:00.4605058+04:00"},{"id":38733,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"eb6ddb4ecbe9eb87c395f2865107ea0e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message_oriented_connection.hpp#26-27","gmt_create":"2026-04-28T21:31:00.4605058+04:00","gmt_modified":"2026-04-28T21:31:00.4605058+04:00"},{"id":38734,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"660390a5b68b3f21fd886800f24fcc78","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#39-45","gmt_create":"2026-04-28T21:31:00.461505+04:00","gmt_modified":"2026-04-28T21:31:00.461505+04:00"},{"id":38735,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"688ff5b9bf8ff49c734540b2a73a1251","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#288-298","gmt_create":"2026-04-28T21:31:00.461505+04:00","gmt_modified":"2026-04-28T21:31:00.461505+04:00"},{"id":38736,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"34e2f989664fd5eff332f3ac19b8b16e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#85-105","gmt_create":"2026-04-28T21:31:00.461505+04:00","gmt_modified":"2026-04-28T21:31:00.461505+04:00"},{"id":38806,"source_id":"724010bf-a048-4a08-bf63-fc4bcac656b4","target_id":"ed702015-695e-4ef5-87fc-be4645f73987","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 724010bf-a048-4a08-bf63-fc4bcac656b4 -\u003e ed702015-695e-4ef5-87fc-be4645f73987","gmt_create":"2026-04-28T21:34:21.0710176+04:00","gmt_modified":"2026-04-28T21:34:21.0710176+04:00"},{"id":38809,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"8908ad91-79b1-4190-aa7c-9a512f259dcb","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ed702015-695e-4ef5-87fc-be4645f73987 -\u003e 8908ad91-79b1-4190-aa7c-9a512f259dcb","gmt_create":"2026-04-28T21:34:21.0780219+04:00","gmt_modified":"2026-04-28T21:34:21.0780219+04:00"},{"id":38810,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"446c4151-6374-4826-9947-cf5133b470cd","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ed702015-695e-4ef5-87fc-be4645f73987 -\u003e 446c4151-6374-4826-9947-cf5133b470cd","gmt_create":"2026-04-28T21:34:21.0790228+04:00","gmt_modified":"2026-04-28T21:34:21.0790228+04:00"},{"id":38811,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"14f4dc36-feae-4d78-a12c-32f851999890","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ed702015-695e-4ef5-87fc-be4645f73987 -\u003e 14f4dc36-feae-4d78-a12c-32f851999890","gmt_create":"2026-04-28T21:34:21.0790228+04:00","gmt_modified":"2026-04-28T21:34:21.0790228+04:00"},{"id":38831,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"3758345d86110e6830e316917030c83c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6669","gmt_create":"2026-04-28T22:02:38.4781865+04:00","gmt_modified":"2026-04-28T22:02:38.4781865+04:00"},{"id":38852,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"73a54c033320ed8363fc781d798df0be","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1420-1510","gmt_create":"2026-04-28T22:02:38.4841857+04:00","gmt_modified":"2026-04-28T22:02:38.4841857+04:00"},{"id":38854,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"21fa05f4a65dabefa29dcd9515e99399","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1432-1498","gmt_create":"2026-04-28T22:02:38.4841857+04:00","gmt_modified":"2026-04-28T22:02:38.4841857+04:00"},{"id":39013,"source_id":"1ccc033e927b7ba78550b64f23026d0b","target_id":"67ee60a8299f84cdefc03cf72aeb2083","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 177-215","gmt_create":"2026-04-28T22:07:49.3113859+04:00","gmt_modified":"2026-04-28T22:07:49.3113859+04:00"},{"id":39015,"source_id":"29a08e70168bdd56a054f1924f1f547c","target_id":"6b69ae0fdd0cccce913fc2d7aaa732b0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 166-186","gmt_create":"2026-04-28T22:07:49.3142262+04:00","gmt_modified":"2026-04-28T22:07:49.3142262+04:00"},{"id":39017,"source_id":"5ba4c9dfb14a49e5bdc63880653930c6","target_id":"7df358ffe779de2ba25c910aced557fd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 21-46","gmt_create":"2026-04-28T22:07:49.3157395+04:00","gmt_modified":"2026-04-28T22:07:49.3157395+04:00"},{"id":39034,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"6fb08c879f27957a5d358cff4532c031","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1440-1500","gmt_create":"2026-04-28T22:07:49.3382713+04:00","gmt_modified":"2026-04-28T22:07:49.3382713+04:00"},{"id":39134,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"51b2088b237a2b23830a054ea53920ff","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5160-5196","gmt_create":"2026-04-28T22:30:12.9848422+04:00","gmt_modified":"2026-04-28T22:30:12.9848422+04:00"},{"id":39141,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"672cdc6d6a7b32cf5afabd6fdbce20d3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3540-3562","gmt_create":"2026-04-28T22:30:12.986345+04:00","gmt_modified":"2026-04-28T22:30:12.986345+04:00"},{"id":39143,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4c8e75baa374d11c9e8793b8772d0db9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3920-3940","gmt_create":"2026-04-28T22:30:12.9873482+04:00","gmt_modified":"2026-04-28T22:30:12.9873482+04:00"},{"id":39145,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"1170f10ffb0d10a348c893486b79ae55","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5501-5534","gmt_create":"2026-04-28T22:30:12.9873482+04:00","gmt_modified":"2026-04-28T22:30:12.9873482+04:00"},{"id":39147,"source_id":"18555f254f50536a15d8591acf982406","target_id":"a6bfdd40fb3a5836fbabdb5158fa34fe","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 103-108","gmt_create":"2026-04-28T22:30:12.9883482+04:00","gmt_modified":"2026-04-28T22:30:12.9883482+04:00"},{"id":39149,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"0eb11834610b569470f2b71aa759f15c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 276-283","gmt_create":"2026-04-28T22:30:12.9893481+04:00","gmt_modified":"2026-04-28T22:30:12.9893481+04:00"},{"id":39151,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"5a8c23e7fa97a43608f760d3dd4af004","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5281-5359","gmt_create":"2026-04-28T22:30:12.9893481+04:00","gmt_modified":"2026-04-28T22:30:12.9893481+04:00"},{"id":39153,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b4c0ee5e14c876c7ba33bc794823a9c4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5181-5196","gmt_create":"2026-04-28T22:30:12.9893481+04:00","gmt_modified":"2026-04-28T22:30:12.9893481+04:00"},{"id":39155,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"65e89e734d42597ee19803d4235f8b40","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5139-5158","gmt_create":"2026-04-28T22:30:12.9893481+04:00","gmt_modified":"2026-04-28T22:30:12.9893481+04:00"},{"id":39164,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"ec345236fa256f6ac48c3f905522f12d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5166-5174","gmt_create":"2026-04-28T22:30:12.9923494+04:00","gmt_modified":"2026-04-28T22:30:12.9923494+04:00"},{"id":39166,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"5d28ce1f0b64958989a7799ef381c8d7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5163-5165","gmt_create":"2026-04-28T22:30:12.9923494+04:00","gmt_modified":"2026-04-28T22:30:12.9923494+04:00"},{"id":39168,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"7290ddf2f171b2a1565c4080ab334499","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5182-5187","gmt_create":"2026-04-28T22:30:12.9923494+04:00","gmt_modified":"2026-04-28T22:30:12.9923494+04:00"},{"id":39174,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"209759d869965a0c3eba1e658fa84845","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 633-689","gmt_create":"2026-04-28T22:30:12.9953495+04:00","gmt_modified":"2026-04-28T22:30:12.9953495+04:00"},{"id":39250,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"ba976b2f-ad3f-4c9c-bbde-af6e23216dbe","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ed702015-695e-4ef5-87fc-be4645f73987 -\u003e ba976b2f-ad3f-4c9c-bbde-af6e23216dbe","gmt_create":"2026-04-28T22:34:00.955766+04:00","gmt_modified":"2026-04-28T22:34:00.955766+04:00"},{"id":39251,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"6c94b84fdfd5c7016b5eeadf8099133e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/plugin.cpp","gmt_create":"2026-04-29T06:59:11.2895379+04:00","gmt_modified":"2026-04-29T06:59:11.2895379+04:00"},{"id":39252,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"ae7188ee9396d8d8aca884b96e9bc4c1","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/include/graphene/plugins/chain/plugin.hpp","gmt_create":"2026-04-29T06:59:11.2910629+04:00","gmt_modified":"2026-04-29T06:59:11.2910629+04:00"},{"id":39253,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39254,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"cb29035725926be38d36ad8c01792b7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database_exceptions.hpp","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39255,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"c4447af409b7f3205a55e5b286557dfe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.cpp","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39256,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"0e6f9014df8500eda2c1aa47cc9e4633","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/appbase/application.cpp","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39257,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"648e1d10af53280c425b922251db1464","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: README.md","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39258,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"4d5bf798ac6e167d6d0e20a669431373","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: documentation/snapshot-plugin.md","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39259,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"a53dc201b0a9ff736da577ae1c524abb","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/log/console_appender.cpp","gmt_create":"2026-04-29T06:59:11.2920673+04:00","gmt_modified":"2026-04-29T06:59:11.2920673+04:00"},{"id":39260,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"455bdc0d380a2cbf0f62da93a6fe203d","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/log/console_defines.h","gmt_create":"2026-04-29T06:59:11.2931424+04:00","gmt_modified":"2026-04-29T06:59:11.2931424+04:00"},{"id":39261,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"d71a233c4690ddd107d80d74f5956ebf","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/log/logger_config.cpp","gmt_create":"2026-04-29T06:59:11.2931424+04:00","gmt_modified":"2026-04-29T06:59:11.2931424+04:00"},{"id":39262,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"fcabf234b34f00b60b0d784b2da5a052","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: programs/vizd/main.cpp","gmt_create":"2026-04-29T06:59:11.2931424+04:00","gmt_modified":"2026-04-29T06:59:11.2931424+04:00"},{"id":39263,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"3d75047030281d13fb2d55905e0c69c8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#183-649","gmt_create":"2026-04-29T06:59:11.2931424+04:00","gmt_modified":"2026-04-29T06:59:11.2931424+04:00"},{"id":39264,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"3d75047030281d13fb2d55905e0c69c8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 183-649","gmt_create":"2026-04-29T06:59:11.2931424+04:00","gmt_modified":"2026-04-29T06:59:11.2931424+04:00"},{"id":39265,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"f75d35fc9523fc188eb18b8741e33f6d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#351-544","gmt_create":"2026-04-29T06:59:11.2931424+04:00","gmt_modified":"2026-04-29T06:59:11.2931424+04:00"},{"id":39266,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"f75d35fc9523fc188eb18b8741e33f6d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 351-544","gmt_create":"2026-04-29T06:59:11.2946449+04:00","gmt_modified":"2026-04-29T06:59:11.2946449+04:00"},{"id":39267,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"22a0b873abfd87b49bc4ca088c641dcd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#3031-3118","gmt_create":"2026-04-29T06:59:11.2952321+04:00","gmt_modified":"2026-04-29T06:59:11.2952321+04:00"},{"id":39268,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"22a0b873abfd87b49bc4ca088c641dcd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3031-3118","gmt_create":"2026-04-29T06:59:11.2952321+04:00","gmt_modified":"2026-04-29T06:59:11.2952321+04:00"},{"id":39269,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"2568823f5e70aacc0929c3200c44c688","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#1-694","gmt_create":"2026-04-29T06:59:11.2952321+04:00","gmt_modified":"2026-04-29T06:59:11.2952321+04:00"},{"id":39270,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"2568823f5e70aacc0929c3200c44c688","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-694","gmt_create":"2026-04-29T06:59:11.2961606+04:00","gmt_modified":"2026-04-29T06:59:11.2961606+04:00"},{"id":39271,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"7171e5fb841e1d5c8e4435a9e0e75f56","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-6314","gmt_create":"2026-04-29T06:59:11.2961606+04:00","gmt_modified":"2026-04-29T06:59:11.2961606+04:00"},{"id":39272,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"7171e5fb841e1d5c8e4435a9e0e75f56","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6314","gmt_create":"2026-04-29T06:59:11.2961606+04:00","gmt_modified":"2026-04-29T06:59:11.2961606+04:00"},{"id":39273,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"668e6362de53625901201e8370b90e80","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/include/graphene/plugins/chain/plugin.hpp#21-124","gmt_create":"2026-04-29T06:59:11.2971602+04:00","gmt_modified":"2026-04-29T06:59:11.2971602+04:00"},{"id":39274,"source_id":"ae7188ee9396d8d8aca884b96e9bc4c1","target_id":"668e6362de53625901201e8370b90e80","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 21-124","gmt_create":"2026-04-29T06:59:11.2971602+04:00","gmt_modified":"2026-04-29T06:59:11.2971602+04:00"},{"id":39275,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"86f066e47903bbd61b02a545ae4e6551","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#21-93","gmt_create":"2026-04-29T06:59:11.2971602+04:00","gmt_modified":"2026-04-29T06:59:11.2971602+04:00"},{"id":39276,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"86f066e47903bbd61b02a545ae4e6551","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 21-93","gmt_create":"2026-04-29T06:59:11.2971602+04:00","gmt_modified":"2026-04-29T06:59:11.2971602+04:00"},{"id":39277,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"9c28f276f937ea7573f48b90b8f92d7f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#103-183","gmt_create":"2026-04-29T06:59:11.298161+04:00","gmt_modified":"2026-04-29T06:59:11.298161+04:00"},{"id":39278,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"9c28f276f937ea7573f48b90b8f92d7f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 103-183","gmt_create":"2026-04-29T06:59:11.298161+04:00","gmt_modified":"2026-04-29T06:59:11.298161+04:00"},{"id":39279,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"89fd5aa6e93582e3684130e9c0920e33","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#438-544","gmt_create":"2026-04-29T06:59:11.298161+04:00","gmt_modified":"2026-04-29T06:59:11.298161+04:00"},{"id":39280,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"371ae6e78ddcaea6c7ab821b81b12e35","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#650-666","gmt_create":"2026-04-29T06:59:11.298161+04:00","gmt_modified":"2026-04-29T06:59:11.298161+04:00"},{"id":39281,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"371ae6e78ddcaea6c7ab821b81b12e35","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 650-666","gmt_create":"2026-04-29T06:59:11.298161+04:00","gmt_modified":"2026-04-29T06:59:11.298161+04:00"},{"id":39282,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"13058dc3cfe4c9f00b04c9e22c6c9b70","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#197-272","gmt_create":"2026-04-29T06:59:11.298161+04:00","gmt_modified":"2026-04-29T06:59:11.298161+04:00"},{"id":39283,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"13058dc3cfe4c9f00b04c9e22c6c9b70","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 197-272","gmt_create":"2026-04-29T06:59:11.2996671+04:00","gmt_modified":"2026-04-29T06:59:11.2996671+04:00"},{"id":39284,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"16a421cb814c03e0297d814e4c7fcbcf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#274-386","gmt_create":"2026-04-29T06:59:11.3001825+04:00","gmt_modified":"2026-04-29T06:59:11.3001825+04:00"},{"id":39285,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"16a421cb814c03e0297d814e4c7fcbcf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 274-386","gmt_create":"2026-04-29T06:59:11.3001825+04:00","gmt_modified":"2026-04-29T06:59:11.3001825+04:00"},{"id":39286,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"e76a2e15b7d528b5eb96fad4fe23bd9c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#388-649","gmt_create":"2026-04-29T06:59:11.3001825+04:00","gmt_modified":"2026-04-29T06:59:11.3001825+04:00"},{"id":39287,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"e76a2e15b7d528b5eb96fad4fe23bd9c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 388-649","gmt_create":"2026-04-29T06:59:11.3001825+04:00","gmt_modified":"2026-04-29T06:59:11.3001825+04:00"},{"id":39288,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"2fa9c0e59e88a84f214e07f4a5eba712","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4253-4323","gmt_create":"2026-04-29T06:59:11.3011859+04:00","gmt_modified":"2026-04-29T06:59:11.3011859+04:00"},{"id":39289,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2fa9c0e59e88a84f214e07f4a5eba712","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4253-4323","gmt_create":"2026-04-29T06:59:11.3011859+04:00","gmt_modified":"2026-04-29T06:59:11.3011859+04:00"},{"id":39290,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"e9a71b0c283c28e9108702f7ca2163f7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4314-4323","gmt_create":"2026-04-29T06:59:11.3021856+04:00","gmt_modified":"2026-04-29T06:59:11.3021856+04:00"},{"id":39291,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e9a71b0c283c28e9108702f7ca2163f7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4314-4323","gmt_create":"2026-04-29T06:59:11.3021856+04:00","gmt_modified":"2026-04-29T06:59:11.3021856+04:00"},{"id":39292,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"97ee13777a0aa5665ed9d742b01a6df1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#420-475","gmt_create":"2026-04-29T06:59:11.3031866+04:00","gmt_modified":"2026-04-29T06:59:11.3031866+04:00"},{"id":39293,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"97ee13777a0aa5665ed9d742b01a6df1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 420-475","gmt_create":"2026-04-29T06:59:11.3031866+04:00","gmt_modified":"2026-04-29T06:59:11.3031866+04:00"},{"id":39294,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"bd4c9ed516dc18cb55f239f6feab6a30","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#3031-3042","gmt_create":"2026-04-29T06:59:11.3031866+04:00","gmt_modified":"2026-04-29T06:59:11.3031866+04:00"},{"id":39295,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"bd4c9ed516dc18cb55f239f6feab6a30","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3031-3042","gmt_create":"2026-04-29T06:59:11.3031866+04:00","gmt_modified":"2026-04-29T06:59:11.3031866+04:00"},{"id":39296,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"284384a88a8e22967e75aef3b6e3562e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#566-649","gmt_create":"2026-04-29T06:59:11.3041867+04:00","gmt_modified":"2026-04-29T06:59:11.3041867+04:00"},{"id":39297,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"284384a88a8e22967e75aef3b6e3562e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 566-649","gmt_create":"2026-04-29T06:59:11.3041867+04:00","gmt_modified":"2026-04-29T06:59:11.3041867+04:00"},{"id":39298,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"00e1ac390ce60cf05032160e029fc05c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#344-382","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39299,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"00e1ac390ce60cf05032160e029fc05c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 344-382","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39300,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"759a8a5afbc2bd120cc23c1a54071c79","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2817-2861","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39301,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"759a8a5afbc2bd120cc23c1a54071c79","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2817-2861","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39302,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"4370e8763bbca8a166f9af80993701e9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2908-2920","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39303,"source_id":"c4447af409b7f3205a55e5b286557dfe","target_id":"4370e8763bbca8a166f9af80993701e9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2908-2920","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39304,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"8c0eb1fd78ceb38fa43fd2a1b7f10412","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#105-121","gmt_create":"2026-04-29T06:59:11.3046915+04:00","gmt_modified":"2026-04-29T06:59:11.3046915+04:00"},{"id":39305,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"8c0eb1fd78ceb38fa43fd2a1b7f10412","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 105-121","gmt_create":"2026-04-29T06:59:11.3056967+04:00","gmt_modified":"2026-04-29T06:59:11.3056967+04:00"},{"id":39306,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"a4377fbb29614b9499ed7cba50185337","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/log/console_appender.cpp#71-84","gmt_create":"2026-04-29T06:59:11.306697+04:00","gmt_modified":"2026-04-29T06:59:11.306697+04:00"},{"id":39307,"source_id":"a53dc201b0a9ff736da577ae1c524abb","target_id":"a4377fbb29614b9499ed7cba50185337","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 71-84","gmt_create":"2026-04-29T06:59:11.306697+04:00","gmt_modified":"2026-04-29T06:59:11.306697+04:00"},{"id":39308,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"9d89f7774c243fa2dcd1a6b44f750558","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/log/console_defines.h#146-188","gmt_create":"2026-04-29T06:59:11.306697+04:00","gmt_modified":"2026-04-29T06:59:11.306697+04:00"},{"id":39309,"source_id":"455bdc0d380a2cbf0f62da93a6fe203d","target_id":"9d89f7774c243fa2dcd1a6b44f750558","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 146-188","gmt_create":"2026-04-29T06:59:11.3076955+04:00","gmt_modified":"2026-04-29T06:59:11.3076955+04:00"},{"id":39310,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"8a80f90958428689e890e5b0a8e84f60","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/log/logger_config.cpp#69-89","gmt_create":"2026-04-29T06:59:11.3076955+04:00","gmt_modified":"2026-04-29T06:59:11.3076955+04:00"},{"id":39311,"source_id":"d71a233c4690ddd107d80d74f5956ebf","target_id":"8a80f90958428689e890e5b0a8e84f60","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 69-89","gmt_create":"2026-04-29T06:59:11.3076955+04:00","gmt_modified":"2026-04-29T06:59:11.3076955+04:00"},{"id":39312,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"65bbe0fb033855becef1089d08ed50bf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/vizd/main.cpp#234-250","gmt_create":"2026-04-29T06:59:11.3086949+04:00","gmt_modified":"2026-04-29T06:59:11.3086949+04:00"},{"id":39313,"source_id":"fcabf234b34f00b60b0d784b2da5a052","target_id":"65bbe0fb033855becef1089d08ed50bf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 234-250","gmt_create":"2026-04-29T06:59:11.3086949+04:00","gmt_modified":"2026-04-29T06:59:11.3086949+04:00"},{"id":39314,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"37d1a2d00fcd56aea0848ebca3f2a926","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#757-816","gmt_create":"2026-04-29T06:59:11.3102874+04:00","gmt_modified":"2026-04-29T06:59:11.3102874+04:00"},{"id":39315,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"37d1a2d00fcd56aea0848ebca3f2a926","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 757-816","gmt_create":"2026-04-29T06:59:11.3102874+04:00","gmt_modified":"2026-04-29T06:59:11.3102874+04:00"},{"id":39316,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"621d366163e32d2a00e77366a0cca1ea","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#547-600","gmt_create":"2026-04-29T06:59:11.3102874+04:00","gmt_modified":"2026-04-29T06:59:11.3102874+04:00"},{"id":39317,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"621d366163e32d2a00e77366a0cca1ea","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 547-600","gmt_create":"2026-04-29T06:59:11.3102874+04:00","gmt_modified":"2026-04-29T06:59:11.3102874+04:00"},{"id":39318,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"be8f40bd072828763bbdf031b6f1620e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database_exceptions.hpp#122","gmt_create":"2026-04-29T06:59:11.3102874+04:00","gmt_modified":"2026-04-29T06:59:11.3102874+04:00"},{"id":39319,"source_id":"cb29035725926be38d36ad8c01792b7e","target_id":"be8f40bd072828763bbdf031b6f1620e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 122","gmt_create":"2026-04-29T06:59:11.3102874+04:00","gmt_modified":"2026-04-29T06:59:11.3102874+04:00"},{"id":39320,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"db43b1af690cc5f915a6971cef72f698","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#1-12","gmt_create":"2026-04-29T06:59:11.3112909+04:00","gmt_modified":"2026-04-29T06:59:11.3112909+04:00"},{"id":39321,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"db43b1af690cc5f915a6971cef72f698","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-12","gmt_create":"2026-04-29T06:59:11.3112909+04:00","gmt_modified":"2026-04-29T06:59:11.3112909+04:00"},{"id":39322,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"ab61b06105c0618ce476f62fc42d04c5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-10","gmt_create":"2026-04-29T06:59:11.3196209+04:00","gmt_modified":"2026-04-29T06:59:11.3196209+04:00"},{"id":39323,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"46d15c6ef6c17169ac51cbdc06ed7053","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/include/graphene/plugins/chain/plugin.hpp#23-24","gmt_create":"2026-04-29T06:59:11.32062+04:00","gmt_modified":"2026-04-29T06:59:11.32062+04:00"},{"id":39324,"source_id":"ae7188ee9396d8d8aca884b96e9bc4c1","target_id":"46d15c6ef6c17169ac51cbdc06ed7053","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 23-24","gmt_create":"2026-04-29T06:59:11.32062+04:00","gmt_modified":"2026-04-29T06:59:11.32062+04:00"},{"id":39325,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"2727250a5ed0225f9707859ab095dfe8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#92-105","gmt_create":"2026-04-29T06:59:11.3211231+04:00","gmt_modified":"2026-04-29T06:59:11.3211231+04:00"},{"id":39326,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"2727250a5ed0225f9707859ab095dfe8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 92-105","gmt_create":"2026-04-29T06:59:11.3211231+04:00","gmt_modified":"2026-04-29T06:59:11.3211231+04:00"},{"id":39327,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"9bf43751ce7ff4f9e6f159edc4314caf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#24-51","gmt_create":"2026-04-29T06:59:11.3211231+04:00","gmt_modified":"2026-04-29T06:59:11.3211231+04:00"},{"id":39328,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"9bf43751ce7ff4f9e6f159edc4314caf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 24-51","gmt_create":"2026-04-29T06:59:11.3211231+04:00","gmt_modified":"2026-04-29T06:59:11.3211231+04:00"},{"id":39329,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"0cd78f3f9caeaceed6acf82a3b374de0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#398-418","gmt_create":"2026-04-29T06:59:11.3216393+04:00","gmt_modified":"2026-04-29T06:59:11.3216393+04:00"},{"id":39330,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"0cd78f3f9caeaceed6acf82a3b374de0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 398-418","gmt_create":"2026-04-29T06:59:11.3216393+04:00","gmt_modified":"2026-04-29T06:59:11.3216393+04:00"},{"id":39331,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"c0c7e7261cd9841beac6c26b47f47cf6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#562-601","gmt_create":"2026-04-29T06:59:11.3221488+04:00","gmt_modified":"2026-04-29T06:59:11.3221488+04:00"},{"id":39332,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"c0c7e7261cd9841beac6c26b47f47cf6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 562-601","gmt_create":"2026-04-29T06:59:11.3221488+04:00","gmt_modified":"2026-04-29T06:59:11.3221488+04:00"},{"id":39333,"source_id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","target_id":"a8aa23bf7495e9cc8c7a2560a6157222","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#251-271","gmt_create":"2026-04-29T06:59:11.3221488+04:00","gmt_modified":"2026-04-29T06:59:11.3221488+04:00"},{"id":39334,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"a8aa23bf7495e9cc8c7a2560a6157222","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 251-271","gmt_create":"2026-04-29T06:59:11.3221488+04:00","gmt_modified":"2026-04-29T06:59:11.3221488+04:00"},{"id":39624,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a7270a78978a13ee55ddc7f94813e3df","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1680-1693","gmt_create":"2026-04-29T07:04:36.8960015+04:00","gmt_modified":"2026-04-29T07:04:36.8960015+04:00"},{"id":39626,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"a7f5c35a309d6a9851c0491c125c63d9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3224-3236","gmt_create":"2026-04-29T07:04:36.8960015+04:00","gmt_modified":"2026-04-29T07:04:36.8960015+04:00"},{"id":39628,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2f5c46c8352dc73f0b90e01cef1bcc9d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3272-3284","gmt_create":"2026-04-29T07:04:36.8970029+04:00","gmt_modified":"2026-04-29T07:04:36.8970029+04:00"},{"id":39630,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"26495d5b0fffb6fefe7f8c69d291521e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 738-742","gmt_create":"2026-04-29T07:04:36.8970029+04:00","gmt_modified":"2026-04-29T07:04:36.8970029+04:00"},{"id":39633,"source_id":"6c94b84fdfd5c7016b5eeadf8099133e","target_id":"3bdfaf9a009f1c5236532831eedfa66b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 760-770","gmt_create":"2026-04-29T07:04:36.8980042+04:00","gmt_modified":"2026-04-29T07:04:36.8980042+04:00"},{"id":39639,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"d21997e6-71b9-44d6-b43f-85770fe1e9dd","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e d21997e6-71b9-44d6-b43f-85770fe1e9dd","gmt_create":"2026-04-29T07:04:37.6295296+04:00","gmt_modified":"2026-04-29T07:04:37.6295296+04:00"},{"id":39689,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"943ef4fb5df40c9942aadddd7040bc7c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4863-5004","gmt_create":"2026-04-29T22:58:12.6645298+04:00","gmt_modified":"2026-04-29T22:58:12.6645298+04:00"},{"id":39692,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"1273c494621ef1c9351fa8b783dc6ae6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 422-427","gmt_create":"2026-04-29T22:58:12.667034+04:00","gmt_modified":"2026-04-29T22:58:12.667034+04:00"},{"id":39695,"source_id":"ee77bf4eb6bfbfb3636aa0bd57416552","target_id":"cf67769b74f5699e4a347dc2d7092ceb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1097-1115","gmt_create":"2026-04-29T22:58:12.6690373+04:00","gmt_modified":"2026-04-29T22:58:12.6690373+04:00"},{"id":39702,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ffdc85ed129bbf1af13ac8fb289ef5cc","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4887-4906","gmt_create":"2026-04-29T22:58:12.6720374+04:00","gmt_modified":"2026-04-29T22:58:12.6720374+04:00"},{"id":39704,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"0368dbe3efce9f73a7859fc98834220c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4907-4914","gmt_create":"2026-04-29T22:58:12.6745476+04:00","gmt_modified":"2026-04-29T22:58:12.6745476+04:00"},{"id":39706,"source_id":"73ada165e99c6ad5f938a94f11fb3e10","target_id":"90239aad5320ac7daf5e885e60b33925","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 261-263","gmt_create":"2026-04-29T22:58:12.6755451+04:00","gmt_modified":"2026-04-29T22:58:12.6755451+04:00"},{"id":39708,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"c1530ddb1ea5d36cd757849d7b1bae47","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2614-2631","gmt_create":"2026-04-29T22:58:12.678052+04:00","gmt_modified":"2026-04-29T22:58:12.678052+04:00"},{"id":39710,"source_id":"b4b9efd79d5b3c9fea00fccd613b2046","target_id":"e233bb5840d58fff00c516dfc7186b2f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 125-128","gmt_create":"2026-04-29T22:58:12.6790528+04:00","gmt_modified":"2026-04-29T22:58:12.6790528+04:00"},{"id":39713,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"d2f89808b761a3c0eca43468d945f372","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1721","gmt_create":"2026-04-29T22:58:12.6820519+04:00","gmt_modified":"2026-04-29T22:58:12.6820519+04:00"},{"id":39761,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"2b851e123aa78f2afb16a52472781e64","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 228-233","gmt_create":"2026-04-29T23:00:33.7910786+04:00","gmt_modified":"2026-04-29T23:00:33.7910786+04:00"},{"id":39778,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"0c5e33130a4b0128f85c5ef5837b2837","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 338-407","gmt_create":"2026-04-29T23:00:33.8068196+04:00","gmt_modified":"2026-04-29T23:00:33.8068196+04:00"},{"id":39780,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"b4b9171ebcdf9b3750b66cea688f5e6b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 411-419","gmt_create":"2026-04-29T23:00:33.8095439+04:00","gmt_modified":"2026-04-29T23:00:33.8095439+04:00"},{"id":39782,"source_id":"609365f8572668c8cf1e1cfa497989e4","target_id":"673f7fff8a03580c3caedc629fc67cd4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 60","gmt_create":"2026-04-29T23:00:33.8104885+04:00","gmt_modified":"2026-04-29T23:00:33.8104885+04:00"},{"id":39784,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"dbf1a067fe97d23702c1dcb8e2df53b6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1890-1892","gmt_create":"2026-04-29T23:00:33.8115405+04:00","gmt_modified":"2026-04-29T23:00:33.8115405+04:00"},{"id":39786,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"2ad181689b70c433e1ec262fbbedb608","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4536-4573","gmt_create":"2026-04-29T23:00:33.8120693+04:00","gmt_modified":"2026-04-29T23:00:33.8120693+04:00"},{"id":39788,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"73337c954cb4cc654d9a0b55b95b2480","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5530-5655","gmt_create":"2026-04-29T23:00:33.8120693+04:00","gmt_modified":"2026-04-29T23:00:33.8120693+04:00"},{"id":39819,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"8c1b3bca4f56b317e9e0acdb4be83a00","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-89","gmt_create":"2026-04-29T23:01:32.3166187+04:00","gmt_modified":"2026-04-29T23:01:32.3166187+04:00"},{"id":39821,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"a3ae1dbcc1c9bd6ef59dec7fb6a6e6be","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-582","gmt_create":"2026-04-29T23:01:32.3181398+04:00","gmt_modified":"2026-04-29T23:01:32.3181398+04:00"},{"id":39830,"source_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","target_id":"445ddb5941dc16c8a6313b9ec556f75a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 35-89","gmt_create":"2026-04-29T23:01:32.3216473+04:00","gmt_modified":"2026-04-29T23:01:32.3216473+04:00"},{"id":39847,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"8d97625477363c05b9feab2d2cbb781b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 31-38","gmt_create":"2026-04-29T23:01:32.3281622+04:00","gmt_modified":"2026-04-29T23:01:32.3281622+04:00"},{"id":39849,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"1b89c3c75aec06d4b094f1f6692436f9","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 59-66","gmt_create":"2026-04-29T23:01:32.3281622+04:00","gmt_modified":"2026-04-29T23:01:32.3281622+04:00"},{"id":39851,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"3f4e4b4d64c6750b34e1fe180b376017","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 119-136","gmt_create":"2026-04-29T23:01:32.3291615+04:00","gmt_modified":"2026-04-29T23:01:32.3291615+04:00"},{"id":39853,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"1853975a7087e8a5073352a9a8ea53c6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 138-155","gmt_create":"2026-04-29T23:01:32.3291615+04:00","gmt_modified":"2026-04-29T23:01:32.3291615+04:00"},{"id":39860,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"23c70bfc8bc9d7d6ea64c01cbc8cfa74","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 545-579","gmt_create":"2026-04-29T23:01:32.3311616+04:00","gmt_modified":"2026-04-29T23:01:32.3311616+04:00"},{"id":39862,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"57b5a41ba76b9bfcb250e93e89805515","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 761-765","gmt_create":"2026-04-29T23:01:32.3311616+04:00","gmt_modified":"2026-04-29T23:01:32.3311616+04:00"},{"id":39869,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"3397b02998da3d1df1abfc39e178cd35","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 74-100","gmt_create":"2026-04-29T23:01:32.3416718+04:00","gmt_modified":"2026-04-29T23:01:32.3416718+04:00"},{"id":39871,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"dfc65f88824f860423d799827fdee7aa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 545-574","gmt_create":"2026-04-29T23:01:32.3416718+04:00","gmt_modified":"2026-04-29T23:01:32.3416718+04:00"},{"id":39873,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"443c70bf08a7229603f07d4b6e2ceb04","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 304-369","gmt_create":"2026-04-29T23:01:32.3426911+04:00","gmt_modified":"2026-04-29T23:01:32.3426911+04:00"},{"id":39919,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"766ad50513305c2c4955788de08b2f70","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 757-765","gmt_create":"2026-04-29T23:01:32.3551881+04:00","gmt_modified":"2026-04-29T23:01:32.3551881+04:00"},{"id":39946,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"61447f1b270e050eaf1594622e8344cb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 303-357","gmt_create":"2026-04-30T07:13:19.6681094+04:00","gmt_modified":"2026-04-30T07:13:19.6681094+04:00"},{"id":39948,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"87460891e469970ebd1b4357e65a305d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2561-2591","gmt_create":"2026-04-30T07:13:19.6686216+04:00","gmt_modified":"2026-04-30T07:13:19.6686216+04:00"},{"id":39950,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e8bcacd1aeb9acccff2e7846bdb418ea","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2596-2612","gmt_create":"2026-04-30T07:13:19.6696503+04:00","gmt_modified":"2026-04-30T07:13:19.6696503+04:00"},{"id":39954,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"3addab2306735a8fd2b97c7337a752aa","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5473-5545","gmt_create":"2026-04-30T07:13:19.6701689+04:00","gmt_modified":"2026-04-30T07:13:19.6701689+04:00"},{"id":39956,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"cf15af9ccdfa43fad1c61be4c143bb9f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5515-5529","gmt_create":"2026-04-30T07:13:19.67068+04:00","gmt_modified":"2026-04-30T07:13:19.67068+04:00"},{"id":40058,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40059,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40060,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40061,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40062,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"4238a9561f85e50a38f76813baeadd7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/block_log.hpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40063,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/dlt_block_log.hpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40064,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40065,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40066,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-30T07:19:20.4378901+04:00","gmt_modified":"2026-04-30T07:19:20.4378901+04:00"},{"id":40067,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"b4b9efd79d5b3c9fea00fccd613b2046","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/config.hpp","gmt_create":"2026-04-30T07:19:20.4388898+04:00","gmt_modified":"2026-04-30T07:19:20.4388898+04:00"},{"id":40068,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"8ede002b6c76d0a07d75e34f812e8305","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/hardfork.d/12.hf","gmt_create":"2026-04-30T07:19:20.4388898+04:00","gmt_modified":"2026-04-30T07:19:20.4388898+04:00"},{"id":40069,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"4e999921565c672e1c3f31f949a25803","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#128-150","gmt_create":"2026-04-30T07:19:20.4388898+04:00","gmt_modified":"2026-04-30T07:19:20.4388898+04:00"},{"id":40070,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"4e999921565c672e1c3f31f949a25803","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 128-150","gmt_create":"2026-04-30T07:19:20.4388898+04:00","gmt_modified":"2026-04-30T07:19:20.4388898+04:00"},{"id":40071,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"3c48450ddf4126f562a2691715c15905","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#80-87","gmt_create":"2026-04-30T07:19:20.4388898+04:00","gmt_modified":"2026-04-30T07:19:20.4388898+04:00"},{"id":40072,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"509be78f5e90ad720bc5cb62f8252773","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1204-1270","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40073,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"2f228d36a18a021f1d730c1fa50466da","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#739-760","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40074,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"2f228d36a18a021f1d730c1fa50466da","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 739-760","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40075,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"2f3627b50fb7b7e496ab7f3d82bb011c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#521-544","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40076,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"da0076fc30299f9f1dd09e50dc181609","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#1-168","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40077,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"da0076fc30299f9f1dd09e50dc181609","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-168","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40078,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"d599d8eb734647e07f075785246b447f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#1-278","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40079,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"2426f410a167c0334267f66a00c3b706","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#1-200","gmt_create":"2026-04-30T07:19:20.4398905+04:00","gmt_modified":"2026-04-30T07:19:20.4398905+04:00"},{"id":40080,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"3758345d86110e6830e316917030c83c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-6669","gmt_create":"2026-04-30T07:19:20.440891+04:00","gmt_modified":"2026-04-30T07:19:20.440891+04:00"},{"id":40081,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"6ebf29a038d578e8864ca6c9c9366bda","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#1-76","gmt_create":"2026-04-30T07:19:20.440891+04:00","gmt_modified":"2026-04-30T07:19:20.440891+04:00"},{"id":40082,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"620825701e1a1b114e822bbda1ceb234","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#1-454","gmt_create":"2026-04-30T07:19:20.440891+04:00","gmt_modified":"2026-04-30T07:19:20.440891+04:00"},{"id":40083,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"13ad0bfb014506790d3eea0ac1e240eb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#1-697","gmt_create":"2026-04-30T07:19:20.440891+04:00","gmt_modified":"2026-04-30T07:19:20.440891+04:00"},{"id":40084,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"24e9e8b3ff6a7469a09e87e94f9832b8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#735-771","gmt_create":"2026-04-30T07:19:20.4418907+04:00","gmt_modified":"2026-04-30T07:19:20.4418907+04:00"},{"id":40085,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"24e9e8b3ff6a7469a09e87e94f9832b8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 735-771","gmt_create":"2026-04-30T07:19:20.4418907+04:00","gmt_modified":"2026-04-30T07:19:20.4418907+04:00"},{"id":40086,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"adbfa85db79875fa5ba5450da427c626","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#110-124","gmt_create":"2026-04-30T07:19:20.4418907+04:00","gmt_modified":"2026-04-30T07:19:20.4418907+04:00"},{"id":40087,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"9f5944a7feb01c0c201e4a490c1c6d47","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/hardfork.d/12.hf#1-7","gmt_create":"2026-04-30T07:19:20.4418907+04:00","gmt_modified":"2026-04-30T07:19:20.4418907+04:00"},{"id":40088,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"532021a3aea4c3d7bfe25231df5687c0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#53-168","gmt_create":"2026-04-30T07:19:20.4418907+04:00","gmt_modified":"2026-04-30T07:19:20.4418907+04:00"},{"id":40089,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"532021a3aea4c3d7bfe25231df5687c0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 53-168","gmt_create":"2026-04-30T07:19:20.4418907+04:00","gmt_modified":"2026-04-30T07:19:20.4418907+04:00"},{"id":40090,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"3697c4125ef62ecb76b9032b46a67ad2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#33-92","gmt_create":"2026-04-30T07:19:20.4429759+04:00","gmt_modified":"2026-04-30T07:19:20.4429759+04:00"},{"id":40091,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"62ea8f0eed608d8eb1dd0911e43f28c3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1223-1267","gmt_create":"2026-04-30T07:19:20.4429759+04:00","gmt_modified":"2026-04-30T07:19:20.4429759+04:00"},{"id":40092,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"35d8d0314587b8714a154437fe7243af","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#13-33","gmt_create":"2026-04-30T07:19:20.4429759+04:00","gmt_modified":"2026-04-30T07:19:20.4429759+04:00"},{"id":40093,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"01b277ebba52e972b94566a19ab057fd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#336-340","gmt_create":"2026-04-30T07:19:20.4439765+04:00","gmt_modified":"2026-04-30T07:19:20.4439765+04:00"},{"id":40094,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"3ae8431619379889035afee8a7f98a96","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1300-1399","gmt_create":"2026-04-30T07:19:20.4439765+04:00","gmt_modified":"2026-04-30T07:19:20.4439765+04:00"},{"id":40095,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"4dbf49507663ef2d477a4e56e7ad4113","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#48-84","gmt_create":"2026-04-30T07:19:20.4449764+04:00","gmt_modified":"2026-04-30T07:19:20.4449764+04:00"},{"id":40096,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"a0f13c35d9ba18928ea0803560d59338","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#48-55","gmt_create":"2026-04-30T07:19:20.4449764+04:00","gmt_modified":"2026-04-30T07:19:20.4449764+04:00"},{"id":40097,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"32f0435e4c1faa3d0978c1dcac09d7ce","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#20-168","gmt_create":"2026-04-30T07:19:20.4449764+04:00","gmt_modified":"2026-04-30T07:19:20.4449764+04:00"},{"id":40098,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"32f0435e4c1faa3d0978c1dcac09d7ce","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 20-168","gmt_create":"2026-04-30T07:19:20.4449764+04:00","gmt_modified":"2026-04-30T07:19:20.4449764+04:00"},{"id":40099,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"78b7d2f924e066f18e5bf4ed42de6954","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#33-278","gmt_create":"2026-04-30T07:19:20.4459761+04:00","gmt_modified":"2026-04-30T07:19:20.4459761+04:00"},{"id":40100,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"de96a3aab8e8260a79d1b9c380481b26","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#111-168","gmt_create":"2026-04-30T07:19:20.4459761+04:00","gmt_modified":"2026-04-30T07:19:20.4459761+04:00"},{"id":40101,"source_id":"75b9bb8cfd2db41c21f328241d191f32","target_id":"de96a3aab8e8260a79d1b9c380481b26","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 111-168","gmt_create":"2026-04-30T07:19:20.4459761+04:00","gmt_modified":"2026-04-30T07:19:20.4459761+04:00"},{"id":40102,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"95e9eb6df5c5b54fc25131e15cebca7b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#269-274","gmt_create":"2026-04-30T07:19:20.4470111+04:00","gmt_modified":"2026-04-30T07:19:20.4470111+04:00"},{"id":40103,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"f32c5cb0846168684a2574ccb17d2cd7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#189-231","gmt_create":"2026-04-30T07:19:20.4470111+04:00","gmt_modified":"2026-04-30T07:19:20.4470111+04:00"},{"id":40104,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"7066405016bb2acea5a040cdac13b646","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1037-1177","gmt_create":"2026-04-30T07:19:20.4470111+04:00","gmt_modified":"2026-04-30T07:19:20.4470111+04:00"},{"id":40105,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"73a54c033320ed8363fc781d798df0be","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1420-1510","gmt_create":"2026-04-30T07:19:20.4470111+04:00","gmt_modified":"2026-04-30T07:19:20.4470111+04:00"},{"id":40106,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"21fa05f4a65dabefa29dcd9515e99399","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1432-1498","gmt_create":"2026-04-30T07:19:20.4480121+04:00","gmt_modified":"2026-04-30T07:19:20.4480121+04:00"},{"id":40107,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"1c914e70d8a846e0ed416af007bd2b78","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#259-294","gmt_create":"2026-04-30T07:19:20.4480121+04:00","gmt_modified":"2026-04-30T07:19:20.4480121+04:00"},{"id":40108,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"e24259987d3c50dee1a87e13d63ccf03","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#57-78","gmt_create":"2026-04-30T07:19:20.4480121+04:00","gmt_modified":"2026-04-30T07:19:20.4480121+04:00"},{"id":40109,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"d2b082cb10acf9968b8eb975d61abc92","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4444-4533","gmt_create":"2026-04-30T07:19:20.4480121+04:00","gmt_modified":"2026-04-30T07:19:20.4480121+04:00"},{"id":40110,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"6742da18bac301be1056c8e6e5adcf69","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#35-72","gmt_create":"2026-04-30T07:19:20.4480121+04:00","gmt_modified":"2026-04-30T07:19:20.4480121+04:00"},{"id":40111,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"da4792d808b920542e8e3f85c533f97d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#118-164","gmt_create":"2026-04-30T07:19:20.4490112+04:00","gmt_modified":"2026-04-30T07:19:20.4490112+04:00"},{"id":40112,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"fb711bedba071dd61482af3dd825bb98","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#115-128","gmt_create":"2026-04-30T07:19:20.4490112+04:00","gmt_modified":"2026-04-30T07:19:20.4490112+04:00"},{"id":40113,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"83d7c3cd276c8b6c3a77f6379e8968e4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#561-580","gmt_create":"2026-04-30T07:19:20.4490112+04:00","gmt_modified":"2026-04-30T07:19:20.4490112+04:00"},{"id":40114,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"420ba4d931c9392dd8824c0faaf3b99b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#738-792","gmt_create":"2026-04-30T07:19:20.4490112+04:00","gmt_modified":"2026-04-30T07:19:20.4490112+04:00"},{"id":40115,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"fc699eb749d790d44345a92dd356956e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#206-230","gmt_create":"2026-04-30T07:19:20.4490112+04:00","gmt_modified":"2026-04-30T07:19:20.4490112+04:00"},{"id":40116,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"217f231a3e1f1591acfb14f4b40fb2b3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#476-515","gmt_create":"2026-04-30T07:19:20.4500189+04:00","gmt_modified":"2026-04-30T07:19:20.4500189+04:00"},{"id":40117,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"b25bd7f79844225b0a1356e5a6dbee8b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#92-103","gmt_create":"2026-04-30T07:19:20.4500189+04:00","gmt_modified":"2026-04-30T07:19:20.4500189+04:00"},{"id":40118,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"447323529866a4410bcb34a227ea5b5a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1075-1087","gmt_create":"2026-04-30T07:19:20.4500189+04:00","gmt_modified":"2026-04-30T07:19:20.4500189+04:00"},{"id":40119,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"d59a4116db64f54b5d2ce9c64e1c2c50","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4581-4594","gmt_create":"2026-04-30T07:19:20.4500189+04:00","gmt_modified":"2026-04-30T07:19:20.4500189+04:00"},{"id":40120,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"98ed598d82d28fa1553560148bcda24e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2125-2142","gmt_create":"2026-04-30T07:19:20.4510137+04:00","gmt_modified":"2026-04-30T07:19:20.4510137+04:00"},{"id":40121,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"aa98d4cf2cd33ba5c1e2a0fee44c3645","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#597-612","gmt_create":"2026-04-30T07:19:20.4510137+04:00","gmt_modified":"2026-04-30T07:19:20.4510137+04:00"},{"id":40122,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"a8acea32ccb60d16cf2952aa7df51926","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4334-4438","gmt_create":"2026-04-30T07:19:20.4510137+04:00","gmt_modified":"2026-04-30T07:19:20.4510137+04:00"},{"id":40123,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"cda8d76def01f28cd58e347939faf10a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4420-4438","gmt_create":"2026-04-30T07:19:20.4510137+04:00","gmt_modified":"2026-04-30T07:19:20.4510137+04:00"},{"id":40124,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"119ec7c643a34aa808f9d5b76965a662","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4444-4450","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40125,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"02a1a9fcc78ccfc4daf328d04696eb6f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#114-124","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40126,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"9d352185df5c8af06a8101e30a248ef8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4360-4398","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40127,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"315cdb5412a417207b9932317c120c97","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4400-4419","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40128,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"450f29f1b3febed1e121639037851576","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#521-526","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40129,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"17c7b337a56de7a5cd94cd10c3d44dac","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4428-4430","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40130,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"48593fdf91cfb9e4763322dc0efe26f4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#565-656","gmt_create":"2026-04-30T07:19:20.4520126+04:00","gmt_modified":"2026-04-30T07:19:20.4520126+04:00"},{"id":40131,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"b71278e4b4266bb8a15b6aba152b2962","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#121","gmt_create":"2026-04-30T07:19:20.4533619+04:00","gmt_modified":"2026-04-30T07:19:20.4533619+04:00"},{"id":40132,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"065d68f5f096b48e88dfb2982a30bcb6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#114-146","gmt_create":"2026-04-30T07:19:20.4543635+04:00","gmt_modified":"2026-04-30T07:19:20.4543635+04:00"},{"id":40133,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"cde5dc8aa90fe3ae3c802f8d7f235e74","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#762-770","gmt_create":"2026-04-30T07:19:20.4553613+04:00","gmt_modified":"2026-04-30T07:19:20.4553613+04:00"},{"id":40134,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"cde5dc8aa90fe3ae3c802f8d7f235e74","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 762-770","gmt_create":"2026-04-30T07:19:20.4553613+04:00","gmt_modified":"2026-04-30T07:19:20.4553613+04:00"},{"id":40135,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"51d09407b23ce3234d4e1de6142dce78","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#739-771","gmt_create":"2026-04-30T07:19:20.4553613+04:00","gmt_modified":"2026-04-30T07:19:20.4553613+04:00"},{"id":40136,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"51d09407b23ce3234d4e1de6142dce78","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 739-771","gmt_create":"2026-04-30T07:19:20.4563617+04:00","gmt_modified":"2026-04-30T07:19:20.4563617+04:00"},{"id":40137,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"732bc579d5f86ebf0e986ecfbdfa490d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#34-46","gmt_create":"2026-04-30T07:19:20.4573626+04:00","gmt_modified":"2026-04-30T07:19:20.4573626+04:00"},{"id":40138,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"c2ea6047c89205fb8308a32ce5248eee","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#48-103","gmt_create":"2026-04-30T07:19:20.4583616+04:00","gmt_modified":"2026-04-30T07:19:20.4583616+04:00"},{"id":40139,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"811ab9cb3d00924a080202c009434d6e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1254-1298","gmt_create":"2026-04-30T07:19:20.4583616+04:00","gmt_modified":"2026-04-30T07:19:20.4583616+04:00"},{"id":40140,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"33f7190f97ab910175bbc25c0d3288cc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#38-46","gmt_create":"2026-04-30T07:19:20.4593614+04:00","gmt_modified":"2026-04-30T07:19:20.4593614+04:00"},{"id":40141,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"536ce91c592f598c4853f295a9a28e59","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#59-75","gmt_create":"2026-04-30T07:19:20.4593614+04:00","gmt_modified":"2026-04-30T07:19:20.4593614+04:00"},{"id":40142,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"49ed36643f75c3d722e6b1a2ff17d7e0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1390-1465","gmt_create":"2026-04-30T07:19:20.4603602+04:00","gmt_modified":"2026-04-30T07:19:20.4603602+04:00"},{"id":40143,"source_id":"0227cdb3-c369-47eb-9251-d897b9181340","target_id":"58d38a217f74656ded3587bd3c6f7dc1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#614-646","gmt_create":"2026-04-30T07:19:20.4603602+04:00","gmt_modified":"2026-04-30T07:19:20.4603602+04:00"},{"id":40144,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"f7dedf31e491c7adbaf05e957360c531","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-30T07:20:23.8055558+04:00","gmt_modified":"2026-04-30T07:20:23.8055558+04:00"},{"id":40145,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-30T07:20:23.8055558+04:00","gmt_modified":"2026-04-30T07:20:23.8055558+04:00"},{"id":40146,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"d72b348a2c3c7943e4a7abb7dbdaa751","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_connection.hpp","gmt_create":"2026-04-30T07:20:23.8055558+04:00","gmt_modified":"2026-04-30T07:20:23.8055558+04:00"},{"id":40147,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/peer_connection.cpp","gmt_create":"2026-04-30T07:20:23.8060738+04:00","gmt_modified":"2026-04-30T07:20:23.8060738+04:00"},{"id":40148,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"3a8d8a10556a0b6501e25aa43e91f913","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_database.hpp","gmt_create":"2026-04-30T07:20:23.8060738+04:00","gmt_modified":"2026-04-30T07:20:23.8060738+04:00"},{"id":40149,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b4467ca30cb6f6d587fc200900ee9ec9","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message.hpp","gmt_create":"2026-04-30T07:20:23.8060738+04:00","gmt_modified":"2026-04-30T07:20:23.8060738+04:00"},{"id":40150,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"c82262dc5275e094efc9474032d15922","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/config.hpp","gmt_create":"2026-04-30T07:20:23.8060738+04:00","gmt_modified":"2026-04-30T07:20:23.8060738+04:00"},{"id":40151,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"3a6c30f4bb3b265155c881ccfafa980e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/core_messages.hpp","gmt_create":"2026-04-30T07:20:23.8060738+04:00","gmt_modified":"2026-04-30T07:20:23.8060738+04:00"},{"id":40152,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"3948eb588d15d01acf21ffd439ec508c","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/exceptions.hpp","gmt_create":"2026-04-30T07:20:23.8065903+04:00","gmt_modified":"2026-04-30T07:20:23.8065903+04:00"},{"id":40153,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"398d9d4b02b6383c0b752cb0196a0475","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/stcp_socket.hpp","gmt_create":"2026-04-30T07:20:23.8065903+04:00","gmt_modified":"2026-04-30T07:20:23.8065903+04:00"},{"id":40154,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"6e587b97bf4080c7754c5ed73736fca7","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message_oriented_connection.hpp","gmt_create":"2026-04-30T07:20:23.8065903+04:00","gmt_modified":"2026-04-30T07:20:23.8065903+04:00"},{"id":40155,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-30T07:20:23.8065903+04:00","gmt_modified":"2026-04-30T07:20:23.8065903+04:00"},{"id":40156,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40157,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40158,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b4b9efd79d5b3c9fea00fccd613b2046","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/config.hpp","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40159,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40160,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40161,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"18555f254f50536a15d8591acf982406","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config.ini","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40162,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b1416c15172aac5cdff13f12c0d385b6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#180-355","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40163,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b2e1954ed604c23c3fe69090870838ec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#869-905","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40164,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"f85f57d0c6b461ab78f906ef6d5854c0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#79-354","gmt_create":"2026-04-30T07:20:23.8071051+04:00","gmt_modified":"2026-04-30T07:20:23.8071051+04:00"},{"id":40165,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"45d31a7a748eda3ea48f7df17810d4cb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#419-448","gmt_create":"2026-04-30T07:20:23.8081084+04:00","gmt_modified":"2026-04-30T07:20:23.8081084+04:00"},{"id":40166,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"45d31a7a748eda3ea48f7df17810d4cb","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 419-448","gmt_create":"2026-04-30T07:20:23.8081084+04:00","gmt_modified":"2026-04-30T07:20:23.8081084+04:00"},{"id":40167,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"7b85de17d00c3c33f3e6ce72493cea24","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#104-134","gmt_create":"2026-04-30T07:20:23.8081084+04:00","gmt_modified":"2026-04-30T07:20:23.8081084+04:00"},{"id":40168,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"4a47af89ac294fea1a93e8ab87bc62a8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#42-114","gmt_create":"2026-04-30T07:20:23.8096649+04:00","gmt_modified":"2026-04-30T07:20:23.8096649+04:00"},{"id":40169,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"fcf5471e2941c73aa4796f0d15ca9d4f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#111-120","gmt_create":"2026-04-30T07:20:23.8096649+04:00","gmt_modified":"2026-04-30T07:20:23.8096649+04:00"},{"id":40170,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"1d8a3a8529f55f725cbd52ef78db6a1f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4334-4463","gmt_create":"2026-04-30T07:20:23.8096649+04:00","gmt_modified":"2026-04-30T07:20:23.8096649+04:00"},{"id":40171,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"7d072be9a3f767f13a99da4cd6df4783","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#110-123","gmt_create":"2026-04-30T07:20:23.8096649+04:00","gmt_modified":"2026-04-30T07:20:23.8096649+04:00"},{"id":40172,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"4f55b43e070bade6f117e02d6faaaac2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#368-379","gmt_create":"2026-04-30T07:20:23.8096649+04:00","gmt_modified":"2026-04-30T07:20:23.8096649+04:00"},{"id":40173,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"5b2dac9b59c644b7b47e991af481e627","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#330-360","gmt_create":"2026-04-30T07:20:23.8096649+04:00","gmt_modified":"2026-04-30T07:20:23.8096649+04:00"},{"id":40174,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"d9910f1ea9014fede12810a52c49c4a4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#952-1047","gmt_create":"2026-04-30T07:20:23.8116686+04:00","gmt_modified":"2026-04-30T07:20:23.8116686+04:00"},{"id":40175,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"2d0f35d8035d544c59943ce9488d042e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1623-1654","gmt_create":"2026-04-30T07:20:23.8116686+04:00","gmt_modified":"2026-04-30T07:20:23.8116686+04:00"},{"id":40176,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"4cb233e3e08980c2c2c76e7762ad457b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2282-2350","gmt_create":"2026-04-30T07:20:23.8116686+04:00","gmt_modified":"2026-04-30T07:20:23.8116686+04:00"},{"id":40177,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"465b6e48aad6641d48e75039b1ba6cf7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#869-931","gmt_create":"2026-04-30T07:20:23.8116686+04:00","gmt_modified":"2026-04-30T07:20:23.8116686+04:00"},{"id":40178,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b06d8268387488f1ba89995b6c673d7f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1805-1865","gmt_create":"2026-04-30T07:20:23.8116686+04:00","gmt_modified":"2026-04-30T07:20:23.8116686+04:00"},{"id":40179,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"b06d8268387488f1ba89995b6c673d7f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1805-1865","gmt_create":"2026-04-30T07:20:23.8126692+04:00","gmt_modified":"2026-04-30T07:20:23.8126692+04:00"},{"id":40180,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"d1aff9a930eaeb19127cc400cd6df14b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5281-5320","gmt_create":"2026-04-30T07:20:23.8126692+04:00","gmt_modified":"2026-04-30T07:20:23.8126692+04:00"},{"id":40181,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"d1aff9a930eaeb19127cc400cd6df14b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5281-5320","gmt_create":"2026-04-30T07:20:23.8126692+04:00","gmt_modified":"2026-04-30T07:20:23.8126692+04:00"},{"id":40182,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"4f2bc001f6d8d7b016c08476699c545c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3396-3475","gmt_create":"2026-04-30T07:20:23.8126692+04:00","gmt_modified":"2026-04-30T07:20:23.8126692+04:00"},{"id":40183,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"4f2bc001f6d8d7b016c08476699c545c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3396-3475","gmt_create":"2026-04-30T07:20:23.8139658+04:00","gmt_modified":"2026-04-30T07:20:23.8139658+04:00"},{"id":40184,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"a3ff28e788c1b6c8bf86034c0dc8dfec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3280-3351","gmt_create":"2026-04-30T07:20:23.8139658+04:00","gmt_modified":"2026-04-30T07:20:23.8139658+04:00"},{"id":40185,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"a3ff28e788c1b6c8bf86034c0dc8dfec","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3280-3351","gmt_create":"2026-04-30T07:20:23.8139658+04:00","gmt_modified":"2026-04-30T07:20:23.8139658+04:00"},{"id":40186,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"fa40cce63b48435566df6c01e8048d9f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#428-448","gmt_create":"2026-04-30T07:20:23.8139658+04:00","gmt_modified":"2026-04-30T07:20:23.8139658+04:00"},{"id":40187,"source_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","target_id":"fa40cce63b48435566df6c01e8048d9f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 428-448","gmt_create":"2026-04-30T07:20:23.8149652+04:00","gmt_modified":"2026-04-30T07:20:23.8149652+04:00"},{"id":40188,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"1029e6d2173388f17be8ad02924bec51","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5321-5351","gmt_create":"2026-04-30T07:20:23.8149652+04:00","gmt_modified":"2026-04-30T07:20:23.8149652+04:00"},{"id":40189,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"1029e6d2173388f17be8ad02924bec51","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5321-5351","gmt_create":"2026-04-30T07:20:23.8149652+04:00","gmt_modified":"2026-04-30T07:20:23.8149652+04:00"},{"id":40190,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"9448259d344c27f9c6996ae60cf5aca0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#276-298","gmt_create":"2026-04-30T07:20:23.8149652+04:00","gmt_modified":"2026-04-30T07:20:23.8149652+04:00"},{"id":40191,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"9448259d344c27f9c6996ae60cf5aca0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 276-298","gmt_create":"2026-04-30T07:20:23.8149652+04:00","gmt_modified":"2026-04-30T07:20:23.8149652+04:00"},{"id":40192,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"64eac8af7d3939075658361c76c2a7f4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2029-2230","gmt_create":"2026-04-30T07:20:23.8159649+04:00","gmt_modified":"2026-04-30T07:20:23.8159649+04:00"},{"id":40193,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"63a72fb69b4b837d5841f87d654e6835","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2232-2250","gmt_create":"2026-04-30T07:20:23.8159649+04:00","gmt_modified":"2026-04-30T07:20:23.8159649+04:00"},{"id":40194,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"7d1df83c34f12b0c857fb327e688236b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1400-1621","gmt_create":"2026-04-30T07:20:23.816965+04:00","gmt_modified":"2026-04-30T07:20:23.816965+04:00"},{"id":40195,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"3497c413bfbe054ece4588e4c9764e38","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#79-80","gmt_create":"2026-04-30T07:20:23.816965+04:00","gmt_modified":"2026-04-30T07:20:23.816965+04:00"},{"id":40196,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"832f176e5f919dc138e93f3d848899bb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3117-3199","gmt_create":"2026-04-30T07:20:23.8179692+04:00","gmt_modified":"2026-04-30T07:20:23.8179692+04:00"},{"id":40197,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"d93f64e1090b7fb0448056ce453003b2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#200-294","gmt_create":"2026-04-30T07:20:23.8184785+04:00","gmt_modified":"2026-04-30T07:20:23.8184785+04:00"},{"id":40198,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"8422adca0104a424dd0293e0e0b74399","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#933-950","gmt_create":"2026-04-30T07:20:23.8184785+04:00","gmt_modified":"2026-04-30T07:20:23.8184785+04:00"},{"id":40199,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"f88975c9b8df22394830934bbd2b8fec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1686-1713","gmt_create":"2026-04-30T07:20:23.8184785+04:00","gmt_modified":"2026-04-30T07:20:23.8184785+04:00"},{"id":40200,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"140ecf3fedde255af2f0a4ce07a0edaa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#211-296","gmt_create":"2026-04-30T07:20:23.8184785+04:00","gmt_modified":"2026-04-30T07:20:23.8184785+04:00"},{"id":40201,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"2a546861a7d7c1c0f13a5e81a60a2663","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1788-1841","gmt_create":"2026-04-30T07:20:23.8184785+04:00","gmt_modified":"2026-04-30T07:20:23.8184785+04:00"},{"id":40202,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"e9dd01a70c4ccc4eff5ea6c67ea751d4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1326-1398","gmt_create":"2026-04-30T07:20:23.8184785+04:00","gmt_modified":"2026-04-30T07:20:23.8184785+04:00"},{"id":40203,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"e396600c03187cea5577399428a6bc7a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2830-2892","gmt_create":"2026-04-30T07:20:23.8194878+04:00","gmt_modified":"2026-04-30T07:20:23.8194878+04:00"},{"id":40204,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"4d84381505174d8f3190a51bf7d50f11","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#111-217","gmt_create":"2026-04-30T07:20:23.8194878+04:00","gmt_modified":"2026-04-30T07:20:23.8194878+04:00"},{"id":40205,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"6e13c0b7bd5f1729508c365e49c421e2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3413-3428","gmt_create":"2026-04-30T07:20:23.8204849+04:00","gmt_modified":"2026-04-30T07:20:23.8204849+04:00"},{"id":40206,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"6e13c0b7bd5f1729508c365e49c421e2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3413-3428","gmt_create":"2026-04-30T07:20:23.8204849+04:00","gmt_modified":"2026-04-30T07:20:23.8204849+04:00"},{"id":40207,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"50f93c7183f911eb73a238f8bcc6a562","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3355-3394","gmt_create":"2026-04-30T07:20:23.8204849+04:00","gmt_modified":"2026-04-30T07:20:23.8204849+04:00"},{"id":40208,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"50f93c7183f911eb73a238f8bcc6a562","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3355-3394","gmt_create":"2026-04-30T07:20:23.8204849+04:00","gmt_modified":"2026-04-30T07:20:23.8204849+04:00"},{"id":40209,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"90fd78bbe7cb7704cadf5d5f0735cb01","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3334-3351","gmt_create":"2026-04-30T07:20:23.8214833+04:00","gmt_modified":"2026-04-30T07:20:23.8214833+04:00"},{"id":40210,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"90fd78bbe7cb7704cadf5d5f0735cb01","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 3334-3351","gmt_create":"2026-04-30T07:20:23.8214833+04:00","gmt_modified":"2026-04-30T07:20:23.8214833+04:00"},{"id":40211,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"d9e91362d84f2e5c27205bacead0d8e5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2251-2280","gmt_create":"2026-04-30T07:20:23.8214833+04:00","gmt_modified":"2026-04-30T07:20:23.8214833+04:00"},{"id":40212,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"b0c24aab481fd56f8134b091b5bf0525","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2137-2168","gmt_create":"2026-04-30T07:20:23.8214833+04:00","gmt_modified":"2026-04-30T07:20:23.8214833+04:00"},{"id":40213,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"731063774f04d46e1f2c0d64963dfe33","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4455-4460","gmt_create":"2026-04-30T07:20:23.8229769+04:00","gmt_modified":"2026-04-30T07:20:23.8229769+04:00"},{"id":40214,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"209759d869965a0c3eba1e658fa84845","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#633-689","gmt_create":"2026-04-30T07:20:23.8229769+04:00","gmt_modified":"2026-04-30T07:20:23.8229769+04:00"},{"id":40215,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"672cdc6d6a7b32cf5afabd6fdbce20d3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3540-3562","gmt_create":"2026-04-30T07:20:23.8229769+04:00","gmt_modified":"2026-04-30T07:20:23.8229769+04:00"},{"id":40216,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"4c8e75baa374d11c9e8793b8772d0db9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3920-3940","gmt_create":"2026-04-30T07:20:23.8229769+04:00","gmt_modified":"2026-04-30T07:20:23.8229769+04:00"},{"id":40217,"source_id":"9dfed239-6d34-405d-a774-0a99f673e816","target_id":"a6bfdd40fb3a5836fbabdb5158fa34fe","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config.ini#103-108","gmt_create":"2026-04-30T07:20:23.8229769+04:00","gmt_modified":"2026-04-30T07:20:23.8229769+04:00"},{"id":40267,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"3926b37345d6d1e98bd8faf06e492e48","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 722-771","gmt_create":"2026-04-30T07:26:56.3700198+04:00","gmt_modified":"2026-04-30T07:26:56.3700198+04:00"},{"id":40315,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"f00d25f601ba380af2e6c7e1cc5c9bd5","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-6760","gmt_create":"2026-04-30T07:30:11.8225633+04:00","gmt_modified":"2026-04-30T07:30:11.8225633+04:00"},{"id":40337,"source_id":"cb10d0f5bb9015fb9ce5f22b75c10fed","target_id":"02d86b87b26250b29b6e613abead3df4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-78","gmt_create":"2026-04-30T07:30:11.8283385+04:00","gmt_modified":"2026-04-30T07:30:11.8283385+04:00"},{"id":40347,"source_id":"cb10d0f5bb9015fb9ce5f22b75c10fed","target_id":"b730f8795ffebcda8a8f085fd025dc0b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 72-78","gmt_create":"2026-04-30T07:30:11.8328416+04:00","gmt_modified":"2026-04-30T07:30:11.8328416+04:00"},{"id":40397,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"67e81d60b5109e19786b8bfbc4a80abd","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2281-2283","gmt_create":"2026-04-30T07:30:11.8585167+04:00","gmt_modified":"2026-04-30T07:30:11.8585167+04:00"},{"id":40399,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"09f8b4a6f66776095afcdc7473efbaf2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2466-2467","gmt_create":"2026-04-30T07:30:11.8585167+04:00","gmt_modified":"2026-04-30T07:30:11.8585167+04:00"},{"id":40401,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"9a263f97b2eae42c0e192fb1cfee8776","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2526-2527","gmt_create":"2026-04-30T07:30:11.8595163+04:00","gmt_modified":"2026-04-30T07:30:11.8595163+04:00"},{"id":40403,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"ea9495482bd4ecf48df793f9abf39cc1","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2536-2537","gmt_create":"2026-04-30T07:30:11.8595163+04:00","gmt_modified":"2026-04-30T07:30:11.8595163+04:00"},{"id":40405,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"47d651650f297dcd4ffcf47ee28027f2","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4536-4537","gmt_create":"2026-04-30T07:30:11.8605162+04:00","gmt_modified":"2026-04-30T07:30:11.8605162+04:00"},{"id":40407,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5452e208808a1bd3d416cced7e6d6f0a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4538-4539","gmt_create":"2026-04-30T07:30:11.8605162+04:00","gmt_modified":"2026-04-30T07:30:11.8605162+04:00"},{"id":40409,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"5affc96a80f081a4ef8bd4b5177ef1a8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4544-4545","gmt_create":"2026-04-30T07:30:11.8605162+04:00","gmt_modified":"2026-04-30T07:30:11.8605162+04:00"},{"id":40411,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"dd981f6523bfe5386b3d4a97ca575fdf","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4567-4568","gmt_create":"2026-04-30T07:30:11.8615165+04:00","gmt_modified":"2026-04-30T07:30:11.8615165+04:00"},{"id":40413,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e0ef3c7d489444d0a7bf6cd609a98c05","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4569-4570","gmt_create":"2026-04-30T07:30:11.8615165+04:00","gmt_modified":"2026-04-30T07:30:11.8615165+04:00"},{"id":40415,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"13ee871790dddd2f670c1493c6665f41","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4571-4572","gmt_create":"2026-04-30T07:30:11.8615165+04:00","gmt_modified":"2026-04-30T07:30:11.8615165+04:00"},{"id":40417,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"90331ddec7a3725739758ff2180d5b8e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 4573-4574","gmt_create":"2026-04-30T07:30:11.8626158+04:00","gmt_modified":"2026-04-30T07:30:11.8626158+04:00"},{"id":40419,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"654c2896a03be742e37554a614a0244c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5530-5531","gmt_create":"2026-04-30T07:30:11.8626158+04:00","gmt_modified":"2026-04-30T07:30:11.8626158+04:00"},{"id":40421,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"e9e61fd4b1cf8706755eb1dfa541ccc7","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5543-5544","gmt_create":"2026-04-30T07:30:11.8626158+04:00","gmt_modified":"2026-04-30T07:30:11.8626158+04:00"},{"id":40423,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"b7aaa429e07e8abc0d4999c4c496d854","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5677-5678","gmt_create":"2026-04-30T07:30:11.8626158+04:00","gmt_modified":"2026-04-30T07:30:11.8626158+04:00"},{"id":40425,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"1f43be6f2eea9de96bd24d20b575c860","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5680-5681","gmt_create":"2026-04-30T07:30:11.8626158+04:00","gmt_modified":"2026-04-30T07:30:11.8626158+04:00"},{"id":40427,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"d9dbf24fcc1a502e794d92f729bcd372","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 159-160","gmt_create":"2026-04-30T07:30:11.863616+04:00","gmt_modified":"2026-04-30T07:30:11.863616+04:00"},{"id":40430,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"abeb5e992b22812a0cdfc0c3dca91067","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 338-340","gmt_create":"2026-04-30T07:30:11.863616+04:00","gmt_modified":"2026-04-30T07:30:11.863616+04:00"},{"id":40432,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"956e60e52272d2047c3d89d0f1fe96a3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 356-357","gmt_create":"2026-04-30T07:30:11.863616+04:00","gmt_modified":"2026-04-30T07:30:11.863616+04:00"},{"id":40434,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"70499ce1f567f2564154072ea2576c3b","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 403-405","gmt_create":"2026-04-30T07:30:11.863616+04:00","gmt_modified":"2026-04-30T07:30:11.863616+04:00"},{"id":40436,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"aad86469ecab79c7481892069f6100df","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 411-412","gmt_create":"2026-04-30T07:30:11.8646163+04:00","gmt_modified":"2026-04-30T07:30:11.8646163+04:00"},{"id":40438,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"57d63528cfa5d7410afabeee4a42845e","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 416-417","gmt_create":"2026-04-30T07:30:11.8646163+04:00","gmt_modified":"2026-04-30T07:30:11.8646163+04:00"},{"id":40440,"source_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","target_id":"daa5dca768a6e2ed062d953de56c3dc6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 418-419","gmt_create":"2026-04-30T07:30:11.8646163+04:00","gmt_modified":"2026-04-30T07:30:11.8646163+04:00"},{"id":40442,"source_id":"cb10d0f5bb9015fb9ce5f22b75c10fed","target_id":"f0fa917f2268095d00a5c33920129393","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 48-78","gmt_create":"2026-04-30T07:30:11.8646163+04:00","gmt_modified":"2026-04-30T07:30:11.8646163+04:00"},{"id":40462,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"0227cdb3-c369-47eb-9251-d897b9181340","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e 0227cdb3-c369-47eb-9251-d897b9181340","gmt_create":"2026-04-30T07:30:12.4924554+04:00","gmt_modified":"2026-04-30T07:30:12.4924554+04:00"},{"id":40463,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"9dfed239-6d34-405d-a774-0a99f673e816","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ed702015-695e-4ef5-87fc-be4645f73987 -\u003e 9dfed239-6d34-405d-a774-0a99f673e816","gmt_create":"2026-04-30T07:30:12.4945148+04:00","gmt_modified":"2026-04-30T07:30:12.4945148+04:00"},{"id":40509,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"b372b2c20cc0d9d9abd9e2254bfefb17","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 576-602","gmt_create":"2026-04-30T07:46:04.9554747+04:00","gmt_modified":"2026-04-30T07:46:04.9554747+04:00"},{"id":40615,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"a1ef6908e910771a074aa8ccbc10adef","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 773-795","gmt_create":"2026-04-30T07:47:41.3416854+04:00","gmt_modified":"2026-04-30T07:47:41.3416854+04:00"},{"id":40638,"source_id":"18555f254f50536a15d8591acf982406","target_id":"463f1a4ea4700713bc6ca4afa0c86f4f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1-143","gmt_create":"2026-04-30T07:47:41.3533333+04:00","gmt_modified":"2026-04-30T07:47:41.3533333+04:00"},{"id":40639,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T08:00:09.9239373+04:00","gmt_modified":"2026-04-30T08:00:09.9239373+04:00"},{"id":40640,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T08:00:09.9249344+04:00","gmt_modified":"2026-04-30T08:00:09.9249344+04:00"},{"id":40641,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ee77bf4eb6bfbfb3636aa0bd57416552","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/chainbase/include/chainbase/chainbase.hpp","gmt_create":"2026-04-30T08:00:09.9249344+04:00","gmt_modified":"2026-04-30T08:00:09.9249344+04:00"},{"id":40642,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1ade3cebbc11a4634bcdf1a7fdb2756e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/chainbase/src/chainbase.cpp","gmt_create":"2026-04-30T08:00:09.9249344+04:00","gmt_modified":"2026-04-30T08:00:09.9249344+04:00"},{"id":40643,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"4238a9561f85e50a38f76813baeadd7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/block_log.hpp","gmt_create":"2026-04-30T08:00:09.9307197+04:00","gmt_modified":"2026-04-30T08:00:09.9307197+04:00"},{"id":40644,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"d2090ff9016be0d896d06e843936e0f4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/block_log.cpp","gmt_create":"2026-04-30T08:00:09.931604+04:00","gmt_modified":"2026-04-30T08:00:09.931604+04:00"},{"id":40645,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/dlt_block_log.hpp","gmt_create":"2026-04-30T08:00:09.9326076+04:00","gmt_modified":"2026-04-30T08:00:09.9326076+04:00"},{"id":40646,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-30T08:00:09.9326076+04:00","gmt_modified":"2026-04-30T08:00:09.9326076+04:00"},{"id":40647,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-30T08:00:09.9326076+04:00","gmt_modified":"2026-04-30T08:00:09.9326076+04:00"},{"id":40648,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T08:00:09.9336054+04:00","gmt_modified":"2026-04-30T08:00:09.9336054+04:00"},{"id":40649,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"cb29035725926be38d36ad8c01792b7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database_exceptions.hpp","gmt_create":"2026-04-30T08:00:09.9336054+04:00","gmt_modified":"2026-04-30T08:00:09.9336054+04:00"},{"id":40650,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"c4447af409b7f3205a55e5b286557dfe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.cpp","gmt_create":"2026-04-30T08:00:09.9336054+04:00","gmt_modified":"2026-04-30T08:00:09.9336054+04:00"},{"id":40651,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"57e07111ef7b80720c419255780e7ece","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/db_with.hpp","gmt_create":"2026-04-30T08:00:09.9336054+04:00","gmt_modified":"2026-04-30T08:00:09.9336054+04:00"},{"id":40652,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-30T08:00:09.9336054+04:00","gmt_modified":"2026-04-30T08:00:09.9336054+04:00"},{"id":40653,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"0dd2a38630da83b11fb3596ad4d60705","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/include/graphene/plugins/witness/witness.hpp","gmt_create":"2026-04-30T08:00:09.9336054+04:00","gmt_modified":"2026-04-30T08:00:09.9336054+04:00"},{"id":40654,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"b4b9efd79d5b3c9fea00fccd613b2046","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/config.hpp","gmt_create":"2026-04-30T08:00:09.9346027+04:00","gmt_modified":"2026-04-30T08:00:09.9346027+04:00"},{"id":40655,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-30T08:00:09.9346027+04:00","gmt_modified":"2026-04-30T08:00:09.9346027+04:00"},{"id":40656,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"3948eb588d15d01acf21ffd439ec508c","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/exceptions.hpp","gmt_create":"2026-04-30T08:00:09.9346027+04:00","gmt_modified":"2026-04-30T08:00:09.9346027+04:00"},{"id":40657,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T08:00:09.9346027+04:00","gmt_modified":"2026-04-30T08:00:09.9346027+04:00"},{"id":40658,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1ccc033e927b7ba78550b64f23026d0b","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/include/fc/exception/exception.hpp","gmt_create":"2026-04-30T08:00:09.9346027+04:00","gmt_modified":"2026-04-30T08:00:09.9346027+04:00"},{"id":40659,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"29a08e70168bdd56a054f1924f1f547c","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/exception.cpp","gmt_create":"2026-04-30T08:00:09.9346027+04:00","gmt_modified":"2026-04-30T08:00:09.9346027+04:00"},{"id":40660,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5ba4c9dfb14a49e5bdc63880653930c6","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/exceptions.hpp","gmt_create":"2026-04-30T08:00:09.9356026+04:00","gmt_modified":"2026-04-30T08:00:09.9356026+04:00"},{"id":40661,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"cb10d0f5bb9015fb9ce5f22b75c10fed","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/stacktrace.cpp","gmt_create":"2026-04-30T08:00:09.9356026+04:00","gmt_modified":"2026-04-30T08:00:09.9356026+04:00"},{"id":40662,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"18555f254f50536a15d8591acf982406","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config.ini","gmt_create":"2026-04-30T08:00:09.9356026+04:00","gmt_modified":"2026-04-30T08:00:09.9356026+04:00"},{"id":40663,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"95da2daaa0065c2230410221cb5b5dbd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#1-670","gmt_create":"2026-04-30T08:00:09.9356026+04:00","gmt_modified":"2026-04-30T08:00:09.9356026+04:00"},{"id":40664,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"f00d25f601ba380af2e6c7e1cc5c9bd5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-6760","gmt_create":"2026-04-30T08:00:09.9366048+04:00","gmt_modified":"2026-04-30T08:00:09.9366048+04:00"},{"id":40665,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"2ad3ade1c893ee16ff7640c396428e46","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/chainbase/include/chainbase/chainbase.hpp#1078-1120","gmt_create":"2026-04-30T08:00:09.9371112+04:00","gmt_modified":"2026-04-30T08:00:09.9371112+04:00"},{"id":40666,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"27490494b39fa9968667b183f60e215a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/chainbase/src/chainbase.cpp#1-200","gmt_create":"2026-04-30T08:00:09.9371112+04:00","gmt_modified":"2026-04-30T08:00:09.9371112+04:00"},{"id":40667,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"2c17018b7a7ecf1dd7b6432e97d0a586","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#1-75","gmt_create":"2026-04-30T08:00:09.9371112+04:00","gmt_modified":"2026-04-30T08:00:09.9371112+04:00"},{"id":40668,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"fef8201a8783aa794440fbd2f9b1ff17","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#1-302","gmt_create":"2026-04-30T08:00:09.9381172+04:00","gmt_modified":"2026-04-30T08:00:09.9381172+04:00"},{"id":40669,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"86991432e69878d08cc76b9d386b7d8f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#1-80","gmt_create":"2026-04-30T08:00:09.9381172+04:00","gmt_modified":"2026-04-30T08:00:09.9381172+04:00"},{"id":40670,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"cdc31aad39121f507723919db7d70dee","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#1-476","gmt_create":"2026-04-30T08:00:09.9381172+04:00","gmt_modified":"2026-04-30T08:00:09.9381172+04:00"},{"id":40671,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"590d1dfeeb49a4029c52d3c49b2f0362","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#1-144","gmt_create":"2026-04-30T08:00:09.9381172+04:00","gmt_modified":"2026-04-30T08:00:09.9381172+04:00"},{"id":40672,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"d599d8eb734647e07f075785246b447f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#1-278","gmt_create":"2026-04-30T08:00:09.9381172+04:00","gmt_modified":"2026-04-30T08:00:09.9381172+04:00"},{"id":40673,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ba99a3c0ac8bffedf50f03f7f8a09c06","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database_exceptions.hpp#1-136","gmt_create":"2026-04-30T08:00:09.9391252+04:00","gmt_modified":"2026-04-30T08:00:09.9391252+04:00"},{"id":40674,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"0397697ee2095dedcc01fa148c7079b9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/db_with.hpp#1-154","gmt_create":"2026-04-30T08:00:09.9391252+04:00","gmt_modified":"2026-04-30T08:00:09.9391252+04:00"},{"id":40675,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"b2901b8a7a24569b4a61c38b4448db06","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1180-1379","gmt_create":"2026-04-30T08:00:09.9391252+04:00","gmt_modified":"2026-04-30T08:00:09.9391252+04:00"},{"id":40676,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"77520f9eb913c04b5230e07f8f3f4ef8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#270-469","gmt_create":"2026-04-30T08:00:09.9391252+04:00","gmt_modified":"2026-04-30T08:00:09.9391252+04:00"},{"id":40677,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"340636e744f71de65b91146c5b8a20bd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/include/graphene/plugins/witness/witness.hpp#38-73","gmt_create":"2026-04-30T08:00:09.9391252+04:00","gmt_modified":"2026-04-30T08:00:09.9391252+04:00"},{"id":40678,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"c05f511b950fdae3b96260134880f8e4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#111-118","gmt_create":"2026-04-30T08:00:09.940122+04:00","gmt_modified":"2026-04-30T08:00:09.940122+04:00"},{"id":40679,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"101205728a0401f1dc07b5e31abe4b30","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3185-3384","gmt_create":"2026-04-30T08:00:09.940122+04:00","gmt_modified":"2026-04-30T08:00:09.940122+04:00"},{"id":40680,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"832619f974a664566d9bae6712c5e6a1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/exceptions.hpp#27-48","gmt_create":"2026-04-30T08:00:09.940122+04:00","gmt_modified":"2026-04-30T08:00:09.940122+04:00"},{"id":40681,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"8939bbbd6e1bb4a18e6bb534a037d0b8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#225-424","gmt_create":"2026-04-30T08:00:09.940122+04:00","gmt_modified":"2026-04-30T08:00:09.940122+04:00"},{"id":40682,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"67ee60a8299f84cdefc03cf72aeb2083","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/include/fc/exception/exception.hpp#177-215","gmt_create":"2026-04-30T08:00:09.940122+04:00","gmt_modified":"2026-04-30T08:00:09.940122+04:00"},{"id":40683,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"6b69ae0fdd0cccce913fc2d7aaa732b0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/exception.cpp#166-186","gmt_create":"2026-04-30T08:00:09.940122+04:00","gmt_modified":"2026-04-30T08:00:09.940122+04:00"},{"id":40684,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"7df358ffe779de2ba25c910aced557fd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/exceptions.hpp#21-46","gmt_create":"2026-04-30T08:00:09.9411218+04:00","gmt_modified":"2026-04-30T08:00:09.9411218+04:00"},{"id":40685,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"02d86b87b26250b29b6e613abead3df4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/stacktrace.cpp#1-78","gmt_create":"2026-04-30T08:00:09.9411218+04:00","gmt_modified":"2026-04-30T08:00:09.9411218+04:00"},{"id":40686,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"b437e864a36924899b70d6b9295e4ac0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#61-115","gmt_create":"2026-04-30T08:00:09.943629+04:00","gmt_modified":"2026-04-30T08:00:09.943629+04:00"},{"id":40687,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"476cdc272b600cf1a7bfe2b524767872","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#281-324","gmt_create":"2026-04-30T08:00:09.943629+04:00","gmt_modified":"2026-04-30T08:00:09.943629+04:00"},{"id":40688,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5c73e14f98ff45ef03bb3a7c9e413218","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/block_log.hpp#38-75","gmt_create":"2026-04-30T08:00:09.943629+04:00","gmt_modified":"2026-04-30T08:00:09.943629+04:00"},{"id":40689,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"6742da18bac301be1056c8e6e5adcf69","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#35-72","gmt_create":"2026-04-30T08:00:09.943629+04:00","gmt_modified":"2026-04-30T08:00:09.943629+04:00"},{"id":40690,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"268918c988500bd0972ad80cef38bdd3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#53-144","gmt_create":"2026-04-30T08:00:09.9446302+04:00","gmt_modified":"2026-04-30T08:00:09.9446302+04:00"},{"id":40691,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"6d7b6e4198a348bc27c0bb28230cb3f9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#929-984","gmt_create":"2026-04-30T08:00:09.9446302+04:00","gmt_modified":"2026-04-30T08:00:09.9446302+04:00"},{"id":40692,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"186fa6ce927d55c0b153413a2981237e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/db_with.hpp#33-100","gmt_create":"2026-04-30T08:00:09.9446302+04:00","gmt_modified":"2026-04-30T08:00:09.9446302+04:00"},{"id":40693,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"99a2db97d03705f31a3929010634a458","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/chainbase/src/chainbase.cpp#225-279","gmt_create":"2026-04-30T08:00:09.9446302+04:00","gmt_modified":"2026-04-30T08:00:09.9446302+04:00"},{"id":40694,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"b730f8795ffebcda8a8f085fd025dc0b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/stacktrace.cpp#72-78","gmt_create":"2026-04-30T08:00:09.9456313+04:00","gmt_modified":"2026-04-30T08:00:09.9456313+04:00"},{"id":40695,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"30a9101c41868617ba6f2a2daa15b849","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#94-184","gmt_create":"2026-04-30T08:00:09.9456313+04:00","gmt_modified":"2026-04-30T08:00:09.9456313+04:00"},{"id":40696,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"3e02993eaec7af3cec0bfb1f83a665d3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database_exceptions.hpp#83","gmt_create":"2026-04-30T08:00:09.9466291+04:00","gmt_modified":"2026-04-30T08:00:09.9466291+04:00"},{"id":40697,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"be8f40bd072828763bbdf031b6f1620e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database_exceptions.hpp#122","gmt_create":"2026-04-30T08:00:09.9466291+04:00","gmt_modified":"2026-04-30T08:00:09.9466291+04:00"},{"id":40698,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"99f54a4b93eefbb48ed1667eff0fa9d3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#330-410","gmt_create":"2026-04-30T08:00:09.9466291+04:00","gmt_modified":"2026-04-30T08:00:09.9466291+04:00"},{"id":40699,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"408bde042901c8590a87dffdf56f7b44","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#134-184","gmt_create":"2026-04-30T08:00:09.9476287+04:00","gmt_modified":"2026-04-30T08:00:09.9476287+04:00"},{"id":40700,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e69115f35f4572b29fb65e977de046e0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#503-519","gmt_create":"2026-04-30T08:00:09.9476287+04:00","gmt_modified":"2026-04-30T08:00:09.9476287+04:00"},{"id":40701,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"732bc579d5f86ebf0e986ecfbdfa490d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#34-46","gmt_create":"2026-04-30T08:00:09.9491337+04:00","gmt_modified":"2026-04-30T08:00:09.9491337+04:00"},{"id":40702,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"27593eff1e7989c53fb119e30b38a106","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#81-88","gmt_create":"2026-04-30T08:00:09.9491337+04:00","gmt_modified":"2026-04-30T08:00:09.9491337+04:00"},{"id":40703,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"6fb08c879f27957a5d358cff4532c031","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1440-1500","gmt_create":"2026-04-30T08:00:09.9491337+04:00","gmt_modified":"2026-04-30T08:00:09.9491337+04:00"},{"id":40704,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"42f304cea32d3254d3d28d390f99a4f4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1216-1286","gmt_create":"2026-04-30T08:00:09.9491337+04:00","gmt_modified":"2026-04-30T08:00:09.9491337+04:00"},{"id":40705,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"4c7480a472e5a837a77edfce9accd782","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1360-1380","gmt_create":"2026-04-30T08:00:09.9501373+04:00","gmt_modified":"2026-04-30T08:00:09.9501373+04:00"},{"id":40706,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"21021513e3f47136fbd9b77f25ac0dda","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#175-192","gmt_create":"2026-04-30T08:00:09.9501373+04:00","gmt_modified":"2026-04-30T08:00:09.9501373+04:00"},{"id":40707,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"978bc8c4cf93eea58a5004412f5a0740","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3192-3211","gmt_create":"2026-04-30T08:00:09.9501373+04:00","gmt_modified":"2026-04-30T08:00:09.9501373+04:00"},{"id":40708,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"9a7186666130ca998f114a82a0decdff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#181-196","gmt_create":"2026-04-30T08:00:09.9511373+04:00","gmt_modified":"2026-04-30T08:00:09.9511373+04:00"},{"id":40709,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ac1d251c502e1e4341518133a47346b6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1556-1588","gmt_create":"2026-04-30T08:00:09.9511373+04:00","gmt_modified":"2026-04-30T08:00:09.9511373+04:00"},{"id":40710,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"03c29e794349e78edd559069a4ea589f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1593-1594","gmt_create":"2026-04-30T08:00:09.9511373+04:00","gmt_modified":"2026-04-30T08:00:09.9511373+04:00"},{"id":40711,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"bc5ca04bd394cfbe05b91187be1ef3e1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#271-300","gmt_create":"2026-04-30T08:00:09.9511373+04:00","gmt_modified":"2026-04-30T08:00:09.9511373+04:00"},{"id":40712,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"32c3cd87cb292dbfecb6d81dc0fc6739","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#506-507","gmt_create":"2026-04-30T08:00:09.9521397+04:00","gmt_modified":"2026-04-30T08:00:09.9521397+04:00"},{"id":40713,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ba68b0a6c770e5eb7e9c206147e04add","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#232-243","gmt_create":"2026-04-30T08:00:09.9521397+04:00","gmt_modified":"2026-04-30T08:00:09.9521397+04:00"},{"id":40714,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"a0ef31a365b500c015295fa0a77cb02a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#639-673","gmt_create":"2026-04-30T08:00:09.9521397+04:00","gmt_modified":"2026-04-30T08:00:09.9521397+04:00"},{"id":40715,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"bad0cf5ac8c5e25ab7697c72386f0c6e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#562-605","gmt_create":"2026-04-30T08:00:09.9531401+04:00","gmt_modified":"2026-04-30T08:00:09.9531401+04:00"},{"id":40716,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"145b14d90766df6ec3acffdb3b52a1a7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#412-422","gmt_create":"2026-04-30T08:00:09.9531401+04:00","gmt_modified":"2026-04-30T08:00:09.9531401+04:00"},{"id":40717,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5ae77d32160a48045d586a25c4949703","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#454-482","gmt_create":"2026-04-30T08:00:09.9531401+04:00","gmt_modified":"2026-04-30T08:00:09.9531401+04:00"},{"id":40718,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"fb42540947b6b7d56487ccd9ec782f86","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#148-164","gmt_create":"2026-04-30T08:00:09.9531401+04:00","gmt_modified":"2026-04-30T08:00:09.9531401+04:00"},{"id":40719,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"4eb8ce2a423658c16ae4df90aeba2184","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#546-556","gmt_create":"2026-04-30T08:00:09.9531401+04:00","gmt_modified":"2026-04-30T08:00:09.9531401+04:00"},{"id":40720,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"0172b0ea2a031177f1c72341a3922614","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#631-632","gmt_create":"2026-04-30T08:00:09.9531401+04:00","gmt_modified":"2026-04-30T08:00:09.9531401+04:00"},{"id":40721,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e8dd15ab10626a2ce715fe8c3f03ca85","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1106-1145","gmt_create":"2026-04-30T08:00:09.954648+04:00","gmt_modified":"2026-04-30T08:00:09.954648+04:00"},{"id":40722,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"8509be38b1ab9f14d108445df0dcd2f1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1460-1470","gmt_create":"2026-04-30T08:00:09.954648+04:00","gmt_modified":"2026-04-30T08:00:09.954648+04:00"},{"id":40723,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"d1e1e1bc28ff1dfbd77617edbf4a23b0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1295-1377","gmt_create":"2026-04-30T08:00:09.955652+04:00","gmt_modified":"2026-04-30T08:00:09.955652+04:00"},{"id":40724,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"929cc0269a5814a951b45c3300f44597","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#789-827","gmt_create":"2026-04-30T08:00:09.955652+04:00","gmt_modified":"2026-04-30T08:00:09.955652+04:00"},{"id":40725,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5e4bd94274850728b00115526a2479ee","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#860-882","gmt_create":"2026-04-30T08:00:09.955652+04:00","gmt_modified":"2026-04-30T08:00:09.955652+04:00"},{"id":40726,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"c53a6db24cea4db25b30783459c40125","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#884-901","gmt_create":"2026-04-30T08:00:09.955652+04:00","gmt_modified":"2026-04-30T08:00:09.955652+04:00"},{"id":40727,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"6abfbb22aee54a4b386173dfc74da72b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5452-5482","gmt_create":"2026-04-30T08:00:09.9566529+04:00","gmt_modified":"2026-04-30T08:00:09.9566529+04:00"},{"id":40728,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"2ba1e391be7a79f5151c8abeb655315d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5467-5480","gmt_create":"2026-04-30T08:00:09.9566529+04:00","gmt_modified":"2026-04-30T08:00:09.9566529+04:00"},{"id":40729,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"17b75751a836de13af2093485914a80b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1147-1202","gmt_create":"2026-04-30T08:00:09.9566529+04:00","gmt_modified":"2026-04-30T08:00:09.9566529+04:00"},{"id":40730,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"3fd79dc7253b83f3fd4a1db36b691537","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#79-96","gmt_create":"2026-04-30T08:00:09.9566529+04:00","gmt_modified":"2026-04-30T08:00:09.9566529+04:00"},{"id":40731,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"fc817fa1448591c306f1d8c94a4912b0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#340-350","gmt_create":"2026-04-30T08:00:09.9576523+04:00","gmt_modified":"2026-04-30T08:00:09.9576523+04:00"},{"id":40732,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"2b22b083099e12af5fb5eff69ac7c45b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4346-4366","gmt_create":"2026-04-30T08:00:09.9576523+04:00","gmt_modified":"2026-04-30T08:00:09.9576523+04:00"},{"id":40733,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ad0c51e89fbda89a4c7ba4a8b1c23f6e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#948-970","gmt_create":"2026-04-30T08:00:09.9576523+04:00","gmt_modified":"2026-04-30T08:00:09.9576523+04:00"},{"id":40734,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1bc38fc420c17ebaaf14eb62dd9dd77e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3652-3711","gmt_create":"2026-04-30T08:00:09.9576523+04:00","gmt_modified":"2026-04-30T08:00:09.9576523+04:00"},{"id":40735,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"bf868153550f3b995f99ef4e396ddd77","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3444-3499","gmt_create":"2026-04-30T08:00:09.9576523+04:00","gmt_modified":"2026-04-30T08:00:09.9576523+04:00"},{"id":40736,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e5c1c7808f2985bb68bc174eb390298a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#218-224","gmt_create":"2026-04-30T08:00:09.9586524+04:00","gmt_modified":"2026-04-30T08:00:09.9586524+04:00"},{"id":40737,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1198bd7cc69669237a261463e8cd3c9d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3986-4039","gmt_create":"2026-04-30T08:00:09.9586524+04:00","gmt_modified":"2026-04-30T08:00:09.9586524+04:00"},{"id":40738,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"a8eaae161939961c892a4e5ff9b1b68f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4144-4175","gmt_create":"2026-04-30T08:00:09.9586524+04:00","gmt_modified":"2026-04-30T08:00:09.9586524+04:00"},{"id":40739,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e6c849182922d3df412daeb900b9e173","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#284-307","gmt_create":"2026-04-30T08:00:09.9597938+04:00","gmt_modified":"2026-04-30T08:00:09.9597938+04:00"},{"id":40740,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"05db694a94704c3e51ed70ce0118a1da","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1158-1198","gmt_create":"2026-04-30T08:00:09.9597938+04:00","gmt_modified":"2026-04-30T08:00:09.9597938+04:00"},{"id":40741,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e6a9a48c0e930773f0a82fa246c53f98","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3652-3655","gmt_create":"2026-04-30T08:00:09.960297+04:00","gmt_modified":"2026-04-30T08:00:09.960297+04:00"},{"id":40742,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"dbf1a067fe97d23702c1dcb8e2df53b6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1890-1892","gmt_create":"2026-04-30T08:00:09.960297+04:00","gmt_modified":"2026-04-30T08:00:09.960297+04:00"},{"id":40743,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"67e81d60b5109e19786b8bfbc4a80abd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2281-2283","gmt_create":"2026-04-30T08:00:09.960297+04:00","gmt_modified":"2026-04-30T08:00:09.960297+04:00"},{"id":40744,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"09f8b4a6f66776095afcdc7473efbaf2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2466-2467","gmt_create":"2026-04-30T08:00:09.960297+04:00","gmt_modified":"2026-04-30T08:00:09.960297+04:00"},{"id":40745,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"9a263f97b2eae42c0e192fb1cfee8776","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2526-2527","gmt_create":"2026-04-30T08:00:09.960297+04:00","gmt_modified":"2026-04-30T08:00:09.960297+04:00"},{"id":40746,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"ea9495482bd4ecf48df793f9abf39cc1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2536-2537","gmt_create":"2026-04-30T08:00:09.960297+04:00","gmt_modified":"2026-04-30T08:00:09.960297+04:00"},{"id":40747,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"47d651650f297dcd4ffcf47ee28027f2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4536-4537","gmt_create":"2026-04-30T08:00:09.9613004+04:00","gmt_modified":"2026-04-30T08:00:09.9613004+04:00"},{"id":40748,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5452e208808a1bd3d416cced7e6d6f0a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4538-4539","gmt_create":"2026-04-30T08:00:09.9613004+04:00","gmt_modified":"2026-04-30T08:00:09.9613004+04:00"},{"id":40749,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5affc96a80f081a4ef8bd4b5177ef1a8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4544-4545","gmt_create":"2026-04-30T08:00:09.9613004+04:00","gmt_modified":"2026-04-30T08:00:09.9613004+04:00"},{"id":40750,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"dd981f6523bfe5386b3d4a97ca575fdf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4567-4568","gmt_create":"2026-04-30T08:00:09.9613004+04:00","gmt_modified":"2026-04-30T08:00:09.9613004+04:00"},{"id":40751,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e0ef3c7d489444d0a7bf6cd609a98c05","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4569-4570","gmt_create":"2026-04-30T08:00:09.9613004+04:00","gmt_modified":"2026-04-30T08:00:09.9613004+04:00"},{"id":40752,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"13ee871790dddd2f670c1493c6665f41","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4571-4572","gmt_create":"2026-04-30T08:00:09.9613004+04:00","gmt_modified":"2026-04-30T08:00:09.9613004+04:00"},{"id":40753,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"90331ddec7a3725739758ff2180d5b8e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4573-4574","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40754,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"654c2896a03be742e37554a614a0244c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5530-5531","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40755,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"e9e61fd4b1cf8706755eb1dfa541ccc7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5543-5544","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40756,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"b7aaa429e07e8abc0d4999c4c496d854","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5677-5678","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40757,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1f43be6f2eea9de96bd24d20b575c860","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5680-5681","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40758,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"d9dbf24fcc1a502e794d92f729bcd372","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#159-160","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40759,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"2b851e123aa78f2afb16a52472781e64","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#228-233","gmt_create":"2026-04-30T08:00:09.9623005+04:00","gmt_modified":"2026-04-30T08:00:09.9623005+04:00"},{"id":40760,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"abeb5e992b22812a0cdfc0c3dca91067","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#338-340","gmt_create":"2026-04-30T08:00:09.9633004+04:00","gmt_modified":"2026-04-30T08:00:09.9633004+04:00"},{"id":40761,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"956e60e52272d2047c3d89d0f1fe96a3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#356-357","gmt_create":"2026-04-30T08:00:09.9633004+04:00","gmt_modified":"2026-04-30T08:00:09.9633004+04:00"},{"id":40762,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"70499ce1f567f2564154072ea2576c3b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#403-405","gmt_create":"2026-04-30T08:00:09.9633004+04:00","gmt_modified":"2026-04-30T08:00:09.9633004+04:00"},{"id":40763,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"aad86469ecab79c7481892069f6100df","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#411-412","gmt_create":"2026-04-30T08:00:09.9633004+04:00","gmt_modified":"2026-04-30T08:00:09.9633004+04:00"},{"id":40764,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"57d63528cfa5d7410afabeee4a42845e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#416-417","gmt_create":"2026-04-30T08:00:09.9633004+04:00","gmt_modified":"2026-04-30T08:00:09.9633004+04:00"},{"id":40765,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"daa5dca768a6e2ed062d953de56c3dc6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#418-419","gmt_create":"2026-04-30T08:00:09.9633004+04:00","gmt_modified":"2026-04-30T08:00:09.9633004+04:00"},{"id":40766,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"f0fa917f2268095d00a5c33920129393","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/stacktrace.cpp#48-78","gmt_create":"2026-04-30T08:00:09.9643004+04:00","gmt_modified":"2026-04-30T08:00:09.9643004+04:00"},{"id":40767,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"321d8cd55620ec252c0b570fa95fd592","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5092-5105","gmt_create":"2026-04-30T08:00:09.9643004+04:00","gmt_modified":"2026-04-30T08:00:09.9643004+04:00"},{"id":40768,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"321d8cd55620ec252c0b570fa95fd592","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5092-5105","gmt_create":"2026-04-30T08:00:09.9643004+04:00","gmt_modified":"2026-04-30T08:00:09.9643004+04:00"},{"id":40769,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"118584993a6d2e21fefe6c8f98dbe2f6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5283-5297","gmt_create":"2026-04-30T08:00:09.9643004+04:00","gmt_modified":"2026-04-30T08:00:09.9643004+04:00"},{"id":40770,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"118584993a6d2e21fefe6c8f98dbe2f6","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5283-5297","gmt_create":"2026-04-30T08:00:09.9708292+04:00","gmt_modified":"2026-04-30T08:00:09.9708292+04:00"},{"id":40771,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"26650c218a6c596d28668797b6045c8c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5616-5635","gmt_create":"2026-04-30T08:00:09.9708292+04:00","gmt_modified":"2026-04-30T08:00:09.9708292+04:00"},{"id":40772,"source_id":"a5661951a63a8a4cb0a563b6ff08335e","target_id":"26650c218a6c596d28668797b6045c8c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 5616-5635","gmt_create":"2026-04-30T08:00:09.9708292+04:00","gmt_modified":"2026-04-30T08:00:09.9708292+04:00"},{"id":40773,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"bd530cb9e845767f0a9b3ff967997984","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#93-141","gmt_create":"2026-04-30T08:00:09.9708292+04:00","gmt_modified":"2026-04-30T08:00:09.9708292+04:00"},{"id":40774,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"f10623a85ff8726287ec33204df6d61a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#458-584","gmt_create":"2026-04-30T08:00:09.9708292+04:00","gmt_modified":"2026-04-30T08:00:09.9708292+04:00"},{"id":40775,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1d8a3a8529f55f725cbd52ef78db6a1f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4334-4463","gmt_create":"2026-04-30T08:00:09.9723323+04:00","gmt_modified":"2026-04-30T08:00:09.9723323+04:00"},{"id":40776,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"8f391118507fdd830a986c86e989a317","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2047-2144","gmt_create":"2026-04-30T08:00:09.9724388+04:00","gmt_modified":"2026-04-30T08:00:09.9724388+04:00"},{"id":40777,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"fa62adcb3d13201bfc9729dde67be04f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4378-4416","gmt_create":"2026-04-30T08:00:09.9724388+04:00","gmt_modified":"2026-04-30T08:00:09.9724388+04:00"},{"id":40778,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"98ed598d82d28fa1553560148bcda24e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2125-2142","gmt_create":"2026-04-30T08:00:09.9724388+04:00","gmt_modified":"2026-04-30T08:00:09.9724388+04:00"},{"id":40779,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"5a5c235262f9722ff7bdf0586bfd8e26","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4220-4230","gmt_create":"2026-04-30T08:00:09.9733359+04:00","gmt_modified":"2026-04-30T08:00:09.9733359+04:00"},{"id":40780,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"a7270a78978a13ee55ddc7f94813e3df","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1680-1693","gmt_create":"2026-04-30T08:00:09.9733359+04:00","gmt_modified":"2026-04-30T08:00:09.9733359+04:00"},{"id":40781,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"a7f5c35a309d6a9851c0491c125c63d9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3224-3236","gmt_create":"2026-04-30T08:00:09.9733359+04:00","gmt_modified":"2026-04-30T08:00:09.9733359+04:00"},{"id":40782,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"2f5c46c8352dc73f0b90e01cef1bcc9d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#3272-3284","gmt_create":"2026-04-30T08:00:09.9733359+04:00","gmt_modified":"2026-04-30T08:00:09.9733359+04:00"},{"id":40783,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"26495d5b0fffb6fefe7f8c69d291521e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#738-742","gmt_create":"2026-04-30T08:00:09.9733359+04:00","gmt_modified":"2026-04-30T08:00:09.9733359+04:00"},{"id":40784,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"6c94b84fdfd5c7016b5eeadf8099133e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/plugin.cpp","gmt_create":"2026-04-30T08:00:09.974339+04:00","gmt_modified":"2026-04-30T08:00:09.974339+04:00"},{"id":40785,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"3bdfaf9a009f1c5236532831eedfa66b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#760-770","gmt_create":"2026-04-30T08:00:09.974339+04:00","gmt_modified":"2026-04-30T08:00:09.974339+04:00"},{"id":40786,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"39b6820362e87b0f937201fb8f54dee6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#1-10","gmt_create":"2026-04-30T08:00:09.974339+04:00","gmt_modified":"2026-04-30T08:00:09.974339+04:00"},{"id":40787,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"1efc9fdbd77068cf27cb00354deff17a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-30","gmt_create":"2026-04-30T08:00:09.974339+04:00","gmt_modified":"2026-04-30T08:00:09.974339+04:00"},{"id":40788,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"d0e2616c3e70f0e809256dcb448916ab","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#270-279","gmt_create":"2026-04-30T08:00:09.9763358+04:00","gmt_modified":"2026-04-30T08:00:09.9763358+04:00"},{"id":40789,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"0855c233a79de418e4577d44bd54b9ba","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#492-501","gmt_create":"2026-04-30T08:00:09.9763358+04:00","gmt_modified":"2026-04-30T08:00:09.9763358+04:00"},{"id":40790,"source_id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","target_id":"295c418f-33b4-4c06-80cf-224d6b633a76","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31 -\u003e 295c418f-33b4-4c06-80cf-224d6b633a76","gmt_create":"2026-04-30T08:00:15.1059847+04:00","gmt_modified":"2026-04-30T08:00:15.1059847+04:00"},{"id":40928,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"82dadfd972805d70bffa99ce756460c4","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 307-327","gmt_create":"2026-04-30T08:47:09.8556473+04:00","gmt_modified":"2026-04-30T08:47:09.8556473+04:00"},{"id":40930,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"ee32480b501d0e9284fc1a7aa7d20b4c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 1042-1113","gmt_create":"2026-04-30T08:47:09.8556473+04:00","gmt_modified":"2026-04-30T08:47:09.8556473+04:00"},{"id":40947,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"f12cdb54f6351fe38c307c4907f93868","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 783-791","gmt_create":"2026-04-30T08:47:09.8598101+04:00","gmt_modified":"2026-04-30T08:47:09.8598101+04:00"},{"id":40949,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"8c9490ebae702a4725d10dc509074042","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 812-816","gmt_create":"2026-04-30T08:47:09.8603579+04:00","gmt_modified":"2026-04-30T08:47:09.8603579+04:00"},{"id":40973,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/dlt_block_log.hpp","gmt_create":"2026-04-30T11:11:44.1777537+04:00","gmt_modified":"2026-04-30T11:11:44.1777537+04:00"},{"id":40974,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-30T11:11:44.179257+04:00","gmt_modified":"2026-04-30T11:11:44.179257+04:00"},{"id":40975,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"d2090ff9016be0d896d06e843936e0f4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/block_log.cpp","gmt_create":"2026-04-30T11:11:44.179257+04:00","gmt_modified":"2026-04-30T11:11:44.179257+04:00"},{"id":40976,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T11:11:44.179257+04:00","gmt_modified":"2026-04-30T11:11:44.179257+04:00"},{"id":40977,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T11:11:44.1802619+04:00","gmt_modified":"2026-04-30T11:11:44.1802619+04:00"},{"id":40978,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"6c94b84fdfd5c7016b5eeadf8099133e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/plugin.cpp","gmt_create":"2026-04-30T11:11:44.1802619+04:00","gmt_modified":"2026-04-30T11:11:44.1802619+04:00"},{"id":40979,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"c4447af409b7f3205a55e5b286557dfe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.cpp","gmt_create":"2026-04-30T11:11:44.1802619+04:00","gmt_modified":"2026-04-30T11:11:44.1802619+04:00"},{"id":40980,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T11:11:44.1807664+04:00","gmt_modified":"2026-04-30T11:11:44.1807664+04:00"},{"id":40981,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T11:11:44.1807664+04:00","gmt_modified":"2026-04-30T11:11:44.1807664+04:00"},{"id":40982,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"8c1b3bca4f56b317e9e0acdb4be83a00","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#1-89","gmt_create":"2026-04-30T11:11:44.1807664+04:00","gmt_modified":"2026-04-30T11:11:44.1807664+04:00"},{"id":40983,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"a3ae1dbcc1c9bd6ef59dec7fb6a6e6be","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#1-582","gmt_create":"2026-04-30T11:11:44.1817708+04:00","gmt_modified":"2026-04-30T11:11:44.1817708+04:00"},{"id":40984,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"fef8201a8783aa794440fbd2f9b1ff17","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#1-302","gmt_create":"2026-04-30T11:11:44.1817708+04:00","gmt_modified":"2026-04-30T11:11:44.1817708+04:00"},{"id":40985,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"233a2d8be5340ec151c42900fcd58a96","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#220-271","gmt_create":"2026-04-30T11:11:44.1827706+04:00","gmt_modified":"2026-04-30T11:11:44.1827706+04:00"},{"id":40986,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"02b88f06fab1f8f2b384ec38dfea95a8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#1-258","gmt_create":"2026-04-30T11:11:44.1827706+04:00","gmt_modified":"2026-04-30T11:11:44.1827706+04:00"},{"id":40987,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"c6c440b24137364c19bc7fe7a800ce3e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#320-330","gmt_create":"2026-04-30T11:11:44.1837705+04:00","gmt_modified":"2026-04-30T11:11:44.1837705+04:00"},{"id":40988,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"7c7114ff694eca47458deb9031af3874","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1960-2039","gmt_create":"2026-04-30T11:11:44.1837705+04:00","gmt_modified":"2026-04-30T11:11:44.1837705+04:00"},{"id":40989,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"5c9a153931730742e72d3183f4f76128","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#255-286","gmt_create":"2026-04-30T11:11:44.1837705+04:00","gmt_modified":"2026-04-30T11:11:44.1837705+04:00"},{"id":40990,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"77740b8f659f6e4296624292e035f0b6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#515-516","gmt_create":"2026-04-30T11:11:44.1847704+04:00","gmt_modified":"2026-04-30T11:11:44.1847704+04:00"},{"id":40991,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"445ddb5941dc16c8a6313b9ec556f75a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#35-89","gmt_create":"2026-04-30T11:11:44.1857959+04:00","gmt_modified":"2026-04-30T11:11:44.1857959+04:00"},{"id":40992,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"44c647e6bdaeba8176c9fd58d7bf204f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#18-278","gmt_create":"2026-04-30T11:11:44.1857959+04:00","gmt_modified":"2026-04-30T11:11:44.1857959+04:00"},{"id":40993,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"1fec0c281b15b8cb253758a81bda9d53","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#230-231","gmt_create":"2026-04-30T11:11:44.186796+04:00","gmt_modified":"2026-04-30T11:11:44.186796+04:00"},{"id":40994,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"20be4db8cc88d28b2576cc8c6640d89d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#24-28","gmt_create":"2026-04-30T11:11:44.186796+04:00","gmt_modified":"2026-04-30T11:11:44.186796+04:00"},{"id":40995,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"4aaf8248e16bc831caf0d6215227a347","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#327-329","gmt_create":"2026-04-30T11:11:44.186796+04:00","gmt_modified":"2026-04-30T11:11:44.186796+04:00"},{"id":40996,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"2496b05edb9e2cce32ff03d614866f80","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1968-1970","gmt_create":"2026-04-30T11:11:44.1877958+04:00","gmt_modified":"2026-04-30T11:11:44.1877958+04:00"},{"id":40997,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"bd936d7f114a4d7505d38477591a432b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#265-272","gmt_create":"2026-04-30T11:11:44.1887957+04:00","gmt_modified":"2026-04-30T11:11:44.1887957+04:00"},{"id":40998,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"09e49fa0e3657a309d205da532bae0d4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1414-1500","gmt_create":"2026-04-30T11:11:44.1887957+04:00","gmt_modified":"2026-04-30T11:11:44.1887957+04:00"},{"id":40999,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"89fd5aa6e93582e3684130e9c0920e33","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#438-544","gmt_create":"2026-04-30T11:11:44.1887957+04:00","gmt_modified":"2026-04-30T11:11:44.1887957+04:00"},{"id":41000,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"733f077750798ad212e3907d2e15c841","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#835-858","gmt_create":"2026-04-30T11:11:44.1887957+04:00","gmt_modified":"2026-04-30T11:11:44.1887957+04:00"},{"id":41001,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"31f6fa48f80c0c6d8c5b09074b2f3f45","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4910-5150","gmt_create":"2026-04-30T11:11:44.1902997+04:00","gmt_modified":"2026-04-30T11:11:44.1902997+04:00"},{"id":41002,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"1e55d2aee2f6d3f5d6247eb14ca9350a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#230-268","gmt_create":"2026-04-30T11:11:44.1902997+04:00","gmt_modified":"2026-04-30T11:11:44.1902997+04:00"},{"id":41003,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"05c7b54032266aa804fecdd672849759","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#560-627","gmt_create":"2026-04-30T11:11:44.1902997+04:00","gmt_modified":"2026-04-30T11:11:44.1902997+04:00"},{"id":41004,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"d00cc75cac98bab547f7549076e3504d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#238-241","gmt_create":"2026-04-30T11:11:44.1908127+04:00","gmt_modified":"2026-04-30T11:11:44.1908127+04:00"},{"id":41005,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"3f58d42a32421c25e81f24320b1d1f20","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#313-328","gmt_create":"2026-04-30T11:11:44.1908127+04:00","gmt_modified":"2026-04-30T11:11:44.1908127+04:00"},{"id":41006,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"7a9e26fca49ab5e068040d1db9fed41f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#259-286","gmt_create":"2026-04-30T11:11:44.1908127+04:00","gmt_modified":"2026-04-30T11:11:44.1908127+04:00"},{"id":41007,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"8d97625477363c05b9feab2d2cbb781b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#31-38","gmt_create":"2026-04-30T11:11:44.1918162+04:00","gmt_modified":"2026-04-30T11:11:44.1918162+04:00"},{"id":41008,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"1b89c3c75aec06d4b094f1f6692436f9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#59-66","gmt_create":"2026-04-30T11:11:44.1918162+04:00","gmt_modified":"2026-04-30T11:11:44.1918162+04:00"},{"id":41009,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"3f4e4b4d64c6750b34e1fe180b376017","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#119-136","gmt_create":"2026-04-30T11:11:44.1918162+04:00","gmt_modified":"2026-04-30T11:11:44.1918162+04:00"},{"id":41010,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"1853975a7087e8a5073352a9a8ea53c6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#138-155","gmt_create":"2026-04-30T11:11:44.1918162+04:00","gmt_modified":"2026-04-30T11:11:44.1918162+04:00"},{"id":41011,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"d059ab60b812d6e824e8fef796efa497","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#161-209","gmt_create":"2026-04-30T11:11:44.1918162+04:00","gmt_modified":"2026-04-30T11:11:44.1918162+04:00"},{"id":41012,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"343b12b915179b0ceab0474a3260def1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#125-159","gmt_create":"2026-04-30T11:11:44.1928495+04:00","gmt_modified":"2026-04-30T11:11:44.1928495+04:00"},{"id":41013,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"edc66ae920408eb6412dad9e65c69f63","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#211-268","gmt_create":"2026-04-30T11:11:44.1928495+04:00","gmt_modified":"2026-04-30T11:11:44.1928495+04:00"},{"id":41014,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"c87f750598b8e3aeb391ebd49f3c8730","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#356-411","gmt_create":"2026-04-30T11:11:44.1938481+04:00","gmt_modified":"2026-04-30T11:11:44.1938481+04:00"},{"id":41015,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"04677ace577bc003b34701bfd451a89d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#523-543","gmt_create":"2026-04-30T11:11:44.1938481+04:00","gmt_modified":"2026-04-30T11:11:44.1938481+04:00"},{"id":41016,"source_id":"ade43b15adadb0a3215b2b7a6866ef22","target_id":"04677ace577bc003b34701bfd451a89d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 523-543","gmt_create":"2026-04-30T11:11:44.1938481+04:00","gmt_modified":"2026-04-30T11:11:44.1938481+04:00"},{"id":41017,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"b372b2c20cc0d9d9abd9e2254bfefb17","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#576-602","gmt_create":"2026-04-30T11:11:44.1948465+04:00","gmt_modified":"2026-04-30T11:11:44.1948465+04:00"},{"id":41018,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"23c70bfc8bc9d7d6ea64c01cbc8cfa74","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#545-579","gmt_create":"2026-04-30T11:11:44.1958468+04:00","gmt_modified":"2026-04-30T11:11:44.1958468+04:00"},{"id":41019,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"57b5a41ba76b9bfcb250e93e89805515","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#761-765","gmt_create":"2026-04-30T11:11:44.1963515+04:00","gmt_modified":"2026-04-30T11:11:44.1963515+04:00"},{"id":41020,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"85e42052e68826778bf6fc0a5c348061","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#266-292","gmt_create":"2026-04-30T11:11:44.1963515+04:00","gmt_modified":"2026-04-30T11:11:44.1963515+04:00"},{"id":41021,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"44fcac467d4197ec65011a88dcc9b983","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#942-1054","gmt_create":"2026-04-30T11:11:44.1973552+04:00","gmt_modified":"2026-04-30T11:11:44.1973552+04:00"},{"id":41022,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"d866d0710f48a708f384cfe833d19819","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2790-2791","gmt_create":"2026-04-30T11:11:44.1973552+04:00","gmt_modified":"2026-04-30T11:11:44.1973552+04:00"},{"id":41023,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"7f9a564b8011c536e8ff555516dffbae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#3254","gmt_create":"2026-04-30T11:11:44.1983555+04:00","gmt_modified":"2026-04-30T11:11:44.1983555+04:00"},{"id":41024,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"cc2057fd8aeaa6b63ca70e0d4192d8f6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#542-555","gmt_create":"2026-04-30T11:11:44.1983555+04:00","gmt_modified":"2026-04-30T11:11:44.1983555+04:00"},{"id":41025,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"3397b02998da3d1df1abfc39e178cd35","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#74-100","gmt_create":"2026-04-30T11:11:44.1993551+04:00","gmt_modified":"2026-04-30T11:11:44.1993551+04:00"},{"id":41026,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"dfc65f88824f860423d799827fdee7aa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#545-574","gmt_create":"2026-04-30T11:11:44.1993551+04:00","gmt_modified":"2026-04-30T11:11:44.1993551+04:00"},{"id":41027,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"443c70bf08a7229603f07d4b6e2ceb04","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#304-369","gmt_create":"2026-04-30T11:11:44.2008582+04:00","gmt_modified":"2026-04-30T11:11:44.2008582+04:00"},{"id":41028,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"5985a883b7cd6e373fa45e06af549458","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#172-202","gmt_create":"2026-04-30T11:11:44.2008582+04:00","gmt_modified":"2026-04-30T11:11:44.2008582+04:00"},{"id":41029,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"27ad3a44022480b087c9dee522cf8f53","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#432-444","gmt_create":"2026-04-30T11:11:44.2008582+04:00","gmt_modified":"2026-04-30T11:11:44.2008582+04:00"},{"id":41030,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"5df133f8f7e5465f2256280233f7492a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4005-4036","gmt_create":"2026-04-30T11:11:44.2018621+04:00","gmt_modified":"2026-04-30T11:11:44.2018621+04:00"},{"id":41031,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"7ed16e639125b23aeb82bbb3768334fb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4170-4172","gmt_create":"2026-04-30T11:11:44.2018621+04:00","gmt_modified":"2026-04-30T11:11:44.2018621+04:00"},{"id":41032,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"ed64be85dbceaa49c4d38b1fd7b0537f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4392-4394","gmt_create":"2026-04-30T11:11:44.2018621+04:00","gmt_modified":"2026-04-30T11:11:44.2018621+04:00"},{"id":41033,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"770f933f07c1261bce810845c6711ec4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4043-4047","gmt_create":"2026-04-30T11:11:44.2028616+04:00","gmt_modified":"2026-04-30T11:11:44.2028616+04:00"},{"id":41034,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"5988d2bd7b0d5bec2b97a885d1928110","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4189-4192","gmt_create":"2026-04-30T11:11:44.2028616+04:00","gmt_modified":"2026-04-30T11:11:44.2028616+04:00"},{"id":41035,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"a9078dfa0e86bfbf3c1bef4a473eb117","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4419-4421","gmt_create":"2026-04-30T11:11:44.2028616+04:00","gmt_modified":"2026-04-30T11:11:44.2028616+04:00"},{"id":41036,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"4af3002393266cb7f00332d69f2019ca","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#233-236","gmt_create":"2026-04-30T11:11:44.2028616+04:00","gmt_modified":"2026-04-30T11:11:44.2028616+04:00"},{"id":41037,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"36faa15d1b9669c10ef981b57dd230bc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#326-329","gmt_create":"2026-04-30T11:11:44.2028616+04:00","gmt_modified":"2026-04-30T11:11:44.2028616+04:00"},{"id":41038,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"69112cf16468224f02ecd1fbd49ea64f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#1-10","gmt_create":"2026-04-30T11:11:44.2043647+04:00","gmt_modified":"2026-04-30T11:11:44.2043647+04:00"},{"id":41039,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"6456782ce4d45cbdc58f90d5677ca2c2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#1-7","gmt_create":"2026-04-30T11:11:44.2043647+04:00","gmt_modified":"2026-04-30T11:11:44.2043647+04:00"},{"id":41040,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"fce2bc849f6a01aeb1da861120260037","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/block_log.cpp#1-6","gmt_create":"2026-04-30T11:11:44.2043647+04:00","gmt_modified":"2026-04-30T11:11:44.2043647+04:00"},{"id":41041,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"ab61b06105c0618ce476f62fc42d04c5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-10","gmt_create":"2026-04-30T11:11:44.2053684+04:00","gmt_modified":"2026-04-30T11:11:44.2053684+04:00"},{"id":41042,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"cf34d537814cc823a0786b3fa43ffca0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#1-6","gmt_create":"2026-04-30T11:11:44.2053684+04:00","gmt_modified":"2026-04-30T11:11:44.2053684+04:00"},{"id":41043,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"7bd018a7b3dab28cc6792e89ffa39efd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#1-10","gmt_create":"2026-04-30T11:11:44.2053684+04:00","gmt_modified":"2026-04-30T11:11:44.2053684+04:00"},{"id":41044,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"96e5aca4a37cf627dff138e440efffa2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#1-10","gmt_create":"2026-04-30T11:11:44.2063683+04:00","gmt_modified":"2026-04-30T11:11:44.2063683+04:00"},{"id":41045,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"8e436d067efeeee9365369d51dc3fd02","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#250-271","gmt_create":"2026-04-30T11:11:44.2073683+04:00","gmt_modified":"2026-04-30T11:11:44.2073683+04:00"},{"id":41046,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"2fb2b5eefe1dda70f9332bca41575f3d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#259-268","gmt_create":"2026-04-30T11:11:44.2083687+04:00","gmt_modified":"2026-04-30T11:11:44.2083687+04:00"},{"id":41047,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"5864019a1f5c741338aa5810a4a9a2a4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#262-267","gmt_create":"2026-04-30T11:11:44.2083687+04:00","gmt_modified":"2026-04-30T11:11:44.2083687+04:00"},{"id":41048,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"ac84b774b43ade0dc3ca1ecbd3b60ea5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#576-580","gmt_create":"2026-04-30T11:11:44.2093682+04:00","gmt_modified":"2026-04-30T11:11:44.2093682+04:00"},{"id":41049,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"29ef92399939d0e10862168a6d547c18","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#609-613","gmt_create":"2026-04-30T11:11:44.2093682+04:00","gmt_modified":"2026-04-30T11:11:44.2093682+04:00"},{"id":41050,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"50f4656fe0d323b661f41cd93f59398f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#599-621","gmt_create":"2026-04-30T11:11:44.2093682+04:00","gmt_modified":"2026-04-30T11:11:44.2093682+04:00"},{"id":41051,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"f1468b7365f4d06080c1395db0eb5819","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#623-640","gmt_create":"2026-04-30T11:11:44.2093682+04:00","gmt_modified":"2026-04-30T11:11:44.2093682+04:00"},{"id":41052,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"06b382c82ee3f42c24d858503dcde41b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#241-249","gmt_create":"2026-04-30T11:11:44.2110988+04:00","gmt_modified":"2026-04-30T11:11:44.2110988+04:00"},{"id":41053,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"ce3f0826aff2367e46f4cd37417c2f67","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#320-325","gmt_create":"2026-04-30T11:11:44.2116948+04:00","gmt_modified":"2026-04-30T11:11:44.2116948+04:00"},{"id":41054,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"55755f9121d991a7682722b9a8b3ab80","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#560-595","gmt_create":"2026-04-30T11:11:44.2130379+04:00","gmt_modified":"2026-04-30T11:11:44.2130379+04:00"},{"id":41055,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"86ba814ebeaa92690f62613e16aa5e7d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#656-697","gmt_create":"2026-04-30T11:11:44.2135409+04:00","gmt_modified":"2026-04-30T11:11:44.2135409+04:00"},{"id":41056,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"ce99ae12d13aaa7b2653e47db773bcce","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1435-1500","gmt_create":"2026-04-30T11:11:44.2141616+04:00","gmt_modified":"2026-04-30T11:11:44.2141616+04:00"},{"id":41057,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"a370301bb49e9efcf0ef5d6724ac9dd2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2691-2696","gmt_create":"2026-04-30T11:11:44.2146646+04:00","gmt_modified":"2026-04-30T11:11:44.2146646+04:00"},{"id":41058,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"09819e68e897288cd817074594e4f548","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2863-2866","gmt_create":"2026-04-30T11:11:44.2146646+04:00","gmt_modified":"2026-04-30T11:11:44.2146646+04:00"},{"id":41059,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"b44834f88165b1116477f30dc5f88a3e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4581-4608","gmt_create":"2026-04-30T11:11:44.2153918+04:00","gmt_modified":"2026-04-30T11:11:44.2153918+04:00"},{"id":41060,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"17512b930411cc392fa7b28fa3ad2b84","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#332-338","gmt_create":"2026-04-30T11:11:44.2164987+04:00","gmt_modified":"2026-04-30T11:11:44.2164987+04:00"},{"id":41061,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"9a668be3a6e1e9f3afa6fcd90677d0b3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5482-5499","gmt_create":"2026-04-30T11:11:44.2170019+04:00","gmt_modified":"2026-04-30T11:11:44.2170019+04:00"},{"id":41062,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"c6612598fe076c0f7d5ca15ca94cf26b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#627-627","gmt_create":"2026-04-30T11:11:44.2175254+04:00","gmt_modified":"2026-04-30T11:11:44.2175254+04:00"},{"id":41063,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"62962be4cb55c4466a47fba7a814b326","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1473-1476","gmt_create":"2026-04-30T11:11:44.2180455+04:00","gmt_modified":"2026-04-30T11:11:44.2180455+04:00"},{"id":41064,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"798b9ae312576643f8fefd434b40bddc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#626-632","gmt_create":"2026-04-30T11:11:44.2180455+04:00","gmt_modified":"2026-04-30T11:11:44.2180455+04:00"},{"id":41065,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"02c2555684691e747182b4078de2ed9b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1472-1477","gmt_create":"2026-04-30T11:11:44.2180455+04:00","gmt_modified":"2026-04-30T11:11:44.2180455+04:00"},{"id":41066,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"c31e2a585be22edd01d86dba51c51e79","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#294-302","gmt_create":"2026-04-30T11:11:44.2185645+04:00","gmt_modified":"2026-04-30T11:11:44.2185645+04:00"},{"id":41067,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"6ef04fbc3dcdfebcf2580c7e7146cbc3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#317-323","gmt_create":"2026-04-30T11:11:44.2185645+04:00","gmt_modified":"2026-04-30T11:11:44.2185645+04:00"},{"id":41068,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"5e4bd94274850728b00115526a2479ee","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#860-882","gmt_create":"2026-04-30T11:11:44.2190818+04:00","gmt_modified":"2026-04-30T11:11:44.2190818+04:00"},{"id":41069,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"c53a6db24cea4db25b30783459c40125","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#884-901","gmt_create":"2026-04-30T11:11:44.2190818+04:00","gmt_modified":"2026-04-30T11:11:44.2190818+04:00"},{"id":41070,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"24e53b6bba24767d436a386f584d39b1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#330-364","gmt_create":"2026-04-30T11:11:44.2190818+04:00","gmt_modified":"2026-04-30T11:11:44.2190818+04:00"},{"id":41071,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"88d1d1609201e580c6cdf488fcd22fad","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#370-489","gmt_create":"2026-04-30T11:11:44.219599+04:00","gmt_modified":"2026-04-30T11:11:44.219599+04:00"},{"id":41072,"source_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","target_id":"766ad50513305c2c4955788de08b2f70","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#757-765","gmt_create":"2026-04-30T11:11:44.2204252+04:00","gmt_modified":"2026-04-30T11:11:44.2204252+04:00"},{"id":41073,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"448c0fd26faf791081a54cd407662e4d","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","gmt_create":"2026-04-30T11:12:37.461325+04:00","gmt_modified":"2026-04-30T11:12:37.461325+04:00"},{"id":41074,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T11:12:37.461325+04:00","gmt_modified":"2026-04-30T11:12:37.461325+04:00"},{"id":41075,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"f7dedf31e491c7adbaf05e957360c531","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-30T11:12:37.4621341+04:00","gmt_modified":"2026-04-30T11:12:37.4621341+04:00"},{"id":41076,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"d72b348a2c3c7943e4a7abb7dbdaa751","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_connection.hpp","gmt_create":"2026-04-30T11:12:37.4627462+04:00","gmt_modified":"2026-04-30T11:12:37.4627462+04:00"},{"id":41077,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"3a8d8a10556a0b6501e25aa43e91f913","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_database.hpp","gmt_create":"2026-04-30T11:12:37.4627462+04:00","gmt_modified":"2026-04-30T11:12:37.4627462+04:00"},{"id":41078,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"3a6c30f4bb3b265155c881ccfafa980e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/core_messages.hpp","gmt_create":"2026-04-30T11:12:37.4627462+04:00","gmt_modified":"2026-04-30T11:12:37.4627462+04:00"},{"id":41079,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"b4467ca30cb6f6d587fc200900ee9ec9","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message.hpp","gmt_create":"2026-04-30T11:12:37.4633262+04:00","gmt_modified":"2026-04-30T11:12:37.4633262+04:00"},{"id":41080,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"c82262dc5275e094efc9474032d15922","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/config.hpp","gmt_create":"2026-04-30T11:12:37.4633262+04:00","gmt_modified":"2026-04-30T11:12:37.4633262+04:00"},{"id":41081,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-30T11:12:37.4633262+04:00","gmt_modified":"2026-04-30T11:12:37.4633262+04:00"},{"id":41082,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T11:12:37.4638289+04:00","gmt_modified":"2026-04-30T11:12:37.4638289+04:00"},{"id":41083,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/dlt_block_log.hpp","gmt_create":"2026-04-30T11:12:37.4756666+04:00","gmt_modified":"2026-04-30T11:12:37.4756666+04:00"},{"id":41084,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-30T11:12:37.4776569+04:00","gmt_modified":"2026-04-30T11:12:37.4776569+04:00"},{"id":41085,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T11:12:37.4776569+04:00","gmt_modified":"2026-04-30T11:12:37.4776569+04:00"},{"id":41086,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"ee77bf4eb6bfbfb3636aa0bd57416552","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/chainbase/include/chainbase/chainbase.hpp","gmt_create":"2026-04-30T11:12:37.4782387+04:00","gmt_modified":"2026-04-30T11:12:37.4782387+04:00"},{"id":41087,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-30T11:12:37.4782387+04:00","gmt_modified":"2026-04-30T11:12:37.4782387+04:00"},{"id":41088,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"f7be79ec222a56c210b7999322790a2f","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/CMakeLists.txt","gmt_create":"2026-04-30T11:12:37.4782387+04:00","gmt_modified":"2026-04-30T11:12:37.4782387+04:00"},{"id":41089,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"18555f254f50536a15d8591acf982406","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config.ini","gmt_create":"2026-04-30T11:12:37.4787417+04:00","gmt_modified":"2026-04-30T11:12:37.4787417+04:00"},{"id":41090,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"e6b951c57c5f8fb2b3553ce8db430760","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#18-55","gmt_create":"2026-04-30T11:12:37.4788215+04:00","gmt_modified":"2026-04-30T11:12:37.4788215+04:00"},{"id":41091,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"467b9030fcd47369d3417c84998c20d0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#910-979","gmt_create":"2026-04-30T11:12:37.4788215+04:00","gmt_modified":"2026-04-30T11:12:37.4788215+04:00"},{"id":41092,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"cbfe85acce275b65a2edb3315aec2941","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#190-320","gmt_create":"2026-04-30T11:12:37.4799027+04:00","gmt_modified":"2026-04-30T11:12:37.4799027+04:00"},{"id":41093,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"9e18fa1bdbee1d9c96d8437bfe20515c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#1-57","gmt_create":"2026-04-30T11:12:37.4799027+04:00","gmt_modified":"2026-04-30T11:12:37.4799027+04:00"},{"id":41094,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"5dce2933cfb42430c2bbcdf0cacc25c3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/CMakeLists.txt#1-49","gmt_create":"2026-04-30T11:12:37.4799027+04:00","gmt_modified":"2026-04-30T11:12:37.4799027+04:00"},{"id":41095,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"1b55f505ae64e9ea22be5142cfa67f93","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#49-126","gmt_create":"2026-04-30T11:12:37.4804293+04:00","gmt_modified":"2026-04-30T11:12:37.4804293+04:00"},{"id":41096,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"8a161abeb389c25b1279bc23d6ff4e57","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#60-167","gmt_create":"2026-04-30T11:12:37.4804293+04:00","gmt_modified":"2026-04-30T11:12:37.4804293+04:00"},{"id":41097,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"f85f57d0c6b461ab78f906ef6d5854c0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#79-354","gmt_create":"2026-04-30T11:12:37.4809431+04:00","gmt_modified":"2026-04-30T11:12:37.4809431+04:00"},{"id":41098,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"21076248fc123c7aacc8cb6e67cd0068","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#758-823","gmt_create":"2026-04-30T11:12:37.481738+04:00","gmt_modified":"2026-04-30T11:12:37.481738+04:00"},{"id":41099,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"4e79c62ed5491dcd85510f3dab144813","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#1-200","gmt_create":"2026-04-30T11:12:37.4822408+04:00","gmt_modified":"2026-04-30T11:12:37.4822408+04:00"},{"id":41100,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"fe82427a3c86c02f05708734fa4c589c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#216-245","gmt_create":"2026-04-30T11:12:37.4826001+04:00","gmt_modified":"2026-04-30T11:12:37.4826001+04:00"},{"id":41101,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"15bc72540b02de4c57e4c976c8150f35","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#855-865","gmt_create":"2026-04-30T11:12:37.4834146+04:00","gmt_modified":"2026-04-30T11:12:37.4834146+04:00"},{"id":41102,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"cd0a62c9a78bb77d3b59a9d5872577f4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#82-106","gmt_create":"2026-04-30T11:12:37.4839927+04:00","gmt_modified":"2026-04-30T11:12:37.4839927+04:00"},{"id":41103,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"7eaff221b9d5916b8e18aa7786630566","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#188-218","gmt_create":"2026-04-30T11:12:37.4839927+04:00","gmt_modified":"2026-04-30T11:12:37.4839927+04:00"},{"id":41104,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"9ec3ef7b5beba7ccd4c8172983e359a7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#247-301","gmt_create":"2026-04-30T11:12:37.4851521+04:00","gmt_modified":"2026-04-30T11:12:37.4851521+04:00"},{"id":41105,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"685c2a44fc90d96152180fc6a5a63df4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#129-208","gmt_create":"2026-04-30T11:12:37.4851521+04:00","gmt_modified":"2026-04-30T11:12:37.4851521+04:00"},{"id":41106,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"16437c2399403aa03ec5eb0bceca78b8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#653-770","gmt_create":"2026-04-30T11:12:37.4851521+04:00","gmt_modified":"2026-04-30T11:12:37.4851521+04:00"},{"id":41107,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"16437c2399403aa03ec5eb0bceca78b8","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 653-770","gmt_create":"2026-04-30T11:12:37.4858117+04:00","gmt_modified":"2026-04-30T11:12:37.4858117+04:00"},{"id":41108,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"d74767289d3d8b429633502bc075cc48","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#773-813","gmt_create":"2026-04-30T11:12:37.4858117+04:00","gmt_modified":"2026-04-30T11:12:37.4858117+04:00"},{"id":41109,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"d74767289d3d8b429633502bc075cc48","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 773-813","gmt_create":"2026-04-30T11:12:37.4858117+04:00","gmt_modified":"2026-04-30T11:12:37.4858117+04:00"},{"id":41110,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"5eaed48196ffe1d0e73f557aacdc6096","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#760-768","gmt_create":"2026-04-30T11:12:37.4863152+04:00","gmt_modified":"2026-04-30T11:12:37.4863152+04:00"},{"id":41111,"source_id":"66c6049f94b83d8f15dc55bb2424efbe","target_id":"5eaed48196ffe1d0e73f557aacdc6096","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 760-768","gmt_create":"2026-04-30T11:12:37.4863152+04:00","gmt_modified":"2026-04-30T11:12:37.4863152+04:00"},{"id":41112,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"55ed0cee7e5f683b8caac133899cb8cd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#295-340","gmt_create":"2026-04-30T11:12:37.4863152+04:00","gmt_modified":"2026-04-30T11:12:37.4863152+04:00"},{"id":41113,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"909f520b94e82193bac04c745bf8f67c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#614-650","gmt_create":"2026-04-30T11:12:37.4873184+04:00","gmt_modified":"2026-04-30T11:12:37.4873184+04:00"},{"id":41114,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"163647b506865a0b9039e6ab0e61b35b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#290-364","gmt_create":"2026-04-30T11:12:37.4873184+04:00","gmt_modified":"2026-04-30T11:12:37.4873184+04:00"},{"id":41115,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"0dc933c238ae7ab73de36f0171dfb48b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#371-405","gmt_create":"2026-04-30T11:12:37.4883183+04:00","gmt_modified":"2026-04-30T11:12:37.4883183+04:00"},{"id":41116,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"82dadfd972805d70bffa99ce756460c4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#307-327","gmt_create":"2026-04-30T11:12:37.4883183+04:00","gmt_modified":"2026-04-30T11:12:37.4883183+04:00"},{"id":41117,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"ee32480b501d0e9284fc1a7aa7d20b4c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#1042-1113","gmt_create":"2026-04-30T11:12:37.4883183+04:00","gmt_modified":"2026-04-30T11:12:37.4883183+04:00"},{"id":41118,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"dfc65f88824f860423d799827fdee7aa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#545-574","gmt_create":"2026-04-30T11:12:37.4893183+04:00","gmt_modified":"2026-04-30T11:12:37.4893183+04:00"},{"id":41119,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"e24259987d3c50dee1a87e13d63ccf03","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#57-78","gmt_create":"2026-04-30T11:12:37.4893183+04:00","gmt_modified":"2026-04-30T11:12:37.4893183+04:00"},{"id":41120,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"6742da18bac301be1056c8e6e5adcf69","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/dlt_block_log.hpp#35-72","gmt_create":"2026-04-30T11:12:37.4899114+04:00","gmt_modified":"2026-04-30T11:12:37.4899114+04:00"},{"id":41121,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"5a09c4b30cf4ad7363c8ddc12bc7c8db","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#290-405","gmt_create":"2026-04-30T11:12:37.4904141+04:00","gmt_modified":"2026-04-30T11:12:37.4904141+04:00"},{"id":41122,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"d8698088d1f1c1cd1343a5552104c443","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#295-405","gmt_create":"2026-04-30T11:12:37.4917778+04:00","gmt_modified":"2026-04-30T11:12:37.4917778+04:00"},{"id":41123,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"bccfbcb90cc33fcb1bad654d203eab3a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#308-340","gmt_create":"2026-04-30T11:12:37.4923716+04:00","gmt_modified":"2026-04-30T11:12:37.4923716+04:00"},{"id":41124,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"3e4f7ec74d72ee9c1f4f5cf2a0f86844","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#298-302","gmt_create":"2026-04-30T11:12:37.4929891+04:00","gmt_modified":"2026-04-30T11:12:37.4929891+04:00"},{"id":41125,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"e13810e67e91d0b4940124f48b98095c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#335-338","gmt_create":"2026-04-30T11:12:37.4935761+04:00","gmt_modified":"2026-04-30T11:12:37.4935761+04:00"},{"id":41126,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"30fcbbca25b602851122e4d2b5ae4754","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#701-765","gmt_create":"2026-04-30T11:12:37.4940787+04:00","gmt_modified":"2026-04-30T11:12:37.4940787+04:00"},{"id":41127,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"2d0ff08ccbd1994ff4f46985361fc3d4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#355-364","gmt_create":"2026-04-30T11:12:37.4950816+04:00","gmt_modified":"2026-04-30T11:12:37.4950816+04:00"},{"id":41128,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"f195c24b5ab419ffbcc0c59a70fc3b0b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#520-528","gmt_create":"2026-04-30T11:12:37.4950816+04:00","gmt_modified":"2026-04-30T11:12:37.4950816+04:00"},{"id":41129,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"10e2e9b3ec934c86877d6a4b57e21d34","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#321-327","gmt_create":"2026-04-30T11:12:37.4950816+04:00","gmt_modified":"2026-04-30T11:12:37.4950816+04:00"},{"id":41130,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"c45e7282b5fb1668e8ae6f5a8da708ea","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#151-208","gmt_create":"2026-04-30T11:12:37.4950816+04:00","gmt_modified":"2026-04-30T11:12:37.4950816+04:00"},{"id":41131,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"3926b37345d6d1e98bd8faf06e492e48","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#722-771","gmt_create":"2026-04-30T11:12:37.4960816+04:00","gmt_modified":"2026-04-30T11:12:37.4960816+04:00"},{"id":41132,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"f12cdb54f6351fe38c307c4907f93868","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#783-791","gmt_create":"2026-04-30T11:12:37.4960816+04:00","gmt_modified":"2026-04-30T11:12:37.4960816+04:00"},{"id":41133,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"8c9490ebae702a4725d10dc509074042","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#812-816","gmt_create":"2026-04-30T11:12:37.4968454+04:00","gmt_modified":"2026-04-30T11:12:37.4968454+04:00"},{"id":41134,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"a1ef6908e910771a074aa8ccbc10adef","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#773-795","gmt_create":"2026-04-30T11:12:37.4974156+04:00","gmt_modified":"2026-04-30T11:12:37.4974156+04:00"},{"id":41135,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"e2711f14cc959993e82e19c033761fff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#16-21","gmt_create":"2026-04-30T11:12:37.4991416+04:00","gmt_modified":"2026-04-30T11:12:37.4991416+04:00"},{"id":41136,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"7232f04ac5a3f960659a84db72e3c8c6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#79-83","gmt_create":"2026-04-30T11:12:37.4996627+04:00","gmt_modified":"2026-04-30T11:12:37.4996627+04:00"},{"id":41137,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"84d9f61aa92a7de46576a47f269a5dcd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#169-171","gmt_create":"2026-04-30T11:12:37.5001803+04:00","gmt_modified":"2026-04-30T11:12:37.5001803+04:00"},{"id":41138,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"c66e1ad4dc89d485e77e61d3776d5e80","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#299-301","gmt_create":"2026-04-30T11:12:37.5007012+04:00","gmt_modified":"2026-04-30T11:12:37.5007012+04:00"},{"id":41139,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"d6af3938508b615a82ba5a3820d850c0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#522-528","gmt_create":"2026-04-30T11:12:37.5007012+04:00","gmt_modified":"2026-04-30T11:12:37.5007012+04:00"},{"id":41140,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"5cdb837c5a6e5f183ed9a818f50b2abf","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#336-338","gmt_create":"2026-04-30T11:12:37.5017042+04:00","gmt_modified":"2026-04-30T11:12:37.5017042+04:00"},{"id":41141,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"4a61426778eec404639d3aa8059fbe1a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#357-364","gmt_create":"2026-04-30T11:12:37.5017042+04:00","gmt_modified":"2026-04-30T11:12:37.5017042+04:00"},{"id":41142,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"1fc0915e82dd4c943724dcd2e29106cb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#159-175","gmt_create":"2026-04-30T11:12:37.504965+04:00","gmt_modified":"2026-04-30T11:12:37.504965+04:00"},{"id":41143,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"29d1adb11bc96c1e7216be1c31cb57f7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#173-204","gmt_create":"2026-04-30T11:12:37.5054676+04:00","gmt_modified":"2026-04-30T11:12:37.5054676+04:00"},{"id":41144,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"1638be790588edb6a3913a76064f24e0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#992-1061","gmt_create":"2026-04-30T11:12:37.5064704+04:00","gmt_modified":"2026-04-30T11:12:37.5064704+04:00"},{"id":41145,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"9ad98a7be17ee5186d08088816474c52","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#540-552","gmt_create":"2026-04-30T11:12:37.506583+04:00","gmt_modified":"2026-04-30T11:12:37.506583+04:00"},{"id":41146,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"a273323d20c428afec092114bb480a23","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/chainbase/include/chainbase/chainbase.hpp#1078-1115","gmt_create":"2026-04-30T11:12:37.5070855+04:00","gmt_modified":"2026-04-30T11:12:37.5070855+04:00"},{"id":41147,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"27c21c9dfb07f579bd0db9fa97c8fd19","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/chainbase/include/chainbase/chainbase.hpp#1130-1137","gmt_create":"2026-04-30T11:12:37.5083887+04:00","gmt_modified":"2026-04-30T11:12:37.5083887+04:00"},{"id":41148,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"3b4fd0aa5c9c47621979a050b80b5fc2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#173-208","gmt_create":"2026-04-30T11:12:37.5088913+04:00","gmt_modified":"2026-04-30T11:12:37.5088913+04:00"},{"id":41149,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"411e466fa1c626bc1fff0647607acabd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#151-156","gmt_create":"2026-04-30T11:12:37.5088913+04:00","gmt_modified":"2026-04-30T11:12:37.5088913+04:00"},{"id":41150,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"b89fc00b26b800a29e1692b23ada0d56","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#168-172","gmt_create":"2026-04-30T11:12:37.5088913+04:00","gmt_modified":"2026-04-30T11:12:37.5088913+04:00"},{"id":41151,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"8eb355e55d14f0a3eec62805ff783a2f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/CMakeLists.txt#27-34","gmt_create":"2026-04-30T11:12:37.5088913+04:00","gmt_modified":"2026-04-30T11:12:37.5088913+04:00"},{"id":41152,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"4fc812a0df4303ac6e74df39697a0893","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#1-13","gmt_create":"2026-04-30T11:12:37.5098942+04:00","gmt_modified":"2026-04-30T11:12:37.5098942+04:00"},{"id":41153,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"cfa97ba993c799f350895b1deb5bfa1a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#596-699","gmt_create":"2026-04-30T11:12:37.5107568+04:00","gmt_modified":"2026-04-30T11:12:37.5107568+04:00"},{"id":41154,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"b372b2c20cc0d9d9abd9e2254bfefb17","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/dlt_block_log.cpp#576-602","gmt_create":"2026-04-30T11:12:37.5112596+04:00","gmt_modified":"2026-04-30T11:12:37.5112596+04:00"},{"id":41155,"source_id":"6c14d115-1e64-4774-9d11-4861953aec78","target_id":"463f1a4ea4700713bc6ca4afa0c86f4f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config.ini#1-143","gmt_create":"2026-04-30T11:12:37.5118703+04:00","gmt_modified":"2026-04-30T11:12:37.5118703+04:00"},{"id":41156,"source_id":"295c418f-33b4-4c06-80cf-224d6b633a76","target_id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: 295c418f-33b4-4c06-80cf-224d6b633a76 -\u003e cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","gmt_create":"2026-04-30T11:12:38.225503+04:00","gmt_modified":"2026-04-30T11:12:38.225503+04:00"},{"id":41157,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T12:35:27.5150739+04:00","gmt_modified":"2026-04-30T12:35:27.5150739+04:00"},{"id":41158,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T12:35:27.5150739+04:00","gmt_modified":"2026-04-30T12:35:27.5150739+04:00"},{"id":41159,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"7ae785f9d5ab154dce6f8eb295b93456","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/global_property_object.hpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41160,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"cf72debd284e30d5218a88ae08868205","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/witness_objects.hpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41161,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41162,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41163,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"b4b9efd79d5b3c9fea00fccd613b2046","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/config.hpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41164,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"bbbc42bba97165e48c1da269d1d84a04","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/config_testnet.hpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41165,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-30T12:35:27.515588+04:00","gmt_modified":"2026-04-30T12:35:27.515588+04:00"},{"id":41166,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"0dd2a38630da83b11fb3596ad4d60705","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/include/graphene/plugins/witness/witness.hpp","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41167,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"8ede002b6c76d0a07d75e34f812e8305","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/hardfork.d/12.hf","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41168,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"1ade3cebbc11a4634bcdf1a7fdb2756e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/chainbase/src/chainbase.cpp","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41169,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"ee77bf4eb6bfbfb3636aa0bd57416552","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/chainbase/include/chainbase/chainbase.hpp","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41170,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"943ef4fb5df40c9942aadddd7040bc7c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4863-5004","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41171,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"27593eff1e7989c53fb119e30b38a106","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#81-88","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41172,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"1273c494621ef1c9351fa8b783dc6ae6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#422-427","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41173,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"e2e78ec9bb315562ae4436bac3d06fb5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1556","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41174,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"cf67769b74f5699e4a347dc2d7092ceb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/chainbase/include/chainbase/chainbase.hpp#1097-1115","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41175,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"188a46b66d800240516e280b06e7f041","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/global_property_object.hpp#24-146","gmt_create":"2026-04-30T12:35:27.5160994+04:00","gmt_modified":"2026-04-30T12:35:27.5160994+04:00"},{"id":41176,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"5aed64f2f61be210a303b341a970b0cc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/witness_objects.hpp#27-132","gmt_create":"2026-04-30T12:35:27.5171032+04:00","gmt_modified":"2026-04-30T12:35:27.5171032+04:00"},{"id":41177,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"02a1a9fcc78ccfc4daf328d04696eb6f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#114-124","gmt_create":"2026-04-30T12:35:27.5176534+04:00","gmt_modified":"2026-04-30T12:35:27.5176534+04:00"},{"id":41178,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"971acfd6fe75fe0b8a1522de5c46bf48","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/witness_objects.hpp#47-61","gmt_create":"2026-04-30T12:35:27.5176534+04:00","gmt_modified":"2026-04-30T12:35:27.5176534+04:00"},{"id":41179,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"ad1ea51f6c6764f6694b4b89a834e8f6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#110-128","gmt_create":"2026-04-30T12:35:27.5176534+04:00","gmt_modified":"2026-04-30T12:35:27.5176534+04:00"},{"id":41180,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"ffdc85ed129bbf1af13ac8fb289ef5cc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4887-4906","gmt_create":"2026-04-30T12:35:27.5186566+04:00","gmt_modified":"2026-04-30T12:35:27.5186566+04:00"},{"id":41181,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"61447f1b270e050eaf1594622e8344cb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#303-357","gmt_create":"2026-04-30T12:35:27.5186566+04:00","gmt_modified":"2026-04-30T12:35:27.5186566+04:00"},{"id":41182,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"87460891e469970ebd1b4357e65a305d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2561-2591","gmt_create":"2026-04-30T12:35:27.5186566+04:00","gmt_modified":"2026-04-30T12:35:27.5186566+04:00"},{"id":41183,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"e8bcacd1aeb9acccff2e7846bdb418ea","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2596-2612","gmt_create":"2026-04-30T12:35:27.5322619+04:00","gmt_modified":"2026-04-30T12:35:27.5322619+04:00"},{"id":41184,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"c1530ddb1ea5d36cd757849d7b1bae47","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2614-2631","gmt_create":"2026-04-30T12:35:27.5327651+04:00","gmt_modified":"2026-04-30T12:35:27.5327651+04:00"},{"id":41185,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"e233bb5840d58fff00c516dfc7186b2f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#125-128","gmt_create":"2026-04-30T12:35:27.5332786+04:00","gmt_modified":"2026-04-30T12:35:27.5332786+04:00"},{"id":41186,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"3addab2306735a8fd2b97c7337a752aa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5473-5545","gmt_create":"2026-04-30T12:35:27.5332786+04:00","gmt_modified":"2026-04-30T12:35:27.5332786+04:00"},{"id":41187,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"cf15af9ccdfa43fad1c61be4c143bb9f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5515-5529","gmt_create":"2026-04-30T12:35:27.5338075+04:00","gmt_modified":"2026-04-30T12:35:27.5338075+04:00"},{"id":41188,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"9f5944a7feb01c0c201e4a490c1c6d47","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/hardfork.d/12.hf#1-7","gmt_create":"2026-04-30T12:35:27.5343326+04:00","gmt_modified":"2026-04-30T12:35:27.5343326+04:00"},{"id":41189,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"d2f89808b761a3c0eca43468d945f372","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1721","gmt_create":"2026-04-30T12:35:27.5343326+04:00","gmt_modified":"2026-04-30T12:35:27.5343326+04:00"},{"id":41190,"source_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","target_id":"0fda0369897c65053dee851ef9ec01f9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#37-612","gmt_create":"2026-04-30T12:35:27.5349376+04:00","gmt_modified":"2026-04-30T12:35:27.5349376+04:00"},{"id":41277,"source_id":"b4bd3a3265ac695da5624024015e0e81","target_id":"18300366cc04c934739121e96aa33a18","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 2520-2590","gmt_create":"2026-04-30T12:37:21.0215773+04:00","gmt_modified":"2026-04-30T12:37:21.0215773+04:00"},{"id":41279,"source_id":"d72b348a2c3c7943e4a7abb7dbdaa751","target_id":"5c8f4ea594d62170eaa300eb27c60cab","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 285-289","gmt_create":"2026-04-30T12:37:21.0225773+04:00","gmt_modified":"2026-04-30T12:37:21.0225773+04:00"},{"id":41304,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"0dd2a38630da83b11fb3596ad4d60705","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/include/graphene/plugins/witness/witness.hpp","gmt_create":"2026-04-30T12:39:24.1868037+04:00","gmt_modified":"2026-04-30T12:39:24.1868037+04:00"},{"id":41305,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-30T12:39:24.1868037+04:00","gmt_modified":"2026-04-30T12:39:24.1868037+04:00"},{"id":41306,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"f8bd5a2c3a4664ae9d5ec472684610dc","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp","gmt_create":"2026-04-30T12:39:24.1868037+04:00","gmt_modified":"2026-04-30T12:39:24.1868037+04:00"},{"id":41307,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"a4f11ca2018649a28877cfdeecdff9a6","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness_api/plugin.cpp","gmt_create":"2026-04-30T12:39:24.1868037+04:00","gmt_modified":"2026-04-30T12:39:24.1868037+04:00"},{"id":41308,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"cf72debd284e30d5218a88ae08868205","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/witness_objects.hpp","gmt_create":"2026-04-30T12:39:24.1868037+04:00","gmt_modified":"2026-04-30T12:39:24.1868037+04:00"},{"id":41309,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"bebd7920dd0967c6039c2adb16d4c52c","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/chain_objects.hpp","gmt_create":"2026-04-30T12:39:24.187887+04:00","gmt_modified":"2026-04-30T12:39:24.187887+04:00"},{"id":41310,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T12:39:24.187887+04:00","gmt_modified":"2026-04-30T12:39:24.187887+04:00"},{"id":41311,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T12:39:24.187887+04:00","gmt_modified":"2026-04-30T12:39:24.187887+04:00"},{"id":41312,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"75b9bb8cfd2db41c21f328241d191f32","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/fork_database.hpp","gmt_create":"2026-04-30T12:39:24.1884644+04:00","gmt_modified":"2026-04-30T12:39:24.1884644+04:00"},{"id":41313,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T12:39:24.1884644+04:00","gmt_modified":"2026-04-30T12:39:24.1884644+04:00"},{"id":41314,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"ebd71fe58bebecee1b2afacbe66909ca","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/time/time.hpp","gmt_create":"2026-04-30T12:39:24.1884644+04:00","gmt_modified":"2026-04-30T12:39:24.1884644+04:00"},{"id":41315,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"13c87583e5739bb6062ee5706cbed132","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/time/time.cpp","gmt_create":"2026-04-30T12:39:24.1884644+04:00","gmt_modified":"2026-04-30T12:39:24.1884644+04:00"},{"id":41316,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"b58bf8be210d82c70605d7f2482ced82","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/network/ntp.cpp","gmt_create":"2026-04-30T12:39:24.188967+04:00","gmt_modified":"2026-04-30T12:39:24.188967+04:00"},{"id":41317,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"fcabf234b34f00b60b0d784b2da5a052","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: programs/vizd/main.cpp","gmt_create":"2026-04-30T12:39:24.188967+04:00","gmt_modified":"2026-04-30T12:39:24.188967+04:00"},{"id":41318,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"c4447af409b7f3205a55e5b286557dfe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.cpp","gmt_create":"2026-04-30T12:39:24.1893352+04:00","gmt_modified":"2026-04-30T12:39:24.1893352+04:00"},{"id":41319,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"b4b9efd79d5b3c9fea00fccd613b2046","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/protocol/include/graphene/protocol/config.hpp","gmt_create":"2026-04-30T12:39:24.1898379+04:00","gmt_modified":"2026-04-30T12:39:24.1898379+04:00"},{"id":41320,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"18555f254f50536a15d8591acf982406","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config.ini","gmt_create":"2026-04-30T12:39:24.1898379+04:00","gmt_modified":"2026-04-30T12:39:24.1898379+04:00"},{"id":41321,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"bb293be9318768f10f69c80fd6b68517","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config_witness.ini","gmt_create":"2026-04-30T12:39:24.1898379+04:00","gmt_modified":"2026-04-30T12:39:24.1898379+04:00"},{"id":41322,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"448c0fd26faf791081a54cd407662e4d","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","gmt_create":"2026-04-30T12:39:24.1904439+04:00","gmt_modified":"2026-04-30T12:39:24.1904439+04:00"},{"id":41323,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"a64d5e5f1d092e1fd6a7916d048be8da","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp","gmt_create":"2026-04-30T12:39:24.1904439+04:00","gmt_modified":"2026-04-30T12:39:24.1904439+04:00"},{"id":41324,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"f0afc0a1f80132e4bf15ac9ff156e256","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness_guard/witness_guard.cpp","gmt_create":"2026-04-30T12:39:24.1904439+04:00","gmt_modified":"2026-04-30T12:39:24.1904439+04:00"},{"id":41325,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"7ae785f9d5ab154dce6f8eb295b93456","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/global_property_object.hpp","gmt_create":"2026-04-30T12:39:24.1904439+04:00","gmt_modified":"2026-04-30T12:39:24.1904439+04:00"},{"id":41326,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"cd8c02da5ea31d3411ad151149d2f64e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: programs/vizd/main.cpp#63-92","gmt_create":"2026-04-30T12:39:24.1904439+04:00","gmt_modified":"2026-04-30T12:39:24.1904439+04:00"},{"id":41327,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"2d883d06f58fd34d81e8588c75185aa9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/include/graphene/plugins/witness/witness.hpp#34-68","gmt_create":"2026-04-30T12:39:24.1904439+04:00","gmt_modified":"2026-04-30T12:39:24.1904439+04:00"},{"id":41328,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"b6e11846d82ee129d5956cf9b8cbbea8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#59-118","gmt_create":"2026-04-30T12:39:24.1914467+04:00","gmt_modified":"2026-04-30T12:39:24.1914467+04:00"},{"id":41329,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"68421d68d4a7045f55767bcf48a402ad","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp#11-48","gmt_create":"2026-04-30T12:39:24.1914467+04:00","gmt_modified":"2026-04-30T12:39:24.1914467+04:00"},{"id":41330,"source_id":"a64d5e5f1d092e1fd6a7916d048be8da","target_id":"68421d68d4a7045f55767bcf48a402ad","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 11-48","gmt_create":"2026-04-30T12:39:24.1914467+04:00","gmt_modified":"2026-04-30T12:39:24.1914467+04:00"},{"id":41331,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"262c71a35dc67c134fb0ac33f44ffb3c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#27-78","gmt_create":"2026-04-30T12:39:24.1914467+04:00","gmt_modified":"2026-04-30T12:39:24.1914467+04:00"},{"id":41332,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"262c71a35dc67c134fb0ac33f44ffb3c","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 27-78","gmt_create":"2026-04-30T12:39:24.1914467+04:00","gmt_modified":"2026-04-30T12:39:24.1914467+04:00"},{"id":41333,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"cda9a1d47dfdb3a7374fa817887892c0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp#56-98","gmt_create":"2026-04-30T12:39:24.192973+04:00","gmt_modified":"2026-04-30T12:39:24.192973+04:00"},{"id":41334,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"f5891db138d66a58674791b9e99bd337","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#13-28","gmt_create":"2026-04-30T12:39:24.192973+04:00","gmt_modified":"2026-04-30T12:39:24.192973+04:00"},{"id":41335,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"51169b91af554f837e41d2913dacad48","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#37-83","gmt_create":"2026-04-30T12:39:24.192973+04:00","gmt_modified":"2026-04-30T12:39:24.192973+04:00"},{"id":41336,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"5aed64f2f61be210a303b341a970b0cc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/witness_objects.hpp#27-132","gmt_create":"2026-04-30T12:39:24.1939785+04:00","gmt_modified":"2026-04-30T12:39:24.1939785+04:00"},{"id":41337,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"47b98e10075d52a89428f617d837a5e5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/chain_objects.hpp#174-201","gmt_create":"2026-04-30T12:39:24.1939785+04:00","gmt_modified":"2026-04-30T12:39:24.1939785+04:00"},{"id":41338,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"42aa356d9e26fadf05fda749f1d89cff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#53-81","gmt_create":"2026-04-30T12:39:24.1949771+04:00","gmt_modified":"2026-04-30T12:39:24.1949771+04:00"},{"id":41339,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"19e78b124cb1653f5e72af6789493e08","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/time/time.cpp#13-53","gmt_create":"2026-04-30T12:39:24.1949771+04:00","gmt_modified":"2026-04-30T12:39:24.1949771+04:00"},{"id":41340,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"1bf53ebbc25ba8c147446f02ce5e44e2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1267-1276","gmt_create":"2026-04-30T12:39:24.195481+04:00","gmt_modified":"2026-04-30T12:39:24.195481+04:00"},{"id":41341,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"cecb2c27bddde9783761743ffbbfac88","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp#50-55","gmt_create":"2026-04-30T12:39:24.195481+04:00","gmt_modified":"2026-04-30T12:39:24.195481+04:00"},{"id":41342,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"4a84a6b27fbcb47e0994f0bda545816f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#206-249","gmt_create":"2026-04-30T12:39:24.1960586+04:00","gmt_modified":"2026-04-30T12:39:24.1960586+04:00"},{"id":41343,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"2768582067cb0ed11345dd25d7b2a582","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#83-191","gmt_create":"2026-04-30T12:39:24.1969866+04:00","gmt_modified":"2026-04-30T12:39:24.1969866+04:00"},{"id":41344,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"2768582067cb0ed11345dd25d7b2a582","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 83-191","gmt_create":"2026-04-30T12:39:24.1969866+04:00","gmt_modified":"2026-04-30T12:39:24.1969866+04:00"},{"id":41345,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"9bfd91580e85c0c0436b214ea3ff332a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#360-369","gmt_create":"2026-04-30T12:39:24.1969866+04:00","gmt_modified":"2026-04-30T12:39:24.1969866+04:00"},{"id":41346,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"9bfd91580e85c0c0436b214ea3ff332a","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 360-369","gmt_create":"2026-04-30T12:39:24.1979868+04:00","gmt_modified":"2026-04-30T12:39:24.1979868+04:00"},{"id":41347,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"3d0cbd79a1648b655b90b7629307cad1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#206-276","gmt_create":"2026-04-30T12:39:24.1989873+04:00","gmt_modified":"2026-04-30T12:39:24.1989873+04:00"},{"id":41348,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"6abe7f6efde355ee9f2d7cf0776677ff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#278-423","gmt_create":"2026-04-30T12:39:24.1989873+04:00","gmt_modified":"2026-04-30T12:39:24.1989873+04:00"},{"id":41349,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"1cf7bf28dd5011954754492ccf7873f5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#447-471","gmt_create":"2026-04-30T12:39:24.1999876+04:00","gmt_modified":"2026-04-30T12:39:24.1999876+04:00"},{"id":41350,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"31e5e32f87baccd25fbb2183951a67bd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#590-695","gmt_create":"2026-04-30T12:39:24.1999876+04:00","gmt_modified":"2026-04-30T12:39:24.1999876+04:00"},{"id":41351,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"1844592144295f47f4238341e8868e6b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#263-266","gmt_create":"2026-04-30T12:39:24.1999876+04:00","gmt_modified":"2026-04-30T12:39:24.1999876+04:00"},{"id":41352,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"f82119bed73a4a0e0870145102b96214","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#455-544","gmt_create":"2026-04-30T12:39:24.2015956+04:00","gmt_modified":"2026-04-30T12:39:24.2015956+04:00"},{"id":41353,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"f82119bed73a4a0e0870145102b96214","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 455-544","gmt_create":"2026-04-30T12:39:24.2015956+04:00","gmt_modified":"2026-04-30T12:39:24.2015956+04:00"},{"id":41354,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"15a3537ebe2816e5402e7fb60462b58e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4317-4332","gmt_create":"2026-04-30T12:39:24.2015956+04:00","gmt_modified":"2026-04-30T12:39:24.2015956+04:00"},{"id":41355,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"6af53e8de30910078bc6fd9dab1d2f7b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/time/time.cpp#74-76","gmt_create":"2026-04-30T12:39:24.2015956+04:00","gmt_modified":"2026-04-30T12:39:24.2015956+04:00"},{"id":41356,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"0ef7b0d7933da804905c2ff76f92cd94","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2824-2839","gmt_create":"2026-04-30T12:39:24.2024959+04:00","gmt_modified":"2026-04-30T12:39:24.2024959+04:00"},{"id":41357,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"667d252413c23e04beb2c069531a1372","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2871-2886","gmt_create":"2026-04-30T12:39:24.2024959+04:00","gmt_modified":"2026-04-30T12:39:24.2024959+04:00"},{"id":41358,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"62ea8f0eed608d8eb1dd0911e43f28c3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1223-1267","gmt_create":"2026-04-30T12:39:24.2024959+04:00","gmt_modified":"2026-04-30T12:39:24.2024959+04:00"},{"id":41359,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"1b91062fbd3e8ce21a7ec705a5ef21ae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#125-133","gmt_create":"2026-04-30T12:39:24.2034958+04:00","gmt_modified":"2026-04-30T12:39:24.2034958+04:00"},{"id":41360,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"c57b368c9aec32de084799a61fc21d81","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#149-155","gmt_create":"2026-04-30T12:39:24.2034958+04:00","gmt_modified":"2026-04-30T12:39:24.2034958+04:00"},{"id":41361,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"9a1726ad4c4d7942894eafbb2fb7c20a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#222-224","gmt_create":"2026-04-30T12:39:24.2034958+04:00","gmt_modified":"2026-04-30T12:39:24.2034958+04:00"},{"id":41362,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"2b851e123aa78f2afb16a52472781e64","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#228-233","gmt_create":"2026-04-30T12:39:24.2044954+04:00","gmt_modified":"2026-04-30T12:39:24.2044954+04:00"},{"id":41363,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"410cd37dc3943354d37ce7542843e6c0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#301-328","gmt_create":"2026-04-30T12:39:24.2044954+04:00","gmt_modified":"2026-04-30T12:39:24.2044954+04:00"},{"id":41364,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"410cd37dc3943354d37ce7542843e6c0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 301-328","gmt_create":"2026-04-30T12:39:24.2044954+04:00","gmt_modified":"2026-04-30T12:39:24.2044954+04:00"},{"id":41365,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"804dc1cd57f82825de2acc450a8496c3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#330-408","gmt_create":"2026-04-30T12:39:24.2054962+04:00","gmt_modified":"2026-04-30T12:39:24.2054962+04:00"},{"id":41366,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"804dc1cd57f82825de2acc450a8496c3","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 330-408","gmt_create":"2026-04-30T12:39:24.2054962+04:00","gmt_modified":"2026-04-30T12:39:24.2054962+04:00"},{"id":41367,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"d572e2edecf45b7b050d30cbb14368d8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/protocol/include/graphene/protocol/config.hpp#57-58","gmt_create":"2026-04-30T12:39:24.2054962+04:00","gmt_modified":"2026-04-30T12:39:24.2054962+04:00"},{"id":41368,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"a3b204b149312d56bea1667800a95fb6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config.ini#99-103","gmt_create":"2026-04-30T12:39:24.2064968+04:00","gmt_modified":"2026-04-30T12:39:24.2064968+04:00"},{"id":41369,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"f72faf82a21dd9049d68549d9c7e5c4f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config_witness.ini#76-80","gmt_create":"2026-04-30T12:39:24.2064968+04:00","gmt_modified":"2026-04-30T12:39:24.2064968+04:00"},{"id":41370,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"9ea04ad05deddb408f8a972fd04ba0d0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config_witness.ini#128-141","gmt_create":"2026-04-30T12:39:24.2064968+04:00","gmt_modified":"2026-04-30T12:39:24.2064968+04:00"},{"id":41371,"source_id":"bb293be9318768f10f69c80fd6b68517","target_id":"9ea04ad05deddb408f8a972fd04ba0d0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 128-141","gmt_create":"2026-04-30T12:39:24.2064968+04:00","gmt_modified":"2026-04-30T12:39:24.2064968+04:00"},{"id":41372,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"c5701be8b76f9a4a85b659f219a10f95","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#509-555","gmt_create":"2026-04-30T12:39:24.2064968+04:00","gmt_modified":"2026-04-30T12:39:24.2064968+04:00"},{"id":41373,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"7f2c3ab5ba63b977d9642aa1c78a2c43","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#120-169","gmt_create":"2026-04-30T12:39:24.2074969+04:00","gmt_modified":"2026-04-30T12:39:24.2074969+04:00"},{"id":41374,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"737d623fe091f7ae2629dcda699b0efa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#171-192","gmt_create":"2026-04-30T12:39:24.2074969+04:00","gmt_modified":"2026-04-30T12:39:24.2074969+04:00"},{"id":41375,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"fba8be1bbb523071f8ce7ad55a13d0f2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/include/graphene/plugins/witness/witness.hpp#31","gmt_create":"2026-04-30T12:39:24.2094956+04:00","gmt_modified":"2026-04-30T12:39:24.2094956+04:00"},{"id":41376,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"2a7a24b7119edca00eb0b21200f484ec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#88","gmt_create":"2026-04-30T12:39:24.2094956+04:00","gmt_modified":"2026-04-30T12:39:24.2094956+04:00"},{"id":41377,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"c9048f7e0344e917d91b3b45d3804a0d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#73","gmt_create":"2026-04-30T12:39:24.2104954+04:00","gmt_modified":"2026-04-30T12:39:24.2104954+04:00"},{"id":41378,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"ff4cacfd8a6a1ce746ce49bd2259ef47","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#151-166","gmt_create":"2026-04-30T12:39:24.2104954+04:00","gmt_modified":"2026-04-30T12:39:24.2104954+04:00"},{"id":41379,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"78279a6057ca0eaea7f987e920fada7f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1456-1471","gmt_create":"2026-04-30T12:39:24.2104954+04:00","gmt_modified":"2026-04-30T12:39:24.2104954+04:00"},{"id":41380,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"95e9eb6df5c5b54fc25131e15cebca7b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#269-274","gmt_create":"2026-04-30T12:39:24.2104954+04:00","gmt_modified":"2026-04-30T12:39:24.2104954+04:00"},{"id":41381,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"4c8c235c40a9885b9ce181cc9af786fe","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2807-2839","gmt_create":"2026-04-30T12:39:24.2121026+04:00","gmt_modified":"2026-04-30T12:39:24.2121026+04:00"},{"id":41382,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"29354043db86eeb48566453a908645c7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2897-2914","gmt_create":"2026-04-30T12:39:24.2126725+04:00","gmt_modified":"2026-04-30T12:39:24.2126725+04:00"},{"id":41383,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"6e4547d3d8a2b1ce7fb2eb8442ba9631","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1294-1311","gmt_create":"2026-04-30T12:39:24.2140206+04:00","gmt_modified":"2026-04-30T12:39:24.2140206+04:00"},{"id":41384,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"0c5e33130a4b0128f85c5ef5837b2837","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#338-407","gmt_create":"2026-04-30T12:39:24.2145431+04:00","gmt_modified":"2026-04-30T12:39:24.2145431+04:00"},{"id":41385,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"b4b9171ebcdf9b3750b66cea688f5e6b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#411-419","gmt_create":"2026-04-30T12:39:24.215077+04:00","gmt_modified":"2026-04-30T12:39:24.215077+04:00"},{"id":41386,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"673f7fff8a03580c3caedc629fc67cd4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#60","gmt_create":"2026-04-30T12:39:24.2156096+04:00","gmt_modified":"2026-04-30T12:39:24.2156096+04:00"},{"id":41387,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"dbf1a067fe97d23702c1dcb8e2df53b6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1890-1892","gmt_create":"2026-04-30T12:39:24.2156096+04:00","gmt_modified":"2026-04-30T12:39:24.2156096+04:00"},{"id":41388,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"2ad181689b70c433e1ec262fbbedb608","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4536-4573","gmt_create":"2026-04-30T12:39:24.2161297+04:00","gmt_modified":"2026-04-30T12:39:24.2161297+04:00"},{"id":41389,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"73337c954cb4cc654d9a0b55b95b2480","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5530-5655","gmt_create":"2026-04-30T12:39:24.2161297+04:00","gmt_modified":"2026-04-30T12:39:24.2161297+04:00"},{"id":41390,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"f46e3fd7859017b5e2c1e7b5c296cb4f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#197-246","gmt_create":"2026-04-30T12:39:24.2171332+04:00","gmt_modified":"2026-04-30T12:39:24.2171332+04:00"},{"id":41391,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"f46e3fd7859017b5e2c1e7b5c296cb4f","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 197-246","gmt_create":"2026-04-30T12:39:24.2171332+04:00","gmt_modified":"2026-04-30T12:39:24.2171332+04:00"},{"id":41392,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"9d47a587156b7ae6cd73b0e5b5ad4f56","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#252-294","gmt_create":"2026-04-30T12:39:24.2177181+04:00","gmt_modified":"2026-04-30T12:39:24.2177181+04:00"},{"id":41393,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"9d47a587156b7ae6cd73b0e5b5ad4f56","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 252-294","gmt_create":"2026-04-30T12:39:24.2177181+04:00","gmt_modified":"2026-04-30T12:39:24.2177181+04:00"},{"id":41394,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"322bbb26c4fa89c5b6c197da6defb3b0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#301-408","gmt_create":"2026-04-30T12:39:24.2183088+04:00","gmt_modified":"2026-04-30T12:39:24.2183088+04:00"},{"id":41395,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"322bbb26c4fa89c5b6c197da6defb3b0","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 301-408","gmt_create":"2026-04-30T12:39:24.2183088+04:00","gmt_modified":"2026-04-30T12:39:24.2183088+04:00"},{"id":41396,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"4c7449e318e56d7531d2b650c48cda91","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_guard/witness_guard.cpp#410-555","gmt_create":"2026-04-30T12:39:24.2183088+04:00","gmt_modified":"2026-04-30T12:39:24.2183088+04:00"},{"id":41397,"source_id":"f0afc0a1f80132e4bf15ac9ff156e256","target_id":"4c7449e318e56d7531d2b650c48cda91","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 410-555","gmt_create":"2026-04-30T12:39:24.2188118+04:00","gmt_modified":"2026-04-30T12:39:24.2188118+04:00"},{"id":41398,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"0dec6783542ea54ba7d81fbeb930e442","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#30-49","gmt_create":"2026-04-30T12:39:24.2188118+04:00","gmt_modified":"2026-04-30T12:39:24.2188118+04:00"},{"id":41399,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"85675dcfc30f216052bdb5cedc7b435c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#75-91","gmt_create":"2026-04-30T12:39:24.2193374+04:00","gmt_modified":"2026-04-30T12:39:24.2193374+04:00"},{"id":41400,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"7f42fa8b501a589d403cb682d1620581","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#102-125","gmt_create":"2026-04-30T12:39:24.2198543+04:00","gmt_modified":"2026-04-30T12:39:24.2198543+04:00"},{"id":41401,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"17a2c941cfb814db6c1623046d8dac1e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#127-159","gmt_create":"2026-04-30T12:39:24.2202891+04:00","gmt_modified":"2026-04-30T12:39:24.2202891+04:00"},{"id":41402,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"53a82d703bff53e589353336964d2eed","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#161-169","gmt_create":"2026-04-30T12:39:24.2202891+04:00","gmt_modified":"2026-04-30T12:39:24.2202891+04:00"},{"id":41403,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"4fcf4072cf8ebe5cd019e9a0da762901","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#171-203","gmt_create":"2026-04-30T12:39:24.2208743+04:00","gmt_modified":"2026-04-30T12:39:24.2208743+04:00"},{"id":41404,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"c4f66fb8fb1d6eeb25bd48eec2ba80a6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#102-159","gmt_create":"2026-04-30T12:39:24.2219552+04:00","gmt_modified":"2026-04-30T12:39:24.2219552+04:00"},{"id":41405,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"68e0135d2e3b03eff760c57654b97092","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness_api/plugin.cpp#161-203","gmt_create":"2026-04-30T12:39:24.2224985+04:00","gmt_modified":"2026-04-30T12:39:24.2224985+04:00"},{"id":41406,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"eeebc7e5a0ce3570715e7391da03b065","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/witness_objects.hpp#104-171","gmt_create":"2026-04-30T12:39:24.2230377+04:00","gmt_modified":"2026-04-30T12:39:24.2230377+04:00"},{"id":41407,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"ef4797348572b382b96b9d19a362eed7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/fork_database.hpp#90-95","gmt_create":"2026-04-30T12:39:24.2247256+04:00","gmt_modified":"2026-04-30T12:39:24.2247256+04:00"},{"id":41408,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"09dcfe86b88d969bbe651733cb34fc2d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/global_property_object.hpp#139","gmt_create":"2026-04-30T12:39:24.2257535+04:00","gmt_modified":"2026-04-30T12:39:24.2257535+04:00"},{"id":41409,"source_id":"7ae785f9d5ab154dce6f8eb295b93456","target_id":"09dcfe86b88d969bbe651733cb34fc2d","source_type":"SOURCE_FILE","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Source file contains code snippet: 139","gmt_create":"2026-04-30T12:39:24.2257535+04:00","gmt_modified":"2026-04-30T12:39:24.2257535+04:00"},{"id":41410,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"85e55e5f6d83a36cd6afac5fcb62fb42","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1626-1805","gmt_create":"2026-04-30T12:39:24.2274826+04:00","gmt_modified":"2026-04-30T12:39:24.2274826+04:00"},{"id":41411,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"1d8a3a8529f55f725cbd52ef78db6a1f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4334-4463","gmt_create":"2026-04-30T12:39:24.2281758+04:00","gmt_modified":"2026-04-30T12:39:24.2281758+04:00"},{"id":41412,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"87584d47dc52c8658341b565e96c989d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#492-499","gmt_create":"2026-04-30T12:39:24.2286787+04:00","gmt_modified":"2026-04-30T12:39:24.2286787+04:00"},{"id":41413,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"cda7dad93173bc2161ab2ff3c92e81ce","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/time/time.cpp#36-39","gmt_create":"2026-04-30T12:39:24.2305969+04:00","gmt_modified":"2026-04-30T12:39:24.2305969+04:00"},{"id":41414,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"73dca3cd312efbc6ecb517ae118fd869","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/network/ntp.cpp#184-201","gmt_create":"2026-04-30T12:39:24.2310995+04:00","gmt_modified":"2026-04-30T12:39:24.2310995+04:00"},{"id":41415,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"c69f6ef1a64e88829a12d7ab4c190897","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: thirdparty/fc/src/network/ntp.cpp#236-266","gmt_create":"2026-04-30T12:39:24.2312447+04:00","gmt_modified":"2026-04-30T12:39:24.2312447+04:00"},{"id":41416,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"26905e829dbbc0af740b97400068843b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#255-271","gmt_create":"2026-04-30T12:39:24.234719+04:00","gmt_modified":"2026-04-30T12:39:24.234719+04:00"},{"id":41417,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"1eb7cc21b9daf17a2c908f3121bcf2f0","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#387-396","gmt_create":"2026-04-30T12:39:24.2350013+04:00","gmt_modified":"2026-04-30T12:39:24.2350013+04:00"},{"id":41418,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"cc4d2fca7cfbc2ed2aa9dc9016360fb5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2826-2836","gmt_create":"2026-04-30T12:39:24.2361746+04:00","gmt_modified":"2026-04-30T12:39:24.2361746+04:00"},{"id":41419,"source_id":"c93fa44d-294e-4802-9676-e73b1a162b2b","target_id":"e0e1ada694da4e9e256e0d6d39aa73e5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#2873-2883","gmt_create":"2026-04-30T12:39:24.2367541+04:00","gmt_modified":"2026-04-30T12:39:24.2367541+04:00"},{"id":41420,"source_id":"f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce","target_id":"6ae7ebac-bb25-4499-8364-0f2f7ac99d8a","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce -\u003e 6ae7ebac-bb25-4499-8364-0f2f7ac99d8a","gmt_create":"2026-04-30T12:41:43.9043639+04:00","gmt_modified":"2026-04-30T12:41:43.9043639+04:00"},{"id":41421,"source_id":"ecd87c29-4938-4318-9bbb-b3c2a87a2d22","target_id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ecd87c29-4938-4318-9bbb-b3c2a87a2d22 -\u003e 61b10976-8eeb-45d9-a3fa-3b71c7d30939","gmt_create":"2026-04-30T12:41:43.9048693+04:00","gmt_modified":"2026-04-30T12:41:43.9048693+04:00"},{"id":41423,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"c4447af409b7f3205a55e5b286557dfe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.cpp","gmt_create":"2026-04-30T13:09:00.7529864+04:00","gmt_modified":"2026-04-30T13:09:00.7529864+04:00"},{"id":41424,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"bd196d115b6bd3d5310dfa247f1e25b2","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp","gmt_create":"2026-04-30T13:09:00.7535048+04:00","gmt_modified":"2026-04-30T13:09:00.7535048+04:00"},{"id":41425,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"991938d306a547ff48a759fb9bd1c5a4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp","gmt_create":"2026-04-30T13:09:00.7535048+04:00","gmt_modified":"2026-04-30T13:09:00.7535048+04:00"},{"id":41426,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"d8e14923de7e4be8f600e264a03ea281","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp","gmt_create":"2026-04-30T13:09:00.7535048+04:00","gmt_modified":"2026-04-30T13:09:00.7535048+04:00"},{"id":41427,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"dc43b9a20a2ae22effbe51aecf8ca751","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/CMakeLists.txt","gmt_create":"2026-04-30T13:09:00.7535048+04:00","gmt_modified":"2026-04-30T13:09:00.7535048+04:00"},{"id":41428,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"55b60ae8ffadd70d41ebd01151dcb237","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/snapshot.json","gmt_create":"2026-04-30T13:09:00.7540292+04:00","gmt_modified":"2026-04-30T13:09:00.7540292+04:00"},{"id":41429,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"2eb0f5e7350fc53dec7939bb93e824ac","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/snapshot-testnet.json","gmt_create":"2026-04-30T13:09:00.7540292+04:00","gmt_modified":"2026-04-30T13:09:00.7540292+04:00"},{"id":41430,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"4d5bf798ac6e167d6d0e20a669431373","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: documentation/snapshot-plugin.md","gmt_create":"2026-04-30T13:09:00.7540292+04:00","gmt_modified":"2026-04-30T13:09:00.7540292+04:00"},{"id":41431,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"6c94b84fdfd5c7016b5eeadf8099133e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/plugin.cpp","gmt_create":"2026-04-30T13:09:00.7540292+04:00","gmt_modified":"2026-04-30T13:09:00.7540292+04:00"},{"id":41432,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ae7188ee9396d8d8aca884b96e9bc4c1","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/chain/include/graphene/plugins/chain/plugin.hpp","gmt_create":"2026-04-30T13:09:00.7540292+04:00","gmt_modified":"2026-04-30T13:09:00.7540292+04:00"},{"id":41433,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41434,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"609365f8572668c8cf1e1cfa497989e4","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41435,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ade43b15adadb0a3215b2b7a6866ef22","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41436,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"7550b6fcbbc44fc4df4f050d5b4fb04a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/witness/witness.cpp","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41437,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"51c2f2c072611ae01260d171fbd12b59","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/interprocess/file_mutex.cpp","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41438,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"18555f254f50536a15d8591acf982406","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config.ini","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41439,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-30T13:09:00.7545483+04:00","gmt_modified":"2026-04-30T13:09:00.7545483+04:00"},{"id":41440,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"f7dedf31e491c7adbaf05e957360c531","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-30T13:09:00.7550662+04:00","gmt_modified":"2026-04-30T13:09:00.7550662+04:00"},{"id":41441,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T13:09:00.7550662+04:00","gmt_modified":"2026-04-30T13:09:00.7550662+04:00"},{"id":41442,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"d71a233c4690ddd107d80d74f5956ebf","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/log/logger_config.cpp","gmt_create":"2026-04-30T13:09:00.7550662+04:00","gmt_modified":"2026-04-30T13:09:00.7550662+04:00"},{"id":41443,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"a53dc201b0a9ff736da577ae1c524abb","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/fc/src/log/console_appender.cpp","gmt_create":"2026-04-30T13:09:00.7550662+04:00","gmt_modified":"2026-04-30T13:09:00.7550662+04:00"},{"id":41444,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"1ade3cebbc11a4634bcdf1a7fdb2756e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: thirdparty/chainbase/src/chainbase.cpp","gmt_create":"2026-04-30T13:09:00.7550662+04:00","gmt_modified":"2026-04-30T13:09:00.7550662+04:00"},{"id":41445,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"f2c0625ed2d13bb0a3699a45bf25d6cc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1-50","gmt_create":"2026-04-30T13:09:00.7555778+04:00","gmt_modified":"2026-04-30T13:09:00.7555778+04:00"},{"id":41446,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"b49193d53f2bca5e5a84c905c1f90429","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#1-88","gmt_create":"2026-04-30T13:09:00.7555778+04:00","gmt_modified":"2026-04-30T13:09:00.7555778+04:00"},{"id":41447,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ca74187bf1151ee3b389754df4ce08d1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp#1-52","gmt_create":"2026-04-30T13:09:00.7555778+04:00","gmt_modified":"2026-04-30T13:09:00.7555778+04:00"},{"id":41448,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"60ec378a66a7653324d69f456e1242c4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/CMakeLists.txt#1-52","gmt_create":"2026-04-30T13:09:00.7560916+04:00","gmt_modified":"2026-04-30T13:09:00.7560916+04:00"},{"id":41449,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"93f965590673de9c5fe2a34fbc24af1c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#42-76","gmt_create":"2026-04-30T13:09:00.7560916+04:00","gmt_modified":"2026-04-30T13:09:00.7560916+04:00"},{"id":41450,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"98907d07c594fe14c157ba203649b596","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp#16-52","gmt_create":"2026-04-30T13:09:00.7560916+04:00","gmt_modified":"2026-04-30T13:09:00.7560916+04:00"},{"id":41451,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"aba83bcb80fe7ecb8f1f224f2fca05da","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp#30-158","gmt_create":"2026-04-30T13:09:00.7566029+04:00","gmt_modified":"2026-04-30T13:09:00.7566029+04:00"},{"id":41452,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"33fc7efcd171685ef3235423dc636724","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#675-780","gmt_create":"2026-04-30T13:09:00.7566029+04:00","gmt_modified":"2026-04-30T13:09:00.7566029+04:00"},{"id":41453,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"0a6e409382fdf7cc6924a9f918b5a4d8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp#37-107","gmt_create":"2026-04-30T13:09:00.7566029+04:00","gmt_modified":"2026-04-30T13:09:00.7566029+04:00"},{"id":41454,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"d419300bbe8000129fd9765417390c9d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#885-987","gmt_create":"2026-04-30T13:09:00.7571149+04:00","gmt_modified":"2026-04-30T13:09:00.7571149+04:00"},{"id":41455,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"c331a645eb7848bda9b3f651866478ec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#789-883","gmt_create":"2026-04-30T13:09:00.7571149+04:00","gmt_modified":"2026-04-30T13:09:00.7571149+04:00"},{"id":41456,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"06ca849a27b79d1edd6a6faaabf7a1fa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1400-1484","gmt_create":"2026-04-30T13:09:00.7571149+04:00","gmt_modified":"2026-04-30T13:09:00.7571149+04:00"},{"id":41457,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"9102ca0b920a6d527e069776bf786565","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1046-1288","gmt_create":"2026-04-30T13:09:00.7576256+04:00","gmt_modified":"2026-04-30T13:09:00.7576256+04:00"},{"id":41458,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"272ad74758e94f746c862c4c91c17496","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1902-2038","gmt_create":"2026-04-30T13:09:00.7576256+04:00","gmt_modified":"2026-04-30T13:09:00.7576256+04:00"},{"id":41459,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ea6973621046033cdcd6899b7a2bcb0f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1470-1599","gmt_create":"2026-04-30T13:09:00.7576256+04:00","gmt_modified":"2026-04-30T13:09:00.7576256+04:00"},{"id":41460,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"2f7386ded19c5e4ae6581ef6148fdd81","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2473-2510","gmt_create":"2026-04-30T13:09:00.7581379+04:00","gmt_modified":"2026-04-30T13:09:00.7581379+04:00"},{"id":41461,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"5fcc5bdcf25fd73b110eede565277186","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: documentation/snapshot-plugin.md#247-273","gmt_create":"2026-04-30T13:09:00.7581379+04:00","gmt_modified":"2026-04-30T13:09:00.7581379+04:00"},{"id":41462,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"7c62e5936f0fc7f60f9a5587800a2578","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1418-1436","gmt_create":"2026-04-30T13:09:00.7581379+04:00","gmt_modified":"2026-04-30T13:09:00.7581379+04:00"},{"id":41463,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"983d0185105181e4f539d636abc549c9","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#737-743","gmt_create":"2026-04-30T13:09:00.7586495+04:00","gmt_modified":"2026-04-30T13:09:00.7586495+04:00"},{"id":41464,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"82acc28cade2d06dac2a207816ccf888","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1390-1484","gmt_create":"2026-04-30T13:09:00.7586495+04:00","gmt_modified":"2026-04-30T13:09:00.7586495+04:00"},{"id":41465,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"1d3d2cb1a9886c18b5c14d6f4bbbec7c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1440-1449","gmt_create":"2026-04-30T13:09:00.7586495+04:00","gmt_modified":"2026-04-30T13:09:00.7586495+04:00"},{"id":41466,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"de42aaaef014331a40dc6a645093a785","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/witness/witness.cpp#335-551","gmt_create":"2026-04-30T13:09:00.7591596+04:00","gmt_modified":"2026-04-30T13:09:00.7591596+04:00"},{"id":41467,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"e9427d96ecb45f8e18859dbaff6e2b3a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1326-1376","gmt_create":"2026-04-30T13:09:00.7591596+04:00","gmt_modified":"2026-04-30T13:09:00.7591596+04:00"},{"id":41468,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ddc5b6727e7907a53519cc63983aabb7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1426-1435","gmt_create":"2026-04-30T13:09:00.7591596+04:00","gmt_modified":"2026-04-30T13:09:00.7591596+04:00"},{"id":41469,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"a54238157d7db2d12e89ea767738f5ea","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#745-750","gmt_create":"2026-04-30T13:09:00.7596894+04:00","gmt_modified":"2026-04-30T13:09:00.7596894+04:00"},{"id":41470,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"8481665c6e787cad6255e9ace82fe2c2","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#697-700","gmt_create":"2026-04-30T13:09:00.7596894+04:00","gmt_modified":"2026-04-30T13:09:00.7596894+04:00"},{"id":41471,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"91b6373025370a39784ff61586453267","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2831-2845","gmt_create":"2026-04-30T13:09:00.7602036+04:00","gmt_modified":"2026-04-30T13:09:00.7602036+04:00"},{"id":41472,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"fa6088c57e5552b03a53d22a8b3371cd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1719-1748","gmt_create":"2026-04-30T13:09:00.7602036+04:00","gmt_modified":"2026-04-30T13:09:00.7602036+04:00"},{"id":41473,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"2ef1c85090d0812eda040eb10dbfab00","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1706-1748","gmt_create":"2026-04-30T13:09:00.7607314+04:00","gmt_modified":"2026-04-30T13:09:00.7607314+04:00"},{"id":41474,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"6c4fffc1a4b8a3dccdccf3bc8b6e478c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#490-560","gmt_create":"2026-04-30T13:09:00.7607314+04:00","gmt_modified":"2026-04-30T13:09:00.7607314+04:00"},{"id":41475,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"002084afc1181b400d0ac1f626010b50","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2945-2959","gmt_create":"2026-04-30T13:09:00.7607314+04:00","gmt_modified":"2026-04-30T13:09:00.7607314+04:00"},{"id":41476,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"a9ac85c210bf25017dc2f3043429bcff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#441-5201","gmt_create":"2026-04-30T13:09:00.7607314+04:00","gmt_modified":"2026-04-30T13:09:00.7607314+04:00"},{"id":41477,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"4083d0aedccff773e235c49f483acdee","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#542-559","gmt_create":"2026-04-30T13:09:00.7612446+04:00","gmt_modified":"2026-04-30T13:09:00.7612446+04:00"},{"id":41478,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"87dbe393410d68cf0b46ab22827e33fe","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#3252-3290","gmt_create":"2026-04-30T13:09:00.7612446+04:00","gmt_modified":"2026-04-30T13:09:00.7612446+04:00"},{"id":41479,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"98c57a9cc27d3a9e444c74d23a63bf9e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#4945-4947","gmt_create":"2026-04-30T13:09:00.7612446+04:00","gmt_modified":"2026-04-30T13:09:00.7612446+04:00"},{"id":41480,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"2d706a9e6bc734297fc6693a4cec7176","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#5139-5140","gmt_create":"2026-04-30T13:09:00.7617705+04:00","gmt_modified":"2026-04-30T13:09:00.7617705+04:00"},{"id":41481,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"95d10140a46b090a8cea978c1c73a9bb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database.hpp#337-338","gmt_create":"2026-04-30T13:09:00.7617705+04:00","gmt_modified":"2026-04-30T13:09:00.7617705+04:00"},{"id":41482,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"5b61f5ff88b75190fc4b2051750444dd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2976-3009","gmt_create":"2026-04-30T13:09:00.7622901+04:00","gmt_modified":"2026-04-30T13:09:00.7622901+04:00"},{"id":41483,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"133becde6f5eef3f56c38aa3650af39f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2468-2570","gmt_create":"2026-04-30T13:09:00.7622901+04:00","gmt_modified":"2026-04-30T13:09:00.7622901+04:00"},{"id":41484,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"7c3bbfa78db4dbdd170795e4ff786767","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#689-697","gmt_create":"2026-04-30T13:09:00.7622901+04:00","gmt_modified":"2026-04-30T13:09:00.7622901+04:00"},{"id":41485,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"de95ec3c9606ef77f32c319a1cee9d67","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5241-5274","gmt_create":"2026-04-30T13:09:00.7622901+04:00","gmt_modified":"2026-04-30T13:09:00.7622901+04:00"},{"id":41486,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"4ad076dd99ea06a22ba2703dfdfbef50","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#284-290","gmt_create":"2026-04-30T13:09:00.7629213+04:00","gmt_modified":"2026-04-30T13:09:00.7629213+04:00"},{"id":41487,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"d21d0e6c03944b9bd0683917197d45ec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#86-88","gmt_create":"2026-04-30T13:09:00.7629213+04:00","gmt_modified":"2026-04-30T13:09:00.7629213+04:00"},{"id":41488,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ce8c1f15287ef92650480373eef95fb8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#735-740","gmt_create":"2026-04-30T13:09:00.7629213+04:00","gmt_modified":"2026-04-30T13:09:00.7629213+04:00"},{"id":41489,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"474f1f81f21526cb90fc2564fdc36457","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1814-1862","gmt_create":"2026-04-30T13:09:00.763425+04:00","gmt_modified":"2026-04-30T13:09:00.763425+04:00"},{"id":41490,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"3fcde09e22fa6291183075924ea0ea2a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#772-785","gmt_create":"2026-04-30T13:09:00.763425+04:00","gmt_modified":"2026-04-30T13:09:00.763425+04:00"},{"id":41491,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"16865a1a8de25574f1f934ab4b21167f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1595-1624","gmt_create":"2026-04-30T13:09:00.763425+04:00","gmt_modified":"2026-04-30T13:09:00.763425+04:00"},{"id":41492,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"25271321cea3b037da1bd49f4a9c73e3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: documentation/snapshot-plugin.md#339-374","gmt_create":"2026-04-30T13:09:00.7639416+04:00","gmt_modified":"2026-04-30T13:09:00.7639416+04:00"},{"id":41493,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ffac59891823846e038c48de0f7fa754","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#585-649","gmt_create":"2026-04-30T13:09:00.7639416+04:00","gmt_modified":"2026-04-30T13:09:00.7639416+04:00"},{"id":41494,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"ce8fa82841cb9ff1bb572dde21cd2dca","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#673-677","gmt_create":"2026-04-30T13:09:00.7639416+04:00","gmt_modified":"2026-04-30T13:09:00.7639416+04:00"},{"id":41495,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"31a0e1cdb8f61a661ea048c18a0c31ab","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#744-755","gmt_create":"2026-04-30T13:09:00.7639416+04:00","gmt_modified":"2026-04-30T13:09:00.7639416+04:00"},{"id":41496,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"e360b37afb60df02b8a51d89f141bf95","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#165-176","gmt_create":"2026-04-30T13:09:00.7644557+04:00","gmt_modified":"2026-04-30T13:09:00.7644557+04:00"},{"id":41497,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"a4b87380168ea56eda2691254c138879","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1587-1596","gmt_create":"2026-04-30T13:09:00.7644557+04:00","gmt_modified":"2026-04-30T13:09:00.7644557+04:00"},{"id":41498,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"0442d9dfa5137479a458da6803ce6d7d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1610-1620","gmt_create":"2026-04-30T13:09:00.7644557+04:00","gmt_modified":"2026-04-30T13:09:00.7644557+04:00"},{"id":41499,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"5f1666b71febd2ce3545e7b9c05598fa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1812-1877","gmt_create":"2026-04-30T13:09:00.7644557+04:00","gmt_modified":"2026-04-30T13:09:00.7644557+04:00"},{"id":41500,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"f7cc310f8ee11ed3c2ac13ce575146d4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp#24-34","gmt_create":"2026-04-30T13:09:00.7649772+04:00","gmt_modified":"2026-04-30T13:09:00.7649772+04:00"},{"id":41501,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"24d1f458412bbfade9d25c0ed1521322","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2598-2680","gmt_create":"2026-04-30T13:09:00.7655018+04:00","gmt_modified":"2026-04-30T13:09:00.7655018+04:00"},{"id":41502,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"985521912575155bfda8b7bce74494df","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/chain/plugin.cpp#364-432","gmt_create":"2026-04-30T13:09:00.7655018+04:00","gmt_modified":"2026-04-30T13:09:00.7655018+04:00"},{"id":41503,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"299aedc88851dabd155fc2edbbad96ef","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/CMakeLists.txt#27-38","gmt_create":"2026-04-30T13:09:00.7655018+04:00","gmt_modified":"2026-04-30T13:09:00.7655018+04:00"},{"id":41504,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"2a76e2c48df7946c93b84c330a1c08ae","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#2294-2464","gmt_create":"2026-04-30T13:09:00.7660159+04:00","gmt_modified":"2026-04-30T13:09:00.7660159+04:00"},{"id":41505,"source_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","target_id":"d7fc3783ff3c58fa7beaff10948417fa","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#1378-1464","gmt_create":"2026-04-30T13:09:00.7660159+04:00","gmt_modified":"2026-04-30T13:09:00.7660159+04:00"},{"id":41506,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d72b348a2c3c7943e4a7abb7dbdaa751","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_connection.hpp","gmt_create":"2026-04-30T13:09:18.7937429+04:00","gmt_modified":"2026-04-30T13:09:18.7937429+04:00"},{"id":41507,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"6c8cc56bd5bd0ef7abed9b709d60f20b","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/peer_connection.cpp","gmt_create":"2026-04-30T13:09:18.794743+04:00","gmt_modified":"2026-04-30T13:09:18.794743+04:00"},{"id":41508,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f7dedf31e491c7adbaf05e957360c531","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-30T13:09:18.794743+04:00","gmt_modified":"2026-04-30T13:09:18.794743+04:00"},{"id":41509,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"b4bd3a3265ac695da5624024015e0e81","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/node.cpp","gmt_create":"2026-04-30T13:09:18.794743+04:00","gmt_modified":"2026-04-30T13:09:18.794743+04:00"},{"id":41510,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"6e587b97bf4080c7754c5ed73736fca7","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message_oriented_connection.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41511,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"69291a4b8d9de900b397578829d3e0d9","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/message_oriented_connection.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41512,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"398d9d4b02b6383c0b752cb0196a0475","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/stcp_socket.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41513,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"01e57c63d684a56829f909c03b8ea162","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/stcp_socket.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41514,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"3a6c30f4bb3b265155c881ccfafa980e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/core_messages.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41515,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"2c99501f0c511d0792a2e5a56e4debfa","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/core_messages.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41516,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"c82262dc5275e094efc9474032d15922","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/config.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41517,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"3a8d8a10556a0b6501e25aa43e91f913","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/peer_database.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41518,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"c70caf63ce77b078dcf89380b228a71a","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/peer_database.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41519,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"b4467ca30cb6f6d587fc200900ee9ec9","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/message.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41520,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"3948eb588d15d01acf21ffd439ec508c","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/network/include/graphene/network/exceptions.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41521,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"66c6049f94b83d8f15dc55bb2424efbe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/p2p/p2p_plugin.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41522,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"a5661951a63a8a4cb0a563b6ff08335e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/database.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41523,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"73ada165e99c6ad5f938a94f11fb3e10","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/fork_database.cpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41524,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"cb29035725926be38d36ad8c01792b7e","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: libraries/chain/include/graphene/chain/database_exceptions.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41525,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"0becb65bb16186368dcabd6b936c5a80","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.hpp","gmt_create":"2026-04-30T13:09:18.7957438+04:00","gmt_modified":"2026-04-30T13:09:18.7957438+04:00"},{"id":41526,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"c4447af409b7f3205a55e5b286557dfe","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: plugins/snapshot/plugin.cpp","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41527,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"4d5bf798ac6e167d6d0e20a669431373","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: documentation/snapshot-plugin.md","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41528,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"18555f254f50536a15d8591acf982406","source_type":"WIKI_ITEM","target_type":"SOURCE_FILE","relationship_type":"REFERENCED_BY","extra":"Wiki references source file: share/vizd/config/config.ini","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41529,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"9f8690911ef66c966743475f294ffb7e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#79-351","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41530,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"ff11a77da594972e0b8a78df4289ada5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message_oriented_connection.hpp#45-79","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41531,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"4e8bc2cdd3d4fc68b2f7138c54288174","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/stcp_socket.hpp#37-93","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41532,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"82787c8d2e7394cf00fa87735394c3d5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#72-95","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41533,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"a480bfeb1604076e7d1db7746687a3b4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#42-106","gmt_create":"2026-04-30T13:09:18.7972489+04:00","gmt_modified":"2026-04-30T13:09:18.7972489+04:00"},{"id":41534,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"930f47bc2c95f0cac4086eeca5749e2c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#190-304","gmt_create":"2026-04-30T13:09:18.7982522+04:00","gmt_modified":"2026-04-30T13:09:18.7982522+04:00"},{"id":41535,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"7b85de17d00c3c33f3e6ce72493cea24","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#104-134","gmt_create":"2026-04-30T13:09:18.7982522+04:00","gmt_modified":"2026-04-30T13:09:18.7982522+04:00"},{"id":41536,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"ab73f6cda0f4389d2bef214161afef81","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#593-601","gmt_create":"2026-04-30T13:09:18.7982522+04:00","gmt_modified":"2026-04-30T13:09:18.7982522+04:00"},{"id":41537,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8caf3345fc407dc61382d80fab63bc93","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5240-5274","gmt_create":"2026-04-30T13:09:18.7982522+04:00","gmt_modified":"2026-04-30T13:09:18.7982522+04:00"},{"id":41538,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"7c3bbfa78db4dbdd170795e4ff786767","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#689-697","gmt_create":"2026-04-30T13:09:18.7982522+04:00","gmt_modified":"2026-04-30T13:09:18.7982522+04:00"},{"id":41539,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8e54da669dc427d415acec15fb6a804a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/snapshot/plugin.cpp#3039-3045","gmt_create":"2026-04-30T13:09:18.7982522+04:00","gmt_modified":"2026-04-30T13:09:18.7982522+04:00"},{"id":41540,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"e6da7f673c730dddfe0373c2e796f71a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1215-1246","gmt_create":"2026-04-30T13:09:18.7992536+04:00","gmt_modified":"2026-04-30T13:09:18.7992536+04:00"},{"id":41541,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"732bc579d5f86ebf0e986ecfbdfa490d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#34-46","gmt_create":"2026-04-30T13:09:18.7992536+04:00","gmt_modified":"2026-04-30T13:09:18.7992536+04:00"},{"id":41542,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f02c5e6d0090d5de89e01b0a1d478c5c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/exceptions.hpp#33-45","gmt_create":"2026-04-30T13:09:18.7992536+04:00","gmt_modified":"2026-04-30T13:09:18.7992536+04:00"},{"id":41543,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f5f7d5764818ed749bc9b829f7aea2ff","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#1-386","gmt_create":"2026-04-30T13:09:18.7992536+04:00","gmt_modified":"2026-04-30T13:09:18.7992536+04:00"},{"id":41544,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"762c4b81ed454ad789c0b3b3cb00cc88","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message_oriented_connection.hpp#1-85","gmt_create":"2026-04-30T13:09:18.7992536+04:00","gmt_modified":"2026-04-30T13:09:18.7992536+04:00"},{"id":41545,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"53a676bf1df5198ba31b6ebe848a29bd","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/stcp_socket.hpp#1-99","gmt_create":"2026-04-30T13:09:18.8002536+04:00","gmt_modified":"2026-04-30T13:09:18.8002536+04:00"},{"id":41546,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"a3e6f8b4e51c838c44eaef16bb205031","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#1-573","gmt_create":"2026-04-30T13:09:18.8002536+04:00","gmt_modified":"2026-04-30T13:09:18.8002536+04:00"},{"id":41547,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"56968eec4c8adc4f9edd153c6ce9e231","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#1-374","gmt_create":"2026-04-30T13:09:18.8002536+04:00","gmt_modified":"2026-04-30T13:09:18.8002536+04:00"},{"id":41548,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f14b0ce0c1ba142fb55271fdc6c2ee9f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#1-141","gmt_create":"2026-04-30T13:09:18.8002536+04:00","gmt_modified":"2026-04-30T13:09:18.8002536+04:00"},{"id":41549,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f59f370133646ba0c40ebdd11ee6d697","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#1-114","gmt_create":"2026-04-30T13:09:18.8002536+04:00","gmt_modified":"2026-04-30T13:09:18.8002536+04:00"},{"id":41550,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8923fae736f09b1cb255636af9a52069","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1-6389","gmt_create":"2026-04-30T13:09:18.8012548+04:00","gmt_modified":"2026-04-30T13:09:18.8012548+04:00"},{"id":41551,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d3bee62a496fa43717911daed4f0bb13","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/fork_database.cpp#1-271","gmt_create":"2026-04-30T13:09:18.8012548+04:00","gmt_modified":"2026-04-30T13:09:18.8012548+04:00"},{"id":41552,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"18382ca1b0f5cd7c4280e057d0242410","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/exceptions.hpp#1-49","gmt_create":"2026-04-30T13:09:18.8012548+04:00","gmt_modified":"2026-04-30T13:09:18.8012548+04:00"},{"id":41553,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"a41d1e5edd6d2706424bcd3b39295264","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#68-162","gmt_create":"2026-04-30T13:09:18.8022551+04:00","gmt_modified":"2026-04-30T13:09:18.8022551+04:00"},{"id":41554,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"3de0360b8d19b4e6a521e737d44e1eec","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/message_oriented_connection.cpp#128-140","gmt_create":"2026-04-30T13:09:18.8022551+04:00","gmt_modified":"2026-04-30T13:09:18.8022551+04:00"},{"id":41555,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"0b26d723a87937f5a3f58770c522d067","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#49-72","gmt_create":"2026-04-30T13:09:18.8022551+04:00","gmt_modified":"2026-04-30T13:09:18.8022551+04:00"},{"id":41556,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"df02d50a1fa9064e2cfe4b033b6ecfc8","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#233-306","gmt_create":"2026-04-30T13:09:18.8022551+04:00","gmt_modified":"2026-04-30T13:09:18.8022551+04:00"},{"id":41557,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"a6b2b3362f68c7523a4e0d06a2a4352f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#424-799","gmt_create":"2026-04-30T13:09:18.8022551+04:00","gmt_modified":"2026-04-30T13:09:18.8022551+04:00"},{"id":41558,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"ee53a7a714337d597cc0a626fceec27f","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_database.cpp#100-174","gmt_create":"2026-04-30T13:09:18.8032547+04:00","gmt_modified":"2026-04-30T13:09:18.8032547+04:00"},{"id":41559,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d6f1f5c1ab7e33b9a558f648113330fb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#208-242","gmt_create":"2026-04-30T13:09:18.8032547+04:00","gmt_modified":"2026-04-30T13:09:18.8032547+04:00"},{"id":41560,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"c32e26ac2223fcbd9ec8280f135d338c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/message_oriented_connection.cpp#135-140","gmt_create":"2026-04-30T13:09:18.8042552+04:00","gmt_modified":"2026-04-30T13:09:18.8042552+04:00"},{"id":41561,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"db2f58e50720e139a95c598355919158","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#69-72","gmt_create":"2026-04-30T13:09:18.8042552+04:00","gmt_modified":"2026-04-30T13:09:18.8042552+04:00"},{"id":41562,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"0543ad31da0059b627fc7cfd5bac0cad","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#233-272","gmt_create":"2026-04-30T13:09:18.8042552+04:00","gmt_modified":"2026-04-30T13:09:18.8042552+04:00"},{"id":41563,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"fa47faf491ab252556d3fc50f00c6a8d","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#662-718","gmt_create":"2026-04-30T13:09:18.8042552+04:00","gmt_modified":"2026-04-30T13:09:18.8042552+04:00"},{"id":41564,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"fee43b750e5fa7d56e56ac6251e6b5bb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#41-66","gmt_create":"2026-04-30T13:09:18.8052538+04:00","gmt_modified":"2026-04-30T13:09:18.8052538+04:00"},{"id":41565,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"09cde54d90657149861df59444b291ba","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#244-338","gmt_create":"2026-04-30T13:09:18.8052538+04:00","gmt_modified":"2026-04-30T13:09:18.8052538+04:00"},{"id":41566,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"e3f78790c3dfc7c45569ce78697bf9ef","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#240-278","gmt_create":"2026-04-30T13:09:18.8052538+04:00","gmt_modified":"2026-04-30T13:09:18.8052538+04:00"},{"id":41567,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"5212be0912badd41a65263c4ef5ab6fc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/message_oriented_connection.cpp#237-283","gmt_create":"2026-04-30T13:09:18.8052538+04:00","gmt_modified":"2026-04-30T13:09:18.8052538+04:00"},{"id":41568,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8743a349115da7520b9567a7d855ca18","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/message_oriented_connection.cpp#148-235","gmt_create":"2026-04-30T13:09:18.8052538+04:00","gmt_modified":"2026-04-30T13:09:18.8052538+04:00"},{"id":41569,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d4b4e356cc64c1eac490f25138ad8337","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/stcp_socket.cpp#132-177","gmt_create":"2026-04-30T13:09:18.8062526+04:00","gmt_modified":"2026-04-30T13:09:18.8062526+04:00"},{"id":41570,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"cd0a62c9a78bb77d3b59a9d5872577f4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#82-106","gmt_create":"2026-04-30T13:09:18.8062526+04:00","gmt_modified":"2026-04-30T13:09:18.8062526+04:00"},{"id":41571,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8c4475690fa2dd5b99756637ea5c7358","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#356-369","gmt_create":"2026-04-30T13:09:18.8062526+04:00","gmt_modified":"2026-04-30T13:09:18.8062526+04:00"},{"id":41572,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"c71708bd330ca79e1ba386a1763f5154","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#718-740","gmt_create":"2026-04-30T13:09:18.807757+04:00","gmt_modified":"2026-04-30T13:09:18.807757+04:00"},{"id":41573,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"9aacd835d38b395734ce6c87619ff59b","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5272-5274","gmt_create":"2026-04-30T13:09:18.807757+04:00","gmt_modified":"2026-04-30T13:09:18.807757+04:00"},{"id":41574,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"a1b4ebbf3bdbfda373c476ae64175283","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#169-242","gmt_create":"2026-04-30T13:09:18.807757+04:00","gmt_modified":"2026-04-30T13:09:18.807757+04:00"},{"id":41575,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"179c82a5b5dc0ef88ef0f3d348aeabf7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#310-338","gmt_create":"2026-04-30T13:09:18.8087613+04:00","gmt_modified":"2026-04-30T13:09:18.8087613+04:00"},{"id":41576,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"50fe97d449649d053646df017c7d5108","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#255-308","gmt_create":"2026-04-30T13:09:18.8087613+04:00","gmt_modified":"2026-04-30T13:09:18.8087613+04:00"},{"id":41577,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"707909d344d5db986aaec2161fe7ec64","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/config.hpp#58-58","gmt_create":"2026-04-30T13:09:18.8087613+04:00","gmt_modified":"2026-04-30T13:09:18.8087613+04:00"},{"id":41578,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"b07239f8068fdc811cbe0313fbee8a56","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#175-279","gmt_create":"2026-04-30T13:09:18.8087613+04:00","gmt_modified":"2026-04-30T13:09:18.8087613+04:00"},{"id":41579,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"89fa671fe61671f5b5ab01a94646d7a3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#428-480","gmt_create":"2026-04-30T13:09:18.8087613+04:00","gmt_modified":"2026-04-30T13:09:18.8087613+04:00"},{"id":41580,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f57a019298b7b9496ef4f4a85f921f75","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#47-71","gmt_create":"2026-04-30T13:09:18.8097612+04:00","gmt_modified":"2026-04-30T13:09:18.8097612+04:00"},{"id":41581,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"228fc59a5268956db45d2f696afd5586","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#518-526","gmt_create":"2026-04-30T13:09:18.8097612+04:00","gmt_modified":"2026-04-30T13:09:18.8097612+04:00"},{"id":41582,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8f4a83adbe444c72194effc3001fe32c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5265-5274","gmt_create":"2026-04-30T13:09:18.8097612+04:00","gmt_modified":"2026-04-30T13:09:18.8097612+04:00"},{"id":41583,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"4488509fca59b45f940f83b5db6daa2a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3874-3908","gmt_create":"2026-04-30T13:09:18.8107603+04:00","gmt_modified":"2026-04-30T13:09:18.8107603+04:00"},{"id":41584,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"313eca37529d6d0a87af6db00b3cae76","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3598-3626","gmt_create":"2026-04-30T13:09:18.8107603+04:00","gmt_modified":"2026-04-30T13:09:18.8107603+04:00"},{"id":41585,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"88cfd8edcbcced125d9358898750ad6a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: plugins/p2p/p2p_plugin.cpp#172-182","gmt_create":"2026-04-30T13:09:18.8107603+04:00","gmt_modified":"2026-04-30T13:09:18.8107603+04:00"},{"id":41586,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"3442c112ec40ff3a7ce211827dd731fb","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#599-600","gmt_create":"2026-04-30T13:09:18.8107603+04:00","gmt_modified":"2026-04-30T13:09:18.8107603+04:00"},{"id":41587,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"22da7c51b4640e33df6b1774de67e615","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_database.cpp#120-137","gmt_create":"2026-04-30T13:09:18.811762+04:00","gmt_modified":"2026-04-30T13:09:18.811762+04:00"},{"id":41588,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"0ff96f9f66fe11cb2bba96b2257c56cc","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5013-5014","gmt_create":"2026-04-30T13:09:18.811762+04:00","gmt_modified":"2026-04-30T13:09:18.811762+04:00"},{"id":41589,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f6e4014b53decf8540f03050dcb248a7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3061-3062","gmt_create":"2026-04-30T13:09:18.811762+04:00","gmt_modified":"2026-04-30T13:09:18.811762+04:00"},{"id":41590,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"3a3515773dce3d23274f19f11b24b8a1","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#279-283","gmt_create":"2026-04-30T13:09:18.8127618+04:00","gmt_modified":"2026-04-30T13:09:18.8127618+04:00"},{"id":41591,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"18300366cc04c934739121e96aa33a18","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#2520-2590","gmt_create":"2026-04-30T13:09:18.8127618+04:00","gmt_modified":"2026-04-30T13:09:18.8127618+04:00"},{"id":41592,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"5c8f4ea594d62170eaa300eb27c60cab","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#285-289","gmt_create":"2026-04-30T13:09:18.8127618+04:00","gmt_modified":"2026-04-30T13:09:18.8127618+04:00"},{"id":41593,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"bced6dc0057c5231cbd868d55b0f5331","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#340-354","gmt_create":"2026-04-30T13:09:18.8137618+04:00","gmt_modified":"2026-04-30T13:09:18.8137618+04:00"},{"id":41594,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"1496fd024a306e65ca11a1074f83e158","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#371-399","gmt_create":"2026-04-30T13:09:18.8137618+04:00","gmt_modified":"2026-04-30T13:09:18.8137618+04:00"},{"id":41595,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"e196c8cfa8e7dbe5852841c2e099bad5","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: share/vizd/config/config.ini#96-101","gmt_create":"2026-04-30T13:09:18.8137618+04:00","gmt_modified":"2026-04-30T13:09:18.8137618+04:00"},{"id":41596,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"c1db40762caab4c9462bb6bc70748152","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_connection.hpp#26-45","gmt_create":"2026-04-30T13:09:18.8157618+04:00","gmt_modified":"2026-04-30T13:09:18.8157618+04:00"},{"id":41597,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"973153247aad2260b57a9e4842ddfeb7","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message_oriented_connection.hpp#26-28","gmt_create":"2026-04-30T13:09:18.8157618+04:00","gmt_modified":"2026-04-30T13:09:18.8157618+04:00"},{"id":41598,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"055bd4ac99dff0f7dad3d2d929f83b6a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/stcp_socket.hpp#26-28","gmt_create":"2026-04-30T13:09:18.8157618+04:00","gmt_modified":"2026-04-30T13:09:18.8157618+04:00"},{"id":41599,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"37d9a1a0250d6f2b30d0b5acfef7b606","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#26-35","gmt_create":"2026-04-30T13:09:18.8167621+04:00","gmt_modified":"2026-04-30T13:09:18.8167621+04:00"},{"id":41600,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"9fbb8ea1084712b6e00c9f6b9ad60c72","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/node.hpp#26-31","gmt_create":"2026-04-30T13:09:18.8167621+04:00","gmt_modified":"2026-04-30T13:09:18.8167621+04:00"},{"id":41601,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"63f471b93ce70266253197686f8d7df6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/peer_database.hpp#26-35","gmt_create":"2026-04-30T13:09:18.8167621+04:00","gmt_modified":"2026-04-30T13:09:18.8167621+04:00"},{"id":41602,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f18f4bdd5e9956c9abb9a7a52882581a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/message.hpp#26-31","gmt_create":"2026-04-30T13:09:18.8167621+04:00","gmt_modified":"2026-04-30T13:09:18.8167621+04:00"},{"id":41603,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"03beec6b648d43a165d35e195b3eb81e","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/core_messages.hpp#285-306","gmt_create":"2026-04-30T13:09:18.8167621+04:00","gmt_modified":"2026-04-30T13:09:18.8167621+04:00"},{"id":41604,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"8620e966ce1a76d2ab405f5a49743457","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/config.hpp#48-50","gmt_create":"2026-04-30T13:09:18.8167621+04:00","gmt_modified":"2026-04-30T13:09:18.8167621+04:00"},{"id":41605,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d6ed83bad3a3124e13f09c4c1191b96a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/peer_connection.cpp#314-325","gmt_create":"2026-04-30T13:09:18.8182667+04:00","gmt_modified":"2026-04-30T13:09:18.8182667+04:00"},{"id":41606,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"fcdabf2022a050e8a2414fb979f5c419","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3448-3470","gmt_create":"2026-04-30T13:09:18.8182667+04:00","gmt_modified":"2026-04-30T13:09:18.8182667+04:00"},{"id":41607,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d614fc51360bb61b8f521167a6db1e11","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/include/graphene/network/config.hpp#26-106","gmt_create":"2026-04-30T13:09:18.8182667+04:00","gmt_modified":"2026-04-30T13:09:18.8182667+04:00"},{"id":41608,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"70f9beafdbca4132e0349e28e51fcd6a","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/database.cpp#1239-1241","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41609,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"e4313416f9a67f60a9a17f977b2367f6","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/chain/include/graphene/chain/database_exceptions.hpp#86-86","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41610,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"b0498790fa704db3f0e0545f4299a7ba","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#79-82","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41611,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"34cf64d4d37824f3883c4c577708cd70","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3278-3281","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41612,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"9619a9ec94b261a592033e0c0b3be117","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3633-3636","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41613,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"99d4fcb1177774e0710d2bd69e6b48d3","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3653-3656","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41614,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"f652bd37bb37781168ae6561fb03f9b4","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#3671-3674","gmt_create":"2026-04-30T13:09:18.8192697+04:00","gmt_modified":"2026-04-30T13:09:18.8192697+04:00"},{"id":41615,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"d2465d6814378cac309bb4813312e463","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#4472-4479","gmt_create":"2026-04-30T13:09:18.8202706+04:00","gmt_modified":"2026-04-30T13:09:18.8202706+04:00"},{"id":41616,"source_id":"0505088e-5f10-4252-bc3b-307363fee60d","target_id":"028e9730d5a18007052fca00e208de6c","source_type":"WIKI_ITEM","target_type":"CODE_SNIPPET","relationship_type":"CONTAINS","extra":"Wiki contains code snippet: libraries/network/node.cpp#5016-5021","gmt_create":"2026-04-30T13:09:18.8202706+04:00","gmt_modified":"2026-04-30T13:09:18.8202706+04:00"},{"id":41617,"source_id":"f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce","target_id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce -\u003e 0a955b97-eb9d-434a-80a4-9bf1bf937dfb","gmt_create":"2026-04-30T13:09:19.4363838+04:00","gmt_modified":"2026-04-30T13:09:19.4363838+04:00"},{"id":41618,"source_id":"ed702015-695e-4ef5-87fc-be4645f73987","target_id":"0505088e-5f10-4252-bc3b-307363fee60d","source_type":"WIKI_ITEM","target_type":"WIKI_ITEM","relationship_type":"PARENT_CHILD","extra":"Wiki parent-child relationship: ed702015-695e-4ef5-87fc-be4645f73987 -\u003e 0505088e-5f10-4252-bc3b-307363fee60d","gmt_create":"2026-04-30T13:09:19.4421382+04:00","gmt_modified":"2026-04-30T13:09:19.4421382+04:00"}],"source_files":[{"id":"448c0fd26faf791081a54cd407662e4d","path":"plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","filename":"p2p_plugin.hpp","gmt_create":"2026-04-28T09:55:13.5573041+04:00","gmt_modified":"2026-04-28T09:55:13.5573041+04:00"},{"id":"66c6049f94b83d8f15dc55bb2424efbe","path":"plugins/p2p/p2p_plugin.cpp","filename":"p2p_plugin.cpp","gmt_create":"2026-04-28T09:55:13.5578078+04:00","gmt_modified":"2026-04-28T09:55:13.5578078+04:00"},{"id":"f7dedf31e491c7adbaf05e957360c531","path":"libraries/network/include/graphene/network/node.hpp","filename":"node.hpp","gmt_create":"2026-04-28T09:55:13.5580195+04:00","gmt_modified":"2026-04-28T09:55:13.5580195+04:00"},{"id":"d72b348a2c3c7943e4a7abb7dbdaa751","path":"libraries/network/include/graphene/network/peer_connection.hpp","filename":"peer_connection.hpp","gmt_create":"2026-04-28T09:55:13.5585906+04:00","gmt_modified":"2026-04-28T09:55:13.5585906+04:00"},{"id":"3a8d8a10556a0b6501e25aa43e91f913","path":"libraries/network/include/graphene/network/peer_database.hpp","filename":"peer_database.hpp","gmt_create":"2026-04-28T09:55:13.5585906+04:00","gmt_modified":"2026-04-28T09:55:13.5585906+04:00"},{"id":"3a6c30f4bb3b265155c881ccfafa980e","path":"libraries/network/include/graphene/network/core_messages.hpp","filename":"core_messages.hpp","gmt_create":"2026-04-28T09:55:13.5590935+04:00","gmt_modified":"2026-04-28T09:55:13.5590935+04:00"},{"id":"b4467ca30cb6f6d587fc200900ee9ec9","path":"libraries/network/include/graphene/network/message.hpp","filename":"message.hpp","gmt_create":"2026-04-28T09:55:13.5590935+04:00","gmt_modified":"2026-04-28T09:55:13.5590935+04:00"},{"id":"c82262dc5275e094efc9474032d15922","path":"libraries/network/include/graphene/network/config.hpp","filename":"config.hpp","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"b4bd3a3265ac695da5624024015e0e81","path":"libraries/network/node.cpp","filename":"node.cpp","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"609365f8572668c8cf1e1cfa497989e4","path":"libraries/chain/include/graphene/chain/database.hpp","filename":"database.hpp","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"ee77bf4eb6bfbfb3636aa0bd57416552","path":"thirdparty/chainbase/include/chainbase/chainbase.hpp","filename":"chainbase.hpp","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"7550b6fcbbc44fc4df4f050d5b4fb04a","path":"plugins/witness/witness.cpp","filename":"witness.cpp","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"f7be79ec222a56c210b7999322790a2f","path":"plugins/p2p/CMakeLists.txt","filename":"CMakeLists.txt","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"18555f254f50536a15d8591acf982406","path":"share/vizd/config/config.ini","filename":"config.ini","gmt_create":"2026-04-28T09:55:13.5596195+04:00","gmt_modified":"2026-04-28T09:55:13.5596195+04:00"},{"id":"0dd2a38630da83b11fb3596ad4d60705","path":"plugins/witness/include/graphene/plugins/witness/witness.hpp","filename":"witness.hpp","gmt_create":"2026-04-28T09:57:03.8211824+04:00","gmt_modified":"2026-04-28T09:57:03.8211824+04:00"},{"id":"f8bd5a2c3a4664ae9d5ec472684610dc","path":"plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp","filename":"plugin.hpp","gmt_create":"2026-04-28T09:57:03.8217721+04:00","gmt_modified":"2026-04-28T09:57:03.8217721+04:00"},{"id":"a4f11ca2018649a28877cfdeecdff9a6","path":"plugins/witness_api/plugin.cpp","filename":"plugin.cpp","gmt_create":"2026-04-28T09:57:03.8222749+04:00","gmt_modified":"2026-04-28T09:57:03.8222749+04:00"},{"id":"cf72debd284e30d5218a88ae08868205","path":"libraries/chain/include/graphene/chain/witness_objects.hpp","filename":"witness_objects.hpp","gmt_create":"2026-04-28T09:57:03.8222749+04:00","gmt_modified":"2026-04-28T09:57:03.8222749+04:00"},{"id":"bebd7920dd0967c6039c2adb16d4c52c","path":"libraries/chain/include/graphene/chain/chain_objects.hpp","filename":"chain_objects.hpp","gmt_create":"2026-04-28T09:57:03.8222749+04:00","gmt_modified":"2026-04-28T09:57:03.8222749+04:00"},{"id":"a5661951a63a8a4cb0a563b6ff08335e","path":"libraries/chain/database.cpp","filename":"database.cpp","gmt_create":"2026-04-28T09:57:03.8226514+04:00","gmt_modified":"2026-04-28T09:57:03.8226514+04:00"},{"id":"75b9bb8cfd2db41c21f328241d191f32","path":"libraries/chain/include/graphene/chain/fork_database.hpp","filename":"fork_database.hpp","gmt_create":"2026-04-28T09:57:03.8226514+04:00","gmt_modified":"2026-04-28T09:57:03.8226514+04:00"},{"id":"73ada165e99c6ad5f938a94f11fb3e10","path":"libraries/chain/fork_database.cpp","filename":"fork_database.cpp","gmt_create":"2026-04-28T09:57:03.8226514+04:00","gmt_modified":"2026-04-28T09:57:03.8226514+04:00"},{"id":"ebd71fe58bebecee1b2afacbe66909ca","path":"libraries/time/time.hpp","filename":"time.hpp","gmt_create":"2026-04-28T09:57:03.8226514+04:00","gmt_modified":"2026-04-28T09:57:03.8226514+04:00"},{"id":"13c87583e5739bb6062ee5706cbed132","path":"libraries/time/time.cpp","filename":"time.cpp","gmt_create":"2026-04-28T09:57:03.8231617+04:00","gmt_modified":"2026-04-28T09:57:03.8231617+04:00"},{"id":"b58bf8be210d82c70605d7f2482ced82","path":"thirdparty/fc/src/network/ntp.cpp","filename":"ntp.cpp","gmt_create":"2026-04-28T09:57:03.8232724+04:00","gmt_modified":"2026-04-28T09:57:03.8232724+04:00"},{"id":"fcabf234b34f00b60b0d784b2da5a052","path":"programs/vizd/main.cpp","filename":"main.cpp","gmt_create":"2026-04-28T09:57:03.8232724+04:00","gmt_modified":"2026-04-28T09:57:03.8232724+04:00"},{"id":"c4447af409b7f3205a55e5b286557dfe","path":"plugins/snapshot/plugin.cpp","filename":"plugin.cpp","gmt_create":"2026-04-28T09:57:03.8232724+04:00","gmt_modified":"2026-04-28T09:57:03.8232724+04:00"},{"id":"b4b9efd79d5b3c9fea00fccd613b2046","path":"libraries/protocol/include/graphene/protocol/config.hpp","filename":"config.hpp","gmt_create":"2026-04-28T09:57:03.8232724+04:00","gmt_modified":"2026-04-28T09:57:03.8232724+04:00"},{"id":"bb293be9318768f10f69c80fd6b68517","path":"share/vizd/config/config_witness.ini","filename":"config_witness.ini","gmt_create":"2026-04-28T09:57:03.8232724+04:00","gmt_modified":"2026-04-28T09:57:03.8232724+04:00"},{"id":"7ae785f9d5ab154dce6f8eb295b93456","path":"libraries/chain/include/graphene/chain/global_property_object.hpp","filename":"global_property_object.hpp","gmt_create":"2026-04-28T09:57:57.4332426+04:00","gmt_modified":"2026-04-28T09:57:57.4332426+04:00"},{"id":"bbbc42bba97165e48c1da269d1d84a04","path":"libraries/protocol/include/graphene/protocol/config_testnet.hpp","filename":"config_testnet.hpp","gmt_create":"2026-04-28T09:57:57.4332426+04:00","gmt_modified":"2026-04-28T09:57:57.4332426+04:00"},{"id":"8ede002b6c76d0a07d75e34f812e8305","path":"libraries/chain/hardfork.d/12.hf","filename":"12.hf","gmt_create":"2026-04-28T09:57:57.433751+04:00","gmt_modified":"2026-04-28T09:57:57.433751+04:00"},{"id":"1ade3cebbc11a4634bcdf1a7fdb2756e","path":"thirdparty/chainbase/src/chainbase.cpp","filename":"chainbase.cpp","gmt_create":"2026-04-28T09:57:57.433751+04:00","gmt_modified":"2026-04-28T09:57:57.433751+04:00"},{"id":"4238a9561f85e50a38f76813baeadd7e","path":"libraries/chain/include/graphene/chain/block_log.hpp","filename":"block_log.hpp","gmt_create":"2026-04-28T10:02:32.5912942+04:00","gmt_modified":"2026-04-28T10:02:32.5912942+04:00"},{"id":"d2090ff9016be0d896d06e843936e0f4","path":"libraries/chain/block_log.cpp","filename":"block_log.cpp","gmt_create":"2026-04-28T10:02:32.5912942+04:00","gmt_modified":"2026-04-28T10:02:32.5912942+04:00"},{"id":"14f7f8b4c6f5b783b573d1fbbcfc1a11","path":"libraries/chain/include/graphene/chain/dlt_block_log.hpp","filename":"dlt_block_log.hpp","gmt_create":"2026-04-28T10:02:32.5912942+04:00","gmt_modified":"2026-04-28T10:02:32.5912942+04:00"},{"id":"ade43b15adadb0a3215b2b7a6866ef22","path":"libraries/chain/dlt_block_log.cpp","filename":"dlt_block_log.cpp","gmt_create":"2026-04-28T10:02:32.5912942+04:00","gmt_modified":"2026-04-28T10:02:32.5912942+04:00"},{"id":"cb29035725926be38d36ad8c01792b7e","path":"libraries/chain/include/graphene/chain/database_exceptions.hpp","filename":"database_exceptions.hpp","gmt_create":"2026-04-28T10:02:32.5925293+04:00","gmt_modified":"2026-04-28T10:02:32.5925293+04:00"},{"id":"57e07111ef7b80720c419255780e7ece","path":"libraries/chain/include/graphene/chain/db_with.hpp","filename":"db_with.hpp","gmt_create":"2026-04-28T10:02:32.5925293+04:00","gmt_modified":"2026-04-28T10:02:32.5925293+04:00"},{"id":"3948eb588d15d01acf21ffd439ec508c","path":"libraries/network/include/graphene/network/exceptions.hpp","filename":"exceptions.hpp","gmt_create":"2026-04-28T10:02:32.5925293+04:00","gmt_modified":"2026-04-28T10:02:32.5925293+04:00"},{"id":"bd196d115b6bd3d5310dfa247f1e25b2","path":"plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp","filename":"plugin.hpp","gmt_create":"2026-04-28T12:27:40.7714294+04:00","gmt_modified":"2026-04-28T12:27:40.7714294+04:00"},{"id":"991938d306a547ff48a759fb9bd1c5a4","path":"plugins/snapshot/include/graphene/plugins/snapshot/snapshot_types.hpp","filename":"snapshot_types.hpp","gmt_create":"2026-04-28T12:27:40.7714294+04:00","gmt_modified":"2026-04-28T12:27:40.7714294+04:00"},{"id":"d8e14923de7e4be8f600e264a03ea281","path":"plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp","filename":"snapshot_serializer.hpp","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"dc43b9a20a2ae22effbe51aecf8ca751","path":"plugins/snapshot/CMakeLists.txt","filename":"CMakeLists.txt","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"55b60ae8ffadd70d41ebd01151dcb237","path":"share/vizd/snapshot.json","filename":"snapshot.json","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"2eb0f5e7350fc53dec7939bb93e824ac","path":"share/vizd/snapshot-testnet.json","filename":"snapshot-testnet.json","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"4d5bf798ac6e167d6d0e20a669431373","path":"documentation/snapshot-plugin.md","filename":"snapshot-plugin.md","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"6c94b84fdfd5c7016b5eeadf8099133e","path":"plugins/chain/plugin.cpp","filename":"plugin.cpp","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"ae7188ee9396d8d8aca884b96e9bc4c1","path":"plugins/chain/include/graphene/plugins/chain/plugin.hpp","filename":"plugin.hpp","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"51c2f2c072611ae01260d171fbd12b59","path":"thirdparty/fc/src/interprocess/file_mutex.cpp","filename":"file_mutex.cpp","gmt_create":"2026-04-28T12:27:40.7719506+04:00","gmt_modified":"2026-04-28T12:27:40.7719506+04:00"},{"id":"d71a233c4690ddd107d80d74f5956ebf","path":"thirdparty/fc/src/log/logger_config.cpp","filename":"logger_config.cpp","gmt_create":"2026-04-28T12:27:40.7724634+04:00","gmt_modified":"2026-04-28T12:27:40.7724634+04:00"},{"id":"a53dc201b0a9ff736da577ae1c524abb","path":"thirdparty/fc/src/log/console_appender.cpp","filename":"console_appender.cpp","gmt_create":"2026-04-28T12:27:40.7724634+04:00","gmt_modified":"2026-04-28T12:27:40.7724634+04:00"},{"id":"d6fb716d9203d54e5aaccd580adc4703","path":"libraries/protocol/include/graphene/protocol/block.hpp","filename":"block.hpp","gmt_create":"2026-04-28T12:54:08.069392+04:00","gmt_modified":"2026-04-28T12:54:08.069392+04:00"},{"id":"3b0d308523637c64e62fac4d1a2a4a66","path":"libraries/protocol/include/graphene/protocol/block_header.hpp","filename":"block_header.hpp","gmt_create":"2026-04-28T12:54:08.069392+04:00","gmt_modified":"2026-04-28T12:54:08.069392+04:00"},{"id":"398d9d4b02b6383c0b752cb0196a0475","path":"libraries/network/include/graphene/network/stcp_socket.hpp","filename":"stcp_socket.hpp","gmt_create":"2026-04-28T14:54:34.9399731+04:00","gmt_modified":"2026-04-28T14:54:34.9399731+04:00"},{"id":"6e587b97bf4080c7754c5ed73736fca7","path":"libraries/network/include/graphene/network/message_oriented_connection.hpp","filename":"message_oriented_connection.hpp","gmt_create":"2026-04-28T14:54:34.9399731+04:00","gmt_modified":"2026-04-28T14:54:34.9399731+04:00"},{"id":"6c8cc56bd5bd0ef7abed9b709d60f20b","path":"libraries/network/peer_connection.cpp","filename":"peer_connection.cpp","gmt_create":"2026-04-28T18:50:34.3806901+04:00","gmt_modified":"2026-04-28T18:50:34.3806901+04:00"},{"id":"2c99501f0c511d0792a2e5a56e4debfa","path":"libraries/network/core_messages.cpp","filename":"core_messages.cpp","gmt_create":"2026-04-28T18:50:34.3809836+04:00","gmt_modified":"2026-04-28T18:50:34.3809836+04:00"},{"id":"01e57c63d684a56829f909c03b8ea162","path":"libraries/network/stcp_socket.cpp","filename":"stcp_socket.cpp","gmt_create":"2026-04-28T18:50:34.3809836+04:00","gmt_modified":"2026-04-28T18:50:34.3809836+04:00"},{"id":"c70caf63ce77b078dcf89380b228a71a","path":"libraries/network/peer_database.cpp","filename":"peer_database.cpp","gmt_create":"2026-04-28T18:50:34.3814865+04:00","gmt_modified":"2026-04-28T18:50:34.3814865+04:00"},{"id":"3f9fffa35f3712fd1f90ebc3c0593373","path":"documentation/debug_node_plugin.md","filename":"debug_node_plugin.md","gmt_create":"2026-04-28T19:48:38.8549045+04:00","gmt_modified":"2026-04-28T19:48:38.8549045+04:00"},{"id":"85e109c379780f5df7bb2695b2862fba","path":"plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp","filename":"plugin.hpp","gmt_create":"2026-04-28T19:48:38.8558545+04:00","gmt_modified":"2026-04-28T19:48:38.8558545+04:00"},{"id":"796d893189111ab0df5e606d82fea700","path":"plugins/debug_node/plugin.cpp","filename":"plugin.cpp","gmt_create":"2026-04-28T19:48:38.8558545+04:00","gmt_modified":"2026-04-28T19:48:38.8558545+04:00"},{"id":"fb3a9999324017fb4d2fad7f61f2660e","path":"plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp","filename":"api_helper.hpp","gmt_create":"2026-04-28T19:48:38.8558545+04:00","gmt_modified":"2026-04-28T19:48:38.8558545+04:00"},{"id":"2b777822e4399aff7d4e5ea26fcbe8b9","path":"share/vizd/config/config_debug.ini","filename":"config_debug.ini","gmt_create":"2026-04-28T19:48:38.8558545+04:00","gmt_modified":"2026-04-28T19:48:38.8558545+04:00"},{"id":"89eb7bdd3591c729dfa6f00de5cdfa1c","path":"programs/util/sign_transaction.cpp","filename":"sign_transaction.cpp","gmt_create":"2026-04-28T19:48:38.8558545+04:00","gmt_modified":"2026-04-28T19:48:38.8558545+04:00"},{"id":"2819790574404236d7b1bca0b253c524","path":"programs/util/sign_digest.cpp","filename":"sign_digest.cpp","gmt_create":"2026-04-28T19:48:38.8558545+04:00","gmt_modified":"2026-04-28T19:48:38.8558545+04:00"},{"id":"69291a4b8d9de900b397578829d3e0d9","path":"libraries/network/message_oriented_connection.cpp","filename":"message_oriented_connection.cpp","gmt_create":"2026-04-28T20:32:44.1445931+04:00","gmt_modified":"2026-04-28T20:32:44.1445931+04:00"},{"id":"0becb65bb16186368dcabd6b936c5a80","path":"plugins/snapshot/plugin.hpp","filename":"plugin.hpp","gmt_create":"2026-04-28T20:32:44.14584+04:00","gmt_modified":"2026-04-28T20:32:44.14584+04:00"},{"id":"01456fc1d03088da2d9080a7ba380f5f","path":"libraries/chain/include/graphene/chain/block_summary_object.hpp","filename":"block_summary_object.hpp","gmt_create":"2026-04-28T21:03:48.5307667+04:00","gmt_modified":"2026-04-28T21:03:48.5307667+04:00"},{"id":"1ccc033e927b7ba78550b64f23026d0b","path":"thirdparty/fc/include/fc/exception/exception.hpp","filename":"exception.hpp","gmt_create":"2026-04-28T22:07:48.9742186+04:00","gmt_modified":"2026-04-28T22:07:48.9742186+04:00"},{"id":"29a08e70168bdd56a054f1924f1f547c","path":"thirdparty/fc/src/exception.cpp","filename":"exception.cpp","gmt_create":"2026-04-28T22:07:48.9752186+04:00","gmt_modified":"2026-04-28T22:07:48.9752186+04:00"},{"id":"5ba4c9dfb14a49e5bdc63880653930c6","path":"libraries/protocol/include/graphene/protocol/exceptions.hpp","filename":"exceptions.hpp","gmt_create":"2026-04-28T22:07:48.9752186+04:00","gmt_modified":"2026-04-28T22:07:48.9752186+04:00"},{"id":"0e6f9014df8500eda2c1aa47cc9e4633","path":"thirdparty/appbase/application.cpp","filename":"application.cpp","gmt_create":"2026-04-29T06:59:11.2761981+04:00","gmt_modified":"2026-04-29T06:59:11.2761981+04:00"},{"id":"648e1d10af53280c425b922251db1464","path":"README.md","filename":"README.md","gmt_create":"2026-04-29T06:59:11.2761981+04:00","gmt_modified":"2026-04-29T06:59:11.2761981+04:00"},{"id":"455bdc0d380a2cbf0f62da93a6fe203d","path":"thirdparty/fc/src/log/console_defines.h","filename":"console_defines.h","gmt_create":"2026-04-29T06:59:11.2767199+04:00","gmt_modified":"2026-04-29T06:59:11.2767199+04:00"},{"id":"cb10d0f5bb9015fb9ce5f22b75c10fed","path":"thirdparty/fc/src/stacktrace.cpp","filename":"stacktrace.cpp","gmt_create":"2026-04-30T07:30:11.779591+04:00","gmt_modified":"2026-04-30T07:30:11.779591+04:00"},{"id":"a64d5e5f1d092e1fd6a7916d048be8da","path":"plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp","filename":"witness_guard.hpp","gmt_create":"2026-04-30T12:39:24.1454414+04:00","gmt_modified":"2026-04-30T12:39:24.1454414+04:00"},{"id":"f0afc0a1f80132e4bf15ac9ff156e256","path":"plugins/witness_guard/witness_guard.cpp","filename":"witness_guard.cpp","gmt_create":"2026-04-30T12:39:24.1454414+04:00","gmt_modified":"2026-04-30T12:39:24.1454414+04:00"}],"wiki_catalogs":[{"id":"80d9e7dd-fa35-469c-bd78-95cb6616e64c","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Project Overview","description":"overview","prompt":"Create comprehensive content for the VIZ CPP Node project overview section. Explain the project's purpose as a Graphene-based blockchain implementation with Fair-DPOS consensus algorithm, its architecture as a full consensus node for the VIZ World platform, and its relationship to the broader blockchain ecosystem. Document the core value proposition, target audience (node operators, application developers, wallet developers), and key differentiators from other blockchain implementations. Include both conceptual overviews for beginners new to blockchain technology and technical highlights for experienced developers. Use terminology consistent with the VIZ codebase. Provide practical examples demonstrating common use cases such as running a full node, developing applications, and operating witness nodes. Document the project's position in the Graphene blockchain family and its unique features like Fair-DPOS consensus and social network integration.","progress_status":"completed","dependent_files":"README.md,programs/vizd/main.cpp,libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:31:56+04:00","raw_data":"WikiEncrypted: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"},{"id":"86d4d313-8f5e-4334-b5d1-220ec01d0971","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"System Overview","description":"system-overview","prompt":"Create comprehensive content for the VIZ CPP Node system overview section. Explain the overall architecture showing how the main vizd process orchestrates all components including the plugin system, core libraries, and external dependencies. Document the modular design that allows for flexible feature addition and removal through plugins. Describe the relationship between the application framework (appbase), the blockchain core (chain library), protocol definitions (protocol library), networking (network library), and wallet functionality (wallet library). Include system boundaries showing how the node interacts with peers, handles API requests, and manages persistent state. Provide high-level component diagrams illustrating the data flow from JSON-RPC requests through plugins to database operations, and explain the observer pattern used for event-driven architecture. Address the separation of concerns between different library layers and how they work together to form a complete blockchain node.","parent_id":"8aeac580-587b-43f3-9292-e62e4d3781f7","progress_status":"completed","dependent_files":"programs/vizd/main.cpp,libraries/chain/include/graphene/chain/database.hpp,libraries/protocol/include/graphene/protocol/operations.hpp","gmt_create":"2026-03-03T07:28:18+04:00","gmt_modified":"2026-03-03T07:39:03+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Build System","description":"build-system","prompt":"Create comprehensive build system documentation for VIZ CPP Node. Document the CMake-based build configuration including cross-platform compilation, dependency management, and build targets. Explain the build helper tools including configure_build.py, cat_parts.py, and newplugin.py. Cover Docker-based development and production builds with detailed instructions for different environments. Document build options, compiler flags, and optimization settings. Include practical examples of common build scenarios such as development builds, release builds, and cross-compilation. Address troubleshooting common build issues, dependency conflicts, and platform-specific compilation problems. Explain the relationship between build configuration and runtime performance, making it accessible to developers while providing sufficient technical depth for advanced build customization.","parent_id":"d896ebd6-7a89-4c1c-a16d-8acb2a61bb9c","progress_status":"completed","dependent_files":"install-deps-linux.sh,build-linux.sh,build-mac.sh,build-mingw.bat,build-msvc.bat,CMakeLists.txt,programs/build_helpers/,share/vizd/docker/,.github/workflows/","gmt_create":"2026-03-03T07:28:48+04:00","gmt_modified":"2026-04-21T16:26:53+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"61febd56-5be6-448d-b2a2-26975fe33d8d","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Node Configuration","description":"node-configuration","prompt":"Create comprehensive node configuration documentation for VIZ CPP Node. Document the complete configuration file structure including all available parameters, their purposes, default values, and acceptable ranges. Explain different node types (full node, witness node, low-memory node, testnet node) and their specific configuration requirements. Cover essential settings such as database location, plugin activation, network parameters, and performance tuning options. Document authentication settings, API access controls, and security configurations. Include practical examples of common configuration scenarios for different deployment environments. Address parameter validation, configuration file syntax, and troubleshooting invalid configurations. Provide guidance on configuration file organization, backup strategies, and version management.","parent_id":"6ca6368d-78f2-436a-89d8-8e2fa9c3d925","progress_status":"completed","dependent_files":"share/vizd/config/config.ini,share/vizd/config/config_testnet.ini,share/vizd/config/config_witness.ini,share/vizd/config/config_mongo.ini,share/vizd/config/config_debug.ini","gmt_create":"2026-03-03T07:28:57+04:00","gmt_modified":"2026-03-03T07:40:51+04:00","raw_data":"WikiEncrypted:hav0US+RXKdtFsxtXP2cXTdu0fNsqg0+VVPPgCJmB+gCYxHtJtj5sAsXXzcH3e6OjLmKyA6xg9oiMc3dQX4r9jIS1EnmQTP9zPNSGk9jNX6MQhFHPY2xml1Z+2RI104yF5HmRTFpCuTFbjFu79PlvVPajTvPtZ6fqfbgFJWD5T6f0lflupZf/dgu9s5taw+3f9RHgzhDVGQg+PmN4W6ABk7HflJPHCHsbOcgD5Ocftg1FER1xkYbWSQu1Wjdgdn+3v8NSFCcL5TU6CcuoP4XTuYr+u0XshzSkg9OuCI7ekf8dTPAzjS8LVdhP4vvJD1g+oyk139sicJYyU6FZ9HsydPOAPVqO2b8R+vfu67zdzq2FSOf/ElGqFcL6l4bOofQtWWtYA0Kl/9bIHEHsfV/HTyG8CgK+thZL7s0SJWevIr6k1TtmQvhyVGXOPeMRiSTtyYi/Ov0U1sa3yz7ZhgJZAFpGwE7jVFh427ykDvvuqHb8IBTGWi2XyOtyaNEWyzFDGY2GQjZVjNvowYqXvOMIITP15tC6pzf7Wxagaspl0IBmwkK4WVGk9rMvAkUxX5plNuHaho4X6Su/ngNqqTjlHrAxY+5MTfxsqIJGDC/bcnLIxBRYhPi77TRI3Jnwll/BZxiTQBbSyqd7uChxMyC1Etgxw/pDhPL9mlNKfv+OoqBzJMo+PGLlA4T3NUWUI+G/my1itOtg948ah7eDSpnCx3KFPq8eSLVsYkTuUtARpxsxpPQBNwMITpg2hJ0oS/nSOPnhLygLChYGXLUcTrENaLftoFb6dsB1mjcDhzt6hBbPnIymVaNznbc3D7vjg3ihNLRUd17Ydl7nT7GrbgmL+1oHqVhE8F/qThMxuGIibB1SKvRUfAoaCKh9YQfE1YwAAU496PeikgRX65YHWfsQvBh6K0qolwQQxjRwXismcaoRfIv8LcKRu0kUgW1xAE/yLGBMSyfeoqumw+BoAH9Ps7wXarDkaMFs7cSqWIOwaUV4c/mC9Zu9FQSNO5hnJ+ubn6m03Qpa7r8NI8f1wdeXYx9HBU54TznymPWQRq/1eLwZSN35Mmfw0q6qIdM1r5H60+HMO0/6Bt/o6NByEO19F1+cZKTsl6QqxODzD+rwE5dVEmTEIJP3jApycz3ki0idASjTF0bQmaPgIZPRSSAnXgfMDiyT5BSw6xN8C3EP6nNbsooC7Qhs0IpHhpouPnCp30nCplA5/Y4Z5C7onvaO40DLfCH7ff1QXMx0WY77uN0MTbRGkh5zQV1Bs5maFbs4neurxMWVdq5PNLrx5+eYU/V3lg2Y8jCNNPKFwGWRmumoa8i3AKI20cZfpx0yTktw0EFw2dc0pXIYiFvixUBGl45ME22RC6j0Iw+vqt1oc37izIulYXcKic3v9qr1AKBeUCPYCYXb/z5klNDX9/kdDuKGwS0lNBX3U+aMCZ/1sUyn0dhM2VUTlC7sTSSbt3/s7wHIqDUx50AUuDB1CyPXxRnzoDzZ2AGCvblgDYjUZBOjx1Skj3tnQK8yLp+LRb03dXtFDy+lEFlPr2YmuIOVDOrxkcZi3nKUYdU17ge5+LxmYZ6WC6xLCbz3czV7B9wU2Tn+/5bXvnTn90cMftbVmKoOytHctClp4B7yXvdKsQiJswgdrvHSkxGnIGKvcz3bA5Svs714RiNV2rw8ND+aKhZ6+XxU/6obpfpwkm9eWL7lF6mSu8YLibpkvf4RfQaVYna+VWFd8BfnsPgFvqvZK3zxRu2DOP+n/TP/Z1/bgJFjqH3vPXnOMJubcslPU9zhpT2bS9QxUfD/cs6vModKaD2vqck03zV1DGCTIKvWZ3NdHkGD0FRnW+bG12IuHFYDNXUpXFCd9l8H0UwMG0UibKTu4Vhkwy6RB7+J9Z88TmYJj5cu+THagMu5PCRdDeyI/HdyyRBYeFEokDaRGZgGERBkgFPo17a5pus01hMYGWe3m/MhBrIe0RLCvPxieoRkYormPXK+rkPQpRWD4qewojK4oZiZrTgBf58VG19Fi6N4/q38fO3DYD/yoAmn/vel4FzviSYXn4ksPY2z7ZYPHOFEZiAKIQvqVQKinds9LAotZsYL2rHkr0+OBN4MOwy","layer_level":1},{"id":"4b6cb85a-4799-425e-9f98-9cd149c004ec","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Node Deployment","description":"node-deployment","prompt":"Create comprehensive node deployment documentation for VIZ CPP Node. Document production deployment strategies including hardware requirements, system prerequisites, and installation procedures. Cover different node types including full nodes, witness nodes, and seed nodes with their specific configuration requirements and operational procedures. Explain the node startup process, configuration file management, and service integration. Include step-by-step installation guides for different operating systems, dependency management, and build optimization. Document performance tuning parameters, resource allocation recommendations, and capacity planning. Address security hardening procedures, firewall configuration, and access control setup. Provide troubleshooting guidance for common deployment issues, startup failures, and configuration errors.","parent_id":"ea5be69a-46ea-4304-9d86-597c6f7fc254","progress_status":"completed","dependent_files":"share/vizd/config/config.ini,share/vizd/vizd.sh,programs/vizd/main.cpp,documentation/building.md","gmt_create":"2026-03-03T07:29:04+04:00","gmt_modified":"2026-03-03T07:40:48+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"bcdd0730-3bcf-4e55-aeac-7cc0a351046b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Plugin Lifecycle and Registration","description":"plugin-lifecycle-registration","prompt":"Create comprehensive content for plugin lifecycle and registration mechanisms. Document the complete plugin lifecycle from initialization to shutdown, including the plugin_initialize, plugin_startup, and plugin_shutdown phases. Explain how plugins register themselves with the appbase framework and the role of APPBASE_PLUGIN_REQUIRES macro in dependency management. Detail the plugin registration process in main.cpp and how the application framework manages plugin loading order. Include concrete examples of plugin initialization sequences and dependency resolution. Document the plugin naming conventions, static name() method implementation, and how plugins declare their requirements. Address plugin startup timing, error handling during initialization, and graceful shutdown procedures. Provide practical examples of plugin registration patterns and common pitfalls to avoid.","parent_id":"64b99394-2424-4a81-ab2a-f41d1b9e973c","progress_status":"completed","dependent_files":"plugins/chain/plugin.cpp,plugins/snapshot/plugin.cpp,programs/vizd/main.cpp,libraries/utilities/include/graphene/utilities/git_revision.hpp","gmt_create":"2026-03-03T07:29:09+04:00","gmt_modified":"2026-04-20T10:26:06+04:00","raw_data":"WikiEncrypted:FgT6N5UmoqQ/n0GhU4kWL0J+Fybs3wSrBZ8i488xBvorI6lbBGjTYhS7bWXZzNetCm3S8JjcFgc9memzvSTO1YQPmT49Uldgjtl2WhFXjcF3WJX4hC0AP6UDYCYSCpEQRv9FPzViWgqq1DAOKLH+wBLA4uPKXHhu9yy+0mxa4aF6S4miAF3erjorTWHLA6eZthaZDF/KtxoS72PiMdX7my+r/UgxX2mhhzK4HFwOQsfLffXQD0QEh+3jSSXo7zksFg4c4HtLPHVniAghLUc4FaiLk4QFZXlsjRxPv9BGuHwpHdYEuf1paaNdIBxZnU6TwCa2zP+eoetFX/hv9cLPE3l34RJBTe3wc1a7XFlDFibG7B0J+cLTdLAoBbZhRWjZtE1jZ38MG19LiU8RC+/BMnG3Kez75HzaUyTdmrrQqVdzv7kWmLDr2ThjIUaJ8DQkZy04RHRESRIBA1lgvJHZvMT8NcvJs61mGdbbjPgm//7a9yxi7My3yX1iwGRqn3a/Lqro9m0YGAhxPL3igGWL4/yIH8gO/GppVOau8yklmHGVQu9sgROoFomlNcZjIUhjPnw/uM0DWMPEnpH8Oc+fqNXXVp8wpQDodiD65535YGFCx/7KABM6loSnvsPbRWYb9pV0KYB0b14G8jaRpOJbgJCApsYhcqTu5sydWErz6v3gUjTszz6cmK/3bvjy0SMoAl7MNDMYebUs8cZiR+8GzHYcHPs9ryxF5ofTQio5BxnxbKH1X7JB00SJVWogJHRL1KkShj5Asor6n/UugH/W+tclRJ/mK9WdtxaV1ifY61reOTJUCmD/eHgvDixPIrC6Z791SNiFzhWLqSJBwOIDrud9LLoSUYWRST13iunjJcEYVzRGkVxR2OU6rsDDFeb+ECIRi4WVM5Kte43i303/Po3fmPSmSdts5p3TU75MteEn71R5ONoH1vNowPjsyuyqGtNssow9U0prKW0nE5FZbXXi3IXUkFWtWroI0kR2htD6Ef70I+bG1C8j+UlcvM9Poffck/PbEruGH+aavXpaxMRcayIT6SGBEy4u+Iu6nughmmP9wKpSzEpStW6ZqgNOnYuOBqJihAHjutfprPUcZjdoifyNNnKRNxBO8EPMBsrIPEysvB7ubU5r0Gb29skV8Wb9LVqm5nGLBC24s8G5bmDfHCqyVxbx+b8AJOVVhAu8dKQK2Pt92R+PXA+VoTQEc0oaxgvsNPJjBx0W/dItwgUB+nItoqSytXeiucfQhINaKD6Ws/KvGKk3mRaS8i6KOJB0V0RoWpwWP0ZjUCGYZXr1i65I9ni82/QtacyhPKeR/dV508moShmcw2S3eD8BqmGKSnPAF7tH8RzIPcvuOFYLH9e7FNcoKV2NhKeNUgZdvVwTcf1NTZTx2ri19AUCa4vIawJUyhQ3/r7UxLrvqBxK8daQ+thKHrXe1gN1K0XAMQoNWSPy+srNGqhpD427EDMl03T99zDRZ8LbeufN2/KsYohFnR/rMKAsI/E1L0YR/5ZNtEKxqNbr68ADWZkE2Sup6GtbMqDmmgYV5G6HXTQsV5ZXSRJnextrT5JNJEoLW+HfSOESLc4syDu5fdoTXi7i772CuEzigL1YKaV1TzR4fpoHzc/CZh6rVObeBEVxqkaLniq228ZuM2e6omqfcHId8BjTE7hi5cBczbOJbVaUEErzMdIvr0EfHByOxwctdQ7JGbMQnxGnYwv2IqQgD1roNER2MFVMmabYAxqqlVGJvH4bRS4uAIK9R6j9O15/bsjfnsWIutsiUJq7WtMZ","layer_level":2},{"id":"56a9c3df-f2c3-4f59-8729-1fed0fcdb9d2","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Hardfork Management","description":"hardfork-management","prompt":"Create comprehensive hardfork management documentation for VIZ CPP Node. Document the hardfork system architecture including version management, scheduled upgrades, and backward compatibility handling. Explain the hardfork directory structure and how hardfork files are processed during node startup. Detail the migration procedures for upgrading from older versions, including data schema changes and state transitions. Cover the hardfork evaluation system and how different operation types are handled across hardfork boundaries. Document the rollback mechanisms and recovery procedures for failed upgrades. Include practical examples of implementing custom hardfork logic, adding new operation types, and modifying existing behavior. Address common hardfork scenarios such as protocol changes, bug fixes, and feature additions. Provide troubleshooting guidance for hardfork-related issues and validation procedures for ensuring successful upgrades.","parent_id":"7a20b53f-0b97-40ec-a630-7e9171a04006","progress_status":"completed","dependent_files":"libraries/chain/hardfork.d/0-preamble.hf,libraries/chain/database.cpp,libraries/chain/hardfork.d/12.hf,libraries/protocol/include/graphene/protocol/config.hpp,libraries/chain/hardfork.d/,libraries/chain/chain_evaluator.cpp","gmt_create":"2026-03-03T07:29:10+04:00","gmt_modified":"2026-04-20T11:24:22+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"08c2583a-92f0-4c14-aa9d-736878711ba1","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Transaction Processing Pipeline","description":"transaction-processing","prompt":"Create comprehensive documentation for the transaction processing pipeline in the VIZ node. Explain the complete flow from transaction reception through validation, operation processing, authority verification, and state application. Document the transaction validation stages including syntax checking, signature verification, and operation validation. Detail the evaluator registry mechanism and how different operation types are processed through their respective evaluators. Include the transaction object lifecycle, from creation to final application. Explain error handling throughout the pipeline and rollback mechanisms. Document performance optimizations like batch processing and caching strategies used during transaction validation.","parent_id":"a486a2be-4bcb-4205-83fd-2ab48f89829b","progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/protocol/transaction.cpp,libraries/chain/transaction_object.cpp,libraries/chain/chain_evaluator.cpp","gmt_create":"2026-03-03T07:29:21+04:00","gmt_modified":"2026-03-03T07:53:30+04:00","raw_data":"WikiEncrypted:pze/wTPA8hT9dADtWGlHVecGIju168riPHUw4TY8/AOhja4aeOzUlmfgdY/KTiokGkE0pwevgTXyU4//H92NNxIZ90VqY2mbD0yvEYoazibaKq9O1InwoMVUng4EkSsQB9oHT43atze2e4KWPpApnOpuXM/kNSqRnBSucHXjsMd+Tjj5PradoQvpJLU8cQA/Z7rNIJC0cpQOT83jnvDj0V6NqUee6Tv82FRi8ClWzP6EZWLJOwLqfzlzlavNBvEzOVrawfC21Hgjv0XI+S8iljUPDEK+/k4kPqQwBtNAZVNvSxdOMdQQd4H8dHYj42tsWQ4M2CHxt/jWDy4RZ7gusItaB76xjlAkrZESkdwZBkd//EMqxuKfE/A4/LQdGHNOTm+uaj6m8t76OHM9h4VR9V4eueF6t3bMM8jowIFMUsjHBlU+dkSn/NSR6LigMB4jIbNt61XMmwk1mFvwxws6PxL8XqLxS6WdTzKfDE4/IBBD7TZqimc2cA4z2+g/OZ39ZEQlIpiv8/TzTdAWlsxWrctDiJB94u/AwbbOehhEdFH2nS66e1wKUfknITsWMgsuyzC0CfWRd/fwrEG58bYEi74d+wo1sJlOuZEBaSfMiOosE7wvOH3oQ8Y2UgwJ0vjzgZUiE9G0ScjOOZWniMF0bqPEe06ztnJIGeefrHJOWjdIfN1dkS9ufS455XmgRlNSE/NqG4hDmxF9R9NWjkexlSw2BcqQfD6budkT9gXrT+DmvhvTAZNbhg3rbqVluS4bL3ruC2r+ajzWHoYzImenOWqpDXtY3PDdWzlF25dvXKPOiFCgnpIhx4/aEBmvfoFugNyi2rgkZ/GxUvgLFwYxUWsrXzOW3ghrF82KYZiOzclpGc9/z4O3aOx2rsywhd3+bwSLqnWiTUPCcJaACADsGSIk2O+4SSb/+dGgJEi0+9f+XVUkdntAmb/GY0r1Pj9Aggwk6gllw1jhIAFrhSeeN9YQkHHngVWQDTIqpEZrLiK8l9KRp0xOidl2MSvQPSjBk4h2qqynktmU6u6INK0QjvFuStt1EBvl9TX3ObV9TqdxQyTeD4wyygPZWQEip1CHg04iAihm5uA9siBylj/kLKn9MnuWugqJDyQmvMVc7PXW2PjXtPzhW7/Jb/SHMJoTD4Gr9dGOx46lLwizMVmH+Wdgu7ThE5TRiLm3zbM6Vva42JyyhlKovJOwOzbdE7oL4PEURcpIb7CwrNkCvuOclVRFhgGE6YTkuBTdHLlFpLoInX5qE2OW9+ydHnq5m3kxjp0jqHR1FEndp/SOAgjK8j5GUBfktjso1ePTCXqGifq82fcPCltrzG7k2QOtOSECTUa+pOeVnhutaJ3CR//hRALu3mrquQnFk0tLrX117kwCT9zFNRljPROygJRvN4BCTzDJgnqakMlqteAIkiRD3Fb0oHccw3FqJPY5lOQvvyGOgQeuBimDKH34MRTRl3pkYQbHOqjUqUi035eAfFu0Rvt/1KkKcP/6anglXytX8/wCssuFHVX8WLr/XarAK8wkI0F8fuBcwaifM6C4xWQNCD3dZMCNDxfik3SCqkRtAzmd1xC3HA1SQ5I/qlbrtzwC","layer_level":2},{"id":"e02c38ec-2618-428e-b338-f93cfe94dc72","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Chain Library","description":"chain-library","prompt":"Create comprehensive content for the Chain Library, which serves as the core blockchain state management system. Document the database.hpp/cpp implementation that handles blockchain state persistence, block validation, and fork resolution. Explain the chain_objects.hpp and chain_object_types.hpp that define the complete blockchain data model including account objects, transaction objects, witness objects, and committee objects. Detail the fork_database.hpp implementation for handling blockchain forks and maintaining consensus. Cover the block_log.hpp functionality for efficient block storage and retrieval. Include the database API methods for querying blockchain state, managing object lifecycles, and handling state transitions. Document the evaluator system for operation processing and the observer pattern implementation for event-driven architecture. Provide examples of common database operations, state queries, and performance optimization techniques.","parent_id":"139b0217-0190-433f-b41d-60fa08c9ee5f","progress_status":"completed","dependent_files":"plugins/chain/plugin.cpp,plugins/chain/include/graphene/plugins/chain/plugin.hpp,libraries/chain/include/graphene/chain/database.hpp,libraries/chain/database.cpp,libraries/chain/include/graphene/chain/chain_objects.hpp,libraries/chain/include/graphene/chain/chain_object_types.hpp,libraries/chain/include/graphene/chain/fork_database.hpp,libraries/chain/include/graphene/chain/block_log.hpp,libraries/chain/include/graphene/chain/transaction_object.hpp,libraries/chain/include/graphene/chain/account_object.hpp,libraries/chain/include/graphene/chain/witness_objects.hpp,libraries/chain/include/graphene/chain/committee_objects.hpp","gmt_create":"2026-03-03T07:29:24+04:00","gmt_modified":"2026-04-23T11:18:36+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"317287b2-3937-4876-97d0-a8c96007d95c","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"CMake Configuration","description":"cmake-configuration","prompt":"Create comprehensive CMake configuration documentation for VIZ CPP Node. Document the primary CMakeLists.txt structure including project setup, compiler requirements (GCC 4.8+, Clang 3.3+), and platform-specific configurations for Windows, macOS, and Linux. Explain build options including BUILD_TESTNET, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, and ENABLE_MONGO_PLUGIN with their effects on compilation flags and feature availability. Detail the Boost library configuration with required components and static/dynamic linking options. Document compiler flags for different platforms, optimization settings, and debug/release configurations. Include practical examples of common CMake invocations for development, testing, and production builds. Address troubleshooting build configuration issues, dependency resolution problems, and platform-specific compilation challenges.","parent_id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","progress_status":"completed","dependent_files":"CMakeLists.txt,programs/build_helpers/configure_build.py,programs/build_helpers/CMakeLists.txt","gmt_create":"2026-03-03T07:29:30+04:00","gmt_modified":"2026-03-03T07:53:46+04:00","raw_data":"WikiEncrypted:g3OHqDqjdBWAhmAZ37hW4JJS2u8qzwOVBZXve9aVGP6gazow1Wn8Wd0+UNTa//LywYD6BGd6YGJkamwOkpXAoi0OlYTJa1FWkUUdxN1EH33gTvgfxPrMV7qyTVAROvgnOaS+tl2HssNZt7914i9BaqorW50sTZye5RfdS89LYex/7mydM08IxLGVg4gJKjYPQuSdcpIS1hg+PsDlN9X/Ug8WYWzJ+yxiG9mx1LcaQ+EI6rrhmGEdPeGU6Qw9fk7ruPSqXVu9l9r4mKjSvUhBVKVkT1jFCBng1YqT9JGF0Ab6sCFyIsYqqszQOLVi+oQb1sqQjqb3zoWeB3SFa1Lms0I815Os4GqTE5eta8dnR9ObaTP2AARVEVUdZ6XTwAhUmyuVXzTyZN1URmGpLwWgMrDc7yhhGMV7769Lp1+4GlGgsYH3BI4ib6bfIlNusoofQp42/eD+SyU89DesRdReUag/QKOjfpsM4gI5MmmTVf7/VR5anxsQqwRyee+KZc9tNkW8u1ACyQ4rBstC3cBbHCmXMO3IBU0SyeskR3omlFced0DsX1iB9QuorjlKPKOnDPGIduPawhEA9p2q/FGQlRvsy69nI4aoEAtK8a1J2btjSCNTptMk8up5/UXig8N3WCwoOHQWylIeW1FS+voCO++HTscJGW8SiGRJ4Hy4okzLnJYhSbr2PCfyU0Ky/55gZOzwUC0tUzLaYJFPT6DhdVrucg/2QGwEW3Qr9xTXZWw3dbwyyJruOZAz9myg8yALLUnLa2oPa6JVEa8rMEXPYxLwUtxTvUz/9bta1Bk+8fV+yOKErm3S3c1Iy4rvdFrAw7EnK51nRMEgByN+6Mhz2At31gy/QH6NlKr9W84zJhxL89GRL+fFeY+cqb+D67sAZwA5Os1+hmQnNA0+Br0N5RCQFesEkPBNwX/P+dCzjBFVVFQJisw5esrClIk6Tyd6ZKfkBanNs/6jz0xhs4J4cSZEuVF1pZ5EuixRGZW9lG1ggwrToUFbxK/vlC48sFwKgMqKHoGLc3hYxJnNlOBzRmeUI0NkdGyBuynWi0YwdOhD+09MwmNRaykuqjMww/WUUvF8ZBftLHxEbkRWve/phBWR3zEpEWL7hxR0ofgKgm3y1glXck1Z4D6MFhfdGwNSXyxqBm/AAmQKEXGL2XLWC6HndhK60NNddB6HnvXjUpoJGUSL27pegS/wjGD2vIA5HXH68AzH+yt9UEj1PKzxWR1Zz9HaStw5nObACNkxOW4w/+j74auyHjuFC3/Hivjjf4vmpEAeRih0rkX3wAoDqZyG7g52qWlnNwPy/KoX2pJd87L865JOVDOXYigrgrSgb2t/CZVhSYYvk5bLD5RxJIbjyewxJ8dsPJ3k9edjffT6KwJ6N53zENqv1r6PPWxM2OVhkwbnKaIw9jBL+sA41LyFEcDjG8lzKt2jHbZvSlGkFf/GMK8ULEEMcLmhsz9337fYZohcNiYN9XyRV6WIL4steB3eN0xB7x25P+N+SzYXoVP4WJdaVruEIzfzwoN0yC4v9dhAWgq4exjh9o3QbKTxrsDjyG0PjP4gBYGypP4906w87pWPHheId+lDzjmGW09YAmm6Hg6SB285rTwUs1mbeWfEq3Xg4wqmBWF8gQp/ux8in34C2cYdn8ytE52DOq5OZbPQ4L9+/cMeoXuJZUBBxogig+Q8Qa3pwTCeedNxpddMOO6/qrfMbIaCm7xlKAkvQs31RM3WucVcQeDtL/0CpS/ECiSuk5lvuqy7Ua3KtF22KFv4BRoxy3FZZTUSw76br3o0DmfPWrDJYT1VoRMKMjztGzsy9kF7S5xcml4w3JpuWP559CFPYHvxznYSLIm6QhlLbIUKsz9WGweCxOnj09bqNE+GvddfuRisAa4=","layer_level":2},{"id":"3690c93b-e823-4c69-8ec9-862feb3c3549","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Unit Testing Infrastructure","description":"unit-testing-infrastructure","prompt":"Create comprehensive unit testing infrastructure documentation for VIZ CPP Node. Document the Boost.Test framework setup and configuration used throughout the testing suite. Explain the test categories including basic_tests for fundamental functionality, block_tests for blockchain operations, live_tests for historical data validation, operation_tests for individual operation validation, operation_time_tests for time-dependent operations, and serialization_tests for data encoding/decoding. Cover test execution commands using make chain_test and the generated chain_test executable. Document runtime configuration options including log_level settings (all, success, test_suite, message, warning, error, cpp_exception, system_error, fatal_error, nothing), report_level controls (no, confirm, short, detailed), and run_test filters for selective test execution. Include practical examples of running specific test suites, interpreting test results, and adding new test cases. Address test data management, mock object usage, and test environment isolation.","parent_id":"cbaaeab2-9ed7-42e0-888e-58f1dff3747b","progress_status":"completed","dependent_files":"documentation/testing.md,programs/util/schema_test.cpp,programs/util/test_block_log.cpp","gmt_create":"2026-03-03T07:29:31+04:00","gmt_modified":"2026-03-03T07:54:54+04:00","raw_data":"WikiEncrypted:oQ7VNoYBl3ApU4O5/TGa/Y321aSX0DMdTAcPI8Ktg2DhOW6X/1bYLO5CQI7N+LKv2z80e4CW1enmyZ5E0nrAiHIV1VAY3fgrFid9I5hFrYrEc1O4+WRyg6s+pKxE+7jqXvxcSRQ3b/jb5TeUcDAtvZ0e02HNcQyRuOwhh5WMF3spUXjDxLRDkEsyhJJRWEDKroGyUO8VgLAJMEv2H3S9QN/IieXOh0e6hl0id3RltC/L51XQ5WWN/NjtFTIwYI9JVmChiuhUowzVjSKbw2XBlWMI+jWG2cfQ7BJr3HUUH9tf51PogQKJ6t9P4K7pqcHUOeSfjmhiOE3S9ljeEIUIbXXVhqeK8emjbvPufHIxSzTbsLliJbehRlEM+jJKk4MVv+eSqdc329AgBMWsHNpJIVBvD4X6vK5uEUIsWlHBdUaEKNkvUYWcmDS6b10alQEkjoHQcRuKJdDjJQ0WM0AZAjlgNIqSL6BJ1RjS9nibc1lbB8nuJUISDO23egcPqhby6a3p91q3wR89LulzwCDcZDFt44cWnlHMcJAL4HG8z1F7IUILxzHqAy30XPJYcfU6hHyMXa3tH9j4VDC3NnPf1V3ULgj/idDZ6Nbi7hDTibbb//5FNUj6tfgVKUPRwpV8nXFU+TM/Q24KZYXJVXifIQhoC3HSVjQp6XCUI7VDd5jorqnTX/c59RochSQbbFH5A7paVTlXE3QbS+8tlgiyJLRT5PaWDAO5JTUkBpxVY5J1mKqvRtaHO8TGtD1tqQXfYQdSLfluAKzH2RnnTclnxsc5hY6JjYGUqriED4tlfi+dKy8fWk+2aoEXQ/FkY8G8wj19luU7yYabScahybNkLlnx/Iwg+RERNpzY0jWIWsohtH/JQNnaDzhCQI0nNGCjvYMC6IzzOLwvmU10NlC1nlQYNoUNGQ33ZzSnDHh+AIIDtjm3kFSpdBTOTmHqx00dXlH0ZGGb6X4da1YT2+pmJWAu9v+eNSwOqknhKY4lZ7U6qUjMpm6IJaFDgbvVUmh6Rx3lOHDlNgX0txNWsSyoh8li5dmZ+qAHLAC0MRtz/xXPoU/TET3kZcl/XJYrG6U/IC0jPFwwPOGHcpS1b5Ddu+d7DaRkmdferkFarFMxJMRuRfJyTI/C9Ny3DzeiPV63FTqTPm5OksWEzkpue4Ak93BbPDzQQepjKz6lu2ESdmVRSyYgc2X1/RZF2o5erUZ3H0b3C+otGltKz5Yl/fYO0LicQ3R22VLNdkAi9DfgPfS4tfbWJaKAvX6GeGM0RQEynnEjN3QwwWc3vUofMQQnAZmXlQiwHuD9q7ozaRo/5IqZhPmvSw4KciUiC0phf0Z39RZ1dsna80ZLIbTC5dgLY6YzLq386K4GikF2VYJPbGWeCMID7O4sP5E4nRI9rkSuhXxGO+JmgrxYMP6bPP8n89B23Mk3+lnqO8F+DJ+1VF6y6wXRu7CrQlmTnX2Q4aongEnieRT0gOoEkjAFma4l4WeSEzzZm1ZfyBSjmi8po6QZt2TkJy0QBA/yR8/+JyeKwjpExS1GD6GBZPK0MCEYgyMuPfGBrHklM8Y3i0BFrMck+o3/5DlXfPR8SWwBw4DTlZBg5QGLpaFx0tf15ncXp5u/BBFqWkiP1aSYdz0IJv+M7pA9bXIzO80o/bFg56sDBaMtO11hUb1RhlsC44787iUwSkJWtX9wjPGyGlsyeGWQyJck7TtjLijfMmNSVutLvSAaoNX9lDshhuWmmhp83k8gIdfX5vzlJuPHUXrpIIBEq+KwRUzA4ZOea5rO3a0kpDenCKtQxeJxRHVb9w2A+gR6KCtnnWdw1HeSYyGZ+zKCl5Qhdac+qt8+pY3QxJVgDQY/JBuoIDVY9qU7ZvlZ/n7ca/Gg/X1pxZnZqWQpzh2dGf3RvpjmQGUnukFzSRooBuIMRA7VSBS8e8TqxjkuUxKHWVGr+EHR+JFjzJuttjCM6FuaBT0UvgmK3ED8zbDxjVgfu35SVSxlkScg2pr3jElU8zgtvdRjKpOAokqWwMO4ylSjnVhp8tqwa/54U+46kAhCg9MMrmttWHJRD2Ggn28bYB9yLjoi+soR6XN0fsQC7z54/ZmX4TMyewWgCLvbszf2Mlt0EeDvRX7kJP1wHnZTFQ9E4FTtSqlACo2QCU+oPgOoG5PwLtig5A31l2t8iSAr4F5MThDnapR7miC0UmifVivBC4zAIUG2lIZEzeU=","layer_level":2},{"id":"ff9d9cb4-10d6-40dd-a6c6-47f4665aee8b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Debug Node Plugin","description":"debug-node-plugin","prompt":"Create comprehensive documentation for the debug node plugin functionality. Document the plugin's purpose as a development and debugging tool for blockchain state manipulation. Explain the available debug APIs including debug_push_blocks, debug_push_json_blocks, debug_generate_blocks, debug_generate_blocks_until, debug_pop_block, debug_get_witness_schedule, debug_set_hardfork, and debug_has_hardfork. Detail the block generation capabilities for creating test scenarios and the block pushing functionality for importing existing blockchain data. Document the witness schedule inspection features and hardfork management utilities. Include practical examples of common debugging workflows such as creating test environments, reproducing edge cases, and validating blockchain state changes. Explain the plugin's integration with the chain plugin and JSON-RPC framework. Address security considerations and proper usage in development vs production environments.","parent_id":"614f1169-202f-4dd4-aaa5-360a10ad6bd8","progress_status":"completed","dependent_files":"plugins/debug_node/include/graphene/plugins/debug_node/plugin.hpp,plugins/debug_node/plugin.cpp,plugins/debug_node/include/graphene/plugins/debug_node/api_helper.hpp","gmt_create":"2026-03-03T07:29:38+04:00","gmt_modified":"2026-03-03T07:55:19+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"a4025412-2ce8-4c57-91cb-0f16dac0132c","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Installation and Setup","description":"installation-setup","prompt":"Create comprehensive installation and setup documentation for VIZ CPP Node. Document system requirements including hardware specifications, operating system compatibility, and dependency prerequisites. Provide step-by-step installation procedures for different platforms (Linux, Windows, macOS) with detailed build instructions using CMake. Cover dependency management including Boost, OpenSSL, and other required libraries. Document cross-compilation procedures and platform-specific considerations. Include Docker containerization setup with pre-built images for production and development environments. Address common installation issues, build errors, and troubleshooting steps. Provide verification procedures to confirm successful installation and basic functionality testing.","parent_id":"4b6cb85a-4799-425e-9f98-9cd149c004ec","progress_status":"completed","dependent_files":"documentation/building.md,CMakeLists.txt,share/vizd/config/config.ini,share/vizd/docker/Dockerfile-production,share/vizd/docker/Dockerfile-testnet","gmt_create":"2026-03-03T07:29:47+04:00","gmt_modified":"2026-03-03T07:56:01+04:00","raw_data":"WikiEncrypted:6nDTd1wU0hYJuRoAJhne4b5ashHc1omfgy81/++xSBe2w9rUrWuXSbrEHiZ6Cy0q5An6XvzHHLEqEmHIyn2epq5KEULOgH2CqfXy5wyGH0EduXcfrSX56fWPXFubhJoHVQNhHkotTEhKINivrUKUNsPV6o/SCw0uS9y0psxcKPtjaDoh0Fsz18Pdv9IgWC70bflhQsm2jmkHFIc62gg8wOTFVxbF44Nq2KVMgUmZu8HHDVmcWYlGBJG4KP3DT1WhmlX7YVXdIyz/7tiSMjzEmTAQvZZnyO7dKtsVFg3ASsOiAFqYVsIAD2SdlPu/dd4xojgsZw4HhAKnTZn+evhBpNEpL8R9UK82ostEvM0NLTbmE0HKj4B4RySYX7KYg/1d2bpsvrnoJ/ZhVZ8+9AvHl2H5pBA9HcIqHHTTGWu/WnGJDaaSjUfCPXaKPFx4joabBkzYJin7GRLyH+5IZNHA6n3VowgfhsraSEw/MnNETir9SG5GGrWTylTeglHHrcie14oBS34syZT+fNopbAHIhb02nGx/6OCLZ93UO3EGJgnIPuGoFjBUQoQNuOYYjLOAGMP/s+sfVM3r/HZHBua59+e/1tldVB/S2mDsKp26kPb8mx7mr2k/G7ZiBT9K41KCmTJsTej2HHTu0Ht9v/g6hYB9YXKnes9JogX33PqeOULXpDei3Xh9WM8PlpHH+Z9tHuDr+Q6CI5yHVart8aTTkL8zqZFHU9Bm9Ann/Nir98TTITDtqGSvAC+73P10xOYmx6FHJlBkoD+dc7yYj+YQF5ObJ3UCsFNrDQUGXpUUsQiSJkmzKzNwLSHkn0VHNi2webLwLUngr9FCvC/l4GtYDDIR0T5p5nJaaog5uzhpZiGACLuZeGcg4/VOQOZ4USyuIqDviAtW1szf7Okr5nIG3LbRQ9SN1zIGrTkdLys3ywOkB2ZE4kiRq5+GYL5cWQJMeWBkMo3pSPrk6Yv/4zQs/h3E1T/C7t+woDjnKOt+Wntxx2CrHFr7dWF+bWjfBTmGxufuACM44zSCzhOiruhjAnz71o70QjkErs3ZDcBYfB+ilY35E4CtfUoS9G4sNESCs4tQ+qt1zPgWOKYXR//oskpoDuzS6dAEiASGk9w0r7hEKuNo/gDh/qkL7UV3HebH2KL6Ugt15CizV8jt1wVKKxi/n1rsQJy7Sj080qAZtdDosKTgGzOwW0GXxC9h92lY/2ev69kX4pfXel0X1+l2LkwkOUqf64q6OSnAmzKWC903+LQT0RnvWZKVioCDvIGH9hSjaBI9wTHVoocTVmhPLLSj76MSfVnvx/2d0AUEliHgZGNzY7KlEJxsQUEc0ZjkXwY7BQwVKrOL8boU+LyTxj8zbBoMmaEykSzX2qgSwLOd1gEsM+jXNh14gKTgmUmO81ca/j13pbb6UIn6KIaAZSQv4dht4V9WEdvUqG+o9w5HClkedaVicqnp2cdRABOBYzxqZvEaumXpOtEg2YhaXBtv44HwWWahfIlw4O5DbN8+kiJ8G8GWQbKikFzXi+5/jDhEgYeNLInwwXK5bZ1ZYQZlLrVVgHDj0SkGxKpw5Aw/vIuOLro4OvglrGOXmuo6IUXnA/5Sz3eCRnEcsSND5LWjDpt6LskiimzqDwvjDD1Pl6sd9EGaILAbu6DSNyaU/xba0wiM22V4Pu11P932NcUzo7155XM9ungXueuXGtu6w/Gf8m7nGscdJw5fqfk1uLML5/6MFsG3RjtSzTgpiPSPNaIjHy3OEC1yI41UqM2FYg33NLyBV0/cmiQxGsPPGKhSp2mJVRVT2+ncIwxcNw==","layer_level":2},{"id":"5e42d05e-3b67-477e-8c56-8aa25b98889b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Operations Definition","description":"operations-definition","prompt":"Develop detailed content for the Operations Definition section covering the comprehensive operation type system. Document the static_variant definition in operations.hpp that enumerates all blockchain operations including account operations (transfer, account_update, account_create), asset operations (vesting transfers, withdrawals), content operations (content operations, votes), governance operations (proposals, witnesses), and specialized operations (committee requests, invites, paid subscriptions). Explain the operation_wrapper structure and serialization mechanisms. Detail the relationship between different operation categories and their hierarchical organization. Include examples of operation creation, validation, and serialization. Document the deprecated operations and their replacements. Address operation ordering requirements and hardfork implications.","parent_id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","progress_status":"completed","dependent_files":"libraries/protocol/include/graphene/protocol/operations.hpp,libraries/protocol/operations.cpp,libraries/protocol/include/graphene/protocol/chain_operations.hpp,libraries/protocol/chain_operations.cpp,libraries/protocol/include/graphene/protocol/proposal_operations.hpp,libraries/protocol/proposal_operations.cpp","gmt_create":"2026-03-03T07:29:54+04:00","gmt_modified":"2026-03-03T08:29:14+04:00","raw_data":"WikiEncrypted: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","layer_level":3},{"id":"eb3415ba-4213-4e37-931c-49f45d7ebe37","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Database Management","description":"database-management","prompt":"Develop detailed content for the Database Management system that serves as the core state persistence layer for the VIZ blockchain. Document the database.hpp/cpp implementation including the database class constructor, destructor, and lifecycle management. Explain the open(), reindex(), and close() methods for database initialization and cleanup. Detail the validation steps enumeration (skip_nothing, skip_witness_signature, skip_transaction_signatures, etc.) and their use cases during block processing. Cover the database session management, memory allocation strategies, and shared memory configuration. Document the checkpoint system for fast synchronization and the block log integration. Include examples of database operations, state queries, and performance optimization techniques. Explain the relationship with chainbase database and the observer pattern implementation for event-driven state changes.","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/chain/include/graphene/chain/database_exceptions.hpp,plugins/witness/witness.cpp,plugins/chain/plugin.cpp,libraries/chain/include/graphene/chain/database.hpp,thirdparty/chainbase/include/chainbase/chainbase.hpp,plugins/p2p/p2p_plugin.cpp,libraries/chain/fork_database.cpp,libraries/chain/include/graphene/chain/db_with.hpp,libraries/chain/include/graphene/chain/shared_db_merkle.hpp","gmt_create":"2026-03-03T07:29:58+04:00","gmt_modified":"2026-04-30T08:00:09.9789246+04:00","raw_data":"WikiEncrypted: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","layer_level":3},{"id":"843bdf5b-2dea-4e14-97c9-d8ddaf902f92","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Node Management","description":"node-management","prompt":"Develop detailed content for the Node Management component that handles overall network node orchestration and peer coordination. Document the node.hpp class implementation including node lifecycle management, peer connection establishment, and network topology maintenance. Explain the node_delegate interface for blockchain integration, including block handling, transaction processing, and synchronization callbacks. Cover node configuration methods like load_configuration(), listen_to_p2p_network(), and connect_to_p2p_network(). Detail peer management functions including add_node(), connect_to_endpoint(), and get_connected_peers(). Document network broadcasting capabilities through broadcast() methods and inventory management. Include examples of node initialization, peer discovery workflows, and network synchronization processes. Address node state management, connection limits, and bandwidth throttling. Provide troubleshooting guidance for common node startup and connection issues.","parent_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","progress_status":"completed","dependent_files":"libraries/network/node.cpp,libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-03-03T07:30:03+04:00","gmt_modified":"2026-04-30T07:20:23.8239769+04:00","raw_data":"WikiEncrypted:84sa6bLhPDu+UNjxUZw5rBAx9KYoUICf6oS5DojEq7h6OODVm92LmG6NSFskdLu4/BlwSqG4Jzo94ijeZaGGCOMnja8I3tGAEbT+3q+n8m/dw+d6coAvelXNpimAGqE+UgFSwNQ+QleFS2KnlnP0Ei+AyYS+W93RhzWk27zbV8GPjpKgme0so6woQQiGN1PmaTl8u03kRB5fRh9sxqVA8qa8YqnyO843mBnm8gceC1VG3ZkElSRJiah67i4rB8mwgmdDzWJDST7UlTgRtRQ6kxvZezrE60FHupzXUoNpmUzeCjYMp4HkB0VGqrOY7Q7zRwtBoyE2zfnXaZuAc8wCdI0TDc8rUfDnRTs2NEQ2+JDZfNlxp+Z5uhT2mCkfhIl0i8ciNT3yXwkxKft6tBOl1KVNCygqrecBFFjE2BprTsr8xv0rYl9g4nMfF8Q55xZxtZ0nrmobnFWDDLiF+5h6wM/rDMGbj5E/+UASJZFSPt4j/cQEKAjp5qI5Axm1BIDHQNDHicG76LbRl3iucTkM4QxCdPYuxcfR3SmzTeFQs2fnkQZKiwMAdWH9ef+B2FDRSverFsTbZg+sKOOw1MpmhghN9EtU6vwfWNi3TzrRj/VRfEnXqE50oX//jSWPsHota4t66ggpqucNRGurBl6aZfFkRXCSFyaYCRmH1sFMinKNS0aijN8tv6oPCYRS2atr0kIBzAX4Ru6MibCtvg80eKvDYujvZZr0bTH6Uh0fmuZwOX7YaDUX1KwBMuja4CmFymM7SGjnkJp/EI44GraxO1o0cuX/hnt5+jmnRqxjqQF5wJ8Tngw/Q3amWUMURWQhBFLGm5Af5QL4Hw5QDvbIJOl/gul82CDG6CC+TP4yW2TFhreAeUNMT8V+n8elOHPgLu7D1EtrLPjg1R4eU1pk1dIRJzlubWIspbm+ymQmlZzwV3AuDT3Eyu4iaY1qta/ZACGBg/DAHUgPDJIExAN5WVclSnIgChTAuOQoEQVMKRVb8oBis85sOEllEH2leXd6p4/vGKuhtX7F70sXWD/OrVT0KA5r/NX/+XUPqKH9WhVgt+kDGu/JcRKm/4MHupYjESCvRTu6orYpKaPYUC18zemoAkkHISq4eaQ4oDoCeyD3T3whl9FqTx4+RoYOrkMAStdijoz2OhY+PRgGaOgiO7MdjnDpj8zlYqbG8A9M/1Wal68lu5FLaOE8slWUY7e29fDGlQUDuUmkkx4FGt1XFM4ID7WgsmA3aNs2miYR/6ROruJVnMBP40Jp64j+HTyBlmE3zphn5KoQK0vPTcWzlXefUYsqxmCjwJ3xGytBa3Tf5p+pNj6gFMNTY/vP9d2W3IRG9Fanzo0hv+MAC4R5VLyipO0vFhsxx5veCg9bh8ZWZW5yobkatfzV/X+gS/CqtWBHf03gDsoxYbdfeLYXcrM4KvREqgVxShOXEBwCdo/g+ukZJkzzc1QYYqEn/Hptz4r8ByAPMzclKREd66MZDBbCj1goZ+J5MWyfy47ZXPJ10iT7neBSO8ggoQGAEs7RXp/SmzdmpYPRG3ScDN7RYIX/K1KenyTNlLuSXQYU4dvS4LmzX9hm2TAL8KScg6z4NSvtYSUR91oZ3EQFL+anrRDjR7TyasN4TdhENpsO2LuGTCeCFqGfDEsPfm06cB42ecQAwzAdVsOaHwhnpibENAAy/M8qD+tDmEiYAkqRDN8FLaIYjC8rRxUHQlx0uamaSK49tqLLd0OqNnpbG9HcJaqr1J1FjhffzOh5q0p4R1KY4ekSD3081e+XuggoOMzQfaJrMWcu+qIA3LFPGuBrC8J1ABpqqdvmYcsiO3d2aE1g1C1get3PYOByvGFsRCDu","layer_level":3},{"id":"c634ee15-c5d1-4600-8f5b-96016ecb0773","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Core Build Options","description":"core-build-options","prompt":"Create detailed documentation for VIZ CPP Node core build options and flags. Document the primary build configuration options including BUILD_TESTNET (enables test network compilation with -DBUILD_TESTNET), LOW_MEMORY_NODE (optimizes for reduced memory usage with -DIS_LOW_MEM), CHAINBASE_CHECK_LOCKING (enables chainbase locking checks with -DCHAINBASE_CHECK_LOCKING), and ENABLE_MONGO_PLUGIN (compiles MongoDB integration with -DMONGODB_PLUGIN_BUILT). Explain how each option affects compilation flags, feature availability, and runtime behavior. Include practical examples of CMake invocations with different option combinations for development, testing, and production environments. Document the relationship between build options and resulting executables, shared libraries, and plugin availability.","parent_id":"317287b2-3937-4876-97d0-a8c96007d95c","progress_status":"completed","dependent_files":"CMakeLists.txt,programs/build_helpers/configure_build.py","gmt_create":"2026-03-03T07:30:07+04:00","gmt_modified":"2026-03-03T08:11:41+04:00","raw_data":"WikiEncrypted:+hgVdflPLL0xmma3oFcNhzO50UdlcHF7zsXDx81IdubJW/ei3OO55+CdCQ8q5WiMdgjekSxvQIh/JW4dHdtvRsVOk97oJYxDnpMU2nARwm7MPVOE1fvGf21aemNsnNtFwaR6q/t3BG8xZS4vVWZKT5owJ77uYsNkUtyJqPayPXgZ6JPk8AgN3ErlK0xSf5sEg+aXImxZoamSb1RzUCLe/AJrMfwO/39F8fRnlooSObSAG9972AB8S3z8qkdT+/NvoP7Yu4c+O4xbkxSPRhVqqoDVzk2WqnvhZkuaibwIrH+emKKGGpww3D5gW5+moLgsLDC6m73VZEeZGSxbE7nms4iAyAJOciJRbc/48yTJ/q3q9oq0k/pV2FuB8A68A1zDWZAlutbh+29TzGfMBPNP1Wxrhno/VTrHMYsiQJOS6dZRxbbN0J/ByB5w5/JmntWDkKeEvOWRKrsw3CMHsKX66UataOYI1UWdk2eEDrSg4SRkNcr3T0prOUhOLk2WM73mmQYCcx/khTM2OtOAJoTBpshyseGs2nk+BdX6kCLmTglTkyiqV+OF0ycuk78gFPSyOQwkeOd6iPGQfzI3nr9VnuL8WYk7kBH9BCJcQTHkwRQjKZejrIH3O5WrLuzX+w5CTP/BrEW8dt29CEh/0s2L78fJo5pCvOsQslGF3j4tRYrDUGtDQ54GiDt0iYCnAIfzF27DbyQVNIkiMfPP9fmE0FoR6wYQKc7FT4NfQpMKTV1xjOzsPolWe2rzXbormwz9pfS0XN3kuarDHx1/UsspkBtInazzzduegUsyG5o1YBrMcnTKcmqphnb5eaQ3+QDWGjsouQfpiCUvowvf7Ajz9YMLtLvcPUK6fTnv47i5C0Ny5eNSnPexPKX+o2LEj1I46MTt2FZz9/UymAj0n5UlOpM297SrQ+WQoJBWKjh03W1x/aWrbpWhLOwmLQVUDz1lGMaKv68c9gNOWX3R8dtU+SuIfFQuMY1XgNpnzH+I1OsbcwOJNB1wtlirZ/XckeHB9fMocf8MICEkSMgPIS4PmRAfUJXh8Emr6GHkO/HIRdzEVykq2GTsMKcTIsnYzLwUsfmy1gH3nXTJRUvP3AdwxG5N4EG4ijcxXFKwenNLlvpzXvG/2uEgZjo728S+7Md1vhLZJ2dsepImJqn1TmEZv5WkEj3lbkemcfx37pZah1qkYgav2mrEVUBqbWdfZpYy6IwDPUCgE5TBzdESGu49HWgYaWW6oug4PTcbaul49Rbiv5LMbfwHEf4EKvuGqQiwjgcWsz+57SmE6omFem0XKE7PUiGMzl1vSMwIaUG3pfaRSMKE5UvrHPHaR1SrsVXt6OQ76yjj73HerLdyDRInKoUzQwkZ9jaHE1hft4Yste+Lej7O2OrUSKE4wzhhVBkwwh9AIUqntzEaaB9U4lFVk5uX6v6IJ9ssNwZMzl5j8bUVDfb+HDc2K26alme+A0EjEKlIL4ENYXCZk8zoRhhzibeDx1ZEZXPjSKdZz73dORKb78hvre4u88arxQuxwmwt","layer_level":3},{"id":"85611a9f-1537-4247-b45d-a6a5bcb6a4ca","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Production Dockerfile","description":"production-dockerfile","prompt":"Create detailed documentation for the production Dockerfile configuration used for VIZ CPP Node deployment. Explain the multi-stage build process, base image selection (phusion/baseimage:bionic-1.0.0), and build environment setup. Document the CMake configuration options including BUILD_SHARED_LIBRARIES, LOW_MEMORY_NODE, CHAINBASE_CHECK_LOCKING, and ENABLE_MONGO_PLUGIN settings. Cover the dependency installation process for Boost, OpenSSL, Python, and development tools. Explain the build optimization techniques including ccache usage and parallel compilation. Detail the runtime image construction, user creation, volume mounting configuration, and exposed ports (8090 for HTTP, 8091 for WebSocket, 2001 for P2P). Include practical examples of building and running the production container, volume management for persistent blockchain data, and network configuration for node connectivity.","parent_id":"3789801b-aaa1-4c34-a35a-c5b48338520a","progress_status":"completed","dependent_files":"share/vizd/docker/Dockerfile-production","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:13:22+04:00","raw_data":"WikiEncrypted:1PcQ+NupkgQiLQ506NXlam2NaTESDp42+Zw7VpfY+CzCiLAg7GShqb8JCcPdfBZaP7WIo1Mgj6cRsriY4RimiUKdLamglD6mtocdtihAp1RQvMgjoGAVK79Z0f2cu7KECfA2Z+Ievqh+9tvt69UamhE1qHeFeJsTu0aWWWpl5rcj2YSIR5H0RebtCzB36Sv/CERib288ap2vqUEZU89BGsYKIWlHMuGgzeFzUMYrV+wk9eG5jVZjG906/zYCaDWTbh/TekpxoiIf6HSW4xXhrMD7AZJWn5k0n6jctguU6jEHQP9nzYFUiTMQ2lMvD/jnXKsLpwJqAZR71nU87TaUO8FVZul2iQ+vQFFwzyNXGE4ddtidjCMxKR2Ww73haXvYbAKHAAAdbw0OXyAcdKL7K2fcga/3WAuxnWWVMGg+E5msVSmXsJcIZSV5C5r4Va9hWjxCi27IEGMvBsLBQXwRNsAn8aSlu/fa/jYxeM297Dx450DFq32jM4NPOOXjC5XJaOBlCvsZa5P7zSnR6NJi/X+7fWOw9yjkvU8Py5K3PMH2RW0KfPHB4nSgLXhw7SBcxT1sdiFVcF6VrY01bksI3y6ZFQeU2Mr1vAWTAnZvhbpann/UGYuYmo9u7R6+OoRu7tP/uEmnBKM159+qF/2olZ7ZKbq0jX9b/9HGNcCEWiPovpxyKkfyNvPpJwaiKsoQKs7Z7VJXgM3qZUJI591RB+ZcnmWCqM8nEQcRvf71HmasT4O8Y3sTM2dBr8kEZhAlpbsQKQuv/tO4kgC39gsKvLSkt5o6PZ8rP/QWZKPdyO214qNoge+jhAzViysspNcJh6FXLe3DD6YtFK+txrue5yI9R1M3aoDc/bXI9A5j56VWBYXHbfPf23c5SX4VJL1tryaUnD7HopiWlFdRMMWK4Lkn/2+Q0acfSzOCJ0FwsfSAC8BpfNe60OO6hYvSrt4uhGDRMGa3SGJOJGLial88QaoDUKgcHRRq6xPKtHWGyD/spZpRXxFYPZcPv4y6RRPSrcytnWWxf2UWcR+HQRb1V4MNKxPXh0RW3gSVo9D7smSAIXNAwuZ5uJBZvg7CYavG0mjPm9+32YDh8vPLBHGXUa4i4T4KTfEvENMVQuZVw0amZ5S231HODaR8LmFhvy9QjC/c0hlkiAyQP5zq7Qh9tc8eQ4p8tuxKAPtrhzVy1xTTKXNmZrKKD55p/3sootgWp8RzFJapVolQ4JkSSNZVtNP7J04uG6jAGktkZw0eHLya0dr0D2IRcuFXNoDjTPzy0wVnv3PmAu8ao05iAinwPhwsx9RoTW58m44Z6rqM6uhOkoIi60y9hPWNK4tNtipS+4aX2ZgQanujtzdVUqDmmDQr/q6VdzTCSHieHN5LHp+5aCHW4oKBPABE9RL8gnzlgRxlPxgRzXW0rAj9TnTyuiD8c3HskKJARSiBAcF1oZKweAISYXvb6QGe1TG87AcdEsvUtzDqgcZuKJvULl/zRwIkJ2jDFaDAeUWeORgrzmYEFNQjYLKrqNZULFVqRxeouW732TCle3fBejZlmUVGxfeshmIRsezj254Gy3zs35hkwnB/9C/kBh4EfPu2rzOAqwbIKoHSfjewV2R4z74esD2maXgH255Nd22YvowdyqvJ7UyFouWyze17p/cTLztRvKY2xfFFQ/DFa8DuFRmLSA==","layer_level":3},{"id":"d0aa68e6-4e49-421c-8bfa-6ef641cd6656","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Code Assembly Tools","description":"code-assembly-tools","prompt":"Create comprehensive documentation for VIZ CPP Node code assembly tools. Document the cat-parts utility (cat-parts.cpp) for combining source code fragments from directory structures, including its command-line syntax, directory scanning logic, and file concatenation process. Explain the Python counterpart (cat_parts.py) for automated code assembly workflows. Detail the hardfork directory (.d) processing mechanism, file sorting algorithms, and change detection capabilities. Include practical examples of using these tools for code generation, schema assembly, and automated build processes. Address command-line options, input validation, error handling, and integration with the main build pipeline. Provide troubleshooting guidance for common file system issues and permission problems.","parent_id":"d54afe72-4975-48c8-b825-ab792ce92a46","progress_status":"completed","dependent_files":"programs/build_helpers/cat-parts.cpp,programs/build_helpers/cat_parts.py","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:13:01+04:00","raw_data":"WikiEncrypted:Ux5Ub3OP0MtzohTlWbMlC5lEm19o2HQfwDVneLB1a1hTXHDwEhsig5jNN5z+LIXqsxmLUM25wY69aB6+bwuz6D/wkQEy8W5IUBq/kV+7tBHA+L6qCLLNXOlhHVGHuo6JbY9p5a9HNMXuJ5REPOH2cRjbMvOpX2JPYNiqO0206sp7CU+POZ9RIr3gREMK5LT9JmW1upbSPZzeGpNAVcWFEvpI7hlGDI0xytSGBevLkpSH0dT1FJ93u/i1N/YAwGNI7V110fcuiklNuAMYwCAM+sTshWG1AasofpIxFVC3HWLgQc3T6aGyEx/vw988/JCZ3dzRc68yQHQgLyzP+e7VTGUrkw42ZTu7b1d0sFkwjZb3Q8i+O5hhuWGjDAb22Pnufu7fRCCbKxLrbs2IFj0YB2urm0PopQh9GmAVCyiTaLtmt2/GkT96Z5vQDYVAkCAXtWP/eXVV7W7HlEZ+x8N/gBpkGiAzDeauGpvdlpZcFTEv2jhqXIdDe/EIWHBYJ18Nwed4Bv3UQDgYA5s5TUXeVEyRF9243AurwhVMW0Y9x7figiVO3AFR5yoHHxjPwzLkI0F8Q1PLK5zs62CmA03WCostwnKhqG1Tj2x/E80KstYRsF7pR1TwqOmeX5gvlNeEir+x0afFhDoXKKLmFUhmqq3mThWMoxONDYxZSSFUNyI3oPDBvVyXHFQglhuFu+ZxTHn7fz1Tsced3XnAHVq/pcbZlt4wi5umSVXi1mPrz9zBZN9irqYU5bU20vhfwqSuLI/s911aqeeJEPIIxb7paftniC2Ou7p3IraD3n026CfuxsgUGDWGLLsopvN36hlEnPXNWCZgU1dVRqd/Na5f5DjzjUc25GCoUeqOSqboRpjxki0D7e5OM232kS+KKeTKrXQX6QPba7xdzt9dPKEfs2ehTU8E9OnosImfPwc7FM7LIswSmEV0xvaXnJehqLwtNs7ABTiNyZ3tst92hHxxBU6YtNfSCFk6lqVzVRBci7stxUXbF9JkIVPnRtaSqJhTQfd9RP9hfTOvPrihnlkWRJzyOMH2jhuVnCAMTFgkUKe8gVqBGrBLoMY0fzXFrkfsNvj5Vq3Rd9SmCtCHH5CvDL3FvgPJmrmwzbaVrQIThX9c383OKjjYmC3PFTFOekX7IxgCvF9eihK/cCM4WWMozIXKkm8UYPEtKApNGLISn3KlPrI9yOretN0iKCHD0gjF14ZgrGlpZdhtp9mp+9YIqTZtjCDAW+T+rgC/rEPcHwcNFJfpWEXvS4Btct7MDBZUq/irXLxBFJe6Js58ALQtDOPVtXH3F8wzTnsBxNeqgRcFPJZhksU2iu1OGRL/60p5RYLKGJ34USY9z+LghuRBh72H2K6GQOtlGNPjOyF9PlQirmbNa2iTGZv6RSnOAXUgtYRVY6FR7hul7EZ0V4V/oaBEnF1RjxZJlenfKmY18X2+yn5pmAy0s6nKT2rkh9iNN1f/LajWKwoV5kQfiyIG7VI2+eIKJq+u6TUy+VmmF630T0evodIP7j1gmAiqx1IQnqY+scdhg1aDsmUymhwjg76gHZ+BnMqHxas940dtVvNZsJT+ZDMIPoI/VF/2x+Jj","layer_level":3},{"id":"cb549eb1-0a24-4153-9474-eae1ed48e8e1","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Getting Started","description":"getting-started","prompt":"Create comprehensive getting started content for VIZ CPP Node. Cover prerequisites including system requirements (Linux Ubuntu LTS, macOS, Windows), dependency installation (Boost 1.57+, OpenSSL, CMake), and environment setup. Document multiple installation approaches including Docker deployment, manual compilation from source, and package installation. Provide step-by-step instructions for first-time node setup, configuration file customization, and initial synchronization. Include practical examples of common setup scenarios such as running a full node, testnet node, and witness node. Document basic troubleshooting for common installation issues, network connectivity problems, and configuration errors. Address security considerations for initial setup and provide guidance on monitoring node health. Make content accessible to beginners while ensuring experienced developers can quickly get up and running.","order":1,"progress_status":"completed","dependent_files":"documentation/building.md,share/vizd/config/config.ini,CMakeLists.txt,README.md","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:31:32+04:00","raw_data":"WikiEncrypted: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"},{"id":"64b99394-2424-4a81-ab2a-f41d1b9e973c","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Plugin Architecture","description":"plugin-architecture","prompt":"Develop detailed content for the plugin architecture section. Thoroughly explain the modular plugin-based design that allows for flexible feature addition and removal. Document the plugin registration process, lifecycle management, and inter-plugin communication mechanisms. Include concrete examples from the actual codebase showing how plugins are registered in main.cpp and how they interact with the appbase framework. Explain the plugin template system created by newplugin.py and how custom plugins can be developed. Document the plugin API design patterns, including how plugins access the chain database, handle operations, and communicate with other plugins. Address plugin loading/unloading mechanisms, runtime plugin management, and the observer pattern implementation using Boost.Signals2. Include practical examples of plugin development workflows and best practices for extending the node functionality.","parent_id":"8aeac580-587b-43f3-9292-e62e4d3781f7","order":1,"progress_status":"completed","dependent_files":"plugins/snapshot/plugin.cpp,plugins/snapshot/include/graphene/plugins/snapshot/snapshot_serializer.hpp,plugins/chain/include/graphene/plugins/chain/plugin.hpp,plugins/chain/plugin.cpp,programs/util/newplugin.py,libraries/utilities/include/graphene/utilities/git_revision.hpp","gmt_create":"2026-03-03T07:28:18+04:00","gmt_modified":"2026-04-15T13:00:48+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"cbaaeab2-9ed7-42e0-888e-58f1dff3747b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Testing Framework","description":"testing-framework","prompt":"Create detailed testing framework documentation for VIZ CPP Node. Document the complete testing infrastructure including unit tests, integration tests, and performance benchmarks. Explain the test categories including basic_tests, block_tests, live_tests, operation_tests, operation_time_tests, and serialization_tests. Cover the Boost.Test framework configuration, test execution commands, and reporting options. Document code coverage testing with lcov integration and HTML report generation. Include practical examples of writing new tests, running specific test suites, and interpreting test results. Address test data management, mock objects, and test environment setup. Explain continuous integration testing workflows and automated test execution. Document performance testing methodologies and benchmarking tools.","parent_id":"d896ebd6-7a89-4c1c-a16d-8acb2a61bb9c","order":1,"progress_status":"completed","dependent_files":"documentation/testing.md,programs/util/schema_test.cpp,programs/util/test_block_log.cpp","gmt_create":"2026-03-03T07:28:48+04:00","gmt_modified":"2026-03-03T07:41:49+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"2c57cd51-91bf-4148-bbc4-53fda0ff7ec3","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Build Configuration","description":"build-configuration","prompt":"Create detailed build configuration documentation for VIZ CPP Node. Document the CMake build system including available build options, compiler flags, and feature toggles. Explain cross-platform compilation requirements, dependency management, and third-party library integration. Cover build variants such as debug builds, release builds, and optimized production builds. Document environment variable requirements, toolchain configuration, and platform-specific considerations. Include practical examples of common build scenarios including development builds, production deployments, and cross-compilation setups. Address build troubleshooting, dependency resolution, and performance optimization during compilation. Provide guidance on build system customization and integration with continuous integration pipelines.","parent_id":"6ca6368d-78f2-436a-89d8-8e2fa9c3d925","order":1,"progress_status":"completed","dependent_files":"build-linux.sh,CMakeLists.txt,.gitmodules,thirdparty/fc/CMakeLists.txt,thirdparty/chainbase/CMakeLists.txt,thirdparty/appbase/CMakeLists.txt,programs/build_helpers/configure_build.py,programs/build_helpers/CMakeLists.txt","gmt_create":"2026-03-03T07:28:57+04:00","gmt_modified":"2026-04-23T06:46:47+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"7c381449-9427-4fc2-ab03-1a1301da306b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Containerization and Docker","description":"containerization-docker","prompt":"Create detailed containerization and Docker deployment documentation for VIZ CPP Node. Document Docker image variants including production, testnet, low-memory, and MongoDB-enabled containers with their specific use cases and configuration options. Explain Dockerfile construction, multi-stage builds, and optimization techniques for reduced image sizes. Cover container orchestration with Docker Compose, Kubernetes deployment patterns, and service discovery mechanisms. Document volume management for persistent data, configuration mounting, and network configuration. Include container security best practices, resource limits, and monitoring integration. Provide examples of container deployment workflows, scaling strategies, and upgrade procedures. Address troubleshooting container-related issues, log management, and performance optimization within containerized environments.","parent_id":"ea5be69a-46ea-4304-9d86-597c6f7fc254","order":1,"progress_status":"completed","dependent_files":"share/vizd/docker/Dockerfile-production,share/vizd/docker/Dockerfile-testnet,share/vizd/docker/Dockerfile-lowmem,share/vizd/docker/Dockerfile-mongo","gmt_create":"2026-03-03T07:29:04+04:00","gmt_modified":"2026-03-03T07:43:26+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"30dc8338-1212-4946-a037-0fcb2222ca44","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Inter-Plugin Communication","description":"inter-plugin-communication","prompt":"Develop detailed content for inter-plugin communication patterns and mechanisms. Thoroughly explain how plugins communicate with each other using the appbase framework's dependency injection system. Document the observer pattern implementation using Boost.Signals2 signals and slots for event-driven communication. Include concrete examples of signal emission and subscription patterns used throughout the codebase. Explain how plugins can expose APIs to other plugins and establish communication channels. Detail the plugin dependency graph and how required plugins are automatically initialized and made available. Document best practices for loose coupling between plugins and avoiding circular dependencies. Address thread safety considerations in plugin communication and synchronization mechanisms. Include practical examples of plugin-to-plugin messaging and event propagation patterns.","parent_id":"64b99394-2424-4a81-ab2a-f41d1b9e973c","order":1,"progress_status":"completed","dependent_files":"plugins/chain/plugin.cpp,libraries/network/include/graphene/network/node.hpp,libraries/utilities/include/graphene/utilities/git_revision.hpp","gmt_create":"2026-03-03T07:29:09+04:00","gmt_modified":"2026-03-03T11:26:31+04:00","raw_data":"WikiEncrypted:+VS3nPXgxjNRzComTBushLS5L02zjsVFvaKCFsqlKpfaDTTPhP96kOqBLeSiPQLmJfMOrOA/aPyYvvtobOnI+yTy5f4Y/9dCTolJoifXEZ5XGFZqDjDoX+djFBEuFlLpFGPST8HMi5z0Ch/1DOLnzXQnZlkaKIp93GltP3r4YI5EER18kAyvOB0UR1rZAxG8UmuZGRoOeXBu0QnM0v3Xce8hayEwp3K/c36ogR1Qn7rZRVjpgCnyg+JP2l6grJxEgapS9MM4uzsM+Ku2QJq3iBjKpNcAZ6WmWmyMNpd6LesnnwaUonLuQ4YvpjRnZtu2lxPpCCkb7x1aYhJNhyhJr7lZkv6hmSrH/NFcjzW50VwA6Em7zVksqvKRHnrksPvdmJB2yWPRJ6MP3Lh+1zBUke1tfZ7evrMKetz1YDTeUWWu+3agM6kRBztnYqRzt+Y3m0UZHdyGo4XFJxZ8qG37YiuKh8xqwnalsKBrw5H7/qRF/eFE5AsjH/YG7zcDUtzMRgM+ajOULwE1//efI2uZyQfmQBLdop1uIFPEMAx/NXkHp5XYHw7pMpqYjjQGCGrI0L44iup2Hpb3t2EGQk5rubDPVo1hYprbomzKYd+3Cn9x1PyMFfGgrIUGNlHUPaxRZU3GgOXGJZ5jZkp5NY54lmzHhkUsxFqg2pKZhTFPZdG2KZJVn3lPEwY9VgOTYSK1mBkHD70mY+1hbRVecnF5biyhKYdxF9EHBRQmdwajNqqPikD4ZQHJztBaN0+1XL1B3E0MJF5y3SwH4i5fqn0nlx3dv88Gff7qR+VqR10q44CaCKYB0Y6xH8rWXqhY615nAuXq/vxY266kyLPhPbI11pWeCUqS9lM7EbQBUlH0Nl2HWMEtF07m4WGB2cFFC0nl0ncXt+htNKg9FQwCluyVL57GanbdPVJAXZHb/Lf663Mvkmy73MHwlPsdOX9JlciDGL0hvKypGxgJx/YXIS9sRec1lx8quZc8pHsMEl8w6+hYdyViF1Wi8K5uNOW5B6dK0N8VxDbgLarfB0b2tX+9W157CYhERVOXIYc1wwhGpMgbzpfrrHixuoUZwQHK2Vu2pUtFiq1x8utSzkwTfO/utllIdTkjMcm/rCGus0j1Gxe0GNTxjpJY4mJjFYDOTpqF1uFBlFy3z3P+aDXUktOK1/GvaHAHKaT9S+4m4n5PSOzjMSnTs7tI3m6ed9yvWgyD5NXmg7WdzkWu+1bmGXD7FkAxqM1n4ftetysxyvLJfm6il0cF1wj/r/gsAR9rXYdeaR/4YT46kEbQCKJNJ5XuE26c7pc3u53eafEB73iKkZ5DgjEdZvjeZEnQMYKoAYgucfPlmF6PxJGpNSzGg7GUrUk8bgQoz56XfSi/UPVqHvBzirD+HOvO0BqMFBirpsqit8ryr1rcIsubw9x/qpnxm32RWo/QZ/J+KTY2WScmFdKTREIXfPFgt8L5UH5+ZiUEHT00xmtYq9T4uWDklpxy4dDFpwdgzRQC6kdKLgSopQ0YTkb0uTzHOGTIzKcj0Qu7MDmB3JZOfN4GIzaRugdUa8ia0oyZsYHKdq1xd9jg11jOya0autYoLgHkNYV5kzcMIYWAAx7AkW6QqbAvd0Q+ZkpTF1hgq+dt3fJ6KUHA9feV15PnROyq29KGCVOFMahYdN1R2Um2mkHNW2xlpNO7w2TSXzFhPuLJMLd5mAfIXWt/Nyq1qTOe5nEQ0HxtWCqz9MRP+JyTlBDOEXB6TH/VrA==","layer_level":2},{"id":"ac1c2473-448f-4372-9f6e-3715d530d978","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Database Schema Design","description":"database-schema-design","prompt":"Create detailed database schema design documentation for VIZ CPP Node. Document the object persistence system including chain object types, database schema definitions, and storage optimization strategies. Explain the fork database implementation including conflict resolution algorithms, branch management, and state synchronization. Cover index management and optimization techniques for query performance including primary keys, foreign keys, and composite indexes. Detail the object relationship patterns and how different entities interact within the database schema. Document the schema evolution process including versioning, migration strategies, and backward compatibility considerations. Include practical examples of extending the database schema with custom objects and optimizing query patterns. Address database maintenance procedures including compaction, cleanup, and performance monitoring. Provide guidance on designing efficient data models for custom plugins and extensions.","parent_id":"7a20b53f-0b97-40ec-a630-7e9171a04006","order":1,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/chain_object_types.hpp,libraries/chain/chain_objects.cpp,libraries/chain/fork_database.cpp","gmt_create":"2026-03-03T07:29:10+04:00","gmt_modified":"2026-03-03T07:45:31+04:00","raw_data":"WikiEncrypted:veTYwq2y4io5qXerCTrkkPJIc1bTbUL5li1eCUHdOyJdZx74CSSkZxMIn6huqICpwixbAF/trC8XC8TQo3hGdBAURFH7jcycN8yIcYJpm2OD7X2KgfgU421JKYo6JlYp92txnZ7xEfTI98Svr+yM7kowC+OcnXh+/u2G44+CpitVhKsmHqJaJ6v0jKZxZcpBS6c90VdI3UnnjP/0UI2H5vsF3hd/Yl39Q+0CdRR8vq5Z0gCmUaIQ365ldK0KtH7q4LHwxV5vRxlgTOhxAmOZX9QI0b4aNCP1fROrTDHGFMCHKMk8TtagNSjy+vugOYA+Et3eInIQXFTH+eo7FZtghoHxz/Pc/vAyLlRne1R0KXOy2/fnlNiVSvZ5e2CCcGzmBC1RNvCZxecePh3JsHJKjiNyKAbKiuezaZ62ciVI/HqhM/Hk0hUeQeAiwNAEjQS9+D/Tgkhjgc58f5MdkY2A+cbS89uXmNagZ1jCghYnq/NMZZhreTUQnOqG2131Z5q04kJXp2AUVk5F9syMVdWeVmfbRsk7In2eYvR071Uk/y+2TwZDqHyP4zXDmPnsqkMVxciZalsxsYvlxkrhXLeObk5WeCqhR6hiOnWfYVEE0X7udRNHTQAFtJ3gCrCTubkW0aHsAJbyM6yq9n7JrxHvuF7MWtcu5wy82X9pNgKM5EGgnuu8DHvSEBLTdjs+V4vByz6l9AwceR5WGPK1OA7xhXFyBkxS+k9W22pZe4sOTJhVrd0PIdZOGZ960krdQ00yBi2MXzU6dRO6uDb9Jl2B+fu4Rv246qUqkaq+RvnpwVnPykDqCuamv1niTZXxFaR7GKpW+JhRP8LFNyktJF7jIjfCoR4tQTXdCL1LS/S2FrwQW689N1zwkt7ofxmrkMwX/DqxvyNfxbr8CEufWtVgwpcmHtB7Fl7NI4Ezroi1IpJWr5BVOR+cU8JgOA31O45wXyYTyh2/Z2fXrnaMiy5yPOn18X71bQYHmfIEq8w5dpN14czaOmhNE6FdRzD3gTFPP7ZnVpqSvhGSxdDRCWtEJSDpWTMMUiEYuAVwzBsqCU1yKivbAEV/WitOtDlQ7XnrAtKhXIIcfM7i8jqBM8HPKjmnhwIF9iYIx595Yun1GBn+1wzz+wIiYmzklp5WFljBsAIJQtjmv07AIaQwmTl4lZs4CL5hJ/LIekgmzQNWGjGEoqp+VCk7JCWSXo2kFW7Y5t+V6hZzjfUFY0y8pj9CnpNoI2f4Gbz3BG7foU8CSIXsRGw6Y9zo7mSKmwV16GAvfRZfIljlIjLAfqrTqmCFLVxRBU2CLzccNYZYYDjCnjWCHvfatr5tns6JoHMkN4O16fUckj3YLFUuJPOzPkQcX+COp9/z5qm1QFGotdTnuOIIQPHDcUfXgrrPo4pii6m0ENEq6sN/C4FcVS52xcwFIhtfBwRbzPqTOHeqrtx6HImHIGcNHspjijFKABTUECwOjWaDAofGLPoK/5DhF8dpZbTbERIppk59LdNxNejP2n4fRQLsjuFmYlivrl/GBVp9prYryb10N6NJ5+fP0Bas0Qw/d/YZq72B0pYjkgvwWMKxTs1R0r1Xydj5BK1qCtWUfR0W+7c+7BKbKeOqY3mvC7pzxUJ5uDEELuF8CeGLHxEg5Sha1YTVETRsVTSN4xTWf558J5v2xXtCaJl3QIOY3MUhz4iP8UwUIl2nvGdW3pghJnXDGEJ1DcTNo+VOApTy28wzaPWkvDEz8Qz0HoSSlKEdwvtXKRiH45shUjCZRN8juOBFcANfmAncuyeZCqDgJKJQBFmw7vfmnLx6VbpQ6Hq98kRBDl/6xTGrsgQ+8cg0nZ3zyQSZ8xYcA58AM8/fLRtzH3uv/405C7R4Be107e+UjRaUr/GSjtTACfMpTJUh+ONlKLAo05qW4HHU0Z5TAaYwe4ryWbfd9AM92MeGswpqEgxyYmr7l2oATjG0ZwWbK+DqBwni6bjpfTmb0Fv7mRewHa07Aj80m9Nt+xgKezZi4562EPTJ1kInvoDOU2Lxa3796aLo/Oezlao4kabK+BfO9Rd3tY904mWcNmPURy7lJ1LwayyQu5SbFcXmHd8KVwMLj0iOVRS/lLCgCaLjAafogoo4pqcRYFW7aZh84Q==","layer_level":1},{"id":"0b0666c8-2764-44f1-b24f-7a1cb2afaf30","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Block Processing and Validation","description":"block-processing","prompt":"Develop detailed documentation for block processing and validation mechanisms. Explain the complete block validation pipeline including header validation, transaction extraction, and state application. Document the fork resolution algorithms and how the node handles conflicting chains. Detail the block logging system and how blocks are persisted to disk. Explain the role of the fork database in maintaining chain history and handling reorganizations. Include block production coordination for witness nodes and block acceptance criteria. Document performance considerations for block processing and optimization techniques used in high-throughput scenarios.","parent_id":"a486a2be-4bcb-4205-83fd-2ab48f89829b","order":1,"progress_status":"completed","dependent_files":"libraries/chain/database.cpp,plugins/witness/witness.cpp,libraries/protocol/block.cpp,libraries/chain/block_log.cpp,libraries/chain/fork_database.cpp","gmt_create":"2026-03-03T07:29:21+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00","raw_data":"WikiEncrypted:R9i/29qd1Uv5xEgS1tKQyKa0Xuqqfm5cq0GV9nRcndmrxsHsnYwJpAewisCUpG2ti9GxrCpxWosUOZaN9wIJ7/8/157xbaOnn2BOhyeyJDPEjOCeYSYjSjv1dxVt5lXdexGoI1KQL06BJsml/tJH8IGm3rSrWyMIJMVaaR4ggiUXOi0xrhPWQEb81TD+YKTlP68hIAEoY/BgSD3P3x3UVjbs9BVxlAPiZWRZtien2V8UiacqqlcKd/5Bq2z12bKdYKcIEsgawje1vnwy2FUKJpIxFZv2NRXi0YFuqTm1zjG5BYFXJtVGvg05szjd3u0osAndDv7ppuz6jwP5/Ew6xb5yos1BXUZKwLOb51aL5gOctkofzduDa21cq/a5y1RkqN+JkJHtVvUh6GpVkgf3cnVHO6oT1iVdi0EdN7ZqINAFcNbEpuCy9EkuvLFpJLSkh3DxvcnxAcCAvHXwF5fHMCC/zMpnOKQ9zMRA/NZbTMSpXtJBTezjkek+AT9ZokXCFWKUfArjXrp5XMS+ATDnIh7L5AWKZGNDCEhqokyGPVgYGyv7ITpD72SMrBjhhOOWpIBXbKvIkk8rV+KFvCgf3RU2Q5FG0EtbMj9qRS5i4WfhrADrKQPy+glGZLUE3i+MD8PqBVHJIM+cRq7Lt64sagUd710RSlu8axcaqivTj+yXzoOSmHvwVpgcU5oej6pNNR2jpAROGzjV9wXJWtgVNUYRyurjx1qi7EtmAsVYZye4c+ZsZjpzsnZqM88dMkuVDMx0qqMnS498ZQjdD6wIaxyPjrOFisUxFquVSzmrX5I+HqVSRMwu/e57OtQoLrfcOMI0kJPBNDrhH+Dn6Up2IzrTbYUNWgpMBLYLdxgBXoHgpJSEyzWV/gEI7+hO/5FqF3Pm9Em4gI9ch1SxblixxbUx+v3U/uDD934SQIebN9a7NXyZ4h+cXJLeR3iB+d9Ac+Z+tfx4j+ilQpBbzHyf7YMd/6nwFtHiAlrny4Ufp0Xhqm/VfELNse22sQdtsHZbd2CwIUmBiYADfZb0Y6X/phX3Mm0uMwodc80BGBo2g2+5Qa6b3iWBTr24aPcl2WkMWS47/AoLOzAbX2m6YUDJdiQQy8Fc5k7KnynVUkLlhr+QWyLZSSRcGt7lgpZIxHGsZJ08wl+L6Ws2rmSIow9ix5yTD5TTnZDSryfbYLt3a9fLIiIlLUe09j1jJkey5nz/DQq4ViHMVKC5/zeasHPTf8YSwUi1MJ11HbuTUYQrumO4jVflatFhP2yEmcZoeKFPELBAYUeXcRd0+wvKUb8kHRrz5pYrEdeIR6BFmxaZCBmoWxuSSzScFqvzaBy2YVOJhnfHOzlElJ2+r5e1epviiKvXNaEPnJDCnTdAPkppTGHPmHDc2dJt6EioeXY/JE9+2iUwBz5LlqmyiYw0gcTkptait8nAZbQbmWfnOppYQNrNFcOCewTodwFUYqjuDVILIWbf+MI+WP5f31Zr8VXFAyiXGVukmKTYnNDDwtbVJTP37RMyKAoDi4BMfS+KldcJHXfH8HxKQDF43PY7BCu2SQ==","layer_level":2},{"id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Protocol Library","description":"protocol-library","prompt":"Create comprehensive content for the Protocol Library that defines the blockchain's operational framework. Document the operations.hpp implementation containing all transaction operation types including account operations, asset operations, content operations, and governance operations. Explain the transaction.hpp structure for transaction validation, signature verification, and operation serialization. Detail the authority.hpp implementation for authority requirement calculation, multi-signature validation, and permission checking. Cover the block.hpp and block_header.hpp structures for block validation and consensus mechanisms. Document the chain_operations.hpp for blockchain-specific operations and their evaluation logic. Include the types.hpp definitions for blockchain data types and their serialization formats. Provide examples of operation creation, transaction building, authority verification, and block validation processes. Address the relationship between protocol definitions and chain library implementations.","parent_id":"139b0217-0190-433f-b41d-60fa08c9ee5f","order":1,"progress_status":"completed","dependent_files":"libraries/protocol/include/graphene/protocol/operations.hpp,libraries/protocol/operations.cpp,libraries/protocol/include/graphene/protocol/transaction.hpp,libraries/protocol/transaction.cpp,libraries/protocol/include/graphene/protocol/authority.hpp,libraries/protocol/authority.cpp,libraries/protocol/include/graphene/protocol/types.hpp,libraries/protocol/include/graphene/protocol/block.hpp,libraries/protocol/include/graphene/protocol/block_header.hpp,libraries/protocol/include/graphene/protocol/chain_operations.hpp,libraries/protocol/chain_operations.cpp","gmt_create":"2026-03-03T07:29:24+04:00","gmt_modified":"2026-03-03T07:57:42+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"d54afe72-4975-48c8-b825-ab792ce92a46","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Build Helper Tools","description":"build-helpers","prompt":"Develop detailed documentation for VIZ CPP Node build helper tools. Document the cat-parts utility for combining source code fragments, cat_parts.py for automated code assembly, and check_reflect.py for reflection validation. Explain the newplugin.py template system for creating custom plugins with proper file structure and boilerplate code. Cover pretty_schema.py for generating formatted schema representations and schema_test.cpp for validating database schemas. Include practical examples of using each tool in the development workflow, command-line options, input/output formats, and integration with the main build process. Address common usage patterns, troubleshooting tool-specific issues, and best practices for maintaining code organization through these utilities.","parent_id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","order":1,"progress_status":"completed","dependent_files":"install-deps-linux.sh,build-linux.sh,programs/build_helpers/cat-parts.cpp,programs/build_helpers/cat_parts.py,programs/build_helpers/check_reflect.py,programs/build_helpers/newplugin.py,programs/build_helpers/pretty_schema.py","gmt_create":"2026-03-03T07:29:30+04:00","gmt_modified":"2026-04-21T16:26:14+04:00","raw_data":"WikiEncrypted:zhwwHcEGfkuzROuyPGwGZAaP8JUBGCZQRchVoluTSwBG0F8TNTcA7NnVPcgD17bZ8tkkmKBmN15pV9qTETgIyoqoLH7M7WPfGtqxxySlrJyCUM6SfuR45v/C5WGemH+0hRlRLA0flEYfomMObFQbk5Yk+NHzIvo4JgCZLW5F0XN7DLQWn8LEfXvdWhYrpycEb2+gRUNqVLrMR4Lj9NZBJQro9Xc43t1OtdTxlnZzJErkb/KowYnn4nnYNDGxG9hIQyrHFcPMamMN8EZgZ0hCu2+dpSJriVuNhJjimoclo6Cw3pyOQAVdprPNkw2xEpbHhZQ+rmE3VZlXZ+MLkJTN/XZYKwiYTp2uKbNEPmGsQQ2LomqWACjNw8wv4WqiN7uh6Y3SVosPebV0G/ZcCESHnBvGtowAOOZYPRnPfpDmnecYGNdDURlmBbiLalrynqnRqKVeASYvVRhaPsQynz2xKpKXOOioDWeEmlzNcUP2D0WeKQdlTbEeVPO5aSdRnCUv+tX5viqMRFV/aZaStqyEbDFofDhf1JqBalT0wv/VNgNo8kTnAZFGDOZ7xz67uGXDGXqJn3n/4th3Mdab/0WAx8kakRGAUxfaS2vsxGHC+7QCunw2c0sltmNXkWX3iBbDI+DygDoA0gCMz/aHmapgS90n237xk3kir9qU9TcRxzWK40xmo7Sw6FKr9WMGDkhPOPoAQ1UyBvkEfiBD4MDJr2rlS6heSMFoZIx8/ywk7nCxEx+Q0GV5xO5lyoVZ7IcaG1qcjeCuXta5O4pp6D0sORk2+3lC3RyHKqAFvJLXJK4Rn0Qj8NxjSLxuPnrMMP/z4i53otH5KRVpmj7PDloLwxm7Se1nx7kDpNs35zcbLlEuIZtMbmAu66DwTp97t86Sb5wAJ/lIQ0mzUqFRHIcTAzQpuMVW8o9h0q6bHxq7EYSG4jTchqULmB+GCKY2k6PKr/7T3DauDBVkxIdONfkS9O9XGHXv2A5vsZxP7uwVDL3u08b+BhwDTNCzmwIX8o+rk/rdpGCvVgOqtfkcl6WaPW/IFKnAEIY8MylFSyU9s4XJ3iBGigwuXdomwt1cuBE7EUfi79p3HEL+wLR+AF4vVVMqJYqppha/U+KsYo4B6U+knbKdteYsN6s3vOK46UeNKZrWoIhf/ef7++6us2IsTJKuSlWUvT+AR/qvbLI8mYQWqGfA+Nb8+207lwvZfwI3+518nB+L/C0pagznPrYzjcKWpHAlmPmTmsqqLvjD4BjCKxbG2sCKnimWkVT0LB+hRvV7lMfAk7Gy5kdQSK26mxsceYr0fxKYmag8dKFwHkHvcghZVIR3IHq+9Ir+X7pUunTFXP9j1L31CRnGHLXOnqTcTVwVKYOpqRDDh+e1bBBdP0cL4At2JEOY/gLxBdDUSv45+taH1UJLnCwRwugCz4BpDWxszi1q2n5a6iwGwjrXT/hhAsY30aXJtxMwTMW0boSb16P/oT6iBHls+6cpem4KKYzDAMclvkD0PwTQVZ4jz1Mmkf/GzDs+VNNW7Kwsh5/pZ/6U0avqrOxjIwkpN6gsFPWWmd3ts2BukG8AQhcQyIbMqDtnfDPqrJoDT55qBSSxNiwZeT3Qd1vdF1BhC6WlQ0ypbxK+GA8206/RTjYD8gy0BauM0Subh2zzN2+NEXWDVhzLdt1B3E2JLbjSdF9G4rwdQk/c+yLBVhWZCKC1RV8Mjd8BCkDs5Codlql20owYs9eT2pD3qaecCu7DPM57z6cARZWlp9xRxrJdEeR6HyDqfUnwAI9CyvBUijzQaiJYOtmmYqFvKhFzcD+y41yyaKxk8e3R9kX6T35ElNuhNiIyEFCtrJZTVFRwpwAe","layer_level":2},{"id":"afe12b98-5441-4e08-9bd0-d8c604b2dbaa","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Code Coverage Analysis","description":"code-coverage-analysis","prompt":"Develop detailed code coverage analysis documentation for VIZ CPP Node testing framework. Document the complete lcov integration workflow including installation prerequisites (brew install lcov), debug build configuration with ENABLE_COVERAGE_TESTING flag, and the multi-step coverage capture process. Explain each stage of the coverage workflow: initial capture with base.info, test execution with chain_test, secondary capture with test.info, tracefile combination with total.info, filtering of test directories with interesting.info, and HTML report generation with genhtml. Cover the cmake configuration options for enabling coverage testing in Debug builds. Document the lcov command-line options including --capture, --initial, --directory traversal, --output-file specification, --no-external filtering, --add-tracefile combination, and --remove-pattern filtering. Include practical examples of generating coverage reports, interpreting coverage metrics, and integrating coverage analysis into CI/CD pipelines. Address common coverage analysis scenarios and troubleshooting coverage collection issues.","parent_id":"cbaaeab2-9ed7-42e0-888e-58f1dff3747b","order":1,"progress_status":"completed","dependent_files":"documentation/testing.md","gmt_create":"2026-03-03T07:29:31+04:00","gmt_modified":"2026-03-03T07:58:43+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"514b44cf-1ee9-477f-afb6-86b4f8c17ee2","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Transaction Debugging Tools","description":"transaction-debugging-tools","prompt":"Develop detailed documentation for transaction debugging utilities in VIZ CPP Node. Document the sign_transaction tool for debugging transaction signing issues, including command-line usage, input/output formats, and common error scenarios. Explain the sign_digest utility for debugging cryptographic operations and signature verification processes. Cover the JavaScript operation serializer for converting between JSON and binary operation formats. Include practical examples of debugging transaction validation failures, signature verification problems, and serialization issues. Document command-line options, input parameter formats, and output interpretation. Address common debugging scenarios such as transaction construction errors, authority verification failures, and network transmission issues. Provide troubleshooting guides for typical transaction-related problems and their solutions.","parent_id":"614f1169-202f-4dd4-aaa5-360a10ad6bd8","order":1,"progress_status":"completed","dependent_files":"programs/util/sign_transaction.cpp,programs/util/sign_digest.cpp,programs/util/js_operation_serializer/main.cpp","gmt_create":"2026-03-03T07:29:38+04:00","gmt_modified":"2026-03-03T07:59:59+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"eb3e00ca-26ca-455a-8670-e309bca1ae7d","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Node Types and Configurations","description":"node-types-configurations","prompt":"Create detailed documentation for different VIZ node types and their specific configurations. Document full node setup with complete blockchain synchronization and API exposure. Cover witness node configuration including block production setup, key management, and witness-specific parameters. Explain seed node configuration for network bootstrap and peer discovery. Document specialized configurations for testnet nodes, debug nodes, and MongoDB-integrated nodes. Include configuration file templates, parameter explanations, and operational differences between node types. Address performance tuning specific to each node type, resource allocation recommendations, and monitoring requirements. Provide comparison matrices showing feature availability and resource requirements across different node configurations.","parent_id":"4b6cb85a-4799-425e-9f98-9cd149c004ec","order":1,"progress_status":"completed","dependent_files":"share/vizd/config/config.ini,share/vizd/config/config_witness.ini,share/vizd/vizd.sh,share/vizd/config/config_testnet.ini,share/vizd/config/config_mongo.ini,share/vizd/config/config_debug.ini","gmt_create":"2026-03-03T07:29:47+04:00","gmt_modified":"2026-04-21T15:32:31+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"6ab3219a-8693-407a-b403-239fa721b5a2","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Transaction Processing","description":"transaction-processing","prompt":"Create comprehensive content for Transaction Processing covering transaction structure, validation, and execution. Document the transaction.hpp implementation including transaction body structure, operation arrays, and metadata fields. Explain transaction validation rules including expiration checking, operation validation, and signature verification. Detail the sign_state.hpp functionality for multi-signature validation and authority checking. Cover transaction serialization formats and network transmission protocols. Include examples of transaction construction, signing workflows, and validation scenarios. Document the relationship between transactions and blocks, and transaction lifecycle management.","parent_id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","order":1,"progress_status":"completed","dependent_files":"libraries/protocol/include/graphene/protocol/transaction.hpp,libraries/protocol/transaction.cpp,libraries/protocol/include/graphene/protocol/sign_state.hpp,libraries/protocol/sign_state.cpp","gmt_create":"2026-03-03T07:29:54+04:00","gmt_modified":"2026-03-03T08:14:41+04:00","raw_data":"WikiEncrypted:pze/wTPA8hT9dADtWGlHVecGIju168riPHUw4TY8/AOhja4aeOzUlmfgdY/KTiokGkE0pwevgTXyU4//H92NNz2DvJDyylHUIIAgFa19IOR1rIcwnR+M5gYnWP+MfMyP9rJdHksUzaXlv9bu0L+nSwyEwxvRjUsxXSJzFBEFHqMkwXP+kKSmf2P5ac7NUAQiXV93Cqe+SJpTYieODHUA2aXvlp8c9CzuexfREJPFweCojE4iuz2MpwhzLDyHYWZ0+Oo13SyBMRm8TSYEiJJ1qEryT0ka1GRDrBNabqCzOK6uY1kcgMVU1jjPuzIozI89v9TgBpUe3ZUX2D/okuey3ZXKbQsrqjCWtXu4h/QZb8rYGL0z4J0n4KskmV2PKYbFtn4srZQu7EbpGVbjsDnBXaaURzTiBAn/5bNsIYm+cIxhaOEE0GSJWPZKvt9h0knjB7xU0AMvSOozrAlPI8UzxVBsdecMiFyAtTBrLnr25+mj3ZwaWngPIkUmE4QfDwFvVBktD5TvLYHH5fYgQ6FQEOqADFR/zowPRsBSgtdJ9Eb/rsHcEzSds3O4vfJPa1xBvv0oREt4BtnPPo7h1GW3BkQTBPCsCLRrdxXj7pn96rupr/KX+UZx+3o2WnK2p8s5xzHrxoZ9FQTtLMrP1QfdpEFyryeihlhvZrCXbyJ0kHE91R4y1JciNNxtI/Bjrh6mVBYmrKujPXiV5CFrRwoI2mH19Wti9DQS6W7gklFuAeemFd4jTCHLmR/CDXLjhJDffCUzHS8513myK6oVS5EH7w6YPaLheZxNpurE5ol03A8c/WxlMAXjnBxaf2ZeHj8uxa1EEM6YHmC9P5jSRuEuUUzsS2GRlFgZVAOID0om/E0ENC/0hYqwiXqX6y9n0EmcAgNlDJkUnWeNYJ0oupFu7tnzV85Ilx3SxDP4ea05c5oMYIfzSWji/XEPnZgnS/O9MC1xoLO1yNnHi+fYQCTlLY1nkz1d4gX6E4KMS0BEe2hiCUeZYF3Ve1o8UByfeYpm2dpWLCmdhlT93SxA9Tc+6H2PULFfOIr+EUDlgTwFIjJeGJARDD7Wv2Eq+POFmLHPM1FVpRzVCzAKsxnI7OCf4iC/EthL2PFmXmYvtkCvAvxwMgEwm493vGA2ADLGhz9VzIRDanfMsMnv783wuN+hvDAanYwmAbdNF/scEaa6o7YYzUlSQaAvJ69tNU51UvQmyiiKCXW4ScC0hZ79uAf0ch71hOj4u/b+KFez1SbDRZpKZRxeU0pm+ZGWNs896vkM3THcbIvEb2jp1FDd9tRhuoP5Pu6q9Dpx/wNYMV3VRE0XMIMwliEXXF/ZEjvZQSw6MJVwTNQ/T7UFKiwYPiWR7zu22AG747F3nrt1QQ12XPYHxupLZYDmpv313bFI77gWmFvQm4wKDhyr+8uPpOKb56paTtid6wBDN9/JGgmMGR7/oSz5cLuCQMv2AuLrUnmh9WKD4cydsTT0W5KNDaGRrHfnZiT88kkOALg/Qn0w8oNN2idlbOac//Mwluk7C0esxezBf92dkmhrQsXZ4LjPCkTjDkSGBbvMeU0JhEyVDtolDVSe8XLlbQRNkp31hO/lC1QhlvCzGrrmSgI32+fgI10SmRUU+tqWWL+USh7BGB/ohXpQ+pQkPB8krp6c+tkn","layer_level":3},{"id":"a8c1e706-27c9-4798-b664-a842a70058b4","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Object Model and Persistence","description":"object-model","prompt":"Create comprehensive content for the Object Model and Persistence system that defines the complete blockchain data structure. Document all object types including account_object, transaction_object, content_object, witness_object, committee_object, and their relationships. Explain the object schema definitions, field types, and validation rules in chain_object_types.hpp. Detail the object lifecycle management including creation, modification, deletion, and indexing strategies. Cover the multi-index container patterns used for efficient object lookup and querying. Document the object serialization, deserialization, and persistence mechanisms. Include examples of object creation, querying, and manipulation operations. Explain the relationship between different object types and how they interact within the blockchain state. Address object versioning, schema evolution, and backward compatibility considerations.","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","order":1,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/chain_objects.hpp,libraries/chain/include/graphene/chain/chain_object_types.hpp,libraries/chain/chain_objects.cpp","gmt_create":"2026-03-03T07:29:58+04:00","gmt_modified":"2026-03-03T08:15:23+04:00","raw_data":"WikiEncrypted:WrtF4UhzC84au2vkp01Wd8VhZuevsmYW0MiRsMATJ1XvzQRUZVUONQBd74I+5hpz5lJT7q5FmmBA+o8DqqOXNjGpt0nJ0BAMDy5P46t2LJR/9eNbkssWxJy2QJkEwyAnWrDBJjJ0RtuHhzMS9XMXa1dC84o0NwHImEkViQDS7Rpsr2tEFtNFHgqhdd5Zjk+OTwvCho2N8LT8e9ja8cNKp9BMFrmvVAq8aF9GWvThumhCK2w2w5CyEP2RMR4PW+tBxGQJFYDPJFgz9pTEzIVaFbKSiCUnWq0ESj25uR6wVSNMSVgq7zRG7y8IIsgrqnhJu+KgNUrZxmwpxoAcsJ0hKFwNo2lam+HL6ml2/2LVFk8Mzco7PC0p/zgxbfQrRFhP110QNXkeT0bYlTVVlMypAQ1Kxj3NBZ1KLmyLLs12q1EsOjSWSgxN9YXmkrXz8yLbFNZYT7qGDIBSXHVviTp2McCAVwIKGOXKskF71GBPcjqo9pXktSpgKxx7qaaBMhX83pvC95fYpq2cJc75oPgeq9KZXR//fZQakWRETvbbLxccAwa57y57RdnEaBfd9daY++/BbkT3oRJ2hqnpXXUBr2uUme/yb0WIntz6DyjzY8xSE7izPZxRsPcc8RJUWILAy7ZprMZASzMlwRn6/Ky/j23YT5a1e4zoyyoHebXifIGG6ZAse1LtKwo9TNNOQpHCamPfYntDKJg50qHWmO6/bi3qavsAFO/yitivdZkArtHqC398p+u7EFZ37JVRbVzlp3qeNVvPsjEgx8fT+bkd3/qwS7xa/muLbLdVBoTsgfnvYr2G3Is+K4mnBYPu/aNEpG4sm7qoBWE7KLYezbuMRJ9+1Fv1NpvrivJxTSjDVwbC5IA+oCD6Pro5lFg5VkenktCY0G79VFuE35+CL+R7esWyfgnM7V8YjaQTsnBr4DmI49qImBb8qqVwyWze9Zyo0WzDrFg59V/8kW+9HBbrh+JqtG2KpXW0GziW4YZTFW4dxYKV2I/LPpCoOMIgeP2YWhpZIV2nrw9FY4247hiAvnJCk2Q1xqRedjgoTM7IuMBq6LdX6BJU6JkzxAw8gnYuryYKtVkenMBIKVCzTQhreo+0OAPufetVLpssW3EcZU1Nn3pPyvncV+MvelP9nELnHgIKy1t6WMpRIx+q/1FhnSFI7ADmVelU+ntYSPmrDe6HoziMx5djs3WbbfCK+IaYGU8lay02M8Q5NC/UpELQYtdKRC/j738pt5904KOmXsVvbw8wRYjN0hqZut9fMN5dgepehlMduVsqXgzmRRVvgbk+K+O3/mGV3qj1swdiR9lt4skvBPrmh+jR2ACCL+MNoszJHNrglzKIBSUS029Nx5J+0ERK2muv4h27pkx2WHp1C44wXmad8a/qI84yR6y1KOFmGn0SIQ+wsUuTxHA37pOzS1mAJiYqLVgGMS+GNG/ENsAWGLkUVSYZohLq5i645j24mmoIj+BLI4PdQaPAXvBvMR4LdiMovmjf73UFLlA8t2TZ+TLEdIqQpAYHoqDj9ZMNLKTFLNkg/CbbVyL4uZpnscLgQGk1kyWR+sMsjy3nG/rQSGm5+rxc+wfeb+xLIM/ekGSKxF6TTOyDLC+fgVMNyQYE+2MRVg/lTKABDNjCFctgYnFwu39sxnx7gLLilxXTx98QGOM/Jp4L4U6eZYSLwI1pK6arSKqy9ls8TCZmRPFZJb3/RO+YK0K1S6R/aNh5Bn40Y5/rp5kYCDptiseq1xWjbUyVaGjKF5jsAkKQuiMnsaSAdYUEJ92RtZw9ZGhHskCV+3pzqkjd2Bsr8NK3ocd6GiMEHrapMKB+oJpQLFVtXgeck1Xl7v9nSipIUd9sDgGZuJ2u8IOf8JYlKi/NzTcJMCgtBM12aT5PPaM=","layer_level":3},{"id":"b47bb56b-832d-4e6d-8045-bdbee53d703a","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Peer Connection Management","description":"peer-connection","prompt":"Create comprehensive content for Peer Connection Management that handles individual peer communication and connection lifecycle. Document the peer_connection.hpp implementation for managing bidirectional peer communication channels, connection state tracking, and message routing. Explain peer connection establishment protocols, authentication mechanisms, and handshake procedures. Cover connection lifecycle management including connection initiation, maintenance, graceful disconnection, and error recovery. Detail peer state tracking, connection quality metrics, and peer reputation systems. Document message queuing, priority handling, and connection multiplexing. Include examples of peer connection setup, message exchange patterns, and connection monitoring. Address connection pooling strategies, timeout handling, and connection reuse optimization. Provide guidance on peer selection algorithms, connection balancing, and fault tolerance mechanisms.","parent_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","order":1,"progress_status":"completed","dependent_files":"libraries/network/include/graphene/network/peer_connection.hpp,libraries/network/node.cpp,libraries/network/peer_connection.cpp,plugins/p2p/p2p_plugin.cpp,libraries/network/node.hpp","gmt_create":"2026-03-03T07:30:03+04:00","gmt_modified":"2026-04-30T13:09:18.8212712+04:00","raw_data":"WikiEncrypted:VR5KHiCGBNdKELy8Scjin5u2xY+Dj5+IyYWoQiU3xOSz7PDUoqR7wxPwIUx3wbq0KT3hCoqZJUqiKiMi3flUl1+CUwwthoJnvOb5U7feLpBHGIw6yavTfsvlZuEq8CPGgQzM3Xf662cw4b3flUYzJTiIoIjl0H3Y3gs2z6KJzJ6gHhDIHNAy16VnuE2k3XoKpoZpnopSt9Pef97+RW3DlL+lnpHu2YAxaUPgpUpwOcfmOvHDj/FtC/6AfRpR8JdNEzMyLX5mVrsDxE9xlzBHjJp18lC8L+VVOAANM7tfdEwhuQnj1QvkD5xEw2shgPbllS1DDgh15pIbYMiiKItSDQ3cQOzE2hSDLEPCXPZzOQLOm/vW5wIW4swJyLKpRsRbz4vichH0HMTnNZ3yz86ex9/1obZps8G1dNZzxHHHVW37DPGJIWjSmtmO9l8dGKrRz+0joIm5WQ3ITrgtKdLStz/mnKQaaU23kH7OM1o1W3ZOftVLejytc8sDy4zj226tENgoGv+KThy2apmYugKxVPbHaw7vXM+Y/+iB+7kwu+3YwX6FD69IMK84aOBotgYF7zvZ9f20RKtsC4MV4+ooocMaZkMKGCOvR034TGOHVW9ORbuwc3T97XUF6wZnbiaJBNgjkqEJ5ewBXSFreRJTTSmuWpgmhecUbuOuNoBX4nXnq7wbYxZ++Ci2gE0fiT8fuHbcQhEdaRCnDqfcsDYgvQuAdMC5mWFYvSL52BId1R1VYNSLHrREPaEwfvg42DRdKDfCVdh9hicPnUCo0DINFIlYy5moydHXlwzgScgQROJQ13oRPuV2oLFzye9WELN+/mcjUBB00n6TcrglmwwUyILQz3H4lfQ27Rnzm7jlQqr0mIT5bGCso1l4IlDaK51gu0epdRu5BRS/YKSGfPYn2vUNkwzLu4MOqB6uKSJqlyqn2P32EkPCI+8zcyVYr+g5ztImlJpOR/yTsHaWPhZFRTOTSYeF5KmpraGzm59MNwK7acLPqGwuQ5ueZprYE8pMefEuM8y59jar3RfLNdO2FXcP8K14CBR5wYAFZgkQzMmtFIaON+0hwS/McWzrOK6p/3G1E53AZwyO6eifGvjf6fwGk9NSPgaysoQhCWViSaYFvmlWV9UV2d5qFx161BxBoJa09nY1MdRV6wQcBSTq2H6ACzYwL3gf51/b9KorTmbbL0UO/HL9IX4Db/nNryXi+3jXHJmvFFpM0xvx3d9x5CuTg4S3KdwrkLJsnk8MUPz/QTlKXLKSiFV52Yk1SCmjOOztsf1JR3ga/+ZEnu3DiH34Ti+KRjo1CoZEcxyrCpVyYb4gX+cmWI8J0pK2vd0zEB+gHWZsvQJRNLI7O7pBTOFk/1CDR1YHlG8LM0KzgI3OMb/SOP6qI6otoEBXdufCboW0Be8Jtp6E4nhgBYfkw9oOhQxKGxazwH+UgP67YqSljeB29Vf/ST6qi4LRxtrEkY3yGBo9Uumh/+nAXzZZ9GD8kMflc5GxwejVvTXifndc58gDyG+xFjR58YAf3rTmr7rjIdF3jAYigHbKTIUc4coCT5lcFA9R5TxBA2pp+M7gNw/vka3yTE49nqMnf02BgJ1e6nRmQRl2fSwTy3WdsdkAwVxg+ItOC+WXky/1o8uUnyqecdgdDcX1QBmz7OCTaal502VGyz0t0nYF7WWKhn/7GujBuNbjy/aRor/m1XBn4r0fn3h+eZO3WVKYuWKoVBU1Zc1B/noXieGpEdC/N/gQeOEmEXBslWIn1GT4rZxcP0n4RjEk08Vc2LFiT01O4L/ekpoxZ5RIq2Sa/jbXtyfh5ESscZyZrDrxvcSeJgYGhtn8u57NWPTVG0O08ue6HNetZLUZjdecVGpIU3omNWCWKkKU8bX5VN3ysSnr/pk0DLGxXWDx7P9LuQOIhMJu","layer_level":3},{"id":"1ec7edf8-46f7-4e71-ba54-bc1cc3533453","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Platform Configurations","description":"platform-configurations","prompt":"Create comprehensive platform-specific CMake configuration documentation for VIZ CPP Node. Document Windows configuration including MSVC and MinGW compiler settings, static linking requirements, Visual Studio project flags (/SAFESEH:NO), and TCL library integration. Detail macOS configuration with libc++ standard library, C++14 standard requirements, and Apple-specific compiler flags. Explain Linux configuration including GCC optimization flags (-O3/-O2), static linking options, pthread and rt library requirements, and OpenSSL detection. Include compiler version requirements (GCC 4.8+, Clang 3.3+) and platform-specific dependency resolution. Provide troubleshooting guidance for common platform-specific build issues, missing libraries, and compiler compatibility problems.","parent_id":"317287b2-3937-4876-97d0-a8c96007d95c","order":1,"progress_status":"completed","dependent_files":"CMakeLists.txt","gmt_create":"2026-03-03T07:30:07+04:00","gmt_modified":"2026-04-17T10:42:56+04:00","raw_data":"WikiEncrypted:Lagc5vDVDWJKf0nDI9TGSA5WFJDHl+XgRFW9Z8nrevG3TCAhYIcpEYU1rn2otzoK0HrEK0jMDM9tEMjTdqSFUT2s/zXxLUj1fG7NZ4iHEEDHSkF3MnlEpA0VfBICYmIEysUPG+skLqepPVWG1KHiaLKuhh5y8KMFGz8vf9czZdpBEri/pDAO0R5RG/XkYCd7kytb28lf7+fpAZojUubG1v/nxZl8JrpQybJss1uZ23nrdEWd86bqk3Cw9sbqiahAhqPJE6VBMtgpmRW7Twshw9gjmP2j80RvHuwzEq8HYraiFBGULt2nREETqCLUSJVay9QdNPFHSmcgv1nlO749K6IOf8jvyJEL5BAqr1Y/QKrIDm7hHZauFcS1WEVy7L6mrCOWY7KUpONwVu+vOaFxTTsdNku0ALldwRVrXmGgQ/wjDv0Bn/PGUcOxgW7n2pG//giNon6YErHA7GEiUYieQFI90ZJ+5oD95pxRWJcMumNRJE+uxkCuFyklJfLs2SAfZuqM2ULw0OH1mNLsaUNTcbBDlArHPV78IzpvWoUgk4tcfConWKCNRv838iec6Z8u2rUUNZR0CDOL7K6RFjybi8Ma6P+3RIGnRE7ZozR893j6kSU/yrhYWxR0m1IewVwhBVq6o95yWyHoMTZu3E3nJeehkNDWLnoi+MOrsYflA/1XiMBKZS60IVPM47jZJkvb9DM+ZoVRcD9lgI7md5J8qRvODiXZARh/hLuyNZCKBLfQkl5GiF7F9F9fOuYavuL/Ejp+R77uxMFnjDgyeFETjfVChxb397xHwJApBA0Rw1Ibcn9X3cWsbgXJgKQixvjfXq3MwLMPYtRFI+o/I8iIS0k8JB8dHihnT9FoOMd4P3CK0HT4KLuS10bC8OG5b78Gv+zA/MwZVOoPkiNyyMKpCJki/1BU+0AW5ZhlyH6W34eNJy+hDKPYFHqxIJNyaNXlKWfY8IA6hQcS4TibT3iohEZixNXcfay5LNd6PBf30w6/zIkVmo1aWX20WM/kmC8aYo8cEpVEfHgwe6sEB8P0J8oQiM/eSBlEyidmCkzEae9bpUkL5zWjdfpbwvOZ+aHxxF+bbjH8bbYCU+Kuef+HNviE0s25BLZqPyeXbJmQvoTAzMRslswCGNNw/yAp6FHylTiRUi59aLjibzqFd3lfEy7fchb+nfgdFtMNW1e8mfi1F4TmMYR5xG72yDllegJCfzjpSCtlXellUaG8LIOrjJ/g5J5s9C+u9Z7AZ14gyUMWwAfpnldXvgup46MMQzWFnkdgtoAXdgnyWIzZPze6reTtZt6vzuK/H0+2JUGiQubKLCmJJtHAOAtMPeERunxT5S0bPLAaGC+dVPQ3Bus9B7CXAUApDxXCj6bbBPOmIvu1mcQRQFSywDqN14NSSnxCb4QM997FzIH+KO0Q6DfXMU6Gt+UcZTyL4slDGwH0v7zW/O2uJW/StZhZZNyCtE3eaOvA4KSKrn5WtUgUDnFScw==","layer_level":3},{"id":"7b9a46a1-f3d0-4034-a4c5-aec28be05f18","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Testnet Dockerfile","description":"testnet-dockerfile","prompt":"Create comprehensive documentation for the testnet Dockerfile variant designed for VIZ test network deployment. Explain the differences from the production configuration, focusing on testnet-specific CMake options and configuration files. Document the testnet bootstrap process, seed node configuration, and testnet-specific volume mounts. Cover the automated testnet snapshot loading, genesis block configuration, and testnet RPC endpoint setup. Include practical examples of running testnet containers, connecting to testnet networks, and testing blockchain functionality. Address testnet-specific security considerations, resource allocation, and monitoring approaches. Provide troubleshooting guidance for common testnet connectivity and synchronization issues.","parent_id":"3789801b-aaa1-4c34-a35a-c5b48338520a","order":1,"progress_status":"completed","dependent_files":"share/vizd/docker/Dockerfile-testnet","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:17:07+04:00","raw_data":"WikiEncrypted:1I+g3iB8Klr7mBC/4DeKMdGxNIcsjOObmJBHKNrFsun7NjeRSkAIkIjgPvn0Ch7mDdGDRfXoAe1bUR41yzysNp6O11x/lcS8a3Ql+s83U89N3Kn9QIK305NUVjepHpGRwCLfuUs6r49/Rmv3IvLnxRoEHmOVl0wtpa/lG3+y1Ne14xPHgmgdT5ReiUN/nXcbHA14+lOVT3SEcda9cKCVHqeI43P0FUZpZcLbL0Hb/8Q3ae4Zsguwd12Hpw95G68CsyAwN1Rm2xgHH7yDcTt5DjjL3XZq/H+YzSdgfFP+TLMsJAx4pY6R4M+6buO8lD5l3FFIQ3FqW/m8g7uM0jnK/8tbFSNYlPMJC1obXfxp0pVqUiYPm85iShtC8nhm/34/TVMpQqFGygDjckU7XuEi07HaP3hTRatT76OMoEXTDLpY232KsbsQjVW68uRk1yj+GQiMyb7g2BDZpxuY+P8Iu3o6DBn3sBUhF43BBVAJwKVoj56yKvSppF2SZfpScCkboybaiHmGwNGUoslVe/CgR9j0pv+uIbjmL2k+XefbYPqCW/DAFQD9YaPMLUX0rkDAGbiawu/EaTAXnYzCzevAwaBvaMimlEHB1EfzGhFbYsFJmwBz9wbcxtE9pPvkbFrzA9Rpz/eIVq7Ufg4lohlimnyu8OW9bP3a5hJML5Ul5tqAr1HfMAC9M5HnAzXqCnYYahVRIMMLjxVechrrwwfEDMHOFNxJmyWQ80unmDR5Dq53k9DL5jP6H6CB5r0z7U4mIMLp3E9yNZGY/1b2/XvcZJ/BWlpLyvfp1XVv+gQcNq7hAsIpyAq4XzBR3Yqlmgdfk2I3qg9AblCrpWwv9mZpvEFcEj8uTq7viBsiX4JurXfPsV79aRzkw0TExgPJV3ZXzglvZ1a49zfz1EcSqU42n68/5+COjjdk6vDvU95/6EoxQBb9zl7eOqluTjQ1R3vDbRKM6OJhrdgMS/nJD3qPjJPcnfGJxGmu5HjqHZm1rk/pqOg6NTm4jag3v1S9HO59r1uPwJXYh3p7tbUOlMYw/chuuNkmcsy6Uah+OKS/uLF9mgqmEey4XnX9U3GJhKy6dHYCUmi8IE4zgP2tWRR1dOX+pgluqLxW7Ub9RyuZ0KgsBEOBmJh5oNrmF602sSudrZcDN22O6PWNwKzukWJrg20wj+ZlZ5j1Hi4AoXBe06kKqLIPY2C8J7Sbra0PDHw14W9H90rpWsI/zOkUKdbcmrAM8x3wt1dBbFIZo7d7SDSMHqPo+MILMlP/cLh4e5zuOQolQhEsJkYT7fnqc3jAoOVSPoMpONhu1RcYpjX5zrVMMd/oJU/H4EVO421T6VMr5aYLgp7gVk6SvcmFXd5Q9NAIi5xge4XsGY8op5iz0Tr4lRrAB+GdneB9fiIOYJEk3L28A/kaWibKkbNe6YOBKCW86vLNQw9kNl0+1NwgRmpuEzxCvt5l7u67gfqHymyp","layer_level":3},{"id":"180359d7-c289-42ff-9805-28e742c9f10d","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Reflection Validation Tools","description":"reflection-validation","prompt":"Develop detailed documentation for the VIZ CPP Node reflection validation tool (check_reflect.py). Explain how this tool validates reflection metadata for blockchain objects and operations, ensuring proper serialization and deserialization capabilities. Document the reflection checking algorithms, validation rules, and error reporting mechanisms. Include practical examples of running reflection checks during development, interpreting validation results, and fixing reflection-related issues. Cover integration with the build system, automated validation workflows, and continuous integration scenarios. Address common reflection errors, debugging techniques, and best practices for maintaining reflection consistency across the codebase.","parent_id":"d54afe72-4975-48c8-b825-ab792ce92a46","order":1,"progress_status":"completed","dependent_files":"programs/build_helpers/check_reflect.py","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:17:31+04:00","raw_data":"WikiEncrypted:bzyetvPZRDtDTnozObvREtrhIrVNlzR9GjOxDARd8qaPKEK0AkNVEuyEWFfIrzjBn8DY6shjGjbqn7TVEpFIluO//1stc1a9TeIVJ9x9W8GnhxuSgajiCEqcxRq/kcWqzRcNq+G5PBS2Vke+Q50oFUmdFPFUbshtige+XHhAM+vr1VbcBaNCvAUnkMSy0cxMpdfxTVp6Lt2BW7voUFp5V5v4wm+eGDYDNJaR/hahDs2WNVGZLd5R0rgYfezTRSEExKq7Q6et6AXVy+pmVgjjZ8ZlGuiO0PQUrn8+34AQSx6pG1b2AZWeaLkhKX7nsekKrRfQhE8xHLo44PBWGtCeP3R+0FPsvW2qbTMUKtpZzwUnfEGZKLte10pyWg39bdvFi7yVl68CABfWhuGT1/bcj615AJhU1WyQObutow+jFnlBjYThqH3JssNmHv1sqy/vwX0aD+YYIftEC94eF7GLoiFluSmUz/f0jk77xwnNPi1B5b3Xp8OarYxWlENi1n//v1POhwuOiylv5EJC8A+vt+k3lyM/E/fLYsZsbN3dxZK5D3ysSEpx5bybq+EOSQCTsMFIqUpGc0keJMdoUq8tLhA8T0ihnlvY3IHyE0Ekojgbf42PCqR5UWjQ2UMuT4qse91Ss00aLjiozuB5ncWKd411zVc3w+V/RuIEOgA7CrShgVwyKQqJY79EvjliATv5Kh4krN0OFZSA5l2BSXKmie4wpSrsKPK0pmDxFlfeuKBCurgkt6vPrUYKtmLy4etO/X9W2BMiMkZQXJSTaIt0rCK7dCPCmeE975IT32alaUaDIlZnBEjvpBqkVFN/Pb046CjE33AdgDHbXQg+T8aNRvOpYZMal3BCnw7IzbMMlG3rawYhXmnXggCxRFexufLtLWKHkxRARivsT7EHcredBAeoSt26ETGMyGZ/Pop47RzOgk0kKCaT3BCAm/I7wZlU+XgnStRjYuMf0wJKf8P8SChYRpK/cUWIlepMtHy6/8E5fkFgrdv5SpyhUoF/wqEHskj5jLC0ZmIcw2l5LrYcd70U2s49AdI4uIuiH4ItRGiqN98cImdA2ryU2izjj9Xde5f1vqBBQpLowqdBNH35QXJsxqIb1RE4+bZ2ySAj7LtV4SPUMffW08rQSlhFImRB9BnWYdCK0Utm+7t/BahAZTEI7L+yFhkq+jj+fdCJzkhzturXGB+OWE6FomYdiD8MYo6LPDyGpfuE8zHe5JoKPFzvp3pWPwxYn5UpnvRVwoOVXviLY2CQOMVLCpxkjXC0BQlrZ7377iK5HtByix3XosgFTKsVppHbl0RdE5iWZVm5lrHwDWctVdM1iB/+tsRrwUpXMUGWYeLvUSVx9b7cJdT87st9oQ6rAlcRyjeTqam5QFb/B8Vukl73nWFLQf4UJTFQyjEGTfvF306mXzZLoRkaeMZN1irqzHCHuS7d9c0=","layer_level":3},{"id":"6b359be4-c5e6-4db1-ad43-abf632c4e050","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Snapshot Plugin System","description":"snapshot-plugin","parent_id":"ad096386-ea33-44d1-b9f2-261f11fb24d5","order":1,"progress_status":"completed","dependent_files":"plugins/snapshot/plugin.cpp,plugins/chain/plugin.cpp,documentation/snapshot-plugin.md,plugins/p2p/p2p_plugin.cpp,libraries/chain/database.cpp,share/vizd/config/config.ini,share/vizd/config/config_witness.ini,libraries/chain/fork_database.cpp,plugins/snapshot/include/graphene/plugins/snapshot/plugin.hpp,plugins/snapshot/CMakeLists.txt,plugins/snapshot/plugin.hpp,libraries/chain/dlt_block_log.cpp,libraries/chain/include/graphene/chain/dlt_block_log.hpp","gmt_create":"2026-04-13T15:59:14+04:00","gmt_modified":"2026-04-30T13:09:00.7660159+04:00","raw_data":"WikiEncrypted:aMBX4tyQy9CspId6SOjUsPjDbWaE0XwjiYFfJC+6JmpoDChB3ZowO5dYf5bnEbW/owWEY1csO33LOVGSLpEpLAsElEIuL6651OxIlbI6Jx4VJMJygyPsMEb3u2618Hv1nZRF8bM+TfiU1NGhj29Hy5By84gyGPEtmnom6gs62R7BlGX/O3S/Amvl/cCWu49KNtB/LRTrl3hRCySiJMTOgbB6kvhyWuhovDff8CXD12Li/yJPDsjaADY4REhlykVtmyLSUnHzKSo17aeqeAVjAjbYuyReEFLWIjy77IO02dKYT4gasIrxJYXx+MZ5UXisi47XRTsja5REt+XzLuMLTwOomLSe4m6RP24YJSWgzalMhDUC1/aeiACHRsAKaO9bSOLlwcAMBVQh6CsC8RUSC/r3yvMLfGZfNglrtQCsmR+EESeI/eacqO4UGUsfdW2x2+BuT4AgLGOLZtsNbx9oIhJv2bjL+0mYbdUqhSPYU7grkHE8Am3tlTggc5QGhANkSYk4y+E2X7ii5BLnTPa5uAuFedqPreKFE/X99oDM/Tgthjgb9ik6NUy6rlfjuJY5sqJp/GZydiK6kiCiH31g/L6cCwlXpbnGfoMOBVuQhSwBErR29TYQIccdjyohnWflxPYlTHo3Zr97zt8x9WcbL7lf+54/lqEt2qWimY3IVTMDqqrJnaDEbKurS87gvhwbKjINIVVG81/oFZlQqcY1SxlQ5DfY2i+LN4Lr3xLdiXdXVAGUcJJng5SJz05tWIPZ/fvvlqPwS9Pwul340LmEp8BiRBRdIjU5A2E3q03m6e5fYuihH/OFGUPbqC27rVgE","layer_level":1},{"id":"6f970632-4b1c-4a78-a129-89269ed82d82","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"DLT Rolling Block Log","description":"dlt-block-log","parent_id":"eb3415ba-4213-4e37-931c-49f45d7ebe37","order":1,"progress_status":"completed","dependent_files":"libraries/chain/dlt_block_log.cpp,libraries/chain/dlt_block_log.hpp,libraries/chain/include/graphene/chain/dlt_block_log.hpp,libraries/chain/database.cpp,libraries/chain/include/graphene/chain/database.hpp,plugins/p2p/p2p_plugin.cpp,libraries/network/node.cpp,libraries/chain/fork_database.cpp","gmt_create":"2026-04-13T16:01:33+04:00","gmt_modified":"2026-04-30T11:11:44.2224692+04:00","raw_data":"WikiEncrypted:s2jjuqSQeKwD1hNHHJ9DR2MPb35AgwzTacHpcPL0OtJB0zaRNLYJ+orR6pmHUkGhks1yVrOPHhwOHdr0evYd0xEi282qASpCMeLKGxgeabO4wWJCefkdwW+00NAyLKPpF5TRyYZ+UwGTHAheIXwbFWqT9C+s+LA3IYd0iPIRaYCeY+tvrYiaAMfMpOXQ6mueHbBChXQUtf9afyep5nwma8mCls9B3VEMXM69LZTRrQ2Uz7Xb6bG9yUgk7Vp4e1w72vxSN+A+N2UdaEXPf1w0sg+V1f+hOJTnNjjhoZMwd2BthVt37JoE6cHYEPh7yhI80vRAy1+br9nfuqCiwsW0vyiwpiVl2LJnfUg+8zs2OpgRsl56vcP7iG1U8SLUORFlxsm/MhBRLnLYQkd1ROnZmcfk28geae5dxHFWL9rwGL4rNBxQP/MClDScksxK0CHNAKGDrzaT9LKv9Ikn818QkXDkhJDogFubViQQh+KldJvpMG5JM2rC0wtXKLx9113ln64pkHDL3q+RPVC2pW+cEFna8mtVtICAFFr3pyZqdr42u2f83GGklmkIBIK2vcRdH6WOC82MYiRl7wioDnJOmQ==","layer_level":4},{"id":"d93a647d-d325-4dd2-96a5-703494dd5d12","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Emergency Consensus System","description":"emergency-consensus-system","parent_id":"7e383bdb-11dd-48d6-bd47-4d350e2df438","order":1,"progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/protocol/include/graphene/protocol/config.hpp,plugins/witness/witness.cpp,libraries/chain/fork_database.cpp,plugins/chain/plugin.cpp,share/vizd/config/config_debug.ini,share/vizd/config/config_mongo.ini,share/vizd/config/config_stock_exchange.ini,share/vizd/config/config_testnet.ini,share/vizd/config/config_witness.ini,libraries/chain/include/graphene/chain/database.hpp,libraries/protocol/include/graphene/protocol/config_testnet.hpp,libraries/network/node.cpp","gmt_create":"2026-04-20T06:57:07+04:00","gmt_modified":"2026-04-30T12:35:27.5354397+04:00","raw_data":"WikiEncrypted:yCC4O4QQiwfuc1LsaLvGZSv7iyGIp30QuTXuQp4YbjBPkP2yi2DSamCQ0erkARNQdFUVyIGCSLZc9P70aEvXU2XDlPi9dn8dxZ/3E7hfD7l8VSySXrg3ONRMw5gixEiS2Is5tNiGzsUuDhqPSmzvhN5QQPsvkDBdKT78rnUi7cRfVOHAqbx5rGtRz+Mx1t+4rzvcqmk5ZcBktnKa/RII3dRG86YHy2qgDjLm1p8D6C2P3NuzpmpRUMJ5H8vuTY9822u/fcASmS6VGPcMcclNEp6PLsBsUMJGY1nTCF9CbtK1WAa0JGnIkEWt1DWsEYTRVmwxexNfR8KVp3Lvxveagio8D6uyPikogm3VJy2SOG8TB0eQkQPMJKiAIYNrfPMq7NbipuOlDDgYCspRIWjmzesfjR42Br7Pr5KnkNlJx0TQRoFGDR8Lz187YJ2kGzaJ3stBnxfooluXtnqX/svl8qYnWPqXUp69rN3ec4uIpt3nrZdnr6osSr6xrzlMII2LuB0BhjpWSymNEnxAReJ/rrS/JWolGuor1Jb+vHER3rwo0ACrHdId5i2a7XZ/4dAI1kMUardPM/7wC0qUz4jdtQzxlk9xNmQvNkNTTyrpUPZVpSzfPFJX9PIqUSNcJoTcSgkF1Q62OnE4ckqJ2mzbbTT5gHa8NVimBS8y03MhL7HekBJanMWwV2t464ROQjc2diWqA5t0RErX7DirXWTPjdj8IIzvgdOQzKaiHe7BLj+Gw4E+HupBBOG5ylZzzJSGslmzNLEUiIRvmWfRcKzyx2N+GWcooVvgsRmT3Ffz0PI5HB2kr3i3Jv+qK7nTdM/Cup17wzrdYDIPXKK8G3/+f9VWHM5lVE87UM/oQyTB9NfANthOx5aHNTYIOc6a6bvfKZdR/+01FMh7xTEzzmQETg==","layer_level":1},{"id":"8aeac580-587b-43f3-9292-e62e4d3781f7","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Architecture Overview","description":"architecture","prompt":"Create architectural documentation for the VIZ CPP Node system. Describe the high-level design showing the relationship between the main vizd process, core libraries (chain, protocol, network, wallet), plugin system, and external dependencies. Document the modular plugin-based architecture that allows for flexible feature addition and removal. Explain component interactions including data flow from JSON-RPC requests through plugins to database operations, and the observer pattern used for event-driven architecture. Include system boundaries showing how the node communicates with peers, handles API requests, and manages persistent state. Document technical decisions like the choice of C++ for performance, Boost.Signals2 for event handling, and the separation of concerns between different library layers. Address cross-cutting concerns like security, monitoring, and performance optimization. Provide system context diagrams and component breakdowns showing how all parts work together to form a complete blockchain node.","order":2,"progress_status":"completed","dependent_files":"programs/vizd/main.cpp,plugins/chain/include/graphene/plugins/chain/plugin.hpp,libraries/chain/include/graphene/chain/database.hpp,libraries/protocol/include/graphene/protocol/operations.hpp","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:32:30+04:00","raw_data":"WikiEncrypted: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"},{"id":"139b0217-0190-433f-b41d-60fa08c9ee5f","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Core Libraries","description":"core-libraries","prompt":"Create comprehensive content for the core libraries section. Explain the four fundamental library layers: chain library for blockchain state management and validation, protocol library for operation definitions and transaction processing, network library for peer-to-peer communication, and wallet library for transaction signing and key management. Document the relationships between these libraries and how they work together to provide complete blockchain functionality. Include architectural patterns used in each library, such as the observer pattern for event handling and factory patterns for object creation. Explain the separation of concerns between libraries and how they maintain loose coupling while enabling tight integration. Address the design decisions behind choosing C++ for performance and the specific technologies used in each library layer.","parent_id":"8aeac580-587b-43f3-9292-e62e4d3781f7","order":2,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/database.hpp,libraries/protocol/include/graphene/protocol/operations.hpp,libraries/network/include/graphene/network/node.hpp,libraries/wallet/include/graphene/wallet/wallet.hpp","gmt_create":"2026-03-03T07:28:18+04:00","gmt_modified":"2026-03-03T07:46:01+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"614f1169-202f-4dd4-aaa5-360a10ad6bd8","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Debugging Tools","description":"debugging-tools","prompt":"Create comprehensive debugging tools documentation for VIZ CPP Node. Document the debug node plugin functionality including state inspection, transaction tracing, and blockchain state visualization. Cover transaction serialization utilities including sign_transaction and sign_digest tools for debugging transaction signing issues. Explain network debugging capabilities and peer connection monitoring. Document performance profiling tools and memory analysis utilities. Include practical examples of common debugging scenarios such as transaction validation failures, consensus issues, and network connectivity problems. Address log analysis techniques, error message interpretation, and systematic debugging approaches. Document integration with external debugging tools and IDE integration. Explain debugging workflows for different development phases from unit testing to production troubleshooting.","parent_id":"d896ebd6-7a89-4c1c-a16d-8acb2a61bb9c","order":2,"progress_status":"completed","dependent_files":"plugins/p2p/p2p_plugin.cpp,plugins/debug_node/,programs/util/sign_transaction.cpp,programs/util/sign_digest.cpp,programs/util/inflation_plot.py","gmt_create":"2026-03-03T07:28:48+04:00","gmt_modified":"2026-04-28T19:48:38.9116038+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"9d487fbd-32f6-4e37-acf4-e314d7f75019","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Docker Configuration","description":"docker-configuration","prompt":"Create comprehensive Docker configuration documentation for VIZ CPP Node containerized deployment. Document all available Docker images including production, testnet, low-memory, and MongoDB variants. Explain container environment variables, volume mounting strategies for persistent data storage, and network configuration. Cover Docker Compose configurations, container orchestration patterns, and multi-container deployment scenarios. Document image customization options, base image selection, and security considerations for containerized deployments. Include practical examples of common Docker deployment patterns such as standalone containers, cluster deployments, and development environments. Address container monitoring, logging configuration, and troubleshooting containerized node operations. Provide guidance on Docker registry usage, image versioning, and update procedures.","parent_id":"6ca6368d-78f2-436a-89d8-8e2fa9c3d925","order":2,"progress_status":"completed","dependent_files":"share/vizd/docker/Dockerfile-production,share/vizd/docker/Dockerfile-testnet,share/vizd/docker/Dockerfile-lowmem,share/vizd/docker/Dockerfile-mongo,share/vizd/vizd.sh","gmt_create":"2026-03-03T07:28:57+04:00","gmt_modified":"2026-04-17T17:31:31+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"3834864b-3db0-4e65-a23c-0fcd2d2759ec","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Cloud and Infrastructure","description":"cloud-infrastructure","prompt":"Create comprehensive cloud and infrastructure deployment documentation for VIZ CPP Node. Document cloud provider deployment strategies for AWS, Google Cloud, Azure, and other major platforms including instance selection, storage configuration, and networking setup. Cover infrastructure-as-code approaches using Terraform, CloudFormation, and other IaC tools. Explain load balancing configurations, auto-scaling policies, and high availability setups. Document CDN integration, SSL/TLS certificate management, and security group configurations. Include monitoring and alerting setup with cloud-native tools, log aggregation, and performance metrics collection. Address cost optimization strategies, reserved instances, and spot instance usage. Provide disaster recovery planning, backup strategies, and multi-region deployment patterns. Document migration procedures, blue-green deployments, and rolling upgrade strategies.","parent_id":"ea5be69a-46ea-4304-9d86-597c6f7fc254","order":2,"progress_status":"completed","dependent_files":"share/vizd/docker/,share/vizd/config/config.ini,documentation/building.md","gmt_create":"2026-03-03T07:29:04+04:00","gmt_modified":"2026-03-03T07:46:55+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"fb7f5918-6ff3-4fba-89b3-37eef3bf402f","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Custom Plugin Development","description":"custom-plugin-development","prompt":"Create comprehensive content for developing custom plugins from scratch. Document the plugin template system created by newplugin.py and how to generate boilerplate code for new plugins. Explain the plugin class structure, required methods, and interface implementation patterns. Include step-by-step tutorials for creating different types of plugins: API plugins, database plugins, and network plugins. Detail the plugin development workflow from template generation to deployment and testing. Document plugin configuration options, command-line parameter handling, and integration with the main application. Address plugin testing strategies, unit testing patterns, and integration testing approaches. Include practical examples of common plugin patterns and anti-patterns. Provide guidelines for plugin packaging, distribution, and version management. Document debugging techniques and common development issues.","parent_id":"64b99394-2424-4a81-ab2a-f41d1b9e973c","order":2,"progress_status":"completed","dependent_files":"programs/util/newplugin.py,plugins/chain/include/graphene/plugins/chain/plugin.hpp,plugins/chain/plugin.cpp","gmt_create":"2026-03-03T07:29:09+04:00","gmt_modified":"2026-03-03T08:00:21+04:00","raw_data":"WikiEncrypted:VrTOMK0P24YINQ3w81YokeSPoB5X9Zo/YG1ok/2a/9ryYLsqc1VARxnFCL4h0y80V3hdIQYc2nWoxIlKRpkvalc7lk5z+JlJ/h/qV4B6VpDkk+7D+iI3Z4iiJ+aP+fnnzj9U/9miQtCCYqlIsgWw+M9ctNXxnyPRWq6ZFHOXO8ReBDq830QA0iedXPe15iArFJ0CpRhfTLTTh9ALhUWxyPZRIZcV2qFU6q+pSX3xNlG5GtLnDcpg9SthmqU/6DxfF0a/GwjHlA0Qm5Xf+F+i4Bdbvby/N9F61bsAtP6CIYEEHZLpvZ7bUxWipx6BT4CGRgByiPmQ39OAPt4yyHIaljAbblr55afTAwZrUbm/zWPAE/pOToMuqYS73Q6aDy9pnGPyS7rCRF9EkKJFcUNHl5hDCnShN+8gSxwHBbKCQS/XR3jbcnxP31XcNG3EM+iT2BUM0KL5nyDV7vLfXXzCeX5cVikVB4Xvdd+GxWgmynOSqmhniOSVBSDu72nUH5kJrVNHoq4Xz1GhNe2cJFUTNkj4PTWcJssbQpwkn+9d9n7cvrMZO3AXrXCluG87yDMzBWORt2Ea0c8YkAiIjgVQlJJwhagve5TCBW7UzjSk6V41rVhKTEM7lCXWLy/yp7YpGIfakUS7Lo/hCeMT/WyMLWXmqKUvfEnAGQUp6mO3rRRUZBCbNkG48j5RTa3tfd0ak0RCGAgJNjiGRso9vwW3Jcy2MDZmKhQmBrhDQU+VTIrOkUnE6BBsC19VAj5nax9ACH+hFBfL7DVYoQIGZQWp1UCDJ8KlWyhVOtYYCq4P4NYjpZTqjd1Vm4RASVSfB0H+7fXJHEKaB9G/eXLp3jG1nbt1IKwDLbT9PpTNnIeF5JChqi049/67JCP4z0rbSq8QP5NrL75g2vXzybKuUtBIRlltbRFisNBnLGabdSJSFZ1IhgJOrOeLMYi2QlJho/MC0Wj47wyZwfdQDYqPaKXRBXQf8g0cukIk1nsIzfdnkDFylPauDWhPeJzDDoj+Xjur1CqrVQgeqxTZl7F5qLt9zHcYMBKkKTGe7HXcBOPiBNyiqDaBxvSLGres01tOjl9CLdct1U01YH4NADG9O0zSpORIsSwTPiucTKNwtQK3KhLpXkhbBHtNc45Yqx96Ok8aKTjwFe7xsSBzPFPdzKDgYzK2O0wbdd5cXeoKtUIn2wloznUviwRJIA9vBh7DtFIUZZgubozRwhs7h3AQ20bM/3F74UthcMhn3AWW3KvV6lwe2ZAUufnBvtPcNQJaJuIk2JPwxkm6TgWeb9GjBtMUaFWYRbw5a5qPyku889Fa6+eB0DrWaDsAPZC01H6k847c3XKGxo/2lCbVnGbNeQC4oAmm677HgDhYaI/Igx75b19V1tEOkUDrxbKeMW+zH+M2XVjD0XvnmpZXJORM9byRnixT9WBsPniMEfMc4Wz6cl/LWi+j1zTYfIB3EVLEJAkZwwtMmwaUvaE/3QeeOrNl6PeYpaFmom2Jduvh1TrvsgC7qp4ii/vL6o76+31OePjzmCIY6ELli+eSVt8H4nqRSlcT541ZYf8jchQUrKXuMk1mtV1oLUgZeW0RZkQx6UN/3aqbbeTypZmueX5+evi4WFejrIPWU7EN0NFHUkZlAql1SXbK3nQ+aFAFVOXeafevdvZpRjQaS0j1idhirFDc/N+POguzTwbVsiJ4x1WByRcs4EuuSUTgWZeIxZLyQ5xd","layer_level":2},{"id":"28bb2a2e-23c2-4009-8847-35e8fac1f151","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Security Implementation","description":"security-implementation","prompt":"Create comprehensive security implementation documentation for VIZ CPP Node. Document the cryptographic security implementation including digital signature verification, key management, and secure communication protocols. Explain the authority system including multi-signature requirements, threshold calculations, and permission validation. Cover API authentication and authorization mechanisms including token-based authentication, rate limiting, and access control. Detail network security measures including peer authentication, secure connections, and protection against various attack vectors. Document vulnerability assessment procedures including common security risks, penetration testing approaches, and security audit methodologies. Address security best practices for plugin development including input validation, secure coding patterns, and threat modeling. Include practical examples of implementing security features in custom plugins and extensions. Provide guidance on security monitoring, incident response, and security update procedures.","parent_id":"7a20b53f-0b97-40ec-a630-7e9171a04006","order":2,"progress_status":"completed","dependent_files":"libraries/protocol/authority.cpp,libraries/protocol/sign_state.cpp,libraries/chain/database.cpp","gmt_create":"2026-03-03T07:29:10+04:00","gmt_modified":"2026-03-03T07:47:20+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"0ffb65de-cf29-46a1-a84e-5b7531aaa9cf","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"API Request Processing","description":"api-request-handling","prompt":"Create detailed documentation for API request processing from receipt to response generation. Explain the JSON-RPC request parsing, method routing, and parameter validation. Document the database query execution and result formatting for different API endpoints. Detail the webserver plugin's role in HTTP/WS request handling and response serialization. Explain authentication mechanisms and rate limiting for API access. Include error handling strategies, exception propagation, and response formatting standards. Document performance monitoring and optimization techniques for API request processing under load.","parent_id":"a486a2be-4bcb-4205-83fd-2ab48f89829b","order":2,"progress_status":"completed","dependent_files":"plugins/database_api/api.cpp,plugins/json_rpc/plugin.cpp,plugins/webserver/webserver_plugin.cpp,libraries/wallet/wallet.cpp","gmt_create":"2026-03-03T07:29:21+04:00","gmt_modified":"2026-03-03T08:01:46+04:00","raw_data":"WikiEncrypted:C34GewOyK1SlumqKiPsSg1JYzeCsCQE5DuJT41k9RTGrPgA2abBsNlRj9b4XM+jkw4/9NpaPKF6EJT5TeM3pEGtAXs6fmJFpD/ClSCokjO5iH688bbQoqGmQTvYhz6YFrqi2ZwZCgLZqxXjeQ+3tTxBnymCnZxB8FG4ZCYnO/aM7mVFF+wX7cs8cHpaPsXcOfr6Hd4fzmo068z3DM7cGFEXM5A9ls1NnArh155cTf2tueq9UMQkcnntwWzMc3neifSA8s7zCMMuRSbFSClMbY6V9dfVNk5WdOY7cmVE3DUCVinPejJUo5YqsnmY9dctLkvnCjD6r/kkmzQGUdQwKWL//akcURyfe4GYPSjtIZ44ZT/CLrBfKXTw7lan46TCZC7F6qfHQ9aadN3F4TnU5H1ujcRcpThzxdMPsuZ5BEQn14HqOBg4t1kbtlKTeiELTT55CEJBjW2rFbl+U9FB08CuygiPBZllXzpMe0RU4PDD9z79SDJBWHgOSd5WGX1YGQkYKq8L4nuJtoWQlrZ0BPAWIbvneiGiA3oNibLVvbq9BAn3NLuJI+AHJ3loWhzJGAZn6YE/6InN+lcFAd947H4Pe35OluAh6wFSB+XAcNyhKupdb5VKxOSe3opO5Qc8am/tofA8ZU9WYaCGGUEqT+/e6sSfhJsMLLmmY4CNJDdej/QfScNXvrkwcxc2LiYr0NgtIOrzGpsQCuhUVJMPO5LTXSgIqFPE6My9mOIRFinxm2t95cIsVXW4w9PLc3nX8s4sDtfXKxBJNNVzoPQqh4ovzWp8W0I3tY9AJTzVwJpyS3nRWg617dFDa4qGd2+4E1erMFsGEK8BgcY49CVHEMFf9sT45cISssrcMnGi02O6q0dnjHEEeAAY/0T9S4FTN7i80L4k4ZmYEqxKSZLyvrD0737gx9ST0tKa6JpoLG9tT9P1uZnS53sE9aD3HmZkiyLupKeDDv+qpXFpKQoFnkXRndxAB3otewfQhgjJBiHkvHJOUCcGwu7oUTksIx2X+p/HOUVm/a/YaOLOlZg30lXvoq/NBVfV6OXioS0MhhybUF1ZIFv7bCJjXmKAjJ8RzXFBIteGan7GbAqfpCj7QXfNKDIbLTzTS8Tcc3xAX5SftkaJP0F1zb/wPjohfgyOydr9/jfRh6sukEK9rzTfSOuFC2Fbbo/6WmxuoHaivcCwy0lnYP6CZSdGlum5moeyIrqTQBKfgud7vvmpVu7KYrWXaidCLR8KPdODZbPhMACrFawOh3JYjSLEpfTMgtfWikcDOVPjVG52gBOdZNLUQlkJi/ZOAXSmAlMebJ4m1bVS5tnVK9x2Xb3MMwNNJrrlh8aX4ugvzgu71ryH9sfQkOi0JNkbmRqzITStS4cC3qdTs9C75L7Vl5oaVmuz5yGSn","layer_level":2},{"id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Network Library","description":"network-library","prompt":"Create comprehensive content for the Network Library that handles peer-to-peer communication and network protocol implementation. Document the node.hpp implementation for network node management, peer discovery, and connection orchestration. Explain the peer_connection.hpp for individual peer communication, message handling, and connection lifecycle management. Detail the core_messages.hpp for standard network messages including block propagation, transaction broadcasting, and handshake protocols. Cover the stcp_socket.hpp implementation for secure TCP socket communication and network transport layer. Document the peer_database.hpp for peer address management, connection tracking, and network topology maintenance. Include the message.hpp structure for message serialization, deserialization, and protocol compliance. Provide examples of peer connection establishment, message exchange patterns, and network synchronization. Address network security considerations, connection pooling, and performance optimization techniques.","parent_id":"139b0217-0190-433f-b41d-60fa08c9ee5f","order":2,"progress_status":"completed","dependent_files":"libraries/network/node.cpp,libraries/network/include/graphene/network/node.hpp,plugins/p2p/p2p_plugin.cpp,libraries/network/include/graphene/network/peer_connection.hpp,libraries/network/peer_connection.cpp,libraries/network/include/graphene/network/core_messages.hpp,libraries/network/core_messages.cpp,libraries/network/include/graphene/network/message.hpp,libraries/network/include/graphene/network/stcp_socket.hpp,libraries/network/stcp_socket.cpp,libraries/network/include/graphene/network/peer_database.hpp,libraries/network/peer_database.cpp","gmt_create":"2026-03-03T07:29:24+04:00","gmt_modified":"2026-04-28T21:31:00.4625046+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"3789801b-aaa1-4c34-a35a-c5b48338520a","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Docker Integration","description":"docker-integration","prompt":"Create comprehensive Docker integration documentation for VIZ CPP Node development and production environments. Document the multi-stage Dockerfile variants including production, testnet, low-memory, and MongoDB-enabled builds. Explain the GitHub Actions CI/CD pipeline configuration for automated Docker builds and testing. Cover container orchestration patterns, volume mounting for persistent data, network configuration for node connectivity, and environment variable setup. Include practical examples of running development containers, connecting to test networks, and deploying production nodes. Address Docker-specific build optimization, image size reduction techniques, security considerations for blockchain node containers, and troubleshooting container-related issues. Document the relationship between Docker configurations and CMake build options.","parent_id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","order":2,"progress_status":"completed","dependent_files":".github/workflows/docker-main.yml,.github/workflows/docker-pr-build.yml,share/vizd/docker/Dockerfile-production,share/vizd/docker/Dockerfile-testnet,share/vizd/docker/Dockerfile-lowmem,share/vizd/docker/Dockerfile-mongo","gmt_create":"2026-03-03T07:29:30+04:00","gmt_modified":"2026-04-17T10:15:28+04:00","raw_data":"WikiEncrypted:CMPQKjsWj44q+b7DSXFoBXMIGf3GaM3SGkmd3tknFiEqzTeBe3V3x4Rl6LRWK8ucLbbUzS3JInyHtCKQF0ZtTRFsUM2pHuNQBR/McP7zrp8kqpYHwp5RGHgFScAIJwhQeXMWt06bLnmVd5jsAHbioGS3UmhZLSzkbyFrOFeAaGEXV1hChm+rrU6fiW6cWeDh/zmPZpRLLe9VMrpkoEeRdFbzZT3WClOHLOc21Q2QZIyRj/POcXUEOYFj7lriSL6UWS5n1a2f0990pasHQZrSROtt5Xmal47gweop/quaPDZwrVU6s1NnlXmCs6rEr4dJA5PRzeRuwA+IdaxWwk3Zb80SobupVgM5K8g0YWDexoK15CgzQujFwTMO2ERL8laZv5KLrIl5W8f5jaoBbQJ/lpn3NzJ7j8V5G2HV/Pxd0mQTNJQnaMEexQshyUq8A92SpD1Gs6HK1JIdhUlzhsRe1GRGoJuuPM8iA4tSta7F+VdgOw1OQR8qMP1qqr/fGPBLGlIv+hJiRrCjjNH7qpZbCcJ+PKj6aYiSpYSoEc9nvZtyH2+YnA2QhNmN4hBmJ6cg7dQo0aeXxovS/SXyorpTTYYDRllJmEvXUA5rZVLd9m4JHDlnClDCncfys3IV4iX9C+0Ah1xl89YcyJQl8XkY/g/I8pqvKkNxvEtpYL/TgSqROPQOwHOJpgfns8EsUoUrXC/j5oCa84HkdfonOoxNcSpP3tZ2DEj/grMQcCXy9p6atU/zKxVo34A94/vTRp+1ipz5l85OxMoNLshI/I3p4+dAn7iuK/aEe33cSFaI+8Inb5ihmdHlttjs5r/LOqmR+MT1od9da+q+eAkI2NU2wMeDjjQrohsBGZiCCc4fTm99YkcmaekEa1pKCELRIAb1HQKR8nJ6Lvcl3fXj/EddyME3X+v51draQWrzB84H0iqVHKBhvctVPQqp8F7UTzC9ZV97vjpmMpCKOUatXSiXCEA4U/zUEZkSXx+Om7XEhUGJjvZ6UOcd2JW17WKykU5RkzualU11W9DGXaQwMkZI2AY8ipfR1C0R7ZBmg1v3m5Ae8vCCzwjnIP1mkLI+YbifLz+WQNRyyx529VyDi+bndgCEnArzpZePLaLaIOTGwU+fupkPjyLtgO0Mkpjhg8Vjrr2boJhLj4YpFopCDcD23akpBgMUjtnkleOZIaM0c/CSz3r84qmWigf5JNU6vxgBsuosmE1RIbj3ql1+qtCEhfGozSOYcyiw9lR0rNCq2UzWi0fNGU8Yu36iNXw0nld+PFDTSNPLb4Ed+KkqeMzyIIMaPjx/FAGy7UnpG9ZlfLGR4lxxGVY2cOCPGYj65fMeBBL6AUWw4J+SxapwO/E+8gfdYpgThk25UybTF2EpYO4Gpn86aa93NSggOT18LsOWm74ys84v2mxBJ6ds1Fbizl3EJX4jLUG0bJBR6kpEMOwkYEgs5tIby0stgF8anPgJC/Npttwf4iGkZ9WrL9fDElVv70dYw61rXyzQV7NyplySti9UNFqHPqNYeW3PW1VdlXOAWcJx6ic4QFCQPHeX/42x3lcgwDmgUBFNuVBMzgHu+ErwRngOCG+eaBoN1RtIFoDrFTAuf17hiDRMN5yE5hIx9xbtb9LKoVnMnBRGHVt7zMpPE8bkGlFDyYfs/0j7xtso4DtUkFY7kOZcgB3HWnJfMXPQmCMahTDHo1bnk5cwa8WtJRLE7Ns87Wt4DgTeN8/JZnRxmY4uEOzAnehwmcb7I2OUEVk0ucosgNmJk56x2PxTuFbfLAgr7ZXznOt5zxJLOKhaRiqoY+T6fbdwdr5dDazRxSqhLEF5KYJbuFEoPvfILO04KeaXNdPZ0GzZXRHUJ+saTGDDDSJDjI/woJxYnpPrA/ejN0UIER/hnmJ4H2GlP6kgjCqt/X6f1ybak0FEPX+1HEModDzw/Cy3tw+ZyEROrOBk8q1iE1FyAoE=","layer_level":2},{"id":"be25befd-151b-41db-97bd-3bc570e2cf2d","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Network Debugging Capabilities","description":"network-debugging-capabilities","prompt":"Create comprehensive documentation for network debugging capabilities in VIZ CPP Node. Document peer connection monitoring and debugging techniques including connection establishment issues, handshake failures, and network protocol problems. Explain message propagation debugging for understanding block and transaction distribution across the network. Cover network performance monitoring and latency analysis tools. Document peer database inspection for diagnosing network topology issues and connection quality problems. Include practical examples of common network debugging scenarios such as peer discovery failures, block propagation delays, and network partition detection. Address logging configuration for network debugging, connection state monitoring, and network traffic analysis. Provide troubleshooting guides for network connectivity issues and performance optimization techniques.","parent_id":"614f1169-202f-4dd4-aaa5-360a10ad6bd8","order":2,"progress_status":"completed","dependent_files":"libraries/network/include/graphene/network/node.hpp,libraries/network/include/graphene/network/peer_connection.hpp,libraries/network/node.cpp,libraries/network/peer_connection.cpp","gmt_create":"2026-03-03T07:29:38+04:00","gmt_modified":"2026-03-03T08:04:07+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"5bb14590-6784-4cf5-b371-7c3778519d8e","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Service Integration","description":"service-integration","prompt":"Create comprehensive service integration documentation for VIZ CPP Node deployment. Document systemd service configuration for Linux environments including service files, startup scripts, and process management. Cover Windows service installation and management procedures. Explain integration with reverse proxies, load balancers, and API gateways. Document monitoring integration including health checks, metrics collection, and alerting setup. Address log rotation, centralized logging, and log aggregation strategies. Include integration with cloud platforms, container orchestration systems, and automated deployment pipelines. Document backup and recovery procedures, disaster recovery planning, and high availability configurations. Provide integration examples with popular monitoring tools, logging systems, and infrastructure management platforms.","parent_id":"4b6cb85a-4799-425e-9f98-9cd149c004ec","order":2,"progress_status":"completed","dependent_files":"share/vizd/vizd.sh,programs/vizd/main.cpp,share/vizd/config/config.ini,plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp","gmt_create":"2026-03-03T07:29:47+04:00","gmt_modified":"2026-03-03T08:03:49+04:00","raw_data":"WikiEncrypted:vMsgMax61wW2sTWhXd8DH8qAw9CtLog8cZ1tiD2BFP+U0bXW3hqFYf3RL9xyiBC/9b4sFP+QmOk2wmA/qm9hf0gT3h2Jws99nvB34YCyl5dFaIRl84rx/o3Zykg3tatTYFluDC7sbSvs1O0o8gMlPHhUonDd9S0Wby8VPQZNvmSEJ/WgHJJygKImU3JR6hCKaVPIJYXeKFOu2MMIoj0sshp9BQnlc1PmbUL9S4on4eMkB10USK/Ro+RgIaYSkS55t21sHWbLTo0AlxIYQ+u0KpIzryme9wNUHxNVv1/IR7MrAd/lUW5ytYLeeT+8Vl87eR7Z3oPoyjsZ3ZbIID+QIAzdNu2832ZZA7xUUgZJBC5mMJz9XR+Ufm671kMd/tgGWmWR36CAQ6tg5WVoTK7FLLOP7NJr8n8JYqcBpGXHRoFWOzLaYd/eBTgigAT2//TsubTjnTst0Xr5wCwHMEizXHcho4YT8rdM97WryHG0LqDXPXpKfJdorLOlz48JKDaCjVGeE0oO77OerszQWRaaHz6Otzq3+Suf167Tp6EQB6ErQpC3x79NVUEBJkgeltwq8BZzB7mT8WjiD8dzA0ouZxOkfye1tPTKgdN9llQJQM8YmsxV7dzAJ9Zk4fQR32f2I+8aMONsLHq8Ye0GRjHmVjSP/I59+8qzq9/vTKE1DLsRm4ATPJm6qpIKnbhA0L/QUgRkjpOOiUiBlYcKuzkNtxwYGT3Kl2H16TVZv92wuUs9Q0igRFhOuMXtMF9dOAD+8d1kAMc7s4bQJGqb03MNS52ZDa2NIjyeDjP0N/ZIVSlCLqlIXeF6nFzlI1+2RLWMSqlSXt6WiZXkhtgnbBXQJLp4ZBaKnDBezrJI1PXBohf13XZ2y6AXkuGKjJwe2fxE0A8vynkyIyTeAHBpfoXcdDVI4GhaTQMaGsASUAJuQjOCFcSj01Z9IjxjFv9fddpSlR6KvnOhqrbnZhltVnNCS1CwWaGGvp6fakDvnZR8lnmI1mukzzde0zZ2GwMrypQo4wuGW5bUH/XJpG/r2UwS31ArKbP6E5Mfq60OymRDINMWSJD016iwBtvaEo1S0F1Vjf4P+4v/qFKApG9mMaDKbbKoS/PxvkW7YxbBhXqSws/TlykHsuEDeoVK2qNyZnn16LP+XaIog+yFNJBrS2FuZVo7pO6c/qdLfuiO550zmM/y3Iq9vfDEywnwrMah00LPAPrIw/Dmqwzn+PZdfz/GeSJifvAvbHN6iqVbUV9Est6nDFcNVqxGylGlPui1mJ3eFg0/wFAKf61Q4yzylgEf9LWqzN8hl6GDzQvN2IY7J0GbIzEPoh3fayxchmU1Zeom4xwtv56vCZFnrdKwgAQ4b3HBzBT75pYKEeNtPfI0y8BJNbfYdA6jocqZP0rnE2GTTX/2LIi+d5ftnKdahlv0oDtGTzHUEnh+q+ymfrrslIS1YjUiWsYi6g2LlRAmZ8gzhK76DQ7/dM8k7Q3SoCRk5z/kcLrgy4Z+lSLqasmUGhewoVBLdudC/Eam77SJ15s/5T58WIE3NT1UOw8h5O5j7YSWM6ErjZC3uTvEVtmRhBi3To4FqO7dWQW2Da8QJewcor/ldmjaEGpFSBFc/6ROzdHI34DTjOHmfroIsfx4/UVKbxb3RgViP4DVjAQj5jnLvHM5mdVIuI6vaHF1UAJZTsyAfcqHB6ryb9xGYZ22mWk11OakmTv73w0PCQRHAR3mqVUfiFG79eu7OLe/5+f4VbaxwegE0DGd9+WbkQl2tWc6wwm700mXO+ZWAB7RbkXEujlUF7HZ6lFfvT6nDQxEnb6riuw/0+d8RHlIWR93YaCmx/7UV+HhspCPHD1G7vaG2lFLfLaEMR1FucZeJsNLzzYDyqTfS7YNjOWpfjCSmzjS7qzViO7leJGrPVv47n//","layer_level":2},{"id":"75eb3c5e-02de-4177-8680-a24fd13d2590","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Authority Management","description":"authority-management","prompt":"Develop detailed content for Authority Management covering permission systems and access control. Document the authority.hpp implementation including authority structures, weight calculations, and threshold validation. Explain multi-signature authority requirements and permission hierarchies. Detail the sign_state.hpp functionality for signature validation and authority checking during transaction processing. Cover authority inheritance patterns, custom authority types, and permission delegation mechanisms. Include examples of authority configuration, signature verification workflows, and permission validation scenarios. Document the relationship between authorities and operations requiring specific permissions.","parent_id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","order":2,"progress_status":"completed","dependent_files":"libraries/protocol/include/graphene/protocol/authority.hpp,libraries/protocol/authority.cpp,libraries/protocol/include/graphene/protocol/sign_state.hpp","gmt_create":"2026-03-03T07:29:54+04:00","gmt_modified":"2026-03-03T08:17:58+04:00","raw_data":"WikiEncrypted:Jgq9fCpm/NkqSNIWYhs+n4jiUARsKF/Jd4ydE+1m2zwQwWHJGmtOerLGG8TW7XbS4S/T35fVYB/yJZzgovCH718BLdmouX/kTxDmd8tNbBdrrta+doQxNGDkod8z/WaaQYLVb4IxX7U/EjoAr7/P+66vrhi6RqYj5fBPtrSgLJUW/RR9MCpaEUYd7pGcSU6JWeCOU2QyjuuYk1SyjFg+g1rcuLzhrRhHI9zKfjN872ae11+XxxBNxki1ElyJsf3BevyOBdOTFNxxS363QkNd/vpvy4NvNUs1OpK8DvQeMiVwEtO8/HAwIm2nwoQnxz0E3q+QPoaUTGSDd7BN9thcQWqlBd1D6lTayxEUnWl33CQOWEqRTocDEicRTnxYYEwujQCeNI8NVyTgieqInU4N2L/J58a02Wz8RQi6AJXBFQ6f1btOEF/VJS5xkGYxJDnigIiZctgWc5R/uQ+g2s9733TZBWZBG5jaZK8dp6ZIH1hX31+DnMRzJnO0xqnFq1JGcThh3FXCYiazMyDvVG4xlJVCLM9Jg61r4bStfBEfdQofNXQXURMWrAWgVRjgBpvKKSIsaJzo/m/mwupcs6A5ROCTsUktizLhzKbWqTUCOhya+jnqDYc966T4LKBkbRgz2G2viJ4xn0ApRwTxKB8eYeMeTPYlpmHdYAOGVqe3dAPJ9kXect7UPNO49edXXDW8c59l+VwSAGXSRIPWC1vBp/hFyYfOI2oO1qF0XiloGQrv69txOEG2PmXtvgz8Wj4UVm6zpeKhwcw/kdmqNBV6EAd4wu7ri2GlggsPU3vp85an6T0UVHXL/OxtJiLQWwrTpGOjaEAo7uCVA6F4KL8kKpsAIC/EPq3CBO2XJkylhWGGK+vbLH1QSzROeOoJQN/RmOUXveFYAkQxysQJi36XfXtBEcI0swK/nJPGsjje8+b8vZpoBlN98dW8t0Ns5PRfjyRuhRbCHYvN9Vvg6/5yuRQFHQPY0GArydv24Du0b4F4jYEfq1U/U0SqxKU2uOBEONQjr873NJVAjWVmM/TChAhjIgGPuIf1anSvWe/+3rCjs9E+YgrTU6fW9XKVinf/wF229mjENAXVsUPeekDSua6JWR3GoZ4n6L08jD57Z1GJDYvZpQC6OmiFbkwl0jZ1LVjqHPMMDtKd+gzj2TOLDHjEewcQLtTUws+fE0Z0A1j2oJOWR7ffPuRW86eGL7eT5croRguM1miNpTx3b7XjIY05QBx+cZfEW3dwauyynARfon3eYMJ1ywVqc+NeZ0uqVxtRhlXc+RD7Qt0rIu2irReEDQaz6Ruvi5GSWsNZSiAsJgNSMoBc6vIcU3vu0nUVZRIxQTZ2E6URcuRuWHR7xxtTZGl0FW7qPWYddIon/o93Y1kOj1tqKyGP3s1OghTd9vzmGKrbovd810tZXWyLrz4s1G3xf/GU9K7mYwUoXKtAOkbj26gosy2d7zXTU7ucfwn1XhZBThySCG6CjiRoCU7dPOBNwN3hcOcpagEr7UrxQQbiFwrVLSa5uy5t823DFb9iMAGkJ2H/IJZ05LeVDBC8cvgklwS7BEkMrU1nIcH0qUOhDN+PiCz74ePlbsdDCXpJ2XcfTk24dkzl05ZzDA==","layer_level":3},{"id":"d7a25826-a156-4b8d-b236-8414ec49a67d","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Fork Resolution and Consensus","description":"fork-resolution","prompt":"Develop detailed content for the Fork Resolution and Consensus system that maintains blockchain integrity and handles network partitions. Document the fork_database.hpp implementation including fork chain management, branch selection algorithms, and irreversible block determination. Explain the fork traversal mechanisms, common ancestor detection, and chain reorganization processes. Detail the block ID tracking, fork chain construction, and conflict resolution strategies. Cover the witness scheduling integration and how fork resolution affects block production. Document the API methods for fork detection, chain validation, and state recovery. Include examples of fork scenarios, resolution processes, and consensus mechanisms. Explain the relationship with the blockchain log and how forks are persisted and recovered. Address performance considerations for large fork chains and optimization strategies.","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","order":2,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/fork_database.hpp,libraries/chain/database.cpp,libraries/chain/fork_database.cpp,libraries/chain/include/graphene/chain/database.hpp,libraries/chain/hardfork.d/12.hf","gmt_create":"2026-03-03T07:29:58+04:00","gmt_modified":"2026-04-30T07:19:20.4603602+04:00","raw_data":"WikiEncrypted:LF5XH+WZjZXH77Oahqhu33D8kMc2V5EyZ5sCQJaB6AbkaftNweUtSuIUOV/O1ju0L84BXRl/rB6mkbN9lJNCBp8gKu1Js50cynDLXNwHl9v3CFjgeOELp0KEyNk7HrPoblpscxDGsnAxbjU6uksUs7O0u7cTueUtpnsEhZYzj9r8lfaBwvcIcwsEB01RRvdhbPL8qluO1DonMjYGtl/u/z+VLRoiv5ZgLMwPnvU4vbwacw4Nl4nBQckEhTWyx8PX4rDQbuccQQ4SXKLqidYPi4B4aeRXfSy+WFUfPBEwabGu6wVVdmAalq7QGFs5G+ZaOoCumfG8JuRmenUNe7txszLy+G3PmfSd2EFhv1fPAsgxkEoUXUhyP+z7mm4ZqZHngO6WzuankZHLSspID5TYCXK2ChsJXZ++8FElt7o7HDjd37v1fFddC0lfsoLHBo2wCqgork3ZG9kruihKJN9BwbnaWIb+n1+SMmDOqHTTn453SvDMygUtsCOUSqEa1AhSJk0jWGFDWYSGpQ7EU7XWK7FetNZJ+qZidr8PgtgwfY98yoTZ411hHRRarvmZEMRkUTDnN9IrC9No2dlAG/yvx5NsfeIBHnCn/U7Qct07ypbU9/Vc56PfP5052v9jhw5uDTosqJJi8eWJRYoZV98PnI+xoU49yM92VJVWYxQ6HJnRLAPWJxDEk5c2LVDjYGPDfurStuNYbY9IJoJh/BaYzQCCLMb4zcXk5eIeYMn7IWzZK2v7wdMGFeG1CcIFBYBaiqxNl5OLQT5u2w7HXFuPIEzcR1hEhhCdj8GQPCpxdn3RgNR1WJOHgJMyuW5jh3mnx9RvL1xvrXBwBnoR3gA6I3Stzg5XyQ7zXz7gfskdj6HhX3V+LMVWc6AlOk74vJQkl1myiZtzunWssa0DH37qbuf4+nzYxAnPGfza3YJoZ/GqFd5YIeIPGMLywZKEx2+VvXHpyuq2ScgROy3pjQmmJsm9Dg26FwlrjeOJ1fb7rKnJL7j6reeSTeBc7ZTcSXEnXZ8HjIWwkVj99LZny7So+qjVDhgPOS6OBiemWQ/g2e8/W50hkhBP6t2ABzLW1HOULcuXCZXDs9guIa4Sf4XMof+lgva60Oz+kyDOpoaqv/EPb5sQsWkexNIYBbKl1NAzTSa1r9zyXX2sCh0dOXq3oXndhsHwVxQXiQ9NwfJTv6d3pqPLe+DBvTRvbTz+32u7RyIBS2F+RU6rHxONYEp7nJhIzKy0TImWHvG4iHhpYG4hvzrpDelalgWFx853AUPJKMMx4zK98Wiuf2NaJTF7rNSQVvKWHaaplCqWXe1A0MmMj5fM/5wO3JXYJRuun2mfe3IFsT7/iG7Q//tOvdVOI8/jv4UES5afZD5xHcoQe82ZVIWWx21OOj23t4A2p12hkuMo6DpKhWf7jtKx7mCTnscAl6RgaLYhgPXPbiMnNKOqISmH5GMQ/1ILnpOv6Jpv4RFkpbSD4EfmRu29FRGRBe9dx7BhuN/Wz2s/330nfzmXKbqQl7Wb8Qw5XJHqzUbJTrpav5fF6MJS/DgPUBCwDvJXmylzy5D6wm5cMazm8qA34DJ693JnIZqLCcBSjsNXsH6kqMDU34FcaY4dny6gfvaL+ENE4gsnDFK6yBxMgii5o8O3N1e2trGlTb/XpbBhD/jKD/wWVawl5uXKu4NCTAJF58QsjWzcnfBjA2RdYbX+oKGlIULRwwNDb9mxuXV41Urm5L3JVcu/XH+3p/hJJBbfLA1FEHOfLJ/Mqsqx1MA/tYKRczvEjUoogvl+pF5S2ZnGmI9f+GzZjKfqG+dQKh/LvXSOsNm+oOTbeVR+0YfE4Gjs/h2zsxspTmJnuRjJIWEVAer2EDIONMeggSrN03WbpB/aEQxyCBF9/4ZbZlagUVsdBES+FAo29yFRsZZroH0N6p/NDFgdcYOM6i21E2pbBy8ndUcggR4zRwImib0vMDf3YLLqA1aRgLxNcIql","layer_level":3},{"id":"853c4d2b-809f-4ae0-9735-c14d174538d3","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Message Handling and Protocol","description":"message-handling","prompt":"Develop detailed content for Message Handling and Protocol that defines network communication standards and message processing. Document the core_messages.hpp implementation covering standard network message types including block_message, trx_message, and item_id structures. Explain message serialization, deserialization, and protocol compliance validation. Cover message routing, delivery guarantees, and message ordering mechanisms. Detail the message.hpp structure for generic message handling, payload encoding, and protocol versioning. Document message propagation tracking, duplicate detection, and message validation workflows. Include examples of message creation, transmission, and processing patterns. Address message compression, fragmentation handling, and protocol upgrade mechanisms. Provide guidance on implementing custom message types, message validation rules, and protocol extension points.","parent_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","order":2,"progress_status":"completed","dependent_files":"libraries/network/include/graphene/network/core_messages.hpp,libraries/network/core_messages.cpp,libraries/network/include/graphene/network/message.hpp","gmt_create":"2026-03-03T07:30:03+04:00","gmt_modified":"2026-03-03T08:20:04+04:00","raw_data":"WikiEncrypted:IaaHBhg4aKVK2Z27STIpf61hPTwuvjJw00wghNRz0dqXZFnsx3+KXuJLymQNNBhm+4HmlhfiPgft+nBCx8lK4ljvr9u1N7eyZnhnwrufcfJWtCJmJAYdT6I/MbNWHUQIKT842G2JFoDfikSnGXpefzkqbmJ9h4HkLavoFO8GveAAfrmDKT8BrkB45sEUfJuABljdMo1Z4Po59a0a4om8B5rJ8OUxvmIemXI2d5efoKGKw5+cKGNGgMwYtApsVIFedpjM4uFtQnlachraAR2ooVuqRRO80ib4jrTSv6yeZ7H51ge5rgRDeYcFxsrJCoVdwdGWygXe8okjzamm+iH8qIoCmIeWbmGhMXfFlxEK/xb+wwY52MBM4YD0HCpQKrh+FO+ELHik/d6lsYRY9gPA/rOwZAD/lHaoyAvRfwGwxZBnI9v1hKYnBr3jkgYNLd2b4C+SN6V0q1D/J5y5JrGIEDD0UDlw9LcEozUbvr8SxlE/c3ssjyO70AP9AkWHy0n/edPKa7I1P8a5PI/LkoGgkbZsgmU2eWdzzRHPD+SAnRdp/wbF747R4uXmCN4zeoRvcIx7OcXyzlai1574JHXrHUXVa2PHa0w8B8126wMxdEQWP7//PnhsaFyMCTJtgGzzl4Z9itYmtAjL63fZhJ4/ulTh6nSVcAaG5zFsedHUkFowgbseLtQe/VZ/cnva8HtoTWZN1mFJ+RqQ9jfwt6LYrzEd2q3MirnV2jaYHYkcstr51bztnhXvUgkhWL+e3AELp6mphweylQexxr7cElbJkAXJKfxSoGgi6yrwn8HZrCLHucOZYM2oPk5hxPtgYidAWjD39B7q9sD5fd5HTpIbtXghy+Y0n03NfM1FVHHCxxjzfqjT2zJHMmFpwEg7TMxgVKXNzsaoXhfOVluWDB6boDg9qics2SnUgCnPjCrCaUfOL/+/3CYa+UyWEEKgG7CyS5/Y82EKHbZzSus9o4j4gANndEAiosEL3j2nl89R6zEhtRLCWEcaj2rvM5aHgTjhKovqUJXkKsmixnyt5qMgzbo5nsmiBjpOUlvVKwEBSXsLxENv+jTt+kgzsIbzy1Jp5xtzSiQhvHOoLZ30UfUSrf3lMZ6XwJ/uVJbfRPnkQxayMicktwKeQtCn4FChxyYZEp76MNcO23bGr0HXdcFwVB+V+6sOw35crzsrOX0G3Y5zxdwwg9AKekR1gVUEilUZk9axa+YC+9LJy7dzNX1rkOd7bVXWcUs1ZrGXwQEKkB1BPYhOsWZG44+DyxwIxnRp+jOUXNaPqE5EejlZDlomg7S1cOn4gOqSktzaWJjoGTILesnSEh2cL6x8Wy3fBDjYFWKF3L+PuCNh+pD4ChQBrf7ypQp+hBk8SvwZS7dp1cjzmzJZxVoJMDfb/TM4Earm3H0uHtPgyNB9Vtphl05jPHAf0f8vYuQ+s7cTaTyab/jpEqRdDzl6/cALbcywQ1V2aRNc0kPxcV2Zaopfp2UHzxC77e6+R15VG0KlBuF3PObzKl8w+w5wkN8x/18+NiQGljAIYuoslW70VeMMwUwo4fuB0U7f0r1Q9yeYVdIdFTT9y2SoL7yQgFs0wpWrAT5vjflJ7nPeIj9yMdKSOh6orsFq+PRqcVGaZPd+qVoghULPAqhBhvKqEe+2fuMItfkb1t/1K1HkFcu0s9KC8xYkZFFbClqzym72hHLhRXCU+9mTwqhkMHujFNlXu11k6j8K1Ep2jNU5J2M9v5RO91IEa0hRaKxJLDF8/SuYNgZoWdOIjPZxsoZyg1dmtaVoVp53","layer_level":3},{"id":"c8b8f71a-3ba6-4723-8e51-78c701b5b4ab","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Dependency Management","description":"dependency-management","prompt":"Create detailed dependency management documentation for VIZ CPP Node CMake configuration. Document Boost library configuration including required components (thread, date_time, system, filesystem, program_options, signals, serialization, chrono, unit_test_framework, context, locale, coroutine), static vs dynamic linking options, and version requirements (1.57+). Explain third-party dependency integration through subdirectories (appbase, chainbase, fc), OpenSSL configuration, and optional dependencies like MongoDB. Detail dependency resolution strategies, version compatibility matrices, and troubleshooting missing or incompatible dependencies. Include guidance for customizing dependency locations, handling corporate firewalls, and managing dependency updates across different platforms.","parent_id":"317287b2-3937-4876-97d0-a8c96007d95c","order":2,"progress_status":"completed","dependent_files":"CMakeLists.txt,plugins/mongo_db/include/graphene/plugins/mongo_db/mongo_db_types.hpp,plugins/witness_api/include/graphene/plugins/witness_api/plugin.hpp,thirdparty/CMakeLists.txt","gmt_create":"2026-03-03T07:30:07+04:00","gmt_modified":"2026-04-19T22:00:10+04:00","raw_data":"WikiEncrypted:Plae3AeD8kzvx5cS0SI01VxUgqb4buHXTzzTWj4gTygU7IDKBYr41L3/hTrS44leIMVa8ywMUh248ZyPirs+eIVofLm7+N0WlOX6fg0khoNhXtfGI1JGQ9weIgIVyjznsmVNtjh2fiQQu82cjIL5lPsNVUenQmG4sEvbSS9dpwJ3rEyNbj0zNlca/sosLrYifdoLBf6wi9rVRkw0R2HPbVqv1K62V8d2h1xVxhoD+mzciX08y+Y626vWX3TQ55XUKWPPezNHVOIkuhKKK/FvzNNEOWkkvBod7s12N1y5zsBJ7OnVtQ+te+Oraj1mZvD3yQNlf7OoLG+dWkEdXHOvf5CZFFffTB/IVrar9FXThN/I7Fc5spsivRhxmS2EtHqcjzaJttxdCdRQQ/BCFLgiPZjRjOfDJSF+XwX5s/wgAZIzXU8Fs+Q5CU2VClMl0ZBUMCFhbGkGobGoibrJW0gv+xCNWo9dvQy90S9DVkg2JHDWq6NQLU9/Ck03b6pCkb4oUVBlX+E2FjRHbbZRNMSRaaKp/+A0F3LfmijkIZtUidn2Ud5ssgcgnZ0xWqxULF0L7nK0uOpChFpQE37ofSZGu0ZYcgLBqoAqGM3z7FWLOT9QNFB2J2ghVFeM8ctDl15x5agpJ6c/n20KeYIQQ3YbyR1w7lEJ2DmAIJ115iDZOxh6T+la0Dxl1e6Z6gGg4R0x1BnYlD1RaeyHxnbcJpmmd25fYpjV0bwt8FMkic75TZmS6y1j4LbrrxRqsSAoq4YRuvS6ry5PgQBOPti5pLq1FtNs7nMTUGfERuRBF77QhfGS4zi95OM0MqSDSW1NNeL7hTmzhlbVNJzyRvgd/6gkuSoyWoccHQGp8IpMeJglYMcUS/1agV1WnMfkJO0IgyiMq03NBKaIDDyvY75Al1xdsUXFFSwS5PytKsQEt1WFbqNBe1dxZTAtRspq5I8JD5AAdK7DQWcjEZdeYgr305oWce1+VxuCkOHk7GIs2j/mFf2bY7c8pzqUhVQNYXfZFHuKet+VgRnEPP/FGnsPmbto4yg4y8H7RWaOD8tyi+e93Di9ifGP6c9XBzLRXZNceCcgqhEQP39gDKqBNFgCfEwmTexbqkDzoAB+uSkFe1g4ABb7klM7h/um1faCYTTD05mQgAcjQlUGu6Kig0OuvO20D/u2w+DkhZticFB7WdkEAMnzEA0J34GWtQjN08T/5j3K1NnlDCxAZQ7sS5MADnsU5FA5/ccz5ZPmHPTsUPPNcH+YLNVERxwyESTIBAfTgEhI0GVthOsevmZIAgvtZGLeT/vIFMhv5bywfq6/Ecu4Ns/AHG+gT0UXUg8r4Nqg8wgE94TVKAkBcthaCrkMgzh+dpgtkgf8dvur67TXqpUHdhuStZpvRlPhfA22RJm9XYNB7NZ+OLWCVxzmEwyncrvdieFuAP5kZs0IY6Fa7kvZ8E6kLTRwHvZU7BuMutV/73vTY4TiA15o0Z09KIRrbYRie/XkiK7Mo4Pe/2qhFQ/hqUvC0GpmyT3uQpk87+6XQO6TUzYuZvzO7fx8glSmEmJOBlIakoCXqY+heH5XcP4uAK0CrcjKNQt3n3/l3JDhZLXGtR4FSzfj7SswLAg5iSUJBG6KST3hab6DYHGqSrrJOHF7p6JnrWx6i2y6epIIluQQltuf6CtUp6ppPR+vZF+V2tnYisy6JXWohcO3wFw1jHbDq5UrBHWcXrEQaJCOA/OV","layer_level":3},{"id":"c387583c-6834-478f-acef-629c155068d6","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Low-Memory Dockerfile","description":"low-memory-dockerfile","prompt":"Create detailed documentation for the low-memory Dockerfile optimized for resource-constrained environments. Explain the LOW_MEMORY_NODE build option impact on memory usage, reduced functionality, and performance trade-offs. Document the minimal dependency requirements, optimized build process, and reduced feature set compared to production builds. Cover use cases for low-memory deployments including lightweight nodes, development environments, and edge computing scenarios. Include practical examples of building low-memory containers, resource monitoring, and performance optimization techniques. Address limitations of low-memory mode, supported operations, and migration paths to full-featured builds when resources allow.","parent_id":"3789801b-aaa1-4c34-a35a-c5b48338520a","order":2,"progress_status":"completed","dependent_files":"share/vizd/docker/Dockerfile-lowmem","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:20:43+04:00","raw_data":"WikiEncrypted:fKjMku8Fpv/b9ZP4Ul2Tm7Zca1h5FLirNykTI/AA7d47CnKOVNgTfrsa18b5sEdeunts328aDl/rj1iuGdoZlRmNliSHTyno4JbtoRql9rKHpIYLsGWMX9G2LUkeMbg5FlADbz3moUepTMX3AczHxUqbYB4AFqFyRp9NfIt2CAJxj+V+xIYQZNJxkd++ojQvXSYZIG5JstNLMOPkzDOBBMSmIkHlovPB1lMrLLJwFhzUpC1xCa+ND63Q5gMlN0G3yfjAIqOGUgrdT+ScVEXVikgbsMs2FR+Yf21LW0JY5SWTiW9asZnrbARtm1I0PBi1SwOnynvG4O70EZX9LRN9utgNzEKbQ/qb7A8SfVmVJ/5d2HzbGUMSHrkrOIOfbq7x+m5zQ+xeVa3ivGOz0zfrx0YS8ylBBcoxuQpJaKppYNws9zDTzNPyPKF6F8R0f1xEekSn/JYo8L8cTTlwORrSEVBL7LFIRgEWWaDNsVi/1O8gCAip4DBDZBQ2LPfC6P1QsUKasNlSO1mVC86ULrnACvyOlzdu4LgiRoNEiae/X7YYeJ27qk6miFPwfkqI74vrZqcB1SJtN1oM7wrhwKFiRe9nBzycLbwqgkO0CcPCTjuD4ODtK8fSqy3BoOB3//ZSvhyZGyn0R8gSDuAPdm+kh0/VZkaih+mXej8gh4sIMDIrJ2wnLHPiSUo0fHeGe7xvqvxBGfsukAs4x/6YobQGQrAcK8lvIiqbuv/zcWei0mxwkL+vtvgCGdgWdZNzVGlKpi+l0uM4+PmlnnUtI6E2ft7hMLxSRFFE0JCZJZibOiVittplbnVeb/JCKsBfBqhKT5ffxVvmAWmmz4FggTt3s6WiYfzMLbT5gs0JbLKyZw2fyzMJinM86Lsg4JoVDV2tEjnC5K+hG0CYL+/Hmdc06IdnAhagszBwJdg6UcIJmPbvVOGO5/yXMYF/PHJb4YEX0bYPFjmlxEiX2VH9mauNLPpt1jyCPGWgnZhyUwTWiQRTInuPux3MmvCBFIdhvbSqewELe+RCUR4kKk9YJS5GXYeZdik3EX7M9JK4p5q0/MxMgt4QtVQ/rYH/5oOzinNpJ6a6qM5G1yQe+AJJiNnMmCPHHNOzcC312TKra6dD7p85Thyog4vHQujObntWLiTnTOyNOJ4xY0pNqOZCXC/Q5ZorjBo1XHSB1Lp0wSdJAtzg644h4eKUjh85lW+FjpNYTVSH3dYhbW8ZQbhBhB1LSTXLsLYMDwhUlV29PLoAS5L+j2tTI5azUAjxI33HBbuYkzHJcOVoz9ZwiNPX/8aOHvoacoHQ3sNu0ZTYwXCfQcn/qQWFY79o04+IJ6ojQPjfOL9PxdOR+3+FXk7ksofNnnVe5/+jF7ZkrgbI13Bg2V2aDwse9dsZ8pT2WIlIDimfa+o8woVUK9ZsEODSEH7BfQ==","layer_level":3},{"id":"85a56025-1d98-4b60-bea6-dc4fe9dd9336","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Plugin Development Tools","description":"plugin-development-tools","prompt":"Create comprehensive documentation for the VIZ CPP Node plugin development tool (newplugin.py). Document the template system for generating custom plugin boilerplate code, including directory structure generation, header file creation, and implementation skeleton templates. Explain the plugin naming conventions, file organization patterns, and integration requirements. Include step-by-step examples of creating new plugins using the template system, modifying generated code, and integrating plugins into the main application. Cover command-line options, customization parameters, and best practices for plugin development. Address common plugin development pitfalls, testing strategies, and deployment considerations.","parent_id":"d54afe72-4975-48c8-b825-ab792ce92a46","order":2,"progress_status":"completed","dependent_files":"programs/build_helpers/newplugin.py","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:21:29+04:00","raw_data":"WikiEncrypted:ukPV2tcWPRGn89Upsee0BNh0I4BWUQUUZnzOP1DB+1vlRoA2DnNNT/bHeFbdtIQSXyk7bUmdFD+AFhUw6F/GhxiiRn+hzkKtze7IIl7cKG/KiTWWAdasY0S/sUREmQTHx6fByz16NvdAO9bhp/o5Zn/mUi/wb3p4250DKByWWZfNOfNXVv2JlV04ypM3wlrlGWlbJ7X8Fy6U0pHiMDV7TSm1MqZ7jRntlHQPGcOntBT/sCsvcMG+2Tz+AhYuUPl5S8hp4VL5aF0Bldz2wMa/U1pJc7TfQFlMxB86sCIBeKzByisy5hcvzrvj4EnFi6GPdH6XeSu/FRtSiNKuU/+BLK4e+xVb299e30vgCpN+yGzMnQSwF6Hl+jcihMZsXvgHDNEI/RrCXZvssPbZrT7sDQLng4yvFg/DbS5ctr/s3st4H4yLiUYjxEqDeeDZJmUsl/gOzg5Npf6VYtyLMLyH9njN3FYygMBEItIWHS/ZMH1W3KPMaE+jELwWFWfW6x3luX1FV+5NfTiGDUZFr5zbOv1nTesTTLLSp0JnpHFC0GvbajAQrZqALmujKNhlB5TCYT05Flf0GcmXhPEou3sfRmVyejF6URcCf/d5kBLMvqRSdfpnl/N03KHaPxYZINuPHBIAvP6NhDUCR/fUp24M1VUaRdEYxuTA0xVGT4B/mJFB1GcM4+9oL3v5BAIoblxDFG0ot39YIWDKAEZJP+FvRnZW7pXYJeHiWSk0qtIKgtaISPvLuJjnsI/dT0oSS3xkL0kd2kYI0vsYF4O+AsB9t2ee1KQCgIm5wS/fpF5ZD6AzqEaVt4Bz7TefBcReXAJpcG3vuPU+gLWvuzjT41eG5LOaY9P3Xn7wAvALCYsFE0vfSh8VR6967R/tPj5malqd3ZlHC0dxWpbxK5zylOGQO4uxR11qWEo2wh0pDE9wImO0X1phGa0q8u9QH0mjXEQ/tKnVJMfCy44Ur4SoVgKhfwCVtzuOaQWJzffT2DQN0zbuTBYBWTCrq9HAR6vNZmH2Z3sEpQgc/+ZJvLWTGHYGzSkGBjeIvPAFT7j3+lGMmHjVnT2E1QbymYUm4bNV6N4jm82YWThsJ9rkZtizBTQnTkbtEeW/TDXtJESiADfebLTnRZTZl7I7WM5mLPsPhlwYWa5D4Ll1vbKBq3TOCFjt7mBHIGiV3sS8VL+r86dHiM5y9zVYhoVkQ6dZnGqT8NcjOrRqF4ysAeIFLlMfihEvI3KCXAR61jbN/0bv2w9K/DcKSf6l2e7fFEfrdNx9kXLwfiDmsII0LnZpIxwKudn1vbk74LhTEW8Cuq4YGMH2AKTpMPt3FKdP/7CGNTpSK6QxHL6TtvmZv+gP7Z3wdC5jd0QjQlu4NnBFMWzpn/9HWmVn0GOavzxl77UxMoNg+Zc3VHivV1ARsjNAEidCVxhg4Mnb7xQ2/trOZlh0kCHFa/M=","layer_level":3},{"id":"63eb59b9-96cb-4c76-82c1-93eb29f163f1","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"NTP Synchronization System","description":"ntp-synchronization-system","parent_id":"7e383bdb-11dd-48d6-bd47-4d350e2df438","order":2,"progress_status":"completed","dependent_files":"libraries/time/time.cpp,libraries/time/include/graphene/time/time.hpp,plugins/witness/witness.cpp","gmt_create":"2026-04-21T15:57:29+04:00","gmt_modified":"2026-04-21T16:27:59+04:00","raw_data":"WikiEncrypted:kk2p6A+hTfq/j31wt7MAgu7PjcLqcqeJIlT11ZAX6rG6RFTstxzezqPewwuq7KJcZnmxN+1kb78oCoyll71+CuHlqFqb2IInJOarRp3GUu1hTuhP5Ph9c12xZHHA3N9Y6gkfFTURztr6jAT6LxmiaTH7GJAiS5z7eF8RNHQWKef/m7HHGsZr01TaIMaqh33KDw6kzrqzxNeZDzLMbyh/x9737bQIhT5RB3tSN8g6hBmfRWfk8EkMtWVf4AcsDkhJOGA2ZXZbKc+Ey4I2k6wZGpgW1sM4jfqwCC5VX8Nk9VocnoDSGLfxgLh/xYV2SF27P7AOCsOaBBGjXqSvStzw1Q==","layer_level":1},{"id":"024bd7a9-84dc-4534-95b8-96539a35367f","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Witness Guard Plugin","description":"witness-guard-plugin","parent_id":"ad096386-ea33-44d1-b9f2-261f11fb24d5","order":2,"progress_status":"completed","dependent_files":"plugins/witness_guard/witness_guard.cpp,plugins/witness_guard/include/graphene/plugins/witness_guard/witness_guard.hpp,plugins/witness_guard/CMakeLists.txt","gmt_create":"2026-04-28T10:02:33.2744641+04:00","gmt_modified":"2026-04-28T10:05:06.7184647+04:00","raw_data":"WikiEncrypted:JeTXIs+pfWQp8HpBYqSHUv6FCVRIRaRMz3xNyEW8d7EhJ4Yb9uJTUDwZy779+JnAHj6NWz8b3Oo5/W9JMIZSMgRD5r6w4EIzwRavY8e4kc1XIH0/yLqyECkYwILkFMHI6V10KB1Xjt96X5Zn/owwRdxlFryES/SODzx2FYuKNSOxkz61rrs8q6Bf2bO4pyez/nGB2SjYTQ0sovrctzO9ILXRIu2ubT2B3BO/ZNolrExOtyi+g6pNHHCc+XRtdL5oNM/GlH21Guyo9bb6wJCyeb88zRhRrD1IyzVpjB9sRliZPsCuVxOEd8G6iJ0lmAq5X/k3RcDg/u/W0yo7Z+MelrFVPO6wsgdDN6w43cTegfCA4phNf9uFNCL4ym6V2JCrbx89vANIrBfGDu8lobaaew==","layer_level":1},{"id":"7e383bdb-11dd-48d6-bd47-4d350e2df438","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Core Libraries","description":"core-libraries","prompt":"Create comprehensive documentation for the VIZ CPP Node core libraries section. Explain the purpose and relationships between the four main library categories: chain library for blockchain state management and validation, protocol library for transaction and operation definitions, network library for peer-to-peer communication, and wallet library for transaction signing and key management. Document how these libraries interact to form the foundation of the blockchain node. Include both conceptual overviews for beginners understanding blockchain architecture and technical implementation details for experienced developers working with the codebase. Use terminology consistent with the VIZ codebase. Provide practical examples demonstrating how different library components work together in typical blockchain operations like transaction processing and block validation. Document public interfaces, key classes, and their responsibilities within the overall system architecture.","order":3,"progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/network/node.cpp,libraries/network/include/graphene/network/peer_connection.hpp,libraries/chain/include/graphene/chain/db_with.hpp,libraries/wallet/include/graphene/wallet/wallet.hpp,libraries/wallet/wallet.cpp,libraries/chain/,libraries/protocol/,libraries/network/,libraries/wallet/,libraries/api/","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-04-19T22:31:11+04:00","raw_data":"WikiEncrypted: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"},{"id":"a486a2be-4bcb-4205-83fd-2ab48f89829b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Data Flow and Processing","description":"data-flow","prompt":"Create detailed documentation for data flow and processing patterns throughout the VIZ node. Explain the complete data flow from incoming JSON-RPC requests through plugin validation, operation processing, state application, and response generation. Document the transaction processing pipeline including validation, authority checking, state application, and fork resolution. Include block processing flow showing how blocks are validated, processed, and integrated into the blockchain. Explain the observer pattern implementation for event-driven architecture and how signals/slots enable decoupled communication between components. Document the data persistence mechanisms, including how state changes are applied to the database and how fork resolution works. Address performance considerations, caching strategies, and optimization techniques used throughout the data processing pipeline.","parent_id":"8aeac580-587b-43f3-9292-e62e4d3781f7","order":3,"progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/protocol/transaction.cpp,plugins/database_api/api.cpp,libraries/network/node.cpp","gmt_create":"2026-03-03T07:28:18+04:00","gmt_modified":"2026-03-03T07:48:50+04:00","raw_data":"WikiEncrypted:PGdHQOrMWxh6s6galmzx60G4c66k6IPhWJyX8d8v0xn2Sl7SJvUGzCeuElhJ4SjnW1f5q1x3OM20oMAyLozbgKnw1geq3fLXvsMfIZaJytjp20ZVI2wKYo7M9ca6xvMqmvLCoFjV2Z0kELRuZLvDkmrlYmvgiiF44ET+JaQD3Tg6UnMB2AfY+GkjGaMezQa+OwIe8+/2009s6DSnQ122sSDAASlZ4rQAQgkeL7LK6gVM4lYmwTx7H0FRKgvN56jg92nJt8Q9ssKuyC3PnuMRtdmLRDD9GHl1UjneLNj58vnJAWszzHCQBtNk3VjHNP0deezRuhtgxwC+tewqd/JQSDdmqyXoyJE348rcbGaYl3xaUS1p7UrDoI32vDeIvbkTHgU7k5h+7g9gk3haKfN9RTzmsbqoL4b717U38BLqEOT3exi9WZkIorQW0NF5G6m0x9Q0Tj3SFO79eAxNuomk15MhXHBdLOea8nqT47FcO/+tqaorLLAUsGEH+NKXmbsnYD8mN6BS9AiVn3+3XJh5WvCSNOMqv2BkONJycxEao4fPiJe3Skv/lMqF2C5EGRNx5VUDJCHu5QSu7iNcehn6sFKJz7hJ1MvCwwEmlcnZTHZdCnSBWIGwj45ejQABCG69LOiZAmidK5KSRBFFqPcIEBWLz8Gjz8nSvvj/YOWHvnBGrJhJx7RWwwBisYRsAy8Z5cW61f+KRnOeiEN/pAz0CVwH/qCZ82Xoa3zbJhxDnVobI/YZxB38kzyIhZzD5f3TmRsWdYpru9hzJFPh4gEZUpsJG6MybcNpBh1TM2pH6c74FIjPfAU7ZiWVr0OrgabTZ7XlHJ7+uB2xNr3k8VzkQVf/V1AHJd8L5ZyI0pt4Dp9E0Op0q76xdp5vQHjmpfTZlnsM9Rjo/49Hsvs8G6a3fnlXts9G3Z+xo853HjLZVtYt2WD+9vjO7shN4ANJp6vwD1LpJWWEZMsRZT78kU0yIH7hAENi2GDclKPltKOA6xqDqNuz9cCyWGu/8JeX+SH00trq7Usn6zm+KCEs27c/T/YH/t93FMy7TOSXt3czenAk2eo5Y0Dwkfg76ikGVoLpj3QrHxmeFx/MCun8RXKjooErmweSQ0xhlAfNLUGdhsWcjsQQoA/g6jyIIfyVzl2FxuT86yu0Lyx13sc7HSif55cl1n3VweLstcitlhCQxCXHY1XnzOEuvylz+3+gqE81pk+e/EtYVpCOAnImJV56kOfkVIPHWVXQT/GazHgAtiW8XjD6WTHkeY/+kVBRMra0MvIz57qKdAw+zFgMJ3LXK+SybRY5ANWjt5RA4Un4tPdsOSE8XBkhZz1UFPkW8dtXySJd9Byr1XKqgUI1u/e7477TafcC/BWJNjeUIcIs4Kcjn+tzA1/wqJ4ImMm97/9cgv4QoDcxTPvZMEWmAKgYbjCsq5JuTsGWZeUd4gLTRYNdZMfeywZ1gARhSkt/BswMFAZFgL7DVbDgy7m3lvEz9l/U2i5ea7PWJv/KkYnXxer6DthsXvkHn+CEoQ7h1S9z3RNXMYEE8kZ+GOhOJib9twOdHelHqFefxCAoJQz7dJL8o9cmHq0Fr2WSTuKvcAvfX8iUnrtB1ur55PFh1Ub21t5HcA+Ofm44YsMlcT+G2k6lZsQpZP0Shrhe2kxei7508e065lBusZAeDpXsqIBC1BdRcJ3quwypSsS4SJyHeLaz4PHY9ubLEL+opj3D8JWdurRx8fFnB2+u1EiFth365AXKQR4baz4MaYaGxaJ4bBc/KzURVnTSI1Ysbh5eQQKGjFu4a7r/0sEw0E8tlDp5iw==","layer_level":1},{"id":"9091fce8-eb05-4150-ae9b-eaeb0c47be15","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Development Workflow","description":"development-workflow","prompt":"Create detailed development workflow documentation for VIZ CPP Node contributors. Document the complete development lifecycle including code style guidelines, commit conventions, and pull request processes. Explain the plugin development workflow with step-by-step instructions for creating new plugins using newplugin.py template. Cover the continuous integration pipeline including GitHub Actions workflows for Docker builds and PR validation. Document code review processes, testing requirements, and merge criteria. Include practical examples of common development tasks such as implementing new features, fixing bugs, and contributing to existing plugins. Address collaboration workflows, issue tracking, and community contribution guidelines. Explain the relationship between development workflow and project quality assurance processes.","parent_id":"d896ebd6-7a89-4c1c-a16d-8acb2a61bb9c","order":3,"progress_status":"completed","dependent_files":"documentation/git_guildelines.md,documentation/plugin.md,programs/util/newplugin.py,.github/workflows/","gmt_create":"2026-03-03T07:28:48+04:00","gmt_modified":"2026-03-03T07:48:38+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"b52a3e7c-7bc9-46ea-afb3-610c7430eb21","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Network Configuration","description":"network-configuration","prompt":"Create detailed network configuration documentation for VIZ CPP Node peer-to-peer networking. Document peer connection settings, seed node configuration, and network discovery mechanisms. Explain listen address configuration, port settings, and firewall requirements for different deployment scenarios. Cover network security settings including TLS configuration, authentication requirements, and connection limits. Document network performance tuning parameters, bandwidth management, and connection quality metrics. Include practical examples of network setup for different environments including private networks, testnets, and public mainnet deployments. Address network troubleshooting including connection issues, latency problems, and peer discovery failures. Provide guidance on network monitoring, bandwidth optimization, and scaling considerations for high-traffic deployments.","parent_id":"6ca6368d-78f2-436a-89d8-8e2fa9c3d925","order":3,"progress_status":"completed","dependent_files":"share/vizd/config/config.ini,share/vizd/config/config_debug.ini,share/vizd/config/config_mongo.ini,share/vizd/config/config_stock_exchange.ini,share/vizd/config/config_testnet.ini,share/vizd/config/config_witness.ini,plugins/chain/plugin.cpp,plugins/snapshot/plugin.cpp,share/vizd/vizd.sh,share/vizd/seednodes,share/vizd/seednodes_empty,libraries/network/include/graphene/network/config.hpp","gmt_create":"2026-03-03T07:28:57+04:00","gmt_modified":"2026-04-23T12:16:49+04:00","raw_data":"WikiEncrypted:4+Fuk8VC5PKnWV6DzNqOvrllkIB0x/Y09KpysLC34yFhb7td6D07Ek5j0cWM9hZnO9alMCMAzlsb4dVhLkVNggd+xajkuZtBIv57trKyz2l+DK7BDepUU3rNXoKg+NgaH0CF8q1amMn4nxfRmeFmsDW/nVl0ffNbTHWeFwTR/H6Zavnh5MdS+zWoWJeUneBzhEXC+D8/IbC+Z3BadZyCu4kpCNjiQe04RqsGA07qF2hxxK0TBtBaatb89Q4F5z0x8u973YA5SRTFpmIH7emE/wmg8ZUng1wD6ZzmBZsrJiHF79ZXgmRc110CaWQMXQhhmNFanrkV2Np+Qu/oEqNM61qwQQnFI182QdoEbhw46Dy3ZHJyN/BLWaOcHHKUNBD+HFaN0lSiweaFuNzzP+drf8uHYEgrM9wGwLLyyqkp2PyzEfwrGcoiE+ecIsUPQ7uIm5K3SBBLrfq+AipkE0HYnUi3HbV3AYhU0KiZFRP3QGY4ta8ThepFNWaiU33Tmufoa9BerIiZOBJ9ylbsfHbCnH5u45uwGXqwAAjCD7ckfB1fJX1CYeYoNJBI6B3EcSHEhgGloOjEG66zvVi6RRoIwXKbxlTrLPzP46t2W0X+bkptabEbxh+lemk+sstOJ4MeRmHdmJCHaiT3hz8l2tmmVndVHG5PrdQ3mebu2JyoVR6HknXAUc2aGRNXOVT2AP4NRvfXwR+4AWfzU+bGctUOD9kWfksQNOMXUzTfaJjPpW2VQ3oRIsf+3jWekz4jp7dsC4xTQopE6iS+Ys6YktD9E2bXPHmOLkd8BluUAfLPpbkAgovfVycBhMN9qeFNmFmiIm5zwKU666d306lEdb7KBNSIa+jPfrpm9mGhEzHz1g6/+ZmauPqWzomeMrpRYD//AeuuzryF2sKflU57yI54Pf/6xBpsriuzOqpbzd85v9KZmebry8c6iFf5wAuHL/I4SNTeB7T6nDxdyeRNsd8B6AFEqkxGiCjLIb6+jhMBoplfjGhsb5objkEedq8s3fSL0Ck5OocE/2O4VlWdl44qRMGz7Q9wKbkW8/dLoqgIudvixtnxDyEeHCEUc0SsJDmbp481U8m7Xark7BIlbQ4OmyoDguRTZL2CXo1GLMF35YiyXf3WlH9ZF8BQXuzAGBJMwRpsbz0fFUd5iwYxQCfZiH3wxZVfCaFNo82RbBwr8ey8hjlNSzQQbIRF5qkZSTIRXEgXR3lZ84cYwivXEYrhSjPpaWs2b/QX2a1ywr8TdR337PbtyD/YOxPljo3CRrkZiFXEje5tGHWLwPzklghy8iUvroMLpDykggsbLa1x+Mo2le9SkrUYsdKR7nQi2sKqRLomdw37kYaEYi3GSr/dS5muQLX/wzq6ETRHQWlQsvDAXCmW7NmS3lDb/plqc+L8Ry7NGG92MLdsys92DBKvaYsu7yz494LrZ8zRYZ2EE5Da7Gj3oQX2PXihZZ48tuNgEhmsWImCt846W3OKfkzs8DF784cx8WYd9sbUiOSSRTxgdq1Njz8S/6w2NKMGopQZGoiQd+Zdf3dqZ7J0MWXtJvCKVCBfEE2mvx8rwDhIog/W1E04v1WGDS1pcL89tKn6ivqnSsZFmdeHtZxY1X4SfV+HGaS2ZzYuOAIt9FFC2ogcekNOIEuAAeX5Pqb1Wsir83YWFy09VsW0DXsee33shnKdErf+ydnn0IapwIt5+0fpaq/2lOO3vRVI3u2uMQvjhkEqJUeu4X9emqQSYX5CDaYa+TCmpoxQmeZRg/FeXT6m6GGchLslIUnqlmOCwG1sARNqQsWOg8mSbGdHNxpEdkGWXABj8FrsRXsIV+kISaJrRTjyHkjYaxL2QEH8byZZE5O4afAnlyPSHwVnJAEXe1Crbkim1gKNl81OLtxD4uzlBaX1uZvlT+NmBmnY6T4f1yUAg4P+wnGhF5X5GIo9QRsdAncKl2aJBS2xJ27pl0KimIR7srzZ270MUz3C5NdgjhDxYM8q44jFrfKWrm5RUoCr1svYDat5S9mq+5riP+8R+JG0PVuCfJWhSEezuQMXCV1pUN8s9YDc8gN0Z+wCWBmhL/uTfwcWAizLzsiPtXXy9cnbrsR/7AI9hgc9ualt7W0ZYA+QHAVK00IN3qZgMNcTxyTsX/IpG1ecQpvuJbL1fE3dk8nalp4uBXiuEfQ+citeXZXberg7FMYJRaSBrUevbqfbkVyI+B0yCTd9xNKhnlt7elAQZnpGdd0LAlHNPISf3VxQWpJWJ24v9qMhVB0fK+QxAGYZ7pA9jwn2PZDnpshXEIXq+HLPj7oE1kaQ6oaMCmxVpkFjRyNE2osDGw==","layer_level":1},{"id":"bf5ec7d7-d376-4b39-8781-5b76b3e90e2b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Monitoring and Maintenance","description":"monitoring-maintenance","prompt":"Create comprehensive monitoring and maintenance documentation for VIZ CPP Node operations. Document health check endpoints, performance metrics collection, and system monitoring integration with tools like Prometheus, Grafana, and ELK stack. Cover log management strategies, log rotation, centralized logging, and log analysis procedures. Explain database maintenance tasks including compaction, optimization, and backup verification procedures. Document performance monitoring including CPU, memory, disk I/O, and network utilization tracking. Include proactive maintenance procedures such as regular updates, security patches, and configuration audits. Address incident response procedures, troubleshooting methodologies, and escalation workflows. Document capacity planning, growth forecasting, and resource optimization strategies. Provide automated maintenance scripts, monitoring dashboards, and alerting configurations for operational efficiency.","parent_id":"ea5be69a-46ea-4304-9d86-597c6f7fc254","order":3,"progress_status":"completed","dependent_files":"plugins/p2p/p2p_plugin.cpp,share/vizd/config/config.ini,plugins/debug_node/,documentation/debug_node_plugin.md","gmt_create":"2026-03-03T07:29:04+04:00","gmt_modified":"2026-04-21T15:57:29+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"95b482d0-a7d5-4c95-92a5-19cfbe2967d6","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Plugin API Design Patterns","description":"plugin-api-patterns","prompt":"Develop detailed content for plugin API design patterns and best practices. Thoroughly explain how plugins access and interact with the blockchain database through the chainbase framework. Document the database access patterns including find, get, and index operations demonstrated in the chain plugin API. Include concrete examples of how plugins implement CRUD operations on blockchain objects. Explain the plugin API surface design principles and how to create clean, maintainable plugin interfaces. Detail the template-based database access patterns used throughout the codebase for type-safe object operations. Document error handling strategies, exception management, and data validation within plugin APIs. Address performance considerations in plugin database access and optimization techniques. Include practical examples of plugin API usage patterns and integration with other system components.","parent_id":"64b99394-2424-4a81-ab2a-f41d1b9e973c","order":3,"progress_status":"completed","dependent_files":"plugins/chain/include/graphene/plugins/chain/plugin.hpp,plugins/database_api/include/graphene/plugins/database_api/plugin.hpp,libraries/chain/include/graphene/chain/database.hpp","gmt_create":"2026-03-03T07:29:09+04:00","gmt_modified":"2026-03-03T08:05:51+04:00","raw_data":"WikiEncrypted:ukPV2tcWPRGn89Upsee0BLgnn7JYGqI+7BDGQTVnjuRHRVrSL6tQDGezspM53gdtUsm79POkhFGbMKDAc/J73KOr8fv9yxwFP0VoRuKhzfwxYc3cuTurCXWgzU9qD33UyKcWZq5jbu8gui6I3BI09h3x8uoako0lZEJvMc987YbO8DFIAMI186UTX0GSkXJAiB+0tTnW0lK7Q7CyvzRlnJ4uvexA1xc+EZhQapFn0hFfn3BkqPvnFiPnhpszja3qVmCu0yxr5AX+OzkI9YrEHhMjnuc8KfaQdW9VHAia2WrQVVakOkiPAtBRLXw1SRpQtkxf6KrABOJaLawhkAo48bB2LOZHqAGu3tOxNEc72vQZfevNODQw/4xudcgAahRGx6P8V5QbfRG+7ZOzoOWphjKBaflPa3rNxq8WGsAT/7iITXZlKmwpq3pzl7blf9ohNDJ10/MGhxEznX6QxuFW4DthLR5kVo5FMpMbegc2qceJcr7rDsOVLLMyn4iJu3UaiybcuK+TNOvl/9FepZrihUrm7Rs1ztzDJ4QcscsNs+neY94/12m5K8AL6Mu2NzlcGwModzUdwsbItYtpPbN3vRWm+Nz4rmPUAYuKtui4c9vNm4avqaez3/tk9hTuof0mO/Pm7zStNDgQyzxVojbY3EL8rrMEPcI/D3OSAaUkUGRG2fLtZsmL+d/ZPTlIBTsNmlndMfEYTDrKr5E+W+3IRTPIKlzPWVBGmWGxi7haFJl2kgSpHJNyeh0Eo+8iETSeKxxu2TDgSHUkuYIg3HEGgdvgYIr1qGaupyRUtoY/q5EFKf5BRPbM5yv+gz+JC83OqEu+q80SULYKezoBiEdC5INZXHTv6NQVWGEl+8cCqdbUhMXEry7NmKGDeMJk5zVvI7BNCOTSguxS0P3q8tOjLfGnCxbOsLD8pxYvoN/z/twkUIVYuDYD322POm0xjzJ4AlPQArTp0rc4KlmH4w85aBBZ/nQ+fE49ONoyT3SW+Cp0lP0zVFHQPxLCqiO0CyKyNngE9HLAHdlN06IdGeLydDYg6DPV12HBIEV76AMcRaUL/CEr8dMWl2JhqmOYzIcZ54x/28IVV6uLzeZqSeBF86UMoiqp9mlO53qdRZ389i6a43ExR/fsho0biQmT1pYOg2z+Bs4JH/7havhY3tRk5/juh8+qSjyoYYJPDsqohP+9mGFBGc1L7BHbT6vyiZ9TjHmRwy+EnJSkDz/glriYfUpFIaSLN57bLnu+/sljxE/9WpEvw78No8/Rg/u5o0zhUxaSohbQRyrtjDWxh+2oFpvfLxStjsxVLZN+WS79BCIEnYef+pRZS/FzWgoaufxJaHm59yNZZYUdUB5rqxr8ex2ShwGoxeOaNmHsdrwGJ8/1m+Vg3BIlGtSTACH+Yt91iRdU2djDMu3OjKI7UKic90Ym6lDwCsnSjtgRZFxOa2+iYkqh87X0eA2XH4fcVs5dZv1zzPVTLYPu7ASfJWHRQQnPxabds2CqBynETnz4ROkyvaSshSNuqU3j0juF0GxFpyUNzVeHf5innJ7jt+muwMjOeff+m8cKbx//GFVgPhrp+2SM89QoZShNDkyyy9BlP71G4v5udRQgl7xC4qFX3ukbL1gg7nD9AT9bEcj4e4FwzePHvt633V7icBSGr0pCiDbBVwZEsmQvAuR8I+Pk8cIPh9trrznEmV1qLVkcEN/a1Rd3RANeS1yOnW2kVHHkpx+vQQWHnTzGpWTeMe7iVK02bsiYRcPro0nLDjwy+huJfeJA0v6pQciFTorypRWxbHa44CcnT9pJNzwdIwj6Qw==","layer_level":2},{"id":"30dbc846-1f3e-44d5-a93a-988c973d064f","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Advanced Plugin Development","description":"advanced-plugin-development","prompt":"Create advanced plugin development documentation for VIZ CPP Node. Document custom plugin creation workflows including plugin architecture patterns, lifecycle management, and integration with the core system. Explain advanced plugin development techniques including custom evaluators, database object extensions, and inter-plugin communication patterns. Cover the plugin testing framework including unit testing strategies, integration testing approaches, and automated testing procedures. Detail advanced plugin features including custom API endpoints, real-time event handling, and asynchronous processing. Document plugin performance optimization techniques including memory management, caching strategies, and resource utilization. Include practical examples of developing complex plugins such as custom validators, specialized APIs, and system integrations. Address plugin deployment and distribution including packaging, installation procedures, and dependency management. Provide guidance on plugin debugging, profiling, and troubleshooting advanced issues.","parent_id":"7a20b53f-0b97-40ec-a630-7e9171a04006","order":3,"progress_status":"completed","dependent_files":"plugins/test_api/,documentation/plugin.md,plugins/chain/include/graphene/plugins/chain/plugin.hpp","gmt_create":"2026-03-03T07:29:10+04:00","gmt_modified":"2026-03-03T07:50:18+04:00","raw_data":"WikiEncrypted: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","layer_level":1},{"id":"e61981dc-5f45-433d-aff8-b4a293795e7e","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Event-Driven Communication Patterns","description":"event-driven-architecture","prompt":"Document the event-driven architecture and observer pattern implementation throughout the VIZ node. Explain how signals/slots enable decoupled communication between plugins and components. Detail the operation notification system and how state changes trigger event propagation. Document the plugin communication mechanisms and inter-component messaging patterns. Explain the role of Boost.Signals2 in managing event subscriptions and callback registration. Include examples of common event patterns like account updates, transaction confirmations, and block notifications. Address performance implications of event-driven architecture and optimization strategies for high-frequency events.","parent_id":"a486a2be-4bcb-4205-83fd-2ab48f89829b","order":3,"progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/chain/operation_notification.cpp,libraries/network/node.cpp,plugins/database_api/state.cpp","gmt_create":"2026-03-03T07:29:21+04:00","gmt_modified":"2026-03-03T08:05:24+04:00","raw_data":"WikiEncrypted:W27BoG2tWSBKbrlfVNVQ+n30XbiFQ0QQI5a6lxogiBPjTg0DvvbBFVxLISoEha9gga8YkbF6lz5DhmIKlkxVqSrOPfvwP98GUOj8LffovPN0x6FVjem0YCS83Npx+6Ibzq2LUJxesBU+qAtPQEe1+kgERFazI8jpVPIEA5SAQs6E+tVyMbrp+izzDgE1An3BkZcmd8WlOiKBTXmO+BX9+gxzlWar87g9CugYmsBzcZwUUzG+TnbkVV8m9VyiERgc7YdTtwBSORP6zbFpLpqimwCl1WGfM2iPx3fmKl9ID7Vqcg9sjWHFHETcAef3vIQcCHn5ZwelrdlGWmjuugEqhEclWt6FQ+85c+7FlrVA/SJEkPHl21tbyLcXRo59nelJ479auS1kaMRkIAbbLLJFA89pTxBHLTh6UpmQfi/PnJX0FHKAQG4cCXDLpbng3u9V4jiScS+kPZDrvwbi0pxVlN3BoDGiYiup5naQqix6syTGND3DBn7DrrN97NOWB6As3M2nXHR2NvXMUoEN1DnpOIMbilF3IRve9B+8ZJCp0RU5fbFQjy1qD+s++dLoQP21N1/cjEFHD1iF0WYvAaqrRk/uDYDHno+tqeQ/mx+ZPbJqVfsvxIMdcTs+jDXnczvtHH+oOzAEz+66ridRpqISPDgcfO2cbHbD8yChkTSvPCRZdWYhTuPGD9FMpDyd0jIjtOB3xxYvHoZkAxGV+nMX9hasGMIF7kwGkhH9rmrYSGjPP7cDfNwTwu/FBc32a47b3jkk6hpJjVqUgKaUQwVLAljvp0rQBygK/1ZzcxVW+BIMsTioFS1YT3txI6Yht/fkrclLUxfXo4emNwybErlc27SOgQsen8u8XVq6ec0ymghCmX2GNpzdyBcoGHndxCnjXNjuytqWC148K/gEYnD8uMDDFR/FDCpTamlo9TnjmLOonJ+/0ajAF3YrxiqACGbHvCrJ4G+yuWD0h8hICR+5PP8s+bFUs/yKn7d9kaDv15PD1TKsDYh9+8aUEFoagYbWqdVUqdoaRVqqeuMH776Y5FkUXnii32wBY7HhXZLhzZSyp1xuekmM7J84yOOK2tBopwNQRS686YPC8+JftwhlJCmWcc0+mYaWoDpSZu4qYzYc84zrabiZWOKq7wECSRjNq1So/eOnTsS8aflZfO3NPSmfrPeog/Q5TzrA9SAmy+fQSmJFuZG2EpQmwndvbB6e6NcyddF6eHMQSXFsIQmzRkclcjFw5DWeX1BZU8uYADfXYb9MLMIiQFqfi7uQoiSiLvJ1KCqNhvLB6OVclq+W/oceE/acm/1XZZ8ivi1Qou4VtLQe1CWJECh3sTOOdIZyP0UWlmqWmT9BUQBQ1G6l5wGdjTmUEhh/r1xHfJHHYuzX2Wgc+JRQIS8LKGWlyVGcRi8l44kHDiM9Clhn2uGn4nL7baAM0wRSOGz/zGx0WsDBvGqL0nVkDgCZJdy0t2Y8WCG0Dfft57uM5GvoSFZ6MOzhOe0koPbRUZA9gA8yNzup8ltl7rocXAO12CAW7XD3rCUxhffjjXddeBpSijY8cpDxEylvPQnGr4rJuIbCjuM=","layer_level":2},{"id":"48f0ee63-f542-40b7-bddc-5746b9b949c8","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Wallet Library","description":"wallet-library","prompt":"Create comprehensive content for the Wallet Library that provides transaction signing capabilities and wallet management functionality. Document the wallet.hpp implementation for wallet state management, key storage, and transaction building. Explain the remote_node_api.hpp for connecting to remote blockchain nodes and delegating transaction signing. Detail the API documentation system for exposing wallet functionality through JSON-RPC interfaces. Cover the reflection utilities for dynamic API generation and type introspection. Include wallet encryption, key derivation, and security best practices. Document transaction construction, signature aggregation, and broadcast mechanisms. Provide examples of wallet creation, key import/export, transaction signing workflows, and remote node integration. Address wallet backup strategies, recovery procedures, and security considerations for key management.","parent_id":"139b0217-0190-433f-b41d-60fa08c9ee5f","order":3,"progress_status":"completed","dependent_files":"libraries/wallet/include/graphene/wallet/wallet.hpp,libraries/wallet/wallet.cpp,libraries/wallet/include/graphene/wallet/remote_node_api.hpp,libraries/wallet/include/graphene/wallet/api_documentation.hpp,libraries/wallet/include/graphene/wallet/reflect_util.hpp","gmt_create":"2026-03-03T07:29:24+04:00","gmt_modified":"2026-03-07T21:45:11+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"19f906f3-0605-4d92-8d4b-7052dddc5ee7","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Cross-Platform Compilation","description":"cross-platform-compilation","prompt":"Develop detailed cross-platform compilation documentation for VIZ CPP Node. Document platform-specific build configurations for Windows (MSVC and MinGW), macOS (Xcode), and Linux distributions. Explain compiler requirements, dependency installation procedures, and platform-specific optimization flags. Cover static vs dynamic linking considerations, library path configurations, and platform-specific build artifacts. Include step-by-step compilation instructions for each supported platform, common compilation errors and their solutions, and performance optimization techniques. Address cross-compilation scenarios, continuous integration setup, and automated build pipelines. Document the relationship between platform choices and runtime performance characteristics.","parent_id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","order":3,"progress_status":"completed","dependent_files":"CMakeLists.txt,programs/build_helpers/configure_build.py,share/vizd/config/config.ini","gmt_create":"2026-03-03T07:29:30+04:00","gmt_modified":"2026-03-03T08:06:47+04:00","raw_data":"WikiEncrypted:2GoRefXUXXQ3Xwp7OyY9nlon887ydNfpOl/itiNfh/4iWIxC9TaxKDiFwjOVFwQTPZYi+aDZcmdacy3T3sTlNOPDniBKjwz+vMIh3cB1plVH4KYwPt2TZXc/+ujGFDRr35exKHL5nJjwwQ0ZMNpW/UibupcGN0tcn8JuX3NpL4WLBra3PxQ3XTSuZYZPVzLj82+fwHHCLPcEK+78OX8e1RcVEklR+N1UsgtET9kO0/b+T0OBqa5ERNN3IEYqcp0mY5DL4MgmhZ5ELHFmyDcLeXPGbENuTTr/wv73OYFptwxgOC7zfdnxEof+OJt7oFB1H14TF9NkYiMrMg21KMYZ7SpknlH2jxvpqfrAZCvqdyxgE59BG/VXVZPBrsUVrClVTeHDodIoKQCzDVpuGZCDEUcM4ywm7n0JaWJSvA5QqTFe6FlcGrO4kJP0nIgvoYFjCNa6wMEbFOKpK082OTtsHnoZAn7Ygz/SktuD3sxaFQutzYzGUpESMp+7KK6ArmF3MZD1kHYrkbx8S1r3t7TRMBIPvNVbc8GJDepR8ER6t3haV1sBwAGTuVTiBApkEg1+z2snamjx/DMbTOTZC0t9VeDuyQmejRJ2nfbysv6hUkQPWiiVsQjp/J+k69s9+Ux7cnj9DlnC7yP61FjH09kWx24MLREOQlg4aqdv02Yaf/0NhCugHWGlzPlZRo9Rcm7A/fYVCWIdyBicPqoNenizmIRw/+0ZUncKEzrvzurMeUV+p6by+Wl4x/LuusxxAJNHAjzKBMDLPygjAixvqHVGme7S3iJkX6GEWMaATXOA58szwt0/m65fztGuJ6QnWZOGheD2ELMTHbAgxv9IgkRcqpDmLp2VaP7YYU4hq0gze2HaeR6u4tAs0fHJZX1HORBQV6m/1mzw/VUtnolpwqE/TDgNPDjy6eYc/8e8OPu/PHoavvf4IzJOMbuw9Jab/F5w158jHfEt9txnl5+n9JhFtPecLcLLIKgnb8rnsbOKzT4D5ggU6rqH4FkXNb3bwpNtX3vMcfTGxzXmertP2Lb88uXGxhfMWPiAvWdUrJtNxnCQIUWII2pyHmrr1hLGTBSzE0qEj2a1Y4kVgfkKUkeDNwNCf97ufWnDMqThJcy6zqCMYqHJIq/hc32CjSkIzzDOQouvmIOPaVwL6fpE699lSOwB+RKRrfZy4TBjrmA++IxZO00dgzzHFOHnN2m7tOKpsMnGukeWgOno6BUIexnY0lI9oY3VNKLjBq1WOxOBwi2qClZoCoD+7QkbJHk9gFUvcuZdAG8QeMQcgeeReC8hK9BHD4hZPTReh0bkSF1wEfAbM6TrdmuRtEECtHx8ccNSDmA0dl9OIyKfqRhsIsVF8knLOjcFBsaRt57n5d82KEfGVC68CqLC53zlhwC0f+83i16CySJnkLCgNBbd15ULQvB0CplndMfsziHgwpuJqtovu8ylKTtPg/wamM1QLvqMB5t4DVEbc90jw7qwR8T2SBAu+WoLfmaD20wd01rLkYH4tYuEUmmUq0XbGQtgOi+rUF1mBwHDdrHFW/gr7FuEdUWCclH5mbo1cA7ExHaHxPYDAxlKWN7m2zuym9yZ+Gf5h3jBjwldO1bHH58e5oSV8DQciXvAjBNDP9wIfpcfNEKgnr17F/GNHOBuQBfTDOUGLSGiAqCT5hu+QpkcGRq4U52dOsMI7FjTQau2lMxTyaQ=","layer_level":2},{"id":"3c3c0db5-9be5-46ca-999c-68f9e09ed1b7","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Performance Profiling Utilities","description":"performance-profiling-utilities","prompt":"Document performance profiling and analysis utilities available in VIZ CPP Node. Explain the inflation_plot.py script for analyzing blockchain economic metrics and inflation patterns. Cover the size_checker utility for examining memory usage and object sizing within the blockchain database. Document test utilities including test_block_log and test_shared_mem for performance benchmarking and memory testing. Include practical examples of performance analysis workflows such as identifying memory bottlenecks, measuring transaction processing throughput, and analyzing database performance. Address profiling techniques for different system components including blockchain processing, network operations, and database performance. Provide guidance on interpreting performance metrics and identifying optimization opportunities. Document integration with external profiling tools and system monitoring approaches.","parent_id":"614f1169-202f-4dd4-aaa5-360a10ad6bd8","order":3,"progress_status":"completed","dependent_files":"programs/util/inflation_plot.py,programs/util/size_checker/main.cpp,programs/util/test_block_log.cpp,programs/util/test_shared_mem.cpp","gmt_create":"2026-03-03T07:29:38+04:00","gmt_modified":"2026-03-03T08:08:06+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"9e84e0cf-c0e7-4a82-9101-9152b572c82a","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Security Hardening","description":"security-hardening","prompt":"Create comprehensive security hardening documentation for VIZ CPP Node deployments. Document network security configuration including firewall rules, port management, and network isolation procedures. Cover API security including authentication mechanisms, rate limiting, and access control implementation. Explain cryptographic security measures including key management, certificate handling, and secure communication protocols. Document system-level security including user permissions, file system security, and process isolation. Address security monitoring and threat detection including intrusion detection, anomaly monitoring, and security event logging. Include vulnerability assessment procedures, security update management, and incident response protocols. Document compliance considerations, audit logging, and security best practices for different deployment environments from development to production.","parent_id":"4b6cb85a-4799-425e-9f98-9cd149c004ec","order":3,"progress_status":"completed","dependent_files":"share/vizd/config/config.ini,share/vizd/config/config_debug.ini,libraries/protocol/include/graphene/protocol/authority.hpp,libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-03-03T07:29:47+04:00","gmt_modified":"2026-03-03T08:08:09+04:00","raw_data":"WikiEncrypted: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","layer_level":2},{"id":"8774fe5b-dc3c-4a02-b907-d2a16b1df420","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Block Structures","description":"block-structures","prompt":"Create comprehensive content for Block Structures covering blockchain block format and consensus mechanisms. Document the block.hpp implementation including block header structure, transaction inclusion, and block metadata. Explain the block_header.hpp specification covering block hash computation, timestamp validation, and witness signatures. Detail block validation rules including Merkle root verification, witness validation, and fork resolution criteria. Cover block production workflows, block propagation mechanisms, and consensus participation. Include examples of block construction, validation scenarios, and network synchronization. Document the relationship between blocks and blockchain state progression.","parent_id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","order":3,"progress_status":"completed","dependent_files":"libraries/protocol/include/graphene/protocol/block.hpp,libraries/protocol/block.cpp,libraries/protocol/include/graphene/protocol/block_header.hpp,libraries/protocol/block_header.cpp","gmt_create":"2026-03-03T07:29:54+04:00","gmt_modified":"2026-03-03T08:21:50+04:00","raw_data":"WikiEncrypted:boPmx5CcA0j/DyoZ7HyhKvDO0uY4WRRkzNPlVPFfuYd8x5dUCvXHd/9oi/DxEkfTxFYD6bAblx+fQQtuLzg3sNwWhDQfuhCUPU0fy858bISDVVeq0bZ9kOv+N1cQafYJexYNmmOXHAtODv6aTvln0mZMaSSdpskonjRSiVDR2JFQ1c3ihfXZ9aWKOJzr9HkollwT6o0GKJ8eorPz1iLxLjuuquHSqCI3XT9AEmwcOaNnQShyTG0htuDWIltom40CsZcu7Cy8U/5nQzjC0xDOrC250aMy5seipxo8FAkVVpMX1XNBS38rhNMjRx2OKCppowA7cIOi2FHW6V4Q5vbu+mU6FUxquGMY6lbq3yu8TbvoMAD/QHS8UL7YYUa+4otdwFZ9QSkbvu5GvmAg2jjbbBzHknhWswLPV2t16urDqHkTqrEYUcmJNobdpZwC6FMjh8IS60obeoHCmUMLWxbZKLs/at3r+7JaWiPt+7xh26AoOr2jvjva8YYv6aSKWGoTMW3H2qpeMDiwUqtqWS0L/REzV01evXpTy69qkgUnRQcWi+FkXRKAwQ4p5veRT8OF23DmlIPRBd9CX73BxgYX+wQmYU/ki8y2bDeb1P+++vOAPh+61ADmkl1O1DMHisuDnaDiCFOfYhQoUbJ8x19MSmRVDwe4AU6+EZrxp8RKz8/a9GwAtETrMQ+90BijOC54NEEGnoN4nI7onVLXrah4q7IPCn7ZsDwfiTROpju69VldR0fx5W3mcW5cR5QkvQ2GxhzLhsLECXMlAfQ8rmumlR3p2VeIdW8e/P/S/ozfNjWZzTfhrhDfu0Vh0Y7Vl6w8M+CVXnOs/tC7MadUAhjrh+JQyC+HMohGmdO7/z9++33J1xvTFJVyMw0GFOMSQFY6Ep07yumrY1mvZl7qZS05ItAmaINydqZu3p6K+KExgwoNhGWchPC4vfZ8xBQ0VJoT+F8Faidd+o4SynyU1Oa9a4nDGc4as1CSHvJdKS7A/xthytbfgo5QJNtZqwsgpqQiSBar6Jt5wb0v34osxOvwjhLvuvDRZ9fuSDvIlOiFT7/h1jeTg77hn49LMeK3SSge8uSY1mhYSX0wFFmSTZ09o0BBULQWJpqNi47MKUitiopByHGADdj6vHInz2cTcAegHNkmJElDw3ojdlDsbQibKm8vkNBNR/J43aDniBItv5XH9lShNCczTfl0ZYuawpqhiTDLOq8QJy8wCli16PSF3Wwq61hM7hq6qXdL1rZfvRsnYBW5GhCSL7weKEaqDTpsk+IxRFFdhwJ1ncnSZGEOLXXOeudTkfZHk8r+YxgLkt0NzpdB6t3M6r975QZs+Tj+4ReU8QJzH2lETRvs5l///xaT2cUdzYBloc/E60aX4zonk7ZyYs3KJwXniHXQefJRk+RsmN1DbuKpwaEFPVI1OiUgtuetyAT05BDA7lh2VSvtej7xZtkMI0FejtchcpCcvJFuH1BgjPfECJKdDQlWhRzLDP+YDKXAP8z5AFFA2tzEw7DXGyBxKo8/9vajdN/f2CiX1x1ryNzep77p2dM5fyk5ZvgmBvQlK5wEf4+BZgjSp+c9B8mojotd6/I5mRMJ6pUqqe92PePdrcW+I4qiFA==","layer_level":3},{"id":"6f3f9c87-cd58-45c0-b90e-7c01d39d3188","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Block Processing and Validation","description":"Block Processing and Validation","prompt":"Create comprehensive content for the Block Processing and Validation system that handles incoming blocks and maintains blockchain consistency. Document the block_log.hpp implementation for efficient block storage, retrieval, and replay functionality. Explain the block validation pipeline including header validation, transaction validation, and state application. Detail the push_block() and validate_block() methods, their validation steps, and error handling mechanisms. Cover the block summary object creation, witness participation tracking, and block production scheduling. Document the block replay mechanisms for node synchronization and state reconstruction. Include examples of block processing workflows, validation scenarios, and performance optimization techniques. Explain the integration with fork database, witness scheduling, and state persistence. Address block size limits, transaction ordering, and consensus enforcement mechanisms.","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","order":3,"progress_status":"completed","dependent_files":"libraries/chain/database.cpp,libraries/chain/include/graphene/chain/block_log.hpp,libraries/chain/block_log.cpp,libraries/chain/include/graphene/chain/block_summary_object.hpp","gmt_create":"2026-03-03T07:29:58+04:00","gmt_modified":"2026-04-28T21:03:48.5839966+04:00","raw_data":"WikiEncrypted:R9i/29qd1Uv5xEgS1tKQyKa0Xuqqfm5cq0GV9nRcndmrxsHsnYwJpAewisCUpG2ti9GxrCpxWosUOZaN9wIJ7/8/157xbaOnn2BOhyeyJDPEjOCeYSYjSjv1dxVt5lXdexGoI1KQL06BJsml/tJH8IGm3rSrWyMIJMVaaR4ggiX6+4EVB5GRcduKv4h7YPDYp3XIn5/mXxo9OTYPUOtsoVOv7rHyTlUbejkiXURwQve9fLOeUlvOIkZF4R06e8IAaiKcWGIvFXcY2VrH3QcxUFJsp4kJhDzGK2FUcgBnx3qjBwarMUwBpmjy4qxtg9mcgmICpt9DkXV/Qj7CgjGXDq0ch298hK9VXiT1gmCEUwmXar2PNH/y9g8c8RiIUMVAxrUCV6mO2cnruxbtn4bQRHyC6sk4Ob2Xl2kVvZuIDBbT66aj/kOiD69bs6LpzdWsztvVvmOAFqak3DVJANV/su1oWfc4dZHy1TVF86Ri31HD6HaOPhqNpMApI4ZuTGA6AuOth9U/yvJQNw9eJa2uqHSJLovcqephYqd/UVvPQUwwoQmeMyQpQGIgDu02ksU/4/umYthxfnKr9BCJZueLWKne0Ib/cxoDN+QlTPByFNxCIyaHLleav2nztHstwhM3KkUtSO48JMcEIF4KuBSW8rzzIVZSm8sfhQ+Z18r/cGnez0cCvKI8I6dIkLNeFeMz/WwUgYBbFFnw9LMJ0u7OulBWbJ6hXLwmS9uQEAywjdogAEWO7ff6hfllQ5ZJjUMBmDO4drvylBiNbQFRrwKjyqKxSoR2js37uA4O0CBA/JESRC6UsjfeRfGNWa1l/rXrdgoGwwBrE8hT7iNKOAgU2x/xiS81GBLWaUdxUtzTAK4j0jIgecLdmg9DWIxP35A4grHki8nATuqPuMzK/XD82HPaERxvdoyjYE/kyUsP/mUfCwD/JXekHhftIDxZUJSqV7l/fDYPQ5ipe2jBnJIy7Nh4rpvA2BW9RJhI41asIZjiphshvPGZWGOF2GAyVwTANhAoRWizBv+itY1p/2WOckumF9RlmFiXVuFJzkCrNMyvjdvkdCYcu003ud8rO4xtpyb+J6jN5TNAoQUSO5fOhVvcHght5e6GzQlmQqJsmhjwCIcHq0OfStaGzE49mWjAcY3UjQhLQluW7CXA6G0qGQ7XoDVDML/QxP22XB8y8Ag/eDa38ZbtVYhXNIhVy9pWH8EeWIPbbGJv1UMvE6KF89Ybpgi9DmWvsZz6grqkqVw7Kig7sPtS0YJOHjCaHQ7LJmHCCudaZuZ2MF2TuMAkYW5yaytTWIhz0H77S/dQmzQmMbytfA1mqlMnBDDXtlT22T3++L+II8JkaX+0jTi1DTG6QXHTBQ7GcRuJfIF8CXjnJPbDSyUgXNRdvY1/Mn9CbPL4uVjXu90DzGhoKrvUGDXTTIzcHERZKN2/WCYDuD2A/kt/ky+a4Ifw0xSaifi271XU3geXOUUbdpVlCcV9aORTRkhN252B0skqL0oEnWgTxGYxoqA7NSU0021Y5Bd8/1cnNsVTN2vGh0YqLujdAfT3t+xV4modisvpwYxQ50Aj0ZJpcYBstxuDpOaAhEkLR5hr5XLkyAkl1niPitqs2+wSSsmDR9AUBpEY9FszPwk0pJOauEP8Jfr1d5BLOVVyTx33OYPcaq7OSFBB+BVeb2PdBWIQ3rXPJh4/C4vFb2Dm7ANtijqKL05LFes9n4IA9RpbvGA4TKXkeuQ29BEmtOSBPphfzyG9SQXOvTw7hmw5S3Kynu9fQaK77qhLq8/bZnW5czbIhiCfmycKKVJJWKkhDVDc/QGJZkuISswBtvOZqyJEogCnVHh20eVcvsGuV4ki3KHOcPx4rUhjTjXNpaaFzlwE8XrbFLZ8u2B/jB+4toP9H8MuRsWry/rKhQmFlz+dl20Ow/ic6jWDvlTRVljvVsPFvB3bDy4T8IKvzVoVtpm8QJHxIkeEhA7FGrf3","layer_level":3},{"id":"7b4370bd-7440-4196-bd4c-e441e1f693d8","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Transport Layer and Sockets","description":"transport-layer","prompt":"Create comprehensive content for Transport Layer and Sockets that implements secure TCP communication for network transport. Document the stcp_socket.hpp implementation for secure socket communication, including TLS/SSL encryption, certificate management, and secure connection establishment. Explain socket lifecycle management, connection pooling, and resource cleanup procedures. Cover transport security features including encryption negotiation, authentication verification, and secure channel establishment. Detail socket configuration options, buffer management, and performance optimization settings. Document error handling, connection recovery, and fault tolerance mechanisms. Include examples of secure socket setup, encrypted communication patterns, and transport layer debugging. Address network security considerations, vulnerability mitigation, and secure communication best practices. Provide guidance on socket monitoring, performance tuning, and troubleshooting transport layer issues.","parent_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","order":3,"progress_status":"completed","dependent_files":"libraries/network/include/graphene/network/stcp_socket.hpp,libraries/network/stcp_socket.cpp","gmt_create":"2026-03-03T07:30:03+04:00","gmt_modified":"2026-03-03T08:24:00+04:00","raw_data":"WikiEncrypted:dkYJ53aRvrNDaS+3NsNNs6w5If3uzOgs0Hbe29DiBx3mo/yRmSwkQRnMBRPnzoWGqPZ92OwU/caHUIgxDw3SIzF6G+l+ERyUQeM+5k8nlB13TwLGj0fVp686VLtYOr41/lerWMRI7r1nkNr74ZsTI//FSbi2G9SrvyKZ61XeHQG52M/cayN69rlGN09zTApYx4BVQSWb2i6BxxSytcKaDWb1J20q6BHyKneNmvNVZgCaF0rwcMeSSoByQylXwRDtVsY/0BwBP4rfWpBp67hPXAduIM1/AM21CdQgxh2c6knORVht0g0QmQsZw0NWcx4YL9Y1nzZyFpQ70i7h6ipwiSBFydgbOJPVkWTCSkMboLQVC+JReq+N76UfDkZcuNeooMsYPKVmR21KRI53d1ksrMsqJKpk8m/l2Gx1mYC8RPnt8hA5HK7RK35cQcnLx+tHRlGouJ37O/Ho3ZjGeYnuXO06taOqh4oHKNhC0CuTM7j5briN+qDNziKL3qrEagkw8MQ2Flhw2Ut609zD4HJIE4DiH/98snKwoFEIvo3HIZ3XOuQrGA8tqN7EGe7F3pG0++qZeOZ7cUJhnNWxqTeXGsLDH7yxQWtZyBLyvjGQX1xr/juOFCZx4bAzdWoycAfVlvjr2uxkBo6EYFbTo/IyJrXg5VPan51QhuxjTDEzVNWlSwt1lnBCnuOCKwHgBdzcSJgJH56iYdia5kRnlRiLeBRmHyIE169xvgvBxxlrH1JndEb33mw9trClKcuQpM2pPzVA56qCbcbiM2Z44kdqKDr4CSG8ooHl6voaTHVMpIhfDUSmE67NTSyHYiI1dd/85iVQ+9FfNfoASUtTAXELUUYQYvzHMPJiU3MEaD2YcNWgsKvzjxN3VN4FTcmw8L40grY9Y82P4hOZiWDDUlAMLciKeF6y2t266s4DZ8JCnab8osDj8QF+6KTb6kkeqFJ3cCCGI0txYTS54YjlXZcPpiqHCPE0t9FvIT5upocG+rYGTWehyi1JYLJWC274vsGtm5GxLvRg6mbSqE7tBd1v8Zer9fNpXLEm4Vbddd/Rl6Eh0qbzB1Tsoq6fYyLBGLyV2fbQav8S8id2khKvjPBzrSRYlKUt4MIeJoj1OPBsETpBlksb/sBe6zXuWQl1xdv1gj9XrQNqLeVVV6nMW0llZzTx7hDht3JryMwsQg1tnfcOHSIT8O7jb/KR3IsyPyzBwErQdZ401BUyu+fG/uzA7BXgy7/APjoVyikPtgFONSsd2LohWJ/+o/xmuwCFIM3eNoMIwhCsFA98lztBOiM9foR5bXUo85+w7OvJTct69ak8sO+4KO5AH5/Tz7uFF7T77SRhUeDI7YjpD+Ik9HLNsoGKRyr8cbNLyac/UxKH1U4TKLDk4AXTIImmwF5rK8DJ0f5l1TNZgMaUZ8DFXO5vQClOfyqFxZWCt6klaU44p54Zp3LCMzEJnexfBmjaSv0DcVFHG8qLi18z3+qLjoEnRpsnnMHegrThAgDc/30MLr7k4e6iUcffJUilYu4Pu1Sc42USdHD2FfKrNDoUSfokki3E/ZKIrFVGQvC8gnoSqAwuh0ioo3iHGiJW1O3Hth8vbhUnFatBI/jLrXyzAFrOTkVzWwxQemSuJIF6jvWcGkgiAogUGWvRS60wJBSFfLyUdAMPIXDDve4qPvZ3uNGSNHXFN5w5oxjkJr9zWEXTyWOpAcb+j7G2l3FtGxwEAfgiPCl3sCAHm/4tpKr2gTx99oBrasStExD6+BpcjkMiFvcLVuwDnAuknrACPbPJ8vXTbEJ3TxUxzy5/jBlGmaFsNKC9zcFZfTn5AJGPPUGUziylI2Tr54IzA7ULa2tuHRctvQB9pah5y+GJCs/E0wSrqA==","layer_level":3},{"id":"7045a421-d36d-4058-b1f7-1eade1482cdf","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Build Targets","description":"build-targets","prompt":"Create comprehensive build targets documentation for VIZ CPP Node CMake configuration. Document the main build targets including vizd (full node executable), cli_wallet (command-line wallet), js_operation_serializer (operation serialization tool), and various utility programs. Explain library targets (libraries/*) and plugin targets (plugins/*) with their dependencies and interconnections. Detail static vs shared library compilation options, test target configuration, and installation targets. Include examples of building specific components, skipping unnecessary targets, and customizing build scope for development workflows. Document target-specific compiler flags, linking requirements, and platform-specific considerations for each build target.","parent_id":"317287b2-3937-4876-97d0-a8c96007d95c","order":3,"progress_status":"completed","dependent_files":"CMakeLists.txt,programs/CMakeLists.txt,libraries/CMakeLists.txt,plugins/CMakeLists.txt","gmt_create":"2026-03-03T07:30:07+04:00","gmt_modified":"2026-03-03T08:23:32+04:00","raw_data":"WikiEncrypted:ZHd6gDb1oAMJeik+Vv5fPmYm+3RvozY/gdL9KOhG1Gzli6r3m1Qs+ajLMwPD/vJq3bXqoW1huXFI4mNN8ySzLIQHLUimXSbYEWathKED4BP6kZB18cGiRNQidMb8cyrDV5Iuqrq7w4IXFOrP49KX1Fj+mOjkDg3HSykKn6cAceQlzfAz0DrSk7eNO5hzUQqSiElf3OV0v7+fK9Cm4IQq0058KWBtgI5xr1ykHT0HpoUy3JFULOqohFCF27f5ON1OWmLfAvFSCDxaw0Cl+BhogR6z4GT18ewS7uuTNy2PkJ1ia0uNOXkxMjsSSmmup5HxBMXdZkQpdPZNEoZL7fDXqwhfd98C+eejOh3Z4tyNJtppazTRl06TCmo9zBZKQAczNueGCUBgz5MmxlMxulgT+9hXgu1cWG4XA3eJ96P6ZBxnW25xp7PSSH9JRkMlDmOcuC46lfmCQWt0aTtIS2ozbWrcE0ww96p2VsQ45j2rNdqMIpi1ulIJUOotFpc/CddsAlKtEVpExeGMLcSkX76K3NIA90qoVwWSndANNc4MJ/8bh9ulFe2+uzoBBu3xinjVvUlbOCjyNGqFd1jnMZtnt3L7/t7PH0V9q2SOflN+SgM9jVRVitER45lZFxsqr/tav2KGrocQE/xxUOIjQIC5CMvx2HWPNHn7xRZU9XceW974jpXKFvL5kBh7vnmnsKJupnB9t42ya5cABJD3AQu6Z0j6CKGPS2/l4uzkYiQ89k5SPmbyPik64SWmpi1um5U9Kj7wI4cWS/oflhQlCNAmvw4uvDwwljAA6qj+M6gLAanR65d03Q9aDTAjBYMVwGmOo4abkQv3nf9TCwKxKDVFAs5ZcueELmtGXDiVHfj+aZxEo+t86mMMQ8g7u+kNm84EszGt09RKbBsjXslT7yQIutKSMsZJpE3XeEFYyhNtdvcx4MZ0/0C07WBxke/L+xeuddZajpKMJ3dJJCBkwNkDH9y1gQCS0cFOpuuviGp6SNKtCxcvsgaWS7wBAW+uPcsoMQE0llh0xjKnOnATbn9DIx0tfuUk7ESBuGr0VaKB9nfzTlBLkjl4PToRWCsnt84L3d+ueb44ND8vu7KMItz8QOKO4zuDAQv3ATK+TZjQcFqFxXPqpOu6tx2+SzU9U0Tm1N/U3VMkQgn+Uz1EBAQjxtp0HMRc8ziGnewPSGizlOykM6jUEZhJPFrMxDefENfoQxGLA+l6IrDKlL/lo86+eCXm4vFSHWEalAjeApSnERSF0wPuU6fm9QJcUdlhu7YfBgb2KoEhKQv99o5kzzGAwEO48dvnbGMlT5tg4Wy00FCskJlqYfDQBVxLTL24kNHfDTz7VM3JENKxyK0FPq8e+xgEtZrVx5vIEPzsppJ7Tm2JvwMNsgBNzTTVrWdB6GFc+ykwf9X+NihR8xU80Of6zgl/EQLw4MGr687/twMImC5bbwRa0EN4Pnpm+wfGnxag1dAxyCl7pFPa00FsMWfFsRJfEMTsBtYzA4fj+y8wFZji35gM1dsWtMer8ZNiTk+A","layer_level":3},{"id":"939d7089-2486-4f03-bd3f-c35c98d5a1db","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"MongoDB Integration Dockerfile","description":"mongo-dockerfile","prompt":"Create comprehensive documentation for the MongoDB-enabled Dockerfile variant that integrates VIZ CPP Node with MongoDB for enhanced indexing and query capabilities. Explain the ENABLE_MONGO_PLUGIN CMake option and its impact on build configuration and dependencies. Document the MongoDB plugin architecture, data synchronization mechanisms, and performance benefits of secondary indexing. Cover the MongoDB container setup, connection configuration, and data persistence strategies. Include practical examples of enabling MongoDB integration, configuring replica sets, and optimizing query performance. Address the trade-offs between traditional SQLite-based storage and MongoDB integration, including storage requirements, backup strategies, and migration procedures.","parent_id":"3789801b-aaa1-4c34-a35a-c5b48338520a","order":3,"progress_status":"completed","dependent_files":"share/vizd/docker/Dockerfile-mongo","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:24:06+04:00","raw_data":"WikiEncrypted:1goLeoGXOcr+JMF7YkmJQ4CA5GfPsgr9AOqxshcHKh0QXHY1oeNCF9v12XmDZHZdXhCy259YG0ezOu7wCXExHWK8rq0XwOg0suG4Rru7URghgXoS9VEljbRd1sl6vaFJhSLystxSvjxxzzlFJvD2tYkg8LaFtAmMvb8rZV8Ez/vZCMyxbPhKJ/eYVSciU7pmsK5xcHztFCn50vpB2r+Tj7t3gDP8WGYotYg9Jf5I0fqC3CInBw+7+Fy7HWpLVUIBIaIjozAoDMsShFMBja8RpTufBS28/UhItMRKddABKXVEZXD7SGIpEsoJoSREJ6yiSO+EmTw7r4tt0ORzmnJhtT97Bxk6r9hcgLBH9wNaggWiZSGB0X7RZSbzLF4qGNJ8kk/Dku4q/6vMsh1tZlnNTz+xeJ9Qdd4d6UNqdaS1i0rQHV/TauwScbWmVjrmaU3qQS5CwPasVE25WCIbXplFghy90JNbFQvZqI1Et3VDOQTiGhrW4WEULb81StceIyHZHPYG0KEkZ7RSnXUbIn1DARL6HzgoHsunnU1r15BIcy9NKrxjvCIQVJHpNSkt3Gj2AKDIAd1VCYM7KU4prw0cF23fcCABWMrCPPFmkFxsp0G/1NATKnFi56Km0ARP9qM78401kh9C5AVW7pDJ3akRvievrgrCd8ffmyrxqPThxJ4FWDo85X5RFbaaRiNj9qln0BKGqrR9t2JF2OHebqvlzwoROLwdN5DaJazsk9B6QVquqRJZLV+jBuJ59vK9g1rpdU1oBluHRJLvB4PZypWLlTVg5Pf/v5jCH0gnPTpCMxPwfPJlxUc2m5hojJE32fkJeAkFXJ8Q2j9mbQAvuDxUCH1844Bhwc+O8q0ZzPHADKs8yp2RPbPIU9cDsrHP8I3rkRdRHQkSpzW1Ja7wDnR7rHFtaV81Ygr5h0DDQqEGeJRycLYDrvbgXJ++N8yk2pMI3NdVp82G4v97ldxc7pmOoyCtSxLmM2L0AVmLCiJfEIIelQtOfafD8S/dXBowqYXgsFsj7pHzEfLu685kww+S0Hi2Ma58ydkYzot32TR3SBK6Euo5mDirRCGoi66xck8Ao84maWlxOIZR4/27ZoQsTCnnRPuONwNxwf4IArp+RpIyq6y8pMDvJS6D9+NUNajJvmdC3ripFYOzzg6AoGuxRkUKXPGkJJeUxIU0YUv6UUx3/Ceh5rSpXlBoWnIrnMvY57waMMR24d54ElfvPd61lvpdCD6zkMGQFpKPL2fuhcyOZXVUzBhG0iHARrL5sDw/qpkjwUp6pMY8lWFvqInaAAfRcYJqSSxcTf5lSDQncMV16JPUH71IzFKM75zM6dxgi5dX1EbdZHeDSFX4sBwGdR6tMxyw7x2Tr59DU2XjpNklRUYK+t81jNTVBFghVIAHiZbZh0iA26/Pgy/WrWoID8+MBc/XNy4wtzJXPUzsy1notTsYCLr/sK9uptZZRYnp","layer_level":3},{"id":"75c2d99d-2330-4db2-a9d0-f4a9fdb902bc","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Schema Generation Tools","description":"schema-generation-tools","prompt":"Develop detailed documentation for VIZ CPP Node schema generation and validation tools. Document the pretty_schema.py utility for generating formatted schema representations and documentation from blockchain object definitions. Explain the schema_test.cpp utility for validating database schemas and object relationships. Cover the schema generation process, formatting options, and output customization. Include practical examples of using these tools for documentation generation, schema validation, and development workflow integration. Address schema evolution patterns, backward compatibility considerations, and automated schema testing approaches. Provide guidance on interpreting schema validation results and resolving schema conflicts.","parent_id":"d54afe72-4975-48c8-b825-ab792ce92a46","order":3,"progress_status":"completed","dependent_files":"programs/build_helpers/pretty_schema.py,programs/util/schema_test.cpp","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:24:57+04:00","raw_data":"WikiEncrypted:7b0rATEHF8+R+XhpZF8awJS9ucITjIW8AZ92lNvy3q9A2A2uvYfv6YpvR0BnMFsd4Q0ovn849WEZySYF9U7MEsIS00oTyk5KL6C0n6FXddBjWTUR5PTJ3ztnl9XxWJzEUhzMMtIdmAd1KyvimpOCYwQ21QpwY5paukgVnjbsw/0pQ82INNTEiOIwF7BlzTf1HYfL7szXIL0WkJpLMdLxt2h9C2FHgQFy60+AKUZg1pt5Jd3K/aBQWV81VNnDXW/LnOx1FPSPwkGkzssRmbbAg0nFhMWsx5SMLXwbx+0OxsTzcsEom77ZZm1yybS/5zIQUFiRjQfIYwJuzu6N5Z2LWfsWSHYqLWmQF3c6umAOf3r1wW2ZNrCeX9orBn5VMOJZ7l8GTVOJXCDCFxdnr1WXweN5VGwCS0Odt+Ron1uBfAzu1GAw58ablMKRKv9VS4SXuxnr2wY/CQOMRjsWbRtvI71HuFhd2RJZIc3eVAsDc1GU1cmrTeTbUnOpZCUhW1oFgJZ7zk11j0lDuC7R2fp03fFJsHDnTeTn+KjHQ8dMIpWtQ8Nit6XrZb6D7tvpAC0SvTT6/p2f7HZgovIfTijfj1y1hPiIdc1d3NgzyEntYVppm5pepIF06TVVx7zfhhiuG9b6HE5hLuEvMq3DS/4XMm90Cnv4sZnjEyT3TEj5mX4J7N5cBAgYCSGgcNeI43T1C15pXX4uGLqfoOPXXnB/SCpxe9nWbiABMh2oto/9Xz2rUuQGDrsSrvRXL3iFGHAh+0c6m//cPkbqieTn8V+jGVQl6W0JlrLbKYhtiihZ2TDx24go8d7f8PL0ejj90ChkWSBVvmztGtG5cLsTAM5lB21FF4PXM72PfYtGrOCtfIOOHZfaXn/3vJKykmXqi9G01zdxiUzn5JV9g4bjIq2pAeYD9x8XC/bIO81rmTigEbB4nnLKa1ElZBsSM0/70h8jS+5z7IliXyuhB5FqyvZwwW0xVyIOUAzLtk33DyepAdhyg5fBwgApQ/HT48joEV2rTs8N48kN92Kxy2Ye79DVSUvQzIQPTLZoQkDnUqCOageMGKRgFzb6cqmHCuPViGnEMOQf0++miVciKXzhicWHaoLzp+sFjN3yGJMY6tZVI1oh/UiPejOTqvAQz8SMLfS229ZyyilSHYo4cKKzCuR9NlRx1X211iyYLzcNfOdkZwdM4CwTPQMSl3VtUnvqTSZHwYv1ZCeN76rrKV5GhJGX/NxT335dN0vycUHuJLIn/NlfOsFxQG5XodPVwtnxZp7S6TnnU1KVuZrm2ftDmexkuoC+050w+4hBvFdc/wjSpNOWgIx5HxbDS9wWBlmnw0TDrlXh3YEqotm+U3Crr7DxCF+D4IgTMGhdtmJ1R1jLTzSqu6SKtryd32ymbDmRgf1/8MZIIPbWljiJyYH5UNsPbK4WhW9WLV2g+eXlh+1UAwBgpyWvaeXm/t1ctPrjmfXsAnoJTYnhyJ6upeg8GnfHo7Z9UDfIJ3pyR/8d0SUQ7usL8Nb9HXxpWxddOk9Evqn5","layer_level":3},{"id":"8fa79728-580d-4d6c-bed4-5ac9ef3ae8d7","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Witness Guard Plugin","description":"witness-guard-plugin","parent_id":"ad096386-ea33-44d1-b9f2-261f11fb24d5","order":3,"progress_status":"completed","dependent_files":"plugins/witness_guard/witness_guard.cpp,plugins/witness_guard/witness_guard.hpp","gmt_create":"2026-04-30T12:39:24.257028+04:00","gmt_modified":"2026-04-30T12:41:43.2873164+04:00","raw_data":"WikiEncrypted:JeTXIs+pfWQp8HpBYqSHUv6FCVRIRaRMz3xNyEW8d7EhJ4Yb9uJTUDwZy779+JnAHj6NWz8b3Oo5/W9JMIZSMgRD5r6w4EIzwRavY8e4kc1XIH0/yLqyECkYwILkFMHI6V10KB1Xjt96X5Zn/owwRdxlFryES/SODzx2FYuKNSOxkz61rrs8q6Bf2bO4pyez7gm8bWEf+++ptw5UQP1jcW0btibEljT/0SoZBuM1AxjryuZZZOPzzWNJtJDVCcWv1YBUW4p7Rqws38KwhEh7rDWU7mvUFwc/NQhS4bLJ3+I=","layer_level":1},{"id":"ad096386-ea33-44d1-b9f2-261f11fb24d5","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Plugin System","description":"plugin-system","prompt":"Create comprehensive documentation for the VIZ CPP Node plugin system architecture. Explain the plugin framework's role in enabling modular functionality, the plugin lifecycle from registration to shutdown, and inter-plugin communication patterns. Document the 40+ built-in plugins covering everything from core blockchain functionality to specialized APIs and integrations. Include both conceptual overviews for beginners understanding the plugin concept and detailed technical implementation for experienced developers creating custom plugins. Use terminology consistent with the appbase framework and VIZ codebase. Provide practical examples demonstrating plugin registration, API endpoint creation, and plugin interaction patterns. Document the plugin development workflow including the template-based creation tool, testing strategies, and deployment considerations. Address plugin configuration, dependency management, and performance implications.","order":4,"progress_status":"completed","dependent_files":"plugins/p2p/CMakeLists.txt,plugins/p2p/p2p_plugin.cpp,documentation/plugin.md,plugins/,plugins/chain/,plugins/webserver/,plugins/database_api/,programs/util/newplugin.py","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-04-23T09:46:06+04:00","raw_data":"WikiEncrypted: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"},{"id":"c5e155d7-00b2-4e55-811e-cffb5860ac55","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Design Patterns and Architectural Decisions","description":"design-patterns","prompt":"Develop comprehensive content for design patterns and architectural decisions section. Document the key architectural patterns used throughout the system including MVC-like separation between data (database), control (plugins), and view (APIs), event-driven architecture using Boost.Signals2, factory pattern for object creation, and strategy pattern for different evaluation strategies. Explain the technical decisions behind major architectural choices such as using C++ for performance, choosing appbase for application framework, and implementing a plugin-based architecture. Include trade-offs analysis for different design approaches and how they impact system performance, maintainability, and extensibility. Document cross-cutting concerns like security implementation, monitoring capabilities, and error handling strategies. Address scalability considerations and how the architecture supports horizontal and vertical scaling.","parent_id":"8aeac580-587b-43f3-9292-e62e4d3781f7","order":4,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/database.hpp,libraries/chain/include/graphene/chain/index.hpp,libraries/chain/include/graphene/chain/evaluator.hpp,libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-03-03T07:28:18+04:00","gmt_modified":"2026-03-03T07:52:04+04:00","raw_data":"WikiEncrypted:tQ/n3TmqqyhuGeI8lCgAb7KSHqqN817DnJ5UMJRH5GHtJ8pVzVm8JaQgnBd74Xbj+okvH8MWIi3BplUjGp8W+TN66zoi1/mlfcCeDuGofIiKOyWB50XJFq+WsJfgo1ctAWwcvFh4sw535YG2fcypZVny+ZkkAaQA9bWHw0Iw0WRIC1v8UMW0ZsnRFnFMazHV58XiOgrD57T9rX38701Mop2/gHcZ8wkxuR5x2+zO2mkR+s9bHRCv+Ng8uousHPf3ZoVgUuMSVMffDogOU5LpwGjgriLVWbpQhchAbYN25hbhxF2oN1eY64VFKEcd+VBahAXSfa3giCPxFzseMXKEx7J7wx0hz3iEHTCN0DV6+NRq+WzkZpLilXFcTNKm4NfGgREi8QfLUclSgGX91dmNw2QUMEv39EW/WmYV5P9NckUjbSJLmJcEdlhONJ7zVeohYzAIF8lZHmC/ZMSZJqlh0PEXVkF+98GBULqynKs+g3Nrq/e74rStbgi3tZp9CqjI9yoib10FeOOI8Y0CNB5vMBD4Vve4uRMYx1bC7HjHem+Cog6mqQq/MIx9u+R+pfe22pROr+NIH812GEtAQoAWwSYN0wa5cQXMUhKgm9luL1FLgIq91ar5Bsl8NoJJU8HmRo5YFYxWEjkTyW1Q8vmCIazHNb5/GwOgqoM9tVV5MGcMkFduitiUsnSymhAbTkRDrGcFcqd6F1fXcOwaST8j70q6U+kCQSinoVAI84gCtCHtWyO3vtgLf51WmnrZahfGx9JySFu3yiKeKB7+z2y4Bw6s7Vb/WfagvwtfOXW6Z8qh6jkUbUWtpIxsjHnqlvvLt1mIXNSE8eNqlkaoeuwGZXsz/sE7ADxtsZkoib635Z7zIuXwlknPgz3lSFOIPlxaI32037hUrbMY7bgc6S6JDu6PJkH4r9BfMnMtNc0Rjjp3OW4W+Ln34acxuvGGQLYVWuOPuq+5QuOi1Okuloa1728uxEWrqZurHJxEg3OXv90H5iuPrX4s9EPSak1Ewf/yY66BWyrLmRc88hYPw6u29ZaETKtdBgRmaRDXJb5Myrbo9ASrmMKTO2/WSllrmEQ46HxWfx6/U67RwXIMraeaEufvIt7D006eMmcCMQdtXFVVQRms+ZBovKGwZf8RMOnzqepqNWQVw5ppnouwT+YAURBKjCQD8Kb8eWnkU5bu75scKCvOKW8nNBgS+ydNPZ0soYt8cZKq4P8GaxPAgCd2AXaFU4e09bBvOW5TBeyWsRiYHdJPnhhhxdiixhQg/wF2sEj9I2/Qxuq9XNvhfCT6JFnIXVqUlOgiLsWz7z7UpJFeYEUk0FmtVDdUXeRHZclbbHL4/1/Xqyyzm6QxDZjjqDqlP+HGe/UiXUiwllmCYxCdYpXvwgNwgP9TUTn8xM62eySoCnBh0kP0TaMZNGznF4C9JzotgWOVAs3ZTHLOzJIv/8mJ2xS9rz8LjDaxfC2o/9bX3fe6hITp//WG/fT91urIwDixmD+NpQ1kHX6OBGodAHiRqmWsNWoTmIPyRW2F7A3lHMJZYTh3eUtQB3i3jeKy1+Ijwx/fS5CMooz0lNEZST6P/X45MjmYNAWHkuspRTE3uMM24N6bws/b/xxIY7OaNiHqb/qYOdjn4hqZNZFSVvXZQ8J3pHILbkfIcSMa1Z/+y8zzsqiR5I7dp34v8kwPTnXHAwPZnZzR8zb30Dnx4SVOsGkWIWSSVsOGs/R7pRQUEMMi1pQLQgxgz6DbzcT+AX/rK7doj4QzhG0/Q3o6bX7hlvwptJ6YmF+zCaiK/KbbKvR9bOFGd5F6Se0gPxiUJByGHsKfSc77VndPkWGbb6pctza7Hx5tzCdkD/HPdEgwJUB+kohQwRMYuHlc07FHVvy3KzcaR0P5RaPBF6tKZD3aXIkbcz0e/PpH8BixVuT9MiNcCOrX0fTM1rfdvVkNbmqkHzbyes5Lwj/V5Gt6hCFl0yg2e3pm040g5akWTHrt1FaMNz9A4zboqFXJc4YMigZfX9j0TUhXnukOkSpUlXPxhV88DDWdzmFghbrgrYlEGX1RjPCZXjI2f/MVn1EGcx/wFKX1VEe/kGy3zPJdSVWnbVtuLU2k9X613IHW","layer_level":1},{"id":"f6b80222-aff8-4f40-9721-fcb497d3cf84","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Data Types and Serialization","description":"data-types-serialization","prompt":"Develop detailed content for Data Types and Serialization covering blockchain data structures and encoding formats. Document the types.hpp definitions including basic data types, smart contracts types, and specialized blockchain types. Explain serialization mechanisms for operations, transactions, and blockchain objects. Detail the operation_util.hpp functionality for operation utilities and helper functions. Cover variant serialization, enum handling, and custom type serialization patterns. Include examples of data type usage, serialization scenarios, and deserialization workflows. Document the relationship between data types and protocol versioning.","parent_id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","order":4,"progress_status":"completed","dependent_files":"libraries/protocol/include/graphene/protocol/types.hpp,libraries/protocol/types.cpp,libraries/protocol/include/graphene/protocol/operation_util.hpp,libraries/protocol/operation_util_impl.cpp","gmt_create":"2026-03-03T07:29:54+04:00","gmt_modified":"2026-03-03T08:25:53+04:00","raw_data":"WikiEncrypted:4o0MsTZSJF8izk/4K4S0sgel0pL3GjUBgmfU2Jyx8LAEeqHSwBDIqrwqYaLa7vz0dZklAexiBrcyBPoGl0UL8BLUGv1A6Pm6qrXwLUFQaxQ3uw6aYd84Czg+l1rsyFCtZkGqcNdvR7IzXAukFAEoBdxczKyxinARYsusL7HRvAfVzJVoHceBpQQ3T8jYuUKHR7KxqUNsY1I1N4cKmpYRNuONQUbY3FaLt836Lwat+eczNxHuemF2h54DgyLIYFx7jGWD64tBO5H18dfTY45jnQWyvHaLi9b34BxaNrhm+fGKARUr4lS123jCX8Ogd6cROYLNUcfKWIV71Lza7SLjR3oy2fJHN2RTB7aek7eUs7BZ29A+DVGyDmeK8uCBtgTPDzclEAp6WftsK75zwen2vDxgiOSXFpRMVMEAOHAwj1IuDC7RNL+ffoS7VdEA68fjVq126qs32YF+sBGMUkEKyWEWX+gkggDLcEHsn/7+jSvzHtKBYqvBHDb4T0RhQ33cQm8MOsWmMxNa/cVQmnkVvOXkGhgMzkTo2aWzaU5vJ/1gxMrhMc+G6+zypIGtUI9UjmYZslWZiPNHcOTa2Fe+62p2Finm8c8m8ItRnKBSpvWLSmz+nIWazm7UWfByDY1S1v1wNfnY2PcdeEAjKCyxthZ5Cky9zotWIT+qrIOpc/qC26r4ZBiQR0j9E1GWg4iNMJxrVhTp02hdLeaTOL2pfLR0G4X7IcxRwvHRz0hH8trmEJpknID6V2HoDQ3xJPAyk7MoVzWBkP0ginPs2PFFCPTQKuJCx9YICn7OxOH8KNNGT8dqz2jLx+oipdS5PkG++aRBaQhBx/3XcXUJu1RLEc5ow1BZ5FfwwFI1SawDt2JI+TJwDy5UvAJg3NVZlw2VZNOWvlMSWjzRd1JXWKRnZ3ttaAdOh8Rnq8018rPe3hTKusOw0H7x6Qutwzuy861i00sXA1EpBOM+h4qwdylwOpck0nF6stCsQKPPthaeVkTF54qbg6oJiJisfp0I9QfmilymYVDuLFzflAylAFe8YSWyBHptb61CK4Ak5snS3b+69+zTp8vXayv56nEDuQ8gn7gtbYOmYf7PpkfCmWz5gUtos9IbU02Fw4OnqLWIyYUH6qOhbS87wfcuSqUp+1WKMYHFNxzTmXG7UQvt/7Ir6SJvZbS6l6YykQlv2yMFV7+LDWkQyA2o7PPDUcTjmV4IQVtY2pd2QEkdQlBFF7doTRILVr7moooL82uAYJK1aSAfGLpPLQ00ZG8qg6d0Wx3YVH00HTPF7xHCvt9YOJwKwPZ/uF5ycShlwxUNsN2qj6QIXK9CdnDV5a5JDGhP/JmICPhmAG/S7PUAL4d5+EE6xxFA4iwcsWyZm1i76v4W5l/+U3wz/4cREOAone0UPOTGBFFvTvrgLgt2iwBpoWCUJJs1T6w3dwWMyZRG1Sy4dJY6reScRpuOoPdBmKELw8zB4hBipR8Tlj5DA517ISX2rXS4QjE53Vk0mEbFdVha4CsZ33/5Ytms3zPrGhiUM2/KFGnEQMevHWbR+98FTktgxyxdpcmc3q9fByqAP2kYSvyRFDeX8oG+KsE+DdUEqk6u","layer_level":3},{"id":"db232a1e-1458-4825-ab19-14e5658aa89b","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Transaction Processing","description":"transaction-processing","prompt":"Develop detailed content for the Transaction Processing system that validates and executes blockchain operations. Document the transaction_object.hpp implementation including transaction metadata, status tracking, and fee calculation. Explain the evaluator system including the evaluator base class, evaluator_registry for operation dispatch, and custom operation interpreters. Detail the transaction validation pipeline including signature verification, authority checks, and operation validation. Cover the apply_transaction() and _apply_transaction() methods, their execution context, and rollback mechanisms. Document the pending transaction management, transaction pool operations, and broadcast mechanisms. Include examples of transaction processing workflows, operation evaluation, and error handling. Explain the relationship with the witness scheduling, fee markets, and state transitions. Address transaction size limits, priority handling, and performance optimization strategies.","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","order":4,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/transaction_object.hpp,libraries/chain/transaction_object.cpp,libraries/chain/include/graphene/chain/evaluator.hpp,libraries/chain/include/graphene/chain/evaluator_registry.hpp,libraries/chain/chain_evaluator.cpp","gmt_create":"2026-03-03T07:29:58+04:00","gmt_modified":"2026-03-03T08:26:05+04:00","raw_data":"WikiEncrypted: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","layer_level":3},{"id":"f53aa7de-1f1e-46ef-8b89-75ca11a2de22","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Peer Database and Discovery","description":"peer-database","prompt":"Develop detailed content for Peer Database and Discovery that manages peer address storage, discovery mechanisms, and network topology maintenance. Document the peer_database.hpp implementation for persistent peer address storage, connection history tracking, and peer reputation management. Explain peer discovery algorithms, bootstrap node configuration, and network seeding procedures. Cover peer address validation, geographic distribution optimization, and connection diversity strategies. Detail database schema for peer records, connection statistics, and peer scoring mechanisms. Document peer pruning policies, stale peer removal, and database maintenance procedures. Include examples of peer database initialization, peer lookup operations, and network discovery workflows. Address peer selection strategies, load balancing across peers, and network partition recovery. Provide guidance on peer database backup, migration procedures, and performance optimization techniques.","parent_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","order":4,"progress_status":"completed","dependent_files":"libraries/network/include/graphene/network/peer_database.hpp,libraries/network/peer_database.cpp","gmt_create":"2026-03-03T07:30:03+04:00","gmt_modified":"2026-03-03T08:27:29+04:00","raw_data":"WikiEncrypted:XiRbxJCvY27UULqlncgSjGeAt+SntVKp6XLlW8ZptMe3cQN5ElOvYsLW2S/yrn5Pm/gdCGXs1y2Piedj9v33wLVOwOus1E+E5H9jI71bXJ8uMEB/sz5NkYwU8/6WNzkQEHsACzMBDHuIu68ikPcAtR/8PnomAHUeME5ZfrZrVhoViH3TDu6y2YrDMdFxjmqinq0fSJUCDY+ReIEVe0DvsdamrkwIGLGY5bVT2dG4wA72SkOilgWxqZTHkxb6lHymUVytk9eYkF++9M1xy+eMmJxQD+TE2u5KZxVCvqgn2YADlbSX3hWCGnZjEjZFVlyDxQaAO1QnQ8yUy2iYYhwwWm7CeMqOc9byb+cFu+/OcEVm+e8BFvLtlLhxtP3eh+6O2Vs973N6W5ysyb0JygjHnJ8nudjMyaHNw8RyMvqSOSLT9U6UwXVTEaUotwIWIKzszrlF5uOiVOgjVs4Vuhd/uYnYqPY7yg1U9Gupcuxvia0BPTymFFSR0GDBDuozAVK3y45LRyXz9WHPutVVQWh88qMX+jfAGvGLPWgoIINUN7qxj7vx/kvAhe+zqVIba7395/OXZF6206+vXtoOr52yCARo1CLxoOxdGiOKHHIbYfGb5rQTXcMLT47TM1pJW77qF2ZR5g5GHIJIMydfYlnCTELuUPbwEQv+04i9jOdcmqTvtikO6WoduzLSRan9imvV39bTHESYJcjmUlckudSYPQtozz7k9e+CM8MImvuLr9XjkYGT1eUYbvc+w1RPzIdkX0510bTwxzf+aDCpoCvs8EQsEWVi7mp9dM2qBWQEdpkW8TIno+0mFwoo9mNRZDjG3uDxVroN5n9AgEtgWwXkrXHOFMpY2E1AIKhxOYjZ1Vy5KzxBMdh2cinDqfAb6ll0vpu9cElmA+zv7ttinHxgQjFyClOod38iDnt5ZR+0FGhcIcs4P74nKN67g708KKvGVd4lwx6dtmZrc24PynmKHwA2TBmrLBNM5ayRIe9uGx7fwf2GBSg2dPNCXEUtOTsDs7IGGa0B9w4wa5CKCIuoB9SoV70Lq1n/P6BwSKUmhNygSrebiD43uHUQ5cOIYPv9jDBgaTNBoQM4TnTC7fjNg5xBcDw0vlGHTq9FwedyYAG47EgYdNoSbNf8YT/GbFLjGaspoWg/aJJfrOiArUoo6Wg4ojQP3O0OGKlX4X0H/+9dL+4J9PwFjIvx/ESPb/xBDznza0ed4IZr+4fZ7TaSScM5euZl2jk16NwcujBriKnp6ABz12CC3fd5IXOG8S/P/iIbQseHgBo7JfDWjLYX20Cap8snp6zDAuJPznfy5geMKJ/JacoOzCglJsV+R03yLHv9QFHCjeSziCtcx+9azEC8MvUzgqr6CzcAI4nkh5GhDwnkmJ7JfJU22HTpajb16qZcHJBnGZWHiG3KZdD8jKV5qSo2fwd+92rHO4mrCEXisYVNImPxfi8jA1nSAp6Y2NXk6bFa02t0jO15651DfABmgqeJxXvGGgN5MrRLKGu6rb3deqKo+dcjh1F7OffKbsMAZH+mQOCdYVTsnEyCJupKzMvhxEqC/LIXyStFyD8xXNLXXMILU+BGDP5Ot99lNtc1tavX26Wadj+ngWdtm1fw3m6GQ9+HUEY2O8NQNTNdGyhn7l58zZA6wDYY3LgzZzzq3DBddqX0/Hlrr2wcjnqsfapDRrVhDfTM5Y5WjHfvmtO2n0v4Wv6ZgGL4pTCzDtXzl0is4FdsexfPlZVbHL2kFxVm7iXsRfmCY7F74GkQUrSNaKjisSho8S7eR3XxH26Kbkww49FudqO3YTCQO2q969KjbtwNxa2MS3UjOh4=","layer_level":3},{"id":"534cfa3d-aef9-406f-b4b2-b732746e7f7a","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"GitHub Actions CI/CD Pipeline","description":"github-actions-ci","prompt":"Create detailed documentation for the GitHub Actions CI/CD pipeline configuration that automates Docker image builds and testing for VIZ CPP Node. Explain the workflow triggers for main branch pushes and pull requests, including build matrix configurations for different Docker variants. Document the automated testing processes, code quality checks, and security scanning integrated into the pipeline. Cover the artifact management, Docker image publishing to registries, and deployment automation workflows. Include practical examples of modifying workflow configurations, adding new build stages, and troubleshooting pipeline failures. Address best practices for CI/CD in blockchain development, including reproducible builds, security considerations, and performance optimization of automated workflows.","parent_id":"3789801b-aaa1-4c34-a35a-c5b48338520a","order":4,"progress_status":"completed","dependent_files":".github/workflows/docker-main.yml,.github/workflows/docker-pr-build.yml","gmt_create":"2026-03-03T07:30:16+04:00","gmt_modified":"2026-03-03T08:27:16+04:00","raw_data":"WikiEncrypted:xtifkbs/d6wNgYxQA0eu1rJ2oLOxPwih+xivji5z+tk4TDdFMY/p/WvAfD0LFZfeJ+Eax8x67PbVPoq2NW8j2tIHrBfX9wrvkZxoA9QdrNVjiLVdVfe6Y1r0ibktm0Avg6vXYAS0SaYo67e5rpbS5THtPtI5Epz3s9cYAf9VR4Ht6UclEB5iAnTvfl+NiNqfwqwXiOspjLPQiRG8p3RfJBmILD7fF74upqMQMTZNR3dX1RyKYAlf52Fl8pvi8nswFcV11CJbcJFMlHuyqv8r1x7sF93nPiITCDAY4YEDZWPtv0HPZbO5ySbZHW9xbssULeAkFufQZ/thD6DxJCi7VRJYes9hYACZbo0sGXne5XDS5Yx/46CVqDbse/jkgCrx32z0z79xufyr+ot9vUQkNIk0/AR44Xp8Jcrmr/X8f1lhsB62/1x3o1O1KJjW9HRPHhR9E8qwNbu3fJzjGJ2T5YyRodDBh1WIsRecGD2oc/aHfbYTLleUIkaF4ywe9XotnNdXE+2AIDZUIu6n4c/5FEPwDpUueJ4Tp9k8+0OWRmWMKDI4nBMsQl78/UW5ztdVu/cLE9zt7D3HhyQEhqD9i5hVXxOIkTI0U//nf+BZWPBGkwxAujRCgHUo83KHF2/rtvvaHVaeLCtDI3KHBfgR3IYbBZfOSKN9t7Pt/SyFiuO+OXYDMzpDJxjLHxbBHQDtDt2r5p3Gm8nG3WssxSuRpEBus11wtbcjXlF2mXLsAl1wCMvARg12hIujsTyX93eiDPM9fBMzi/vFbFUOpWiA1wr9v/dDH7eMYbYiSIg4yl/RGFtCLvn6HbxwUkiYKS3TCv/o3gjbNRpr6fx9S+slOi3AgEtxdTTwarmmGxIW3MIC1ttWgDOIDKCR5UKIW49MPA9YXncu1oMKGb8O5oegsXc/GYpBkBlJQcoCzYKXZweonUUybw20/ubLULaB7u4MKBsN2b+StKx+79GvhVXFstIGp5avC1FRLQzt0TnQq9mYErtlpdZptJP8ICL6VD9ixgiWv9h7xNkCODRVkl3iMLjqfEW2qAZEvHHFMqRPP0ahgx7pp+hgFZPwKU0mjPKeUXl8krbNPXMeEIIKU4IHI0x3mpIfJqIYGm8frGZkdEgeHdiebGnPkRSMwQS5nPieC6CCoi0njCZkSNzttDzq606Hg+uAeO6k8sdlYJMnnVChv1lV5PjJPeHCPiWzQ5OjVeB2Eev+ZRsYt9H3ki7ze7YVwsVr0uI9Oq4T2F71RffnMEi85KedLxjLysvnZX2sUg1vhKCdBfIGsAD2WknQG+UifpDs+lBI4+lxP+BreYfl0r/B83QtGC43dNsT6U/tlrhi6AlVMRXn4PtnpTty596UHw/q7nIST+ypHUfADM1CFQJTqpQG5UbAOH9Nx+mz32w0nG+e/owYNzindwUlSLihC1265vOzGpP3xZFwPKHT15sdqcruYTTkrbWxzpsoOrjyZb4iobm6VNhIB2RPlTwGjUMtx1uUnum0VzuS7x7xpo8CjYM6HuyYfu/jacSvKPqPn1A2TrS2CmIxl4Nb59/V9OjhS75lWzk5rUXo7SLHSmTnwBMg77E4cxSlfVHeRvxSDXS4KdCgvq3owWlaIw==","layer_level":3},{"id":"a4a4d47c-bb91-469b-9531-67491f9f186f","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Build Helper Scripts","description":"build-helpers","parent_id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","order":4,"progress_status":"completed","dependent_files":"build-linux.sh,build-mac.sh,build-mingw.bat,build-msvc.bat","gmt_create":"2026-04-19T22:01:25+04:00","gmt_modified":"2026-04-19T22:03:11+04:00","raw_data":"WikiEncrypted:zhwwHcEGfkuzROuyPGwGZD/0WI/CTybfsgnvf0a+/fBPmrYtLd5NuaTqipWJR9I45kt0KlRWyKnQXBlUGFuKVXIWHd8zpK1bD/dws3Y+0y5bmVLWHKp6dzQZhaCQ8PqdbqH6Ef6BOExueeAKMrg3VG4nGQ4qnkwQuwN3IYhGJSf0t1yOpT1aqpbR+u9hEgg+mf/t+np2afE7VAT75QUC3xAJ/6WmRABeDId8fKJ8AJi2YCHWLM/x6M/vbZTtQNbB3k8sTMjZxrWdv7Jdh1CgCg==","layer_level":2},{"id":"77fc8335-bca5-4260-9f79-76df553d0950","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"API Reference","description":"api-reference","prompt":"Create comprehensive API documentation for the VIZ CPP Node JSON-RPC API system. Document all public API endpoints organized by functional categories including database API for blockchain state queries, social network APIs for content and user interactions, governance APIs for committee and proposal management, and custom protocol APIs for specialized business logic. For each API group, document HTTP methods, URL patterns, request/response schemas, authentication requirements, and error handling. Include practical examples showing common API usage patterns, parameter specifications, and response interpretations. Document WebSocket endpoints for real-time data streaming and subscription management. Address rate limiting, security considerations, and performance optimization. Provide client implementation guidelines and common integration patterns. Include migration guides for API changes and backwards compatibility notes where applicable.","order":5,"progress_status":"completed","dependent_files":"plugins/database_api/,plugins/social_network/,plugins/committee_api/,plugins/custom_protocol_api/,libraries/api/","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T11:26:06+04:00","raw_data":"WikiEncrypted:RZNXu19SJ/2tmZCrVS63Y22apEi7GzLCNEfRMnzWirpXt13SoDaP1qe23OLxeGSWild/EjV6axUq+K62cfQVmzf9dRKxs0sFQ/Fwj1OJv5b8oqQ2s2r2qPkSzOjqC2XY3VdTt0vHgW/2fFMJoqG/3n5ul83yH4fYaSXzn547LK3pNxEvp0tGxLN7ChFylWh7I9HE0iAGC4UxUYBF20P3UI+BEQg/O81U019YAnUlRohT4aYAQ+cPSBeDHVP5L3pCZ8Ap8xaQx678bKcCStlVfJynpH8C6VkRLcrdE5jzNDmomEYM9Wn2GbwKdk8AcWOkSFHw3OWjf/zwS1ETuTjvzmfiTQHLc56IAaqr5bIYk/YeDvKOALqKFv1pfnKuJzn7wFaLhp8ulxkRat1p2H0Asdgf4wkvCcuPILFuZWk1+R+1MwUvfwHSYN+u3JsB+dUj5L96WMiy9y5bTNMyEkt0GzMgZ+LxPPZz9ah2jmB7rWbr7BxIS2jMu8GuPflgXdcYOvVrOJb9SkhuOQRjc4G4twlZ+cuKhX5O80p4hU6TLp8/RxAdy8TXUnHkoRTV2emGlP6VMgcNydSu/YrpUpghKLmBT8gu3LUbb8DG/2JVtbKcHs3NPX1DE2Uux1vn1xTBxs+Q9/Ny4G2FFrex54FA72/daixMJpRBwpQMw/5PNwjLCaqpyRyeLcgCXgzA/RnBEqQ+64bUMMkyUVJxhVIGsxd6/FpOGi6GLFpDbwz0GP9xGrN/7XsJpC/3SpJ8WglZoedjo8UOAM3RRh3kaEEa6JPJ+yOr4ZGT3p36K8u32DOIEUy/m8RAjyo1p/+yxaXvpzax/f0XE/yfvKxxZ9AC06DPfnlunNBffkqQdxCLFNW/y9zuItCjHZh6NnP3RkBg6qZMXQkOV6vNGTRw+QCDo/76ZT8PPKzXSD8jhboBTNJJYBWBLJjO4/EbU13vLgyd4iHvE3G3aDbgcle6Ztdkg2v6tqZyLKfqDMQCGgpOjBdG5QuWwS0Oi8OkflVdufxxbIVPMlN35Vfl3dIyu6+wW7/Xn3050eNChA0b0AQluxVp/7nvgfFmHxC+P5HoEy7O82O+kXqIPWQEdElbQliZ/tvuznpan2GiKCW6bkvgMcnZ/NNUf5NuIQPzCD31jDU26Xlk29fQ6/F3lgPAjyce5MR7F4eypK6Dng717i+kBPdreg+VUz8zL1BYToPz1UGR5TfpTOKsvo+G26X72GS5j85pSQFxWZMXoMZv4WIlbdahxaOsGGHvBxaGsuBdX7xioUYvnmfeSBCaAKnhd9uP7Y+c6sP8MbbkZbePcfwqYmf35G55TKQnpE0DoGV2uhhmKcwOz2H2ARCjvU8mey6AQ92kdUnuga6OdTHkA8WdAI0RXm2Cve4J4YWjnLRNIe9CBI1E2VQrDOPSjnrW8QDT+a3j8dpR0dAo+7amS90DBhJcuaox5sKkCfIo/yRElmquADvFOF8gPKw4PcSoXRz9TG7yUg33MXHCHDoSg8jWwgGONJoiZxrmHV46WuPrdnWJudLa4RrnHqp0TGKoxJ4AEw40h2wzT+5Lj0fyVdO5iW4aMKXVWlclyWNU1EQMSxwJb4ESonxBdmLrMM8wJxs8VLDRx4XyTWBVFwPsjQPwIUHZV19/cWHpORmG6TOttDbgPCYC6ccvGsLdzQC4b/7C6rE/736foCG41pRyvBeYzaSWtXUAscXpRAEMQCQkzZgEnbYjSUy4AzVAzTe2cnACdI9tpuE4hF2AF5hL6tq4evmxDgdvIqSzgzY78zGFedj0ZXtX4OsPLHmvifdVLRa1gAA0Ydjpc4fZH3EEBxdPcB2InqRTPOQXf47c5ja9qw/oPMCSSu+vWYCu5e2vxjEvqcRyVtIK4+bt+HPyIvJwo2gXCkXrnqe7QOxZxxEO/9eF7NcG05QaB42jRb87nDKirsTQkqzvlZ3niK6zc7in+XDcGIPhGP0IXitPqa/2YgY6bMRZg/4KOrAymgZgrZH+MpBwnIZv/2cjQhDrZzLZE90nzpAWp8fgkYIS63lMyiJJOSJebPltGtwNXDDM+bBhp1ttmGrg4WhL6HbJOqdkSj5MCyVRX5EaHVafl1YMJL1WxoluaNx5ceGdpmJ8z4PiC5Awq4A4wfRNchzFNSyDV3YeEZnlyzhi3MC2w2VtvVFj3JMWKhHe8ieuMuHq7/F61Q3eL1prsmL1q48NmPQJmAV8QRd1MDrcpLyIcBzI8azcg8ZTbWccUvR/z8HgpWTlH+6XY8IV13wj6/xtImCiY1+zF1Eeem9Lglfctp+19aFFY4cM5tywOzIE1gbbysGBG9mXW7gjTQ1K5MMWB1eYTWN2IKCulk23mzSQt9tfHUhT3LJvrfWDIexhB5Jlo7XwmBtIw5Dr+3sJ+a4H/KIpb+OPb3XpCEOB1Fhpwtq3wH0WFLiwbR838GWGfzqvPKIyet1TEY69aq2OevJLAp7dp80xJ3WlZ0R9YCaWi39fbVuEAz+nrwv+Pyc6D+BKtJDo7n0q0sOSD2skVELYLRRskQDbg133QhZsg1tZRwhy6Cv3"},{"id":"29346336-4f9c-4ae7-9233-d7d4ccef1e6e","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Block Log Reader Module","description":"block-log-reader-module","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","order":5,"progress_status":"completed","dependent_files":"libraries/chain/include/graphene/chain/block_log.hpp,libraries/chain/include/graphene/chain/dlt_block_log.hpp,libraries/chain/block_log.cpp,libraries/chain/dlt_block_log.cpp","gmt_create":"2026-04-14T14:39:36+04:00","gmt_modified":"2026-04-14T14:41:40+04:00","raw_data":"WikiEncrypted:R9i/29qd1Uv5xEgS1tKQyI1I0rpdsO9IumbhMrSZOSV7pWU+H3/REFsGh6aDTot4hNxJPS2VAN0rfbzcg4VekaBmG/Alt/nqGifIFlLhScM8FHHmLRuS2zB0uFzQkJdXUBV23GOZdcOPSusr1YBSe5C1pBy92GXe11lEhG++DJeD7uDwx/BwFgme27p7RN3ShHGpOKO6KZLVq4NtrBbRqXBgj+BPN896vHdpQsGPy1Z9xdzm3DDw7+wmkNL+5lwLXW+0miCGz0DCrZ8PWFoJJhE3W1JhA/46ieUfQOPY78J4877vX/mqkUnYqvTQFp7WP5nOGjPH/csixyLK1BUw6K2TEcBagf+in3k6twTXnVUyf1ZZPU/VpryPBLYtnWHcOZgglvMtupZVMfciEg1Ziasrbccx96hkUHVxE6c59taZWMVDyPuepIVEIVVoBJZJ","layer_level":3},{"id":"1c7c44d6-22c3-42c5-953f-357d54ffc8ad","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Originating Peer Tracking","description":"originating-peer-tracking","parent_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","order":5,"progress_status":"completed","dependent_files":"libraries/network/node.cpp,plugins/p2p/p2p_plugin.cpp,libraries/network/include/graphene/network/node.hpp","gmt_create":"2026-04-28T22:31:26.0072844+04:00","gmt_modified":"2026-04-28T22:34:00.325344+04:00","raw_data":"WikiEncrypted:Vf/2EfYbH0IRFc1VwBfj5SHqpGLD7tVUjbXPvqCoV3C7/EOAaOOTRP35BEvVpaDiiCmbSBwF+8d00H2lYCKAr6IKI5OcjZmUTqgeVF/tPsb3pOh/sfmhgjTy0ddmB054gPxFO7LYEZYupbH1TmEn7wSlT9F8WQ+4ootXTjq2gHlpPMRfgqhQT1xQuFmlCszmkkhoWj1O5J/tRs99K9vXv9SNvRkG/Kawaa98SdVnHbA/jkdcb1EhIeiGsKbgG41pT3RJw6V0LyOoBYSfXjtXzpHRzaGp60neB1cDUM6erdvqtjYCL7nlmeDXeDAuI2GHifn3VUcwsfWVyjpO9U534g==","layer_level":3},{"id":"6ca6368d-78f2-436a-89d8-8e2fa9c3d925","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Configuration Management","description":"configuration-management","prompt":"Create comprehensive configuration management documentation for VIZ CPP Node. Document the complete configuration system including configuration file structure, runtime parameters, and environment variable overrides. Explain different node types (full node, witness node, low-memory node) and their specific configuration requirements. Cover network configuration including seed nodes, listen addresses, and peer connectivity settings. Document plugin activation and configuration, performance tuning parameters, and logging configuration. Include practical examples of common configuration scenarios such as production deployments, testnet setups, and development environments. Address Docker-specific configuration, container environment variables, and volume mounting for persistent data. Document build-time configuration options, compiler flags, and feature toggles. Provide troubleshooting guidance for common configuration issues and validation techniques.","order":6,"progress_status":"completed","dependent_files":"plugins/chain/plugin.cpp,plugins/snapshot/plugin.cpp,plugins/witness/witness.cpp,share/vizd/config/,CMakeLists.txt,share/vizd/docker/,documentation/building.md","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-04-23T11:18:10+04:00","raw_data":"WikiEncrypted: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"},{"id":"612848bb-0178-4a56-b82c-aefc05b2cae9","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Memory Management System","description":"memory-management-system","parent_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","order":6,"progress_status":"completed","dependent_files":"thirdparty/chainbase/include/chainbase/chainbase.hpp,thirdparty/chainbase/src/chainbase.cpp,libraries/chain/database.cpp,plugins/p2p/p2p_plugin.cpp,plugins/witness/witness.cpp,libraries/network/node.cpp,libraries/chain/include/graphene/chain/database_exceptions.hpp,libraries/network/include/graphene/network/exceptions.hpp","gmt_create":"2026-04-23T07:21:32+04:00","gmt_modified":"2026-04-29T06:53:57.1834315+04:00","raw_data":"WikiEncrypted:4UGnUWsfWDZbqjKXeP7l3nIFoUg1QR8mX7Mg3xz3Yqw2RQAhFYQlnJrNzDWK62g0p28n0x68xCnCqGl889LahGm2SmDbdbatAKMUU6rWXYBKNn6+N3eH9m9gUn1MBPeGPJPHmyJDQng37bgqLvcdCCzuu17Ub3i4B1eusNKAczpEh9B4otX++juvK+SOqz4uuxqM+jzfKiGwBdlUp7esUiYKYKGCt6g83LkJ2qX2/Eepyk6bKUccX8yqa6TEzS2DkYADCyD6mz1pYmB0HSgQblgT20mPlTcQGuVPirNpZgO4k28aUJ1k0m2W0gcvQKSw5XQhaXeeebb314Q2scMUuIIxLxSgDeuvMdtW3X9EWgR0yuwws3QZ4t7i2epJi2VEAkCbAs6ZamIEz9GQ7Fivc2eik/s8ZVu+IAX2PTccFCouIF8NvsF1vrXJdxOXB/g60KZhuX2xSIqxpycKaDTmDYuh1PdJ4hWqJ2Zqn+of7Si6aSYTBsPnYniYIMfVfmkcruGyBHiznC3j15v0K/FUy1oust+OSkYN4epr7dy/wyzutivgrdWNvNccR8EGkbvg5kDeGJBwZXZVt4sFFZUWLOByQS7SO1jgVOE4xR4d3iQgM2MhIZ2Jl5AfgU+tbtNXv1q7vJNiYhuNTl1Sr8QcBA==","layer_level":3},{"id":"d896ebd6-7a89-4c1c-a16d-8acb2a61bb9c","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Development Tools","description":"development-tools","prompt":"Create comprehensive development tools documentation for VIZ CPP Node. Document the complete development toolkit including the CMake build system, cross-platform compilation instructions, and Docker-based development environments. Cover the testing framework with unit tests, integration tests, and performance benchmarks. Document debugging tools including the debug node plugin, transaction serialization utilities, and network debugging capabilities. Explain the development workflow including code style guidelines, pull request processes, and continuous integration. Include practical examples of common development tasks such as building custom plugins, running tests, and profiling performance. Address code generation tools, schema validation utilities, and development environment setup. Document the relationship between development tools and the overall project structure, making it accessible to contributors while providing sufficient technical depth for advanced development tasks.","order":7,"progress_status":"completed","dependent_files":"programs/,documentation/testing.md,programs/util/,.github/workflows/","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:35:27+04:00","raw_data":"WikiEncrypted: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"},{"id":"ea5be69a-46ea-4304-9d86-597c6f7fc254","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Deployment and Operations","description":"deployment-operations","prompt":"Create comprehensive deployment and operations documentation for VIZ CPP Node. Document production deployment strategies including hardware requirements, security hardening, and performance optimization. Cover Docker containerization with different image types (production, testnet, low-memory) and orchestration options. Explain cloud deployment options, load balancing considerations, and high availability configurations. Document node types including full nodes, witness nodes, and seed nodes with their specific requirements and operational procedures. Include monitoring and maintenance procedures such as log management, health checks, database maintenance, and performance optimization. Address security considerations including firewall configuration, SSL/TLS setup, and access control. Provide troubleshooting guidance for common operational issues, performance bottlenecks, and recovery procedures. Document backup and disaster recovery strategies.","order":8,"progress_status":"completed","dependent_files":"share/vizd/docker/,share/vizd/vizd.sh,share/vizd/config/config.ini,documentation/testnet.md","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:36:27+04:00","raw_data":"WikiEncrypted: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"},{"id":"7a20b53f-0b97-40ec-a630-7e9171a04006","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Advanced Topics","description":"advanced-topics","prompt":"Create comprehensive advanced topics documentation for VIZ CPP Node. Cover complex subjects including hardfork implementation and management, database schema design and optimization, security considerations and vulnerability assessment, and advanced plugin development patterns. Document the hardfork system including version management, migration procedures, and backward compatibility handling. Explain database design patterns including object persistence strategies, index optimization, and fork database implementation. Address security topics including cryptographic implementation, API authentication, network security, and vulnerability mitigation. Cover advanced plugin development including custom evaluators, database object extensions, and inter-plugin communication patterns. Include performance optimization techniques, memory management, and scalability considerations. Provide expert-level guidance on extending the core functionality and integrating with external systems.","order":9,"progress_status":"completed","dependent_files":"libraries/chain/hardfork.d/,libraries/chain/include/graphene/chain/chain_object_types.hpp,plugins/test_api/,documentation/plugin.md","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:37:26+04:00","raw_data":"WikiEncrypted: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"},{"id":"827bb973-d548-4605-9a5b-103c1b308c31","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Troubleshooting and FAQ","description":"troubleshooting-faq","prompt":"Create comprehensive troubleshooting and FAQ documentation for VIZ CPP Node. Document common build issues and their solutions including dependency problems, compilation errors, and platform-specific issues. Cover network connectivity problems such as peer discovery failures, sync issues, and firewall configuration. Address performance optimization including memory usage, CPU utilization, and disk I/O optimization. Document error message interpretation and diagnostic procedures for various failure scenarios. Include frequently asked questions about node operation, API usage, plugin development, and integration challenges. Provide systematic debugging approaches using the debug node plugin, log analysis techniques, and diagnostic tools. Address recovery procedures for common failure scenarios including database corruption, sync desynchronization, and configuration errors. Include community resources, support channels, and escalation procedures for complex issues.","order":10,"progress_status":"completed","dependent_files":"documentation/building.md,documentation/debug_node_plugin.md,documentation/testnet.md,share/vizd/config/config.ini","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:37:39+04:00","raw_data":"WikiEncrypted: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"},{"id":"0e7eda4e-d736-4dea-834b-eb08eb84d4d9","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Contributing and Development","description":"contributing-development","prompt":"Create comprehensive contributing and development documentation for VIZ CPP Node. Document the development workflow including code style guidelines, commit message conventions, and review processes. Explain the contribution process from issue identification through pull request submission and merge. Cover code quality standards including testing requirements, documentation expectations, and performance criteria. Document the plugin development contribution process including template usage, testing requirements, and integration guidelines. Address community contribution opportunities including bug reporting, feature requests, and documentation improvements. Include practical examples of common contribution scenarios such as fixing bugs, adding features, and improving documentation. Document the relationship between contributions and the overall project governance, licensing requirements, and intellectual property considerations. Provide guidance for new contributors on getting started with development and finding suitable projects to contribute to.","order":11,"progress_status":"completed","dependent_files":"documentation/git_guildelines.md,documentation/plugin.md,documentation/api_notes.md,programs/util/newplugin.py","gmt_create":"2026-03-03T07:27:58+04:00","gmt_modified":"2026-03-03T07:37:44+04:00","raw_data":"WikiEncrypted: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"},{"id":"a85aa407-665b-425b-b5e1-fc693d469cd7","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Witness","description":"witness","order":12,"progress_status":"completed","dependent_files":"plugins/witness_guard/witness_guard.cpp,libraries/chain/database.cpp,plugins/witness/witness.cpp,plugins/witness/include/graphene/plugins/witness/witness.hpp","gmt_create":"2026-04-13T21:23:28+04:00","gmt_modified":"2026-04-30T12:39:24.2387906+04:00","raw_data":"WikiEncrypted:JeTXIs+pfWQp8HpBYqSHUt9pgs+Z3ta6nCRipvtinj2ZibPEEeHHjj4s1YQflAL5kw0thomwlF/m298PbgVpHcMYZUHRQqAsMYtr07dfBLOo+zFv+W+D15w/E/4EN0I18VG1kogm2C52/pqntBpMHHQWi2HWNMI83JaiO1F0VurwzKmQ65PYBSw4dXAs+l4jeUjO8SswzVE6OFsp8RNPdj0h5kBtGKesfwW3SOqa0AxZmIqU3jg38nTzAw9BnYUr83UhDetPgX8JPXzWIjalYZ3vcjLVMNl7bpTm957vtBwxp1CmZoONclppVImQSxAI3YqFVGGTJbkeptK6yWB2GNGwOs1o0276HfIKxOMC37BlJ44emeXqvsTqlYCdSu5b"},{"id":"336017d8-00e0-43f8-afef-4d3d4a2e5ccb","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Webserver Plugin","description":"webserver-plugin","order":13,"progress_status":"completed","dependent_files":"plugins/json_rpc/plugin.cpp,plugins/webserver/webserver_plugin.cpp,documentation/webserver-plugin.md","gmt_create":"2026-04-14T09:27:36+04:00","gmt_modified":"2026-04-23T15:42:30+04:00","raw_data":"WikiEncrypted:DVkoReQyyhdwnmC6pD+XJl1726hWlyrdb/kexi9P3mlDxrXesxG0TGa6cLaC0YXste3eGQsJLCuBJsmN7jS41YrtOzDmXjZ20dpluGZh7TF8d5Ih/dkUyBAv83zgG5dT5X/GX2Xf8jjsJPRMiH5sknmR3OG8wocD5A9ncHBEVFRyBp0HihXukxhHlpTsrCQUq/zYGQhCvSGL3yzBUH/lFvEHVNY6wumep+VY9J36gqSbAsRpvnsVgmmROXkXabCmVhKGXF5QLsRelkbIqbSqDh/TeX+yd1kbbAhQ4VkC2JqtmnY6UJeSCFsR3Ul9lwh/"},{"id":"cff6b813-9c5a-4ab6-a45c-f9382c25ff61","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Chain Plugin","description":"chain-plugin","order":14,"progress_status":"completed","dependent_files":"plugins/chain/plugin.cpp,plugins/chain/include/graphene/plugins/chain/plugin.hpp,libraries/chain/database.cpp,libraries/chain/include/graphene/chain/database.hpp,share/vizd/config/config.ini,share/vizd/config/config_witness.ini","gmt_create":"2026-04-20T08:54:36+04:00","gmt_modified":"2026-04-29T06:59:11.3226702+04:00","raw_data":"WikiEncrypted:2nxDQCjwbtJzhDuGjsVF7mHsV7DvjvnRRbUVGKq7sKBjc03f9mwX6sCrl1O98+zpUP3CrZGNY7+od97OqysKwp6V8pyGSudE2hJ3DdykrmiIDKhcn3MAes8dpg0JRzFtSSQgwpIZiOnespKZn4hpTLOyeq4hVG5hqc6ABc/sw/nvfHWfqyo65GeakfCQ+gpaGSQjBupzQlsscQ+/o/hMvQU/XNc8xpL81ukyIZXuDZfJKuk7FwaGm3cFsv9MqXrj+A7BEF1Xq4aPataHskdRoX8p40Ykjyeal642bhpYLpWd+dPPT9HsVOna+wKQS7r/ZgGRsPVLIOqyAXu8QV79wV+K4ht4iIZXx/61hOyRV3r2ukL9YPAtUVPy5ihSo50NJ7lXFfJ/fZ1JeC9T7rG7WMnAehMmhvxLQFtaeZuwB8rT1TE591uoSHLFcc23qiBrZ7f5w8wUp2DgWU6LKwjhfjDS+ELv3bx6bsW4SFi3/2k="},{"id":"766a7066-9703-4342-a54b-f827e0e13757","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"P2p Plugin","description":"p2p-plugin","order":15,"progress_status":"completed","dependent_files":"plugins/p2p/p2p_plugin.cpp,plugins/p2p/p2p_plugin.hpp,plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp","gmt_create":"2026-04-23T11:49:52+04:00","gmt_modified":"2026-04-30T11:12:37.5129528+04:00","raw_data":"WikiEncrypted:LjUwH/yUcbWfnBumtHMxuKISe72JbF8T+AYe9w/08E+XtSR36PLbVQBinOspVDlWetMPFkhRiTMoRAUUG8lGn0m5x7RHi+E+u8xLwer77SCrno3g0kQXsOkD1Q1KBhNs+BVWEhWp1jKV61HpFgn7Ii2DSYcoPqruBnuf9DUj0GmPTp6uczQEOm0dKxUy+TvURdnCFU8uhpNAss/Dq1MH66dQNLpms7Ha1KR1P26a88f2fsbRkeCIp8bKCrmbV/B/wyJvUzBcFMNlqsuwBDC3tpY6mVYiEpVoupNHqujr0CG2ziqC/HV+tso3ofU/UAGu"},{"id":"e09223c9-c6d0-4de2-bc9e-e71b821821e9","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"Logging System","description":"logging-system","order":16,"progress_status":"completed","gmt_create":"2026-04-28T22:07:50.137316+04:00","gmt_modified":"2026-04-28T22:09:11.9888453+04:00","raw_data":"WikiEncrypted:yXtkOK/klHQ6fg8DXtAtTgXcdJlvAxws0gHlGuZdj1Nq0qCgdW36gO4KWZvggEHP3mw5DUw7tq468fzgZ4wHWUCLlo4HtkDTFqTWGh+xHCjmGARpn3yWzRgRrE9LGGpCxnS4z/0db4/mvocXTFSTmz6AFUcRoyFLbPag4UL5FZc="}],"wiki_items":[{"catalog_id":"cb549eb1-0a24-4153-9474-eae1ed48e8e1","title":"Getting Started","description":"getting-started","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"7803d50e-ee66-4670-83ec-32f2c6719409","gmt_create":"2026-03-03T07:31:32+04:00","gmt_modified":"2026-03-03T07:31:32+04:00"},{"catalog_id":"80d9e7dd-fa35-469c-bd78-95cb6616e64c","title":"Project Overview","description":"overview","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"299812b4-7d0c-42f7-b530-6d057ba096bb","gmt_create":"2026-03-03T07:31:56+04:00","gmt_modified":"2026-03-03T07:31:56+04:00"},{"catalog_id":"8aeac580-587b-43f3-9292-e62e4d3781f7","title":"Architecture Overview","description":"architecture","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"419b0fb1-31a5-4b4b-87f1-7adba3743630","gmt_create":"2026-03-03T07:32:30+04:00","gmt_modified":"2026-03-03T07:32:30+04:00"},{"catalog_id":"77fc8335-bca5-4260-9f79-76df553d0950","title":"API Reference","description":"api-reference","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"4c1475f4-4dd1-4888-8a4e-dde1e0cec83c","gmt_create":"2026-03-03T07:33:49+04:00","gmt_modified":"2026-03-03T11:26:06+04:00"},{"catalog_id":"ad096386-ea33-44d1-b9f2-261f11fb24d5","title":"Plugin System","description":"plugin-system","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"f5eb1fa5-a4ca-4e54-b68b-b234545fb8ce","gmt_create":"2026-03-03T07:33:58+04:00","gmt_modified":"2026-04-23T09:46:06+04:00"},{"catalog_id":"7e383bdb-11dd-48d6-bd47-4d350e2df438","title":"Core Libraries","description":"core-libraries","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"ecd87c29-4938-4318-9bbb-b3c2a87a2d22","gmt_create":"2026-03-03T07:34:30+04:00","gmt_modified":"2026-04-19T22:31:11+04:00"},{"catalog_id":"d896ebd6-7a89-4c1c-a16d-8acb2a61bb9c","title":"Development Tools","description":"development-tools","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"4493b728-953f-4c7d-9b30-89aa33255a3b","gmt_create":"2026-03-03T07:35:27+04:00","gmt_modified":"2026-03-03T07:35:27+04:00"},{"catalog_id":"6ca6368d-78f2-436a-89d8-8e2fa9c3d925","title":"Configuration Management","description":"configuration-management","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"e885f837-dcb8-4f4a-b0ff-5c620c375d5e","gmt_create":"2026-03-03T07:35:41+04:00","gmt_modified":"2026-04-23T11:18:10+04:00"},{"catalog_id":"ea5be69a-46ea-4304-9d86-597c6f7fc254","title":"Deployment and Operations","description":"deployment-operations","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"cafaced8-cceb-47ca-9052-70e14192d570","gmt_create":"2026-03-03T07:36:27+04:00","gmt_modified":"2026-03-03T07:36:27+04:00"},{"catalog_id":"7a20b53f-0b97-40ec-a630-7e9171a04006","title":"Advanced Topics","description":"advanced-topics","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"43570872-cdce-4428-b8dd-dd01164aa9b3","gmt_create":"2026-03-03T07:37:26+04:00","gmt_modified":"2026-03-03T07:37:26+04:00"},{"catalog_id":"827bb973-d548-4605-9a5b-103c1b308c31","title":"Troubleshooting and FAQ","description":"troubleshooting-faq","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"63ed2c61-53af-4c7c-aaf3-7d8586a249e8","gmt_create":"2026-03-03T07:37:39+04:00","gmt_modified":"2026-03-03T07:37:39+04:00"},{"catalog_id":"0e7eda4e-d736-4dea-834b-eb08eb84d4d9","title":"Contributing and Development","description":"contributing-development","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"aa5583cd-5e97-4e63-b478-467c9d34674e","gmt_create":"2026-03-03T07:37:44+04:00","gmt_modified":"2026-03-03T07:37:44+04:00"},{"catalog_id":"86d4d313-8f5e-4334-b5d1-220ec01d0971","title":"System Overview","description":"system-overview","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"fec1e146-3e5c-4c90-9824-44c18fd37d36","gmt_create":"2026-03-03T07:39:03+04:00","gmt_modified":"2026-03-03T07:39:03+04:00"},{"catalog_id":"9cfa64b0-5249-4b35-ae4c-c94b4ab1b246","title":"Build System","description":"build-system","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"c4b8579d-6410-4a31-8523-e38a9b1d69d3","gmt_create":"2026-03-03T07:39:35+04:00","gmt_modified":"2026-04-21T16:26:53+04:00"},{"catalog_id":"4b6cb85a-4799-425e-9f98-9cd149c004ec","title":"Node Deployment","description":"node-deployment","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"101024ac-5da4-4315-80c7-0cd7955ea4f8","gmt_create":"2026-03-03T07:40:48+04:00","gmt_modified":"2026-03-03T07:40:48+04:00"},{"catalog_id":"61febd56-5be6-448d-b2a2-26975fe33d8d","title":"Node Configuration","description":"node-configuration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0fc8c6be-5e2e-4d16-aa17-5e6b88335450","gmt_create":"2026-03-03T07:40:51+04:00","gmt_modified":"2026-03-03T07:40:51+04:00"},{"catalog_id":"56a9c3df-f2c3-4f59-8729-1fed0fcdb9d2","title":"Hardfork Management","description":"hardfork-management","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"d992f398-cce4-49e2-a21a-44ff13893da3","gmt_create":"2026-03-03T07:41:25+04:00","gmt_modified":"2026-04-20T11:24:22+04:00"},{"catalog_id":"cbaaeab2-9ed7-42e0-888e-58f1dff3747b","title":"Testing Framework","description":"testing-framework","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0cb251fd-c588-4acc-8c03-1b2980db606b","gmt_create":"2026-03-03T07:41:49+04:00","gmt_modified":"2026-03-03T07:41:49+04:00"},{"catalog_id":"2c57cd51-91bf-4148-bbc4-53fda0ff7ec3","title":"Build Configuration","description":"build-configuration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"efbfc523-380a-4c5c-89b1-b1b22375fdd1","gmt_create":"2026-03-03T07:42:49+04:00","gmt_modified":"2026-04-23T06:46:47+04:00"},{"catalog_id":"7c381449-9427-4fc2-ab03-1a1301da306b","title":"Containerization and Docker","description":"containerization-docker","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"46914ef3-870f-4299-b0b1-9b5d299c5cad","gmt_create":"2026-03-03T07:43:26+04:00","gmt_modified":"2026-03-03T07:43:26+04:00"},{"catalog_id":"64b99394-2424-4a81-ab2a-f41d1b9e973c","title":"Plugin Architecture","description":"plugin-architecture","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"c6931d42-c58d-4696-88f0-c17996821ffa","gmt_create":"2026-03-03T07:43:34+04:00","gmt_modified":"2026-04-15T13:00:48+04:00"},{"catalog_id":"ac1c2473-448f-4372-9f6e-3715d530d978","title":"Database Schema Design","description":"database-schema-design","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"c9cc2572-d693-45ec-a7b0-6a1fa0a3d47e","gmt_create":"2026-03-03T07:45:31+04:00","gmt_modified":"2026-03-03T07:45:31+04:00"},{"catalog_id":"614f1169-202f-4dd4-aaa5-360a10ad6bd8","title":"Debugging Tools","description":"debugging-tools","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"3db3b667-c9b0-4ef0-bdbd-1e4638995b5c","gmt_create":"2026-03-03T07:45:42+04:00","gmt_modified":"2026-04-28T19:48:38.9116038+04:00"},{"catalog_id":"139b0217-0190-433f-b41d-60fa08c9ee5f","title":"Core Libraries","description":"core-libraries","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"724010bf-a048-4a08-bf63-fc4bcac656b4","gmt_create":"2026-03-03T07:46:01+04:00","gmt_modified":"2026-03-03T07:46:01+04:00"},{"catalog_id":"3834864b-3db0-4e65-a23c-0fcd2d2759ec","title":"Cloud and Infrastructure","description":"cloud-infrastructure","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"760f37d6-5fcd-49a4-ac1d-f5e886377331","gmt_create":"2026-03-03T07:46:55+04:00","gmt_modified":"2026-03-03T07:46:55+04:00"},{"catalog_id":"28bb2a2e-23c2-4009-8847-35e8fac1f151","title":"Security Implementation","description":"security-implementation","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"4f51c881-94ed-4e49-b8cf-e3b1fab21e0e","gmt_create":"2026-03-03T07:47:20+04:00","gmt_modified":"2026-03-03T07:47:20+04:00"},{"catalog_id":"9d487fbd-32f6-4e37-acf4-e314d7f75019","title":"Docker Configuration","description":"docker-configuration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"ecdb8c95-8cbb-4c16-b315-822594095634","gmt_create":"2026-03-03T07:48:32+04:00","gmt_modified":"2026-04-17T17:31:31+04:00"},{"catalog_id":"9091fce8-eb05-4150-ae9b-eaeb0c47be15","title":"Development Workflow","description":"development-workflow","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"be44fe88-0512-4e4b-a4b8-9e082214a604","gmt_create":"2026-03-03T07:48:38+04:00","gmt_modified":"2026-03-03T07:48:38+04:00"},{"catalog_id":"a486a2be-4bcb-4205-83fd-2ab48f89829b","title":"Data Flow and Processing","description":"data-flow","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"9e5f8d60-bebb-4161-a630-0880704f9f81","gmt_create":"2026-03-03T07:48:50+04:00","gmt_modified":"2026-03-03T07:48:50+04:00"},{"catalog_id":"30dbc846-1f3e-44d5-a93a-988c973d064f","title":"Advanced Plugin Development","description":"advanced-plugin-development","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"09728486-f6d3-4cf1-bb8b-b5b404117331","gmt_create":"2026-03-03T07:50:18+04:00","gmt_modified":"2026-03-03T07:50:18+04:00"},{"catalog_id":"b52a3e7c-7bc9-46ea-afb3-610c7430eb21","title":"Network Configuration","description":"network-configuration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"52d914f6-5cef-4a53-bd37-4c7c074ddcb2","gmt_create":"2026-03-03T07:50:58+04:00","gmt_modified":"2026-04-23T12:16:49+04:00"},{"catalog_id":"c5e155d7-00b2-4e55-811e-cffb5860ac55","title":"Design Patterns and Architectural Decisions","description":"design-patterns","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"06bc216a-07e4-48fb-bdff-38335db957cc","gmt_create":"2026-03-03T07:52:04+04:00","gmt_modified":"2026-03-03T07:52:04+04:00"},{"catalog_id":"bcdd0730-3bcf-4e55-aeac-7cc0a351046b","title":"Plugin Lifecycle and Registration","description":"plugin-lifecycle-registration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"cc17934e-5862-4f4b-99a7-0ed4fd1c2e2a","gmt_create":"2026-03-03T07:52:10+04:00","gmt_modified":"2026-04-20T10:26:06+04:00"},{"catalog_id":"bf5ec7d7-d376-4b39-8781-5b76b3e90e2b","title":"Monitoring and Maintenance","description":"monitoring-maintenance","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"a3afccb6-22df-4d5b-bb93-2f591af782bf","gmt_create":"2026-03-03T07:52:29+04:00","gmt_modified":"2026-04-21T15:57:29+04:00"},{"catalog_id":"08c2583a-92f0-4c14-aa9d-736878711ba1","title":"Transaction Processing Pipeline","description":"transaction-processing","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"28f563ee-bb1b-42be-8243-e7bfa17eb793","gmt_create":"2026-03-03T07:53:30+04:00","gmt_modified":"2026-03-03T07:53:30+04:00"},{"catalog_id":"317287b2-3937-4876-97d0-a8c96007d95c","title":"CMake Configuration","description":"cmake-configuration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"e9540698-3e7d-4a1c-bdaa-0d96c8be0745","gmt_create":"2026-03-03T07:53:46+04:00","gmt_modified":"2026-03-03T07:53:46+04:00"},{"catalog_id":"3690c93b-e823-4c69-8ec9-862feb3c3549","title":"Unit Testing Infrastructure","description":"unit-testing-infrastructure","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"c08ac7ca-a94d-4e15-8e9e-3dae3b1a5752","gmt_create":"2026-03-03T07:54:54+04:00","gmt_modified":"2026-03-03T07:54:54+04:00"},{"catalog_id":"ff9d9cb4-10d6-40dd-a6c6-47f4665aee8b","title":"Debug Node Plugin","description":"debug-node-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"589f3f8e-0048-4b0d-befe-693f063b0ce4","gmt_create":"2026-03-03T07:55:19+04:00","gmt_modified":"2026-03-03T07:55:19+04:00"},{"catalog_id":"e02c38ec-2618-428e-b338-f93cfe94dc72","title":"Chain Library","description":"chain-library","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"4f950ad3-7c9b-4d85-8ae3-b1b342b6ce31","gmt_create":"2026-03-03T07:55:53+04:00","gmt_modified":"2026-04-23T11:18:36+04:00"},{"catalog_id":"a4025412-2ce8-4c57-91cb-0f16dac0132c","title":"Installation and Setup","description":"installation-setup","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"f7e77aea-7354-410f-a8b6-ad12fbed8361","gmt_create":"2026-03-03T07:56:01+04:00","gmt_modified":"2026-03-03T07:56:01+04:00"},{"catalog_id":"30dc8338-1212-4946-a037-0fcb2222ca44","title":"Inter-Plugin Communication","description":"inter-plugin-communication","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"6ff3d5ed-5a93-44e9-b8fc-ee03618c7b1c","gmt_create":"2026-03-03T07:57:09+04:00","gmt_modified":"2026-03-03T11:26:31+04:00"},{"catalog_id":"4e47c09b-f1e5-4405-8940-c9714fcf5965","title":"Protocol Library","description":"protocol-library","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"b52202cc-02f2-44ed-bba8-d9428b217809","gmt_create":"2026-03-03T07:57:42+04:00","gmt_modified":"2026-03-03T07:57:42+04:00"},{"catalog_id":"0b0666c8-2764-44f1-b24f-7a1cb2afaf30","title":"Block Processing and Validation","description":"block-processing","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"885ce864-d616-4f9f-8a3e-6198a88feda6","gmt_create":"2026-03-03T07:57:58+04:00","gmt_modified":"2026-04-28T12:54:08.1046315+04:00"},{"catalog_id":"afe12b98-5441-4e08-9bd0-d8c604b2dbaa","title":"Code Coverage Analysis","description":"code-coverage-analysis","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"36300f0a-f814-4ee7-8fea-443822b5ee7e","gmt_create":"2026-03-03T07:58:43+04:00","gmt_modified":"2026-03-03T07:58:43+04:00"},{"catalog_id":"d54afe72-4975-48c8-b825-ab792ce92a46","title":"Build Helper Tools","description":"build-helpers","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"18a3eced-7204-4ae6-ace6-9325629f7540","gmt_create":"2026-03-03T07:58:47+04:00","gmt_modified":"2026-04-21T16:26:14+04:00"},{"catalog_id":"514b44cf-1ee9-477f-afb6-86b4f8c17ee2","title":"Transaction Debugging Tools","description":"transaction-debugging-tools","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"3c6c03d8-c73c-48e4-8076-4deaff61fc83","gmt_create":"2026-03-03T07:59:59+04:00","gmt_modified":"2026-03-03T07:59:59+04:00"},{"catalog_id":"eb3e00ca-26ca-455a-8670-e309bca1ae7d","title":"Node Types and Configurations","description":"node-types-configurations","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"7e78b6cc-7d2d-4326-820e-37969b08647b","gmt_create":"2026-03-03T08:00:19+04:00","gmt_modified":"2026-04-21T15:32:31+04:00"},{"catalog_id":"fb7f5918-6ff3-4fba-89b3-37eef3bf402f","title":"Custom Plugin Development","description":"custom-plugin-development","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"20e80b63-adb3-4fae-a0ff-7b4553335673","gmt_create":"2026-03-03T08:00:20+04:00","gmt_modified":"2026-03-03T08:00:21+04:00"},{"catalog_id":"0ffb65de-cf29-46a1-a84e-5b7531aaa9cf","title":"API Request Processing","description":"api-request-handling","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"f8aac207-e623-4724-8003-ced4b4d90bf8","gmt_create":"2026-03-03T08:01:46+04:00","gmt_modified":"2026-03-03T08:01:46+04:00"},{"catalog_id":"3789801b-aaa1-4c34-a35a-c5b48338520a","title":"Docker Integration","description":"docker-integration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"bf24a62f-5138-4dfb-9ecb-9d4133953010","gmt_create":"2026-03-03T08:02:06+04:00","gmt_modified":"2026-04-17T10:15:28+04:00"},{"catalog_id":"5bb14590-6784-4cf5-b371-7c3778519d8e","title":"Service Integration","description":"service-integration","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"3b02cb64-064b-4b4c-804e-62b2ba00d9ad","gmt_create":"2026-03-03T08:03:49+04:00","gmt_modified":"2026-03-03T08:03:49+04:00"},{"catalog_id":"02937d37-0e8d-4e1e-8464-7e8c7717aaea","title":"Network Library","description":"network-library","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"ed702015-695e-4ef5-87fc-be4645f73987","gmt_create":"2026-03-03T08:03:52+04:00","gmt_modified":"2026-04-28T21:31:00.4625046+04:00"},{"catalog_id":"be25befd-151b-41db-97bd-3bc570e2cf2d","title":"Network Debugging Capabilities","description":"network-debugging-capabilities","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"7e6094b6-9310-43af-993d-e3bebf0c7ee7","gmt_create":"2026-03-03T08:04:07+04:00","gmt_modified":"2026-03-03T08:04:07+04:00"},{"catalog_id":"e61981dc-5f45-433d-aff8-b4a293795e7e","title":"Event-Driven Communication Patterns","description":"event-driven-architecture","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"aedcc69e-d00f-471d-a338-b290a71a8310","gmt_create":"2026-03-03T08:05:24+04:00","gmt_modified":"2026-03-03T08:05:24+04:00"},{"catalog_id":"95b482d0-a7d5-4c95-92a5-19cfbe2967d6","title":"Plugin API Design Patterns","description":"plugin-api-patterns","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"faf4c29b-a303-4f0e-b3de-f9679789aa09","gmt_create":"2026-03-03T08:05:51+04:00","gmt_modified":"2026-03-03T08:05:51+04:00"},{"catalog_id":"19f906f3-0605-4d92-8d4b-7052dddc5ee7","title":"Cross-Platform Compilation","description":"cross-platform-compilation","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"85f42623-d6f4-4a64-9df9-84d74edb3ff8","gmt_create":"2026-03-03T08:06:47+04:00","gmt_modified":"2026-03-03T08:06:47+04:00"},{"catalog_id":"48f0ee63-f542-40b7-bddc-5746b9b949c8","title":"Wallet Library","description":"wallet-library","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"32f6a790-8183-436f-a550-04552fa6b462","gmt_create":"2026-03-03T08:07:16+04:00","gmt_modified":"2026-03-07T21:45:11+04:00"},{"catalog_id":"3c3c0db5-9be5-46ca-999c-68f9e09ed1b7","title":"Performance Profiling Utilities","description":"performance-profiling-utilities","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"40c4f036-b025-4e82-846a-a71cf89d5e5a","gmt_create":"2026-03-03T08:08:06+04:00","gmt_modified":"2026-03-03T08:08:06+04:00"},{"catalog_id":"9e84e0cf-c0e7-4a82-9101-9152b572c82a","title":"Security Hardening","description":"security-hardening","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"64d83c3f-4775-41fd-9d42-d6c12ca8054b","gmt_create":"2026-03-03T08:08:09+04:00","gmt_modified":"2026-03-03T08:08:09+04:00"},{"catalog_id":"c634ee15-c5d1-4600-8f5b-96016ecb0773","title":"Core Build Options","description":"core-build-options","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"371d43b9-4b79-40d6-a740-d0e09043a493","gmt_create":"2026-03-03T08:11:41+04:00","gmt_modified":"2026-03-03T08:11:41+04:00"},{"catalog_id":"843bdf5b-2dea-4e14-97c9-d8ddaf902f92","title":"Node Management","description":"node-management","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"9dfed239-6d34-405d-a774-0a99f673e816","gmt_create":"2026-03-03T08:11:43+04:00","gmt_modified":"2026-04-30T07:20:23.8239769+04:00"},{"catalog_id":"eb3415ba-4213-4e37-931c-49f45d7ebe37","title":"Database Management","description":"database-management","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"295c418f-33b4-4c06-80cf-224d6b633a76","gmt_create":"2026-03-03T08:12:40+04:00","gmt_modified":"2026-04-30T08:00:09.9789246+04:00"},{"catalog_id":"d0aa68e6-4e49-421c-8bfa-6ef641cd6656","title":"Code Assembly Tools","description":"code-assembly-tools","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"6173e746-57a9-469a-ac23-90061ef56ab6","gmt_create":"2026-03-03T08:13:01+04:00","gmt_modified":"2026-03-03T08:13:01+04:00"},{"catalog_id":"85611a9f-1537-4247-b45d-a6a5bcb6a4ca","title":"Production Dockerfile","description":"production-dockerfile","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"9c3a79b2-0a34-4da6-8159-eb775b084846","gmt_create":"2026-03-03T08:13:22+04:00","gmt_modified":"2026-03-03T08:13:22+04:00"},{"catalog_id":"6ab3219a-8693-407a-b403-239fa721b5a2","title":"Transaction Processing","description":"transaction-processing","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"83f9e213-4dbf-4d44-8484-71f9339f5ed7","gmt_create":"2026-03-03T08:14:41+04:00","gmt_modified":"2026-03-03T08:14:41+04:00"},{"catalog_id":"a8c1e706-27c9-4798-b664-a842a70058b4","title":"Object Model and Persistence","description":"object-model","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"81bc0ef1-cb71-44d4-a313-c0adacd3b67b","gmt_create":"2026-03-03T08:15:23+04:00","gmt_modified":"2026-03-03T08:15:23+04:00"},{"catalog_id":"b47bb56b-832d-4e6d-8045-bdbee53d703a","title":"Peer Connection Management","description":"peer-connection","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0505088e-5f10-4252-bc3b-307363fee60d","gmt_create":"2026-03-03T08:16:05+04:00","gmt_modified":"2026-04-30T13:09:18.8212712+04:00"},{"catalog_id":"1ec7edf8-46f7-4e71-ba54-bc1cc3533453","title":"Platform Configurations","description":"platform-configurations","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"818151df-a208-4937-9c96-909884f6bbb8","gmt_create":"2026-03-03T08:16:10+04:00","gmt_modified":"2026-04-17T10:42:56+04:00"},{"catalog_id":"7b9a46a1-f3d0-4034-a4c5-aec28be05f18","title":"Testnet Dockerfile","description":"testnet-dockerfile","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"58e53605-3c83-4017-8c41-9a5919796003","gmt_create":"2026-03-03T08:17:07+04:00","gmt_modified":"2026-03-03T08:17:07+04:00"},{"catalog_id":"180359d7-c289-42ff-9805-28e742c9f10d","title":"Reflection Validation Tools","description":"reflection-validation","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"cbe0e279-74ff-4c6f-bc5d-ac88e9bcc6ac","gmt_create":"2026-03-03T08:17:31+04:00","gmt_modified":"2026-03-03T08:17:31+04:00"},{"catalog_id":"75eb3c5e-02de-4177-8680-a24fd13d2590","title":"Authority Management","description":"authority-management","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"c911bbfb-6d46-458b-ac1c-dbba112490e9","gmt_create":"2026-03-03T08:17:58+04:00","gmt_modified":"2026-03-03T08:17:58+04:00"},{"catalog_id":"c8b8f71a-3ba6-4723-8e51-78c701b5b4ab","title":"Dependency Management","description":"dependency-management","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"93891608-7b30-4aca-9ab2-5d88a2e3225f","gmt_create":"2026-03-03T08:19:13+04:00","gmt_modified":"2026-04-19T22:00:10+04:00"},{"catalog_id":"d7a25826-a156-4b8d-b236-8414ec49a67d","title":"Fork Resolution and Consensus","description":"fork-resolution","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0227cdb3-c369-47eb-9251-d897b9181340","gmt_create":"2026-03-03T08:19:55+04:00","gmt_modified":"2026-04-30T07:19:20.4603602+04:00"},{"catalog_id":"853c4d2b-809f-4ae0-9735-c14d174538d3","title":"Message Handling and Protocol","description":"message-handling","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"8908ad91-79b1-4190-aa7c-9a512f259dcb","gmt_create":"2026-03-03T08:20:04+04:00","gmt_modified":"2026-03-03T08:20:04+04:00"},{"catalog_id":"c387583c-6834-478f-acef-629c155068d6","title":"Low-Memory Dockerfile","description":"low-memory-dockerfile","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"2d5de852-4c30-4e77-8e31-3963f875ae85","gmt_create":"2026-03-03T08:20:43+04:00","gmt_modified":"2026-03-03T08:20:43+04:00"},{"catalog_id":"85a56025-1d98-4b60-bea6-dc4fe9dd9336","title":"Plugin Development Tools","description":"plugin-development-tools","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"43b1b513-1146-4508-9929-bfca0e7bc21d","gmt_create":"2026-03-03T08:21:29+04:00","gmt_modified":"2026-03-03T08:21:29+04:00"},{"catalog_id":"8774fe5b-dc3c-4a02-b907-d2a16b1df420","title":"Block Structures","description":"block-structures","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0f6511c5-05bc-4ef7-8e04-442a302db49c","gmt_create":"2026-03-03T08:21:50+04:00","gmt_modified":"2026-03-03T08:21:50+04:00"},{"catalog_id":"6f3f9c87-cd58-45c0-b90e-7c01d39d3188","title":"Block Processing and Validation","description":"block-processing","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"868816de-43fc-4fe0-9cf9-0e89f661447c","gmt_create":"2026-03-03T08:22:26+04:00","gmt_modified":"2026-04-28T21:03:48.5839966+04:00"},{"catalog_id":"7045a421-d36d-4058-b1f7-1eade1482cdf","title":"Build Targets","description":"build-targets","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"3e146dd5-1515-4f08-a766-7c555069570d","gmt_create":"2026-03-03T08:23:32+04:00","gmt_modified":"2026-03-03T08:23:32+04:00"},{"catalog_id":"7b4370bd-7440-4196-bd4c-e441e1f693d8","title":"Transport Layer and Sockets","description":"transport-layer","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"446c4151-6374-4826-9947-cf5133b470cd","gmt_create":"2026-03-03T08:24:00+04:00","gmt_modified":"2026-03-03T08:24:00+04:00"},{"catalog_id":"939d7089-2486-4f03-bd3f-c35c98d5a1db","title":"MongoDB Integration Dockerfile","description":"mongo-dockerfile","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"e2b16877-3e41-4465-91f2-48e5dacfd2ad","gmt_create":"2026-03-03T08:24:06+04:00","gmt_modified":"2026-03-03T08:24:06+04:00"},{"catalog_id":"75c2d99d-2330-4db2-a9d0-f4a9fdb902bc","title":"Schema Generation Tools","description":"schema-generation-tools","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"75c788ef-0d7a-4a59-a02a-d56403aa3459","gmt_create":"2026-03-03T08:24:57+04:00","gmt_modified":"2026-03-03T08:24:57+04:00"},{"catalog_id":"f6b80222-aff8-4f40-9721-fcb497d3cf84","title":"Data Types and Serialization","description":"data-types-serialization","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"d7562df5-2f68-4c8b-9b1b-1aff56d025ab","gmt_create":"2026-03-03T08:25:53+04:00","gmt_modified":"2026-03-03T08:25:53+04:00"},{"catalog_id":"db232a1e-1458-4825-ab19-14e5658aa89b","title":"Transaction Processing","description":"transaction-processing","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0c2a4f4d-a142-41b8-81d4-a4c72022cd6d","gmt_create":"2026-03-03T08:26:05+04:00","gmt_modified":"2026-03-03T08:26:05+04:00"},{"catalog_id":"534cfa3d-aef9-406f-b4b2-b732746e7f7a","title":"GitHub Actions CI/CD Pipeline","description":"github-actions-ci","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"44884ed2-2c27-4d94-af1e-c939b85e2dba","gmt_create":"2026-03-03T08:27:16+04:00","gmt_modified":"2026-03-03T08:27:16+04:00"},{"catalog_id":"f53aa7de-1f1e-46ef-8b89-75ca11a2de22","title":"Peer Database and Discovery","description":"peer-database","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"14f4dc36-feae-4d78-a12c-32f851999890","gmt_create":"2026-03-03T08:27:29+04:00","gmt_modified":"2026-03-03T08:27:29+04:00"},{"catalog_id":"5e42d05e-3b67-477e-8c56-8aa25b98889b","title":"Operations Definition","description":"operations-definition","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"52188625-f997-45b2-ab6a-1b01391bde00","gmt_create":"2026-03-03T08:29:14+04:00","gmt_modified":"2026-03-03T08:29:14+04:00"},{"catalog_id":"6b359be4-c5e6-4db1-ad43-abf632c4e050","title":"Snapshot Plugin System","description":"snapshot-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"4d58ea88-1cbb-46a1-9bb9-477c8e3d9846","gmt_create":"2026-04-13T16:01:32+04:00","gmt_modified":"2026-04-30T13:09:00.7660159+04:00"},{"catalog_id":"6f970632-4b1c-4a78-a129-89269ed82d82","title":"DLT Rolling Block Log","description":"dlt-block-log","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"cb4b06a3-d41b-4c45-b1c6-e1b6306eff9f","gmt_create":"2026-04-13T16:03:19+04:00","gmt_modified":"2026-04-30T11:11:44.2224692+04:00"},{"catalog_id":"a85aa407-665b-425b-b5e1-fc693d469cd7","title":"Witness","description":"witness","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"c93fa44d-294e-4802-9676-e73b1a162b2b","gmt_create":"2026-04-13T21:25:30+04:00","gmt_modified":"2026-04-30T12:39:24.2387906+04:00"},{"catalog_id":"336017d8-00e0-43f8-afef-4d3d4a2e5ccb","title":"Webserver Plugin","description":"webserver-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"cfbf2561-8dc1-4657-9990-87f4d9de95ae","gmt_create":"2026-04-14T09:29:12+04:00","gmt_modified":"2026-04-23T15:42:30+04:00"},{"catalog_id":"29346336-4f9c-4ae7-9233-d7d4ccef1e6e","title":"Block Log Reader Module","description":"block-log-reader-module","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"03ac3143-5983-4c15-be9a-0e032445a800","gmt_create":"2026-04-14T14:41:40+04:00","gmt_modified":"2026-04-14T14:41:40+04:00"},{"catalog_id":"6b359be4-c5e6-4db1-ad43-abf632c4e050","title":"Snapshot Plugin System","description":"snapshot-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0a955b97-eb9d-434a-80a4-9bf1bf937dfb","gmt_create":"2026-04-16T12:35:54+04:00","gmt_modified":"2026-04-30T13:09:00.7660159+04:00"},{"catalog_id":"a4a4d47c-bb91-469b-9531-67491f9f186f","title":"Build Helper Scripts","description":"build-helpers","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"f1d8e320-e614-4981-b139-99ffa4534e71","gmt_create":"2026-04-19T22:03:11+04:00","gmt_modified":"2026-04-19T22:03:11+04:00"},{"catalog_id":"d93a647d-d325-4dd2-96a5-703494dd5d12","title":"Emergency Consensus System","description":"emergency-consensus-system","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"61b10976-8eeb-45d9-a3fa-3b71c7d30939","gmt_create":"2026-04-20T06:59:08+04:00","gmt_modified":"2026-04-30T12:35:27.5354397+04:00"},{"catalog_id":"cff6b813-9c5a-4ab6-a45c-f9382c25ff61","title":"Chain Plugin","description":"chain-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"b12a10e1-437e-49c8-ad45-2322fee38c9b","gmt_create":"2026-04-20T08:56:19+04:00","gmt_modified":"2026-04-29T06:59:11.3226702+04:00"},{"catalog_id":"63eb59b9-96cb-4c76-82c1-93eb29f163f1","title":"NTP Synchronization System","description":"ntp-synchronization-system","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"0be54f9c-0831-4de9-a839-fe492741178a","gmt_create":"2026-04-21T15:59:39+04:00","gmt_modified":"2026-04-21T16:27:59+04:00"},{"catalog_id":"612848bb-0178-4a56-b82c-aefc05b2cae9","title":"Memory Management System","description":"memory-management-system","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"d21997e6-71b9-44d6-b43f-85770fe1e9dd","gmt_create":"2026-04-23T07:24:03+04:00","gmt_modified":"2026-04-29T06:53:57.1824317+04:00"},{"catalog_id":"766a7066-9703-4342-a54b-f827e0e13757","title":"P2p Plugin","description":"p2p-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"6c14d115-1e64-4774-9d11-4861953aec78","gmt_create":"2026-04-23T11:53:01+04:00","gmt_modified":"2026-04-30T11:12:37.5129528+04:00"},{"catalog_id":"024bd7a9-84dc-4534-95b8-96539a35367f","title":"Witness Guard Plugin","description":"witness-guard-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"24d2e14a-472d-4051-8c51-776999cfe4cc","gmt_create":"2026-04-28T10:05:06.7179502+04:00","gmt_modified":"2026-04-28T10:05:06.7184647+04:00"},{"catalog_id":"e09223c9-c6d0-4de2-bc9e-e71b821821e9","title":"Logging System","description":"logging-system","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"9b86b8a5-506c-4454-a574-0aff36f0faf2","gmt_create":"2026-04-28T22:09:11.9818468+04:00","gmt_modified":"2026-04-28T22:09:11.9888453+04:00"},{"catalog_id":"1c7c44d6-22c3-42c5-953f-357d54ffc8ad","title":"Originating Peer Tracking","description":"originating-peer-tracking","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"ba976b2f-ad3f-4c9c-bbde-af6e23216dbe","gmt_create":"2026-04-28T22:34:00.3243045+04:00","gmt_modified":"2026-04-28T22:34:00.325344+04:00"},{"catalog_id":"8fa79728-580d-4d6c-bed4-5ac9ef3ae8d7","title":"Witness Guard Plugin","description":"witness-guard-plugin","extend":"{}","progress_status":"completed","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","id":"6ae7ebac-bb25-4499-8364-0f2f7ac99d8a","gmt_create":"2026-04-30T12:41:43.2858749+04:00","gmt_modified":"2026-04-30T12:41:43.2873164+04:00"}],"wiki_overview":{"content":"\u003cblog\u003e\n\n# VIZ CPP Node - Comprehensive Project Analysis\n\n## 1. Project Introduction\n\n### Purpose Statement\nVIZ is a C++ implementation of a decentralized blockchain node designed for the VIZ World platform. It serves as a full consensus node that validates transactions, maintains the blockchain state, and provides APIs for interacting with the distributed ledger system.\n\n### Core Goals and Objectives\n- **Consensus Validation**: Maintain blockchain integrity through cryptographic verification and consensus mechanisms\n- **Network Participation**: Act as a peer-to-peer node in the VIZ network infrastructure\n- **API Provision**: Expose comprehensive JSON-RPC APIs for external applications and wallets\n- **Extensibility**: Support modular plugin architecture for specialized functionality\n- **Performance**: Optimize for both full node operations and lightweight consensus-only modes\n\n### Target Audience\n- Blockchain developers building applications on VIZ\n- Node operators running full nodes or witness nodes\n- Wallet developers integrating with VIZ blockchain\n- Researchers studying blockchain consensus mechanisms\n\n## 2. Technical Architecture\n\n### Component Breakdown\n\nThe VIZ project follows a modular architecture built on the appbase framework:\n\n```mermaid\ngraph TD\n A[VIZ Node] --\u003e B[Core Libraries]\n A --\u003e C[Plugins]\n A --\u003e D[Programs]\n \n B --\u003e E[Chain Library]\n B --\u003e F[Protocol Library]\n B --\u003e G[Network Library]\n B --\u003e H[Wallet Library]\n \n C --\u003e I[Chain Plugin]\n C --\u003e J[P2P Plugin]\n C --\u003e K[Webserver Plugin]\n C --\u003e L[Database API Plugin]\n C --\u003e M[JSON-RPC Plugin]\n \n D --\u003e N[vizd - Full Node]\n D --\u003e O[cli_wallet - CLI Wallet]\n D --\u003e P[js_operation_serializer - Tools]\n```\n\n### Design Patterns\n\n**Plugin Architecture**: The project extensively uses the appbase plugin system, allowing modular functionality through independent plugins that can be enabled/disabled at runtime.\n\n**Database Pattern**: Chainbase provides an object-oriented database abstraction layer, enabling efficient blockchain state management.\n\n**Observer Pattern**: Event-driven architecture through Boost.Signals2 for handling blockchain events and state changes.\n\n### System Relationships\n\n```mermaid\nsequenceDiagram\n participant Client\n participant Webserver\n participant JSON_RPC\n participant Chain\n participant Database\n \n Client-\u003e\u003e+Webserver: HTTP Request\n Webserver-\u003e\u003e+JSON_RPC: Parse JSON-RPC\n JSON_RPC-\u003e\u003e+Chain: Validate Operation\n Chain-\u003e\u003e+Database: Query State\n Database--\u003e\u003e-Chain: Return Data\n Chain--\u003e\u003e-JSON_RPC: Process Result\n JSON_RPC--\u003e\u003e-Webserver: JSON Response\n Webserver--\u003e\u003e-Client: HTTP Response\n```\n\n### Data Flow Diagrams\n\n```mermaid\nflowchart TD\n A[Transaction Input] --\u003e B[JSON-RPC Parser]\n B --\u003e C[Operation Validation]\n C --\u003e D[Authority Verification]\n D --\u003e E[State Application]\n E --\u003e F[Block Production]\n F --\u003e G[P2P Broadcasting]\n \n H[Block Input] --\u003e I[Block Validation]\n I --\u003e J[Fork Resolution]\n J --\u003e K[State Commit]\n K --\u003e L[Peer Sync]\n```\n\n## 3. Key Implementation\n\n### Main Entry Points\n\n**Primary Node Entry Point**: `programs/vizd/main.cpp`\n- Initializes the application with core plugins\n- Configures logging and command-line options\n- Manages plugin lifecycle and startup sequence\n\n**Configuration Management**: `share/vizd/config/config.ini`\n- Network settings (P2P endpoints, webserver ports)\n- Plugin activation and configuration\n- Performance tuning parameters\n- Logging configuration sections\n\n### Core Modules\n\n**Database Layer**: `libraries/chain/include/graphene/chain/database.hpp`\n- Blockchain state management\n- Object persistence and retrieval\n- Fork database handling\n- Transaction validation pipeline\n\n**Protocol Definitions**: `libraries/protocol/include/graphene/protocol/operations.hpp`\n- Complete operation type definitions\n- Transaction structure validation\n- Authority requirement calculation\n- Virtual operation handling\n\n**Plugin System**: `plugins/chain/include/graphene/plugins/chain/plugin.hpp`\n- Core blockchain functionality\n- Block acceptance and validation\n- Transaction processing\n- Index management\n\n### Configuration Approach\n\n**Build Configuration**: `CMakeLists.txt`\n- Cross-platform compilation support\n- Compiler-specific optimizations\n- Feature toggles (testnet, low-memory)\n- Dependency management\n\n**Runtime Configuration**: `share/vizd/config/config.ini`\n- Plugin enable/disable directives\n- Network connectivity settings\n- Performance tuning parameters\n- Logging configuration sections\n\n### External Dependencies\n\n**Third-party Libraries**: `thirdparty/CMakeLists.txt`\n- appbase: Application framework foundation\n- fc: Fast Crypto library for networking and cryptography\n- chainbase: High-performance object database\n\n**Build Dependencies**:\n- Boost 1.57+: Core C++ utilities\n- OpenSSL: Cryptographic operations\n- CMake: Build system\n- Platform-specific toolchains\n\n### Integration Points\n\n**P2P Network**: `plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp`\n- Peer discovery and connection management\n- Block and transaction propagation\n- Network protocol compliance\n\n**Web API**: `plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp`\n- HTTP/HTTPS server implementation\n- WebSocket support for real-time updates\n- JSON-RPC 2.0 compliance\n\n**Database API**: `plugins/database_api/include/graphene/plugins/database_api/plugin.hpp`\n- Comprehensive blockchain state queries\n- Account and transaction history\n- Market data and governance information\n\n### Component Relationship Diagrams\n\n```mermaid\ngraph LR\n A[Chain Plugin] --\u003e B[Database Layer]\n A --\u003e C[P2P Plugin]\n D[Webserver Plugin] --\u003e E[JSON-RPC Plugin]\n E --\u003e A\n F[Database API Plugin] --\u003e A\n G[Account History Plugin] --\u003e A\n H[Tags Plugin] --\u003e A\n I[Follow Plugin] --\u003e A\n```\n\n## 4. Key Features\n\n### Functionality Overview\n\n**Blockchain Consensus**: Full validation of blocks and transactions according to VIZ protocol rules\n**Network Participation**: Active participation in the peer-to-peer network with configurable connectivity\n**API Exposure**: Comprehensive JSON-RPC APIs covering all blockchain functionality\n**Plugin Extensibility**: Modular architecture supporting specialized functionality through plugins\n**Performance Optimization**: Configurable memory usage and processing modes for different node types\n\n### Implementation Highlights\n\n**Modular Plugin System**: `plugins/*/include/graphene/plugins/*/plugin.hpp`\n- Independent plugin development and deployment\n- Runtime plugin loading/unloading\n- Inter-plugin communication through appbase framework\n\n**High-performance Database**: `libraries/chain/database.cpp`\n- Optimized object storage and retrieval\n- Efficient fork resolution algorithms\n- Memory-mapped file support for large datasets\n\n**Comprehensive API Coverage**: `plugins/database_api/api.cpp`\n- Complete blockchain state queries\n- Historical data access\n- Real-time event streaming\n\n### Feature Architecture Diagrams\n\n```mermaid\nstateDiagram-v2\n [*] --\u003e Initializing\n Initializing --\u003e Syncing: Start Node\n Syncing --\u003e Live: Catch Up Complete\n Live --\u003e Producing: Witness Enabled\n Live --\u003e Listening: Normal Mode\n Producing --\u003e Live: Block Production\n Listening --\u003e Live: New Blocks\n Live --\u003e [*]: Shutdown\n \n Syncing --\u003e [*]: Error\n Live --\u003e [*]: Error\n```\n\n**Development Workflow**: `documentation/building.md`\n- Cross-platform build system\n- Docker containerization support\n- Automated testing and CI/CD integration\n- Release packaging and distribution\n\n**Testing Infrastructure**: `documentation/testing.md`\n- Unit test framework integration\n- Integration testing capabilities\n- Performance benchmarking tools\n- Continuous integration workflows\n\n## 5. Conclusion and Recommendations\n\n### Project Architecture Summary\n\nThe VIZ CPP Node demonstrates a well-architected blockchain implementation with several key strengths:\n\n- **Modular Design**: Clean separation of concerns through the plugin architecture enables easy maintenance and feature development\n- **Performance Focus**: Multiple optimization modes (full node vs. low-memory) accommodate diverse deployment scenarios\n- **Comprehensive API**: Broad coverage of blockchain functionality through standardized JSON-RPC interfaces\n- **Cross-platform Compatibility**: Robust build system supporting multiple operating systems and compilers\n\n### Architectural Strengths\n\n1. **Scalability**: Plugin-based architecture allows selective feature activation based on node requirements\n2. **Maintainability**: Clear module boundaries facilitate independent development and testing\n3. **Interoperability**: Standardized JSON-RPC interface enables seamless integration with external applications\n4. **Reliability**: Comprehensive error handling and logging throughout the system\n\n### Potential Improvement Areas\n\n1. **Documentation**: Enhanced inline documentation would improve code maintainability\n2. **Testing Coverage**: Expanded unit and integration test suites would increase confidence in changes\n3. **Monitoring**: Additional metrics and health check endpoints would improve operational visibility\n4. **Security**: Regular security audits and vulnerability assessments should be integrated into the development process\n\n### Actionable Recommendations\n\n1. **Implement Comprehensive Logging**: Add structured logging with correlation IDs for better debugging\n2. **Enhance Test Coverage**: Target 80%+ code coverage across all modules\n3. **Performance Monitoring**: Integrate metrics collection for key performance indicators\n4. **Security Hardening**: Implement regular security assessments and dependency updates\n5. **Developer Experience**: Create comprehensive developer guides and example applications\n\n### Next Steps for Project Evolution\n\n1. **Microservices Migration**: Consider breaking down monolithic components into microservices for better scalability\n2. **Cloud-native Deployment**: Enhance Kubernetes support and cloud-native features\n3. **Advanced Analytics**: Implement real-time analytics and reporting capabilities\n4. **Mobile Integration**: Develop mobile SDKs for broader ecosystem adoption\n5. **Community Development**: Establish contributor guidelines and development workflows\n\n---\n\n**Sources:**\n- [CMakeLists.txt](CMakeLists.txt)\n- [main.cpp](programs/vizd/main.cpp)\n- [database.hpp](libraries/chain/include/graphene/chain/database.hpp)\n- [operations.hpp](libraries/protocol/include/graphene/protocol/operations.hpp)\n- [plugin.hpp](plugins/chain/include/graphene/plugins/chain/plugin.hpp)\n- [config.ini](share/vizd/config/config.ini)\n- [building.md](documentation/building.md)\n- [plugin.hpp](plugins/database_api/include/graphene/plugins/database_api/plugin.hpp)\n- [p2p_plugin.hpp](plugins/p2p/include/graphene/plugins/p2p/p2p_plugin.hpp)\n- [webserver_plugin.hpp](plugins/webserver/include/graphene/plugins/webserver/webserver_plugin.hpp)\n- [CMakeLists.txt](thirdparty/CMakeLists.txt)\n\n\u003c/blog\u003e","gmt_create":"2026-03-03T07:24:05+04:00","gmt_modified":"2026-03-03T07:24:05+04:00","id":"825a060f-64a6-4072-8338-e1ab100bb1b0","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6"},"wiki_readme":{"content":"No readme file","gmt_create":"2026-03-03T07:22:14+04:00","gmt_modified":"2026-03-03T07:22:14+04:00","id":"2c1da76b-6fd5-4e91-b0e1-3dcd4e769a29","repo_id":"1eebf745-cdbd-49c6-992c-a603f46f10d6"},"wiki_repo":{"id":"1eebf745-cdbd-49c6-992c-a603f46f10d6","name":"viz-cpp-node","progress_status":"completed","wiki_present_status":"COMPLETED","optimized_catalog":"\".\\n├── .github\\\\workflows\\\\\\n│ ├── docker-main.yml\\n│ └── docker-pr-build.yml\\n├── .qoder\\\\\\n│ ├── agents\\\\\\n│ └── skills\\\\\\n├── documentation\\\\\\n│ ├── doxygen\\\\\\n│ │ ├── images\\\\\\n│ │ │ └── viz.png\\n│ │ ├── DoxygenLayout.xml\\n│ │ ├── customdoxygen.css\\n│ │ ├── footer.html\\n│ │ └── header.html\\n│ ├── api_notes.md\\n│ ├── building.md\\n│ ├── debug_node_plugin.md\\n│ ├── git_guildelines.md\\n│ ├── plugin.md\\n│ ├── testing.md\\n│ └── testnet.md\\n├── libraries\\\\\\n│ ├── api\\\\\\n│ │ ├── include\\\\graphene\\\\api\\\\\\n│ │ │ ├── account_api_object.hpp\\n│ │ │ ├── account_vote.hpp\\n│ │ │ ├── chain_api_properties.hpp\\n│ │ │ ├── committee_api_object.hpp\\n│ │ │ ├── content_api_object.hpp\\n│ │ │ ├── discussion.hpp\\n│ │ │ ├── discussion_helper.hpp\\n│ │ │ ├── invite_api_object.hpp\\n│ │ │ ├── paid_subscription_api_object.hpp\\n│ │ │ ├── vote_state.hpp\\n│ │ │ └── witness_api_object.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── account_api_object.cpp\\n│ │ ├── chain_api_properties.cpp\\n│ │ ├── committee_api_object.cpp\\n│ │ ├── content_api_object.cpp\\n│ │ ├── discussion_helper.cpp\\n│ │ ├── invite_api_object.cpp\\n│ │ ├── paid_subscription_api_object.cpp\\n│ │ └── witness_api_object.cpp\\n│ ├── chain\\\\\\n│ │ ├── hardfork.d\\\\\\n│ │ │ ├── 0-preamble.hf\\n│ │ │ ├── 1.hf\\n│ │ │ ├── 10.hf\\n│ │ │ ├── 11.hf\\n│ │ │ ├── 2.hf\\n│ │ │ ├── 3.hf\\n│ │ │ ├── 4.hf\\n│ │ │ ├── 5.hf\\n│ │ │ ├── 6.hf\\n│ │ │ ├── 7.hf\\n│ │ │ ├── 8.hf\\n│ │ │ └── 9.hf\\n│ │ ├── include\\\\graphene\\\\chain\\\\\\n│ │ │ ├── account_object.hpp\\n│ │ │ ├── block_log.hpp\\n│ │ │ ├── block_summary_object.hpp\\n│ │ │ ├── chain_evaluator.hpp\\n│ │ │ ├── chain_object_types.hpp\\n│ │ │ ├── chain_objects.hpp\\n│ │ │ ├── committee_objects.hpp\\n│ │ │ ├── compound.hpp\\n│ │ │ ├── content_object.hpp\\n│ │ │ ├── custom_operation_interpreter.hpp\\n│ │ │ ├── database.hpp\\n│ │ │ ├── database_exceptions.hpp\\n│ │ │ ├── db_with.hpp\\n│ │ │ ├── evaluator.hpp\\n│ │ │ ├── evaluator_registry.hpp\\n│ │ │ ├── fork_database.hpp\\n│ │ │ ├── generic_custom_operation_interpreter.hpp\\n│ │ │ ├── global_property_object.hpp\\n│ │ │ ├── immutable_chain_parameters.hpp\\n│ │ │ ├── index.hpp\\n│ │ │ ├── invite_objects.hpp\\n│ │ │ ├── node_property_object.hpp\\n│ │ │ ├── operation_notification.hpp\\n│ │ │ ├── paid_subscription_objects.hpp\\n│ │ │ ├── proposal_object.hpp\\n│ │ │ ├── shared_authority.hpp\\n│ │ │ ├── shared_db_merkle.hpp\\n│ │ │ ├── transaction_object.hpp\\n│ │ │ └── witness_objects.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── block_log.cpp\\n│ │ ├── chain_evaluator.cpp\\n│ │ ├── chain_objects.cpp\\n│ │ ├── chain_properties_evaluators.cpp\\n│ │ ├── committee_evaluator.cpp\\n│ │ ├── database.cpp\\n│ │ ├── database_proposal_object.cpp\\n│ │ ├── fork_database.cpp\\n│ │ ├── invite_evaluator.cpp\\n│ │ ├── paid_subscription_evaluator.cpp\\n│ │ ├── proposal_evaluator.cpp\\n│ │ ├── proposal_object.cpp\\n│ │ ├── shared_authority.cpp\\n│ │ └── transaction_object.cpp\\n│ ├── network\\\\\\n│ │ ├── include\\\\graphene\\\\network\\\\\\n│ │ │ ├── config.hpp\\n│ │ │ ├── core_messages.hpp\\n│ │ │ ├── exceptions.hpp\\n│ │ │ ├── message.hpp\\n│ │ │ ├── message_oriented_connection.hpp\\n│ │ │ ├── node.hpp\\n│ │ │ ├── peer_connection.hpp\\n│ │ │ ├── peer_database.hpp\\n│ │ │ └── stcp_socket.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── core_messages.cpp\\n│ │ ├── message_oriented_connection.cpp\\n│ │ ├── node.cpp\\n│ │ ├── peer_connection.cpp\\n│ │ ├── peer_database.cpp\\n│ │ └── stcp_socket.cpp\\n│ ├── protocol\\\\\\n│ │ ├── include\\\\graphene\\\\protocol\\\\\\n│ │ │ ├── README.md\\n│ │ │ ├── asset.hpp\\n│ │ │ ├── authority.hpp\\n│ │ │ ├── base.hpp\\n│ │ │ ├── block.hpp\\n│ │ │ ├── block_header.hpp\\n│ │ │ ├── chain_operations.hpp\\n│ │ │ ├── chain_virtual_operations.hpp\\n│ │ │ ├── config.hpp\\n│ │ │ ├── config_testnet.hpp\\n│ │ │ ├── exceptions.hpp\\n│ │ │ ├── get_config.hpp\\n│ │ │ ├── operation_util.hpp\\n│ │ │ ├── operation_util_impl.hpp\\n│ │ │ ├── operations.hpp\\n│ │ │ ├── proposal_operations.hpp\\n│ │ │ ├── protocol.hpp\\n│ │ │ ├── sign_state.hpp\\n│ │ │ ├── transaction.hpp\\n│ │ │ ├── types.hpp\\n│ │ │ └── version.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── asset.cpp\\n│ │ ├── authority.cpp\\n│ │ ├── block.cpp\\n│ │ ├── chain_operations.cpp\\n│ │ ├── get_config.cpp\\n│ │ ├── operation_util_impl.cpp\\n│ │ ├── operations.cpp\\n│ │ ├── proposal_operations.cpp\\n│ │ ├── sign_state.cpp\\n│ │ ├── transaction.cpp\\n│ │ ├── types.cpp\\n│ │ └── version.cpp\\n│ ├── time\\\\\\n│ │ ├── include\\\\graphene\\\\time\\\\\\n│ │ │ └── time.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── time.cpp\\n│ ├── utilities\\\\\\n│ │ ├── include\\\\graphene\\\\utilities\\\\\\n│ │ │ ├── git_revision.hpp\\n│ │ │ ├── key_conversion.hpp\\n│ │ │ ├── padding_ostream.hpp\\n│ │ │ ├── string_escape.hpp\\n│ │ │ ├── tempdir.hpp\\n│ │ │ └── words.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── git_revision.cpp.in\\n│ │ ├── key_conversion.cpp\\n│ │ ├── string_escape.cpp\\n│ │ ├── tempdir.cpp\\n│ │ └── words.cpp\\n│ ├── wallet\\\\\\n│ │ ├── include\\\\graphene\\\\wallet\\\\\\n│ │ │ ├── api_documentation.hpp\\n│ │ │ ├── reflect_util.hpp\\n│ │ │ ├── remote_node_api.hpp\\n│ │ │ └── wallet.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── Doxyfile.in\\n│ │ ├── api_documentation_standin.cpp\\n│ │ ├── generate_api_documentation.pl\\n│ │ └── wallet.cpp\\n│ └── CMakeLists.txt\\n├── plugins\\\\\\n│ ├── account_by_key\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\account_by_key\\\\\\n│ │ │ ├── account_by_key_objects.hpp\\n│ │ │ └── account_by_key_plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── account_by_key_plugin.cpp\\n│ ├── account_history\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\account_history\\\\\\n│ │ │ ├── history_object.hpp\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── auth_util\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\auth_util\\\\\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── block_info\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\block_info\\\\\\n│ │ │ ├── block_info.hpp\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── chain\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\chain\\\\\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── committee_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\committee_api\\\\\\n│ │ │ └── committee_api.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── committee_api.cpp\\n│ ├── custom_protocol_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\custom_protocol_api\\\\\\n│ │ │ ├── custom_protocol_api.hpp\\n│ │ │ ├── custom_protocol_api_object.hpp\\n│ │ │ └── custom_protocol_api_visitor.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── custom_protocol_api.cpp\\n│ │ └── custom_protocol_api_visitor.cpp\\n│ ├── database_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\database_api\\\\\\n│ │ │ ├── api_objects\\\\\\n│ │ │ │ ├── account_recovery_request_api_object.hpp\\n│ │ │ │ ├── master_authority_history_api_object.hpp\\n│ │ │ │ └── proposal_api_object.hpp\\n│ │ │ ├── forward.hpp\\n│ │ │ ├── plugin.hpp\\n│ │ │ └── state.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── api.cpp\\n│ │ └── proposal_api_object.cpp\\n│ ├── debug_node\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\debug_node\\\\\\n│ │ │ ├── api_helper.hpp\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── follow\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\follow\\\\\\n│ │ │ ├── follow_api_object.hpp\\n│ │ │ ├── follow_evaluators.hpp\\n│ │ │ ├── follow_forward.hpp\\n│ │ │ ├── follow_objects.hpp\\n│ │ │ ├── follow_operations.hpp\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── follow_evaluators.cpp\\n│ │ ├── follow_operations.cpp\\n│ │ └── plugin.cpp\\n│ ├── invite_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\invite_api\\\\\\n│ │ │ └── invite_api.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── invite_api.cpp\\n│ ├── json_rpc\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\json_rpc\\\\\\n│ │ │ ├── plugin.hpp\\n│ │ │ └── utility.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── mongo_db\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\mongo_db\\\\\\n│ │ │ ├── mongo_db_operations.hpp\\n│ │ │ ├── mongo_db_plugin.hpp\\n│ │ │ ├── mongo_db_state.hpp\\n│ │ │ ├── mongo_db_types.hpp\\n│ │ │ └── mongo_db_writer.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── mongo_db_operations.cpp\\n│ │ ├── mongo_db_plugin.cpp\\n│ │ ├── mongo_db_state.cpp\\n│ │ ├── mongo_db_types.cpp\\n│ │ └── mongo_db_writer.cpp\\n│ ├── network_broadcast_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\network_broadcast_api\\\\\\n│ │ │ └── network_broadcast_api_plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── network_broadcast_api.cpp\\n│ ├── operation_history\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\operation_history\\\\\\n│ │ │ ├── applied_operation.hpp\\n│ │ │ ├── history_object.hpp\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── applied_operation.cpp\\n│ │ └── plugin.cpp\\n│ ├── p2p\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\p2p\\\\\\n│ │ │ └── p2p_plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── p2p_plugin.cpp\\n│ ├── paid_subscription_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\paid_subscription_api\\\\\\n│ │ │ └── paid_subscription_api.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── paid_subscription_api.cpp\\n│ ├── private_message\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\private_message\\\\\\n│ │ │ ├── private_message_evaluators.hpp\\n│ │ │ ├── private_message_objects.hpp\\n│ │ │ └── private_message_plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── private_message_objects.cpp\\n│ │ └── private_message_plugin.cpp\\n│ ├── raw_block\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\raw_block\\\\\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ ├── social_network\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\social_network\\\\\\n│ │ │ └── social_network.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── social_network.cpp\\n│ ├── tags\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\tags\\\\\\n│ │ │ ├── discussion_query.hpp\\n│ │ │ ├── plugin.hpp\\n│ │ │ ├── tag_api_object.hpp\\n│ │ │ ├── tag_visitor.hpp\\n│ │ │ ├── tags_object.hpp\\n│ │ │ └── tags_sort.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ ├── discussion_query.cpp\\n│ │ ├── plugin.cpp\\n│ │ └── tag_visitor.cpp\\n│ ├── test_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\test_api\\\\\\n│ │ │ └── test_api_plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── test_api_plugin.cpp\\n│ ├── webserver\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\webserver\\\\\\n│ │ │ └── webserver_plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── webserver_plugin.cpp\\n│ ├── witness\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\witness\\\\\\n│ │ │ └── witness.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── witness.cpp\\n│ ├── witness_api\\\\\\n│ │ ├── include\\\\graphene\\\\plugins\\\\witness_api\\\\\\n│ │ │ ├── api_objects\\\\\\n│ │ │ │ ├── feed_history_api_object.hpp\\n│ │ │ │ └── witness_api_object.hpp\\n│ │ │ └── plugin.hpp\\n│ │ ├── CMakeLists.txt\\n│ │ └── plugin.cpp\\n│ └── CMakeLists.txt\\n├── programs\\\\\\n│ ├── build_helpers\\\\\\n│ │ ├── CMakeLists.txt\\n│ │ ├── cat-parts.cpp\\n│ │ ├── cat_parts.py\\n│ │ ├── check_reflect.py\\n│ │ └── configure_build.py\\n│ ├── cli_wallet\\\\\\n│ │ ├── CMakeLists.txt\\n│ │ └── main.cpp\\n│ ├── js_operation_serializer\\\\\\n│ │ ├── CMakeLists.txt\\n│ │ └── main.cpp\\n│ ├── size_checker\\\\\\n│ │ ├── CMakeLists.txt\\n│ │ └── main.cpp\\n│ ├── util\\\\\\n│ │ ├── CMakeLists.txt\\n│ │ ├── get_dev_key.cpp\\n│ │ ├── inflation_plot.py\\n│ │ ├── newplugin.py\\n│ │ ├── pretty_schema.py\\n│ │ ├── saltpass.py\\n│ │ ├── schema_test.cpp\\n│ │ ├── sign_digest.cpp\\n│ │ ├── sign_transaction.cpp\\n│ │ ├── test_block_log.cpp\\n│ │ └── test_shared_mem.cpp\\n│ ├── vizd\\\\\\n│ │ ├── CMakeLists.txt\\n│ │ └── main.cpp\\n│ └── CMakeLists.txt\\n├── share\\\\vizd\\\\\\n│ ├── config\\\\\\n│ │ ├── config.ini\\n│ │ ├── config_debug.ini\\n│ │ ├── config_debug_mongo.ini\\n│ │ ├── config_mongo.ini\\n│ │ ├── config_stock_exchange.ini\\n│ │ ├── config_testnet.ini\\n│ │ └── config_witness.ini\\n│ ├── docker\\\\\\n│ │ ├── Dockerfile-lowmem\\n│ │ ├── Dockerfile-mongo\\n│ │ ├── Dockerfile-production\\n│ │ └── Dockerfile-testnet\\n│ ├── seednodes\\n│ ├── seednodes_empty\\n│ ├── snapshot-testnet.json\\n│ ├── snapshot.json\\n│ └── vizd.sh\\n├── thirdparty\\\\\\n│ ├── appbase\\\\\\n│ ├── chainbase\\\\\\n│ ├── fc\\\\\\n│ └── CMakeLists.txt\\n├── .gitignore\\n├── .gitmodules\\n├── .travis.yml\\n├── CMakeLists.txt\\n├── Doxyfile\\n├── LICENSE.md\\n└── README.md\\n\"","current_document_structure":"WikiEncrypted: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","catalogue_think_content":"WikiEncrypted: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","recovery_checkpoint":"incremental_processing","last_commit_id":"93ce9f3e0908c4e1445a502330f0e3e6a70f0c81","last_commit_update":"2026-04-30T13:09:19.4616936+04:00","gmt_create":"2026-03-03T06:42:09+04:00","gmt_modified":"2026-04-30T13:09:19.4616936+04:00","extend_info":"{\"language\":\"en\",\"active\":true,\"branch\":\"fix-witness\",\"shareStatus\":\"\",\"server_error_code\":\"\",\"cosy_version\":\"0.8.2\"}"}} \ No newline at end of file diff --git a/.qoder/research/consensus-emergency-recovery.md b/.qoder/research/consensus-emergency-recovery.md deleted file mode 100644 index 97577e8a79..0000000000 --- a/.qoder/research/consensus-emergency-recovery.md +++ /dev/null @@ -1,1513 +0,0 @@ -# Research: Consensus Emergency Recovery & Micro-Fork Prevention - -## Status: Research + Proposal - -## Problem Statement - -When a significant portion of VIZ witnesses go offline, the network stalls. The current recovery procedure requires operators to manually set `enable-stale-production = true` and `required-participation = 0`, but: - -1. **Operators forget to revert** these settings after recovery, causing micro-forks during subsequent network partitions -2. **fork_db depth = 1024 blocks (~51 min)** means if a partitioned witness produces blocks alone for >51 min, fork resolution fails -3. **No automatic detection or recovery** exists — everything depends on manual operator intervention -4. **Witness shutdown after 200 missed blocks (~10 min)** ([config.hpp:32](../../libraries/protocol/include/graphene/protocol/config.hpp#L32)) removes witnesses from the schedule, making recovery even harder -5. **Penalties accumulate** during the stall, further reducing witness `counted_votes` and their scheduling priority - ---- - -## Current Mechanism Analysis - -### 1. Block Production Safeguards (witness.cpp) - -Two safeguards prevent isolated block production: - -| Safeguard | Check | Condition | Effect | -|---|---|---|---| -| Stale check | `_production_enabled` | `get_slot_time(1) >= now` | Stops production if node hasn't received recent blocks | -| Participation check | `prate < _required_witness_participation` | 33% default threshold | Stops production if < 33% of slots are filled | - -Source: [witness.cpp:333-339](../../plugins/witness/witness.cpp#L333), [witness.cpp:436-439](../../plugins/witness/witness.cpp#L436) - -Both are **bypassed** by emergency settings. There is no time-based automatic re-enablement of these safeguards. - -### 2. Witness Shutdown Mechanism (database.cpp:4046-4051) - -```cpp -if (head_block_num() - w.last_confirmed_block_num > CHAIN_MAX_WITNESS_MISSED_BLOCKS) { - w.signing_key = public_key_type(); // Zero key = disabled - push_virtual_operation(shutdown_witness_operation(w.owner)); -} -``` - -`CHAIN_MAX_WITNESS_MISSED_BLOCKS = 200` (~10 min). After a witness misses 200 blocks, their `signing_key` is set to null, removing them from the schedule. This is **irreversible on-chain** — the witness must submit a new `witness_update_operation` to re-enable their key. - -**Problem during stall**: If the network stalls for >10 min, ALL witnesses get shut down. Recovery then requires external intervention (CLI wallet) to re-register all witness keys. - -### 3. Penalty Accumulation (database.cpp:4035-4043) - -During any gap, missed blocks trigger penalties: - -```cpp -w.penalty_percent += consensus.median_props.witness_miss_penalty_percent; // 1% -// penalty expires after witness_miss_penalty_duration (1 day) -``` - -During a prolonged stall, penalties accumulate massively. Even after recovery, witnesses are penalized for blocks they couldn't possibly produce — their `counted_votes` drops, affecting schedule position. - -### 4. Fork Database Depth (fork_database.hpp:117) - -```cpp -uint32_t _max_size = 1024; // ~51 minutes at 3s intervals -``` - -The fork_db dynamically resizes to `head - LIB + 1` during normal operation. But during solo production: -- LIB doesn't advance (no 75% witness validation possible) -- fork_db stays at 1024 -- After 1024 blocks of solo production, older blocks are pruned -- Reconnection with the main chain becomes impossible without full replay - -### 5. Last Irreversible Block (LIB) Advancement - -LIB advances via two mechanisms: -1. **`update_last_irreversible_block()`** — uses `nth_element` of `last_supported_block_num` at 75% threshold -2. **Block post-validation** — requires 75% of `num_scheduled_witnesses` to sign off - -During a stall or solo production, neither mechanism works. LIB freezes at the last block where 75% of witnesses were participating. - -### 6. Witness Scheduling - -```cpp -account_name_type database::get_scheduled_witness(uint32_t slot_num) const { - uint64_t current_aslot = dpo.current_aslot + slot_num; - return wso.current_shuffled_witnesses[current_aslot % wso.num_scheduled_witnesses]; -} -``` - -The schedule is deterministic — `current_aslot % num_scheduled_witnesses`. With fewer witnesses (after shutdowns), the schedule wraps faster, but only among remaining active witnesses. - -### 7. Current Emergency Procedure (Manual) - -| Step | Action | Risk | -|---|---|---| -| 1 | Set `enable-stale-production = true` | Bypasses sync check permanently | -| 2 | Set `required-participation = 0` | Removes participation safeguard permanently | -| 3 | Restart node | Node produces blocks even if isolated | -| 4 | Re-register shut-down witnesses via CLI | Manual, error-prone | -| 5 | **FORGET to revert settings** | **Micro-forks on next partition** | - ---- - -## Root Cause Analysis - -The fundamental problem is that **safeguards are binary and manual**: - -1. `enable-stale-production` is either `true` or `false` — no time-based auto-revert -2. `required-participation` has no automatic adjustment based on network conditions -3. Witness shutdown (`signing_key = null`) is permanent on-chain — no automatic recovery -4. No on-chain concept of "emergency mode" — all recovery is off-chain configuration - -### The Micro-Fork Chain of Events - -``` -Network Stall - │ - ├─ Witnesses miss blocks → penalties accumulate - ├─ After 200 missed blocks → signing_key = null (shutdown) - ├─ Participation rate → 0% → production stops - │ - ├─ Operator manually sets enable-stale=true, required-participation=0 - ├─ One witness starts producing alone - ├─ Other witnesses re-register via CLI, rejoin - ├─ Network recovers, operator FORGETS to revert settings - │ - ├─ Later: network partition - ├─ Isolated witness: enable-stale=true → keeps producing - ├─ Isolated witness: required-participation=0 → never stops - ├─ Isolated witness: skip_undo_history_check → no LIB gap limit - │ - ├─ After 51 min (1024 blocks): fork_db prunes old blocks - ├─ Reconnection: fork resolution FAILS - └─ Full replay required → extended downtime -``` - ---- - -## Proposal: On-Chain Emergency Consensus Mode - -### Core Concept - -Add a **consensus-level emergency mode** that activates automatically when the network has been unable to produce blocks for a configurable duration. In emergency mode, a single well-known committee key becomes the sole block producer, ensuring chain continuity without requiring operators to disable safety checks. - -### Design Principles - -1. **Automatic activation** — no manual intervention needed to enter emergency mode -2. **Automatic deactivation** — emergency mode exits when normal witnesses resume -3. **No permanent safety bypass** — `enable-stale-production` and `required-participation` remain at safe values -4. **Deterministic committee key** — all nodes agree on who produces blocks during emergency -5. **Minimal consensus changes** — smallest possible diff to existing objects - ---- - -### Change 1: New Constants (config.hpp) - -```cpp -/// Emergency consensus mode: activates when no block has been produced for -/// this many seconds since the last irreversible block. -#define CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC 3600 // 1 hour - -/// The witness account name that produces blocks during emergency mode -#define CHAIN_EMERGENCY_WITNESS_ACCOUNT CHAIN_COMMITTEE_ACCOUNT // "committee" - -/// The public key used to sign blocks during emergency mode -#define CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY_STR "VIZ75CRHVHPwYiUESy1bgN3KhVFbZCQQRA9jT6TnpzKAmpxMPD6Xv" -#define CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY (graphene::protocol::public_key_type(CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY_STR)) - -/// Number of consecutive blocks produced by the emergency witness that -/// triggers automatic exit from emergency mode (witnesses have rejoined). -#define CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS 21 // 1 full round of 21 witnesses -``` - -### Change 2: Emergency Mode Flag in dynamic_global_property_object (Breaking) - -Add a new field to track emergency state: - -```cpp -// In global_property_object.hpp, inside dynamic_global_property_object: -bool emergency_consensus_active = false; - -/// Block number at which emergency consensus mode was activated. -/// Zero if never activated. Used to compute duration and for logging. -uint32_t emergency_consensus_start_block = 0; -``` - -**FC_REFLECT update** (breaking change — requires hardfork): - -```cpp -FC_REFLECT((graphene::chain::dynamic_global_property_object), - // ... existing fields ... - (emergency_consensus_active) - (emergency_consensus_start_block) -) -``` - -### Change 3: Emergency Mode Detection — Consensus-Level (database.cpp) - -In `update_global_dynamic_data()`, after the existing missed-blocks logic: - -```cpp -// After the existing participation/missed-blocks update logic: - -const dynamic_global_property_object &dgp = get_dynamic_global_properties(); - -if (!dgp.emergency_consensus_active) { - // Check if we should enter emergency mode: - // No block has been produced by a normal witness since LIB, - // and more than CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC has elapsed. - fc::time_point_sec lib_time = get_block_time(dgp.last_irreversible_block_num); - uint32_t seconds_since_lib = (b.timestamp - lib_time).to_seconds(); - - if (seconds_since_lib >= CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC) { - // Enter emergency consensus mode - modify(dgp, [&](dynamic_global_property_object &_dgp) { - _dgp.emergency_consensus_active = true; - _dgp.emergency_consensus_start_block = b.block_num(); - }); - - // Override witness schedule to single emergency witness - const witness_schedule_object &wso = get_witness_schedule_object(); - modify(wso, [&](witness_schedule_object &_wso) { - for (int i = 0; i < _wso.num_scheduled_witnesses; i++) { - _wso.current_shuffled_witnesses[i] = CHAIN_EMERGENCY_WITNESS_ACCOUNT; - } - // Keep num_scheduled_witnesses unchanged to preserve schedule rhythm - }); - - ilog("EMERGENCY CONSENSUS MODE activated at block ${b}. " - "No blocks for ${sec} seconds since LIB ${lib}. " - "Emergency witness: ${w}", - ("b", b.block_num())("sec", seconds_since_lib) - ("lib", dgp.last_irreversible_block_num) - ("w", CHAIN_EMERGENCY_WITNESS_ACCOUNT)); - } -} -``` - -### Change 4: Emergency Block Production — Witness Plugin (witness.cpp) - -In `maybe_produce_block()`, add emergency mode awareness: - -```cpp -// After the _production_enabled check, add: - -auto &db = database(); -const auto &dgp = db.get_dynamic_global_properties(); - -if (dgp.emergency_consensus_active) { - // In emergency mode, any node with the emergency private key - // can produce blocks. The schedule only contains the emergency witness. - // - // The emergency key must be configured in the node's config: - // emergency-private-key = 5JPYE8UhnxDgURG7mFDcSPQaNjwT5VmCrq3L4QQ2sZSQhbTUZkZ - // - // This is safe because: - // 1. The key is only used during emergency mode - // 2. Any number of nodes can have this key (only one produces per slot) - // 3. Emergency mode auto-exits when normal witnesses return -} -``` - -Also add a new program option: - -```cpp -// In set_program_options: -("emergency-private-key", bpo::value>()->composing()->multitoken(), - "WIF PRIVATE KEY for emergency consensus block production") -``` - -And in `plugin_initialize`, load the emergency key alongside existing witness keys: - -```cpp -if (options.count("emergency-private-key")) { - const std::vector keys = options["emergency-private-key"].as>(); - for (const std::string &wif_key : keys) { - fc::optional private_key = graphene::utilities::wif_to_key(wif_key); - FC_ASSERT(private_key.valid(), "unable to parse emergency private key"); - pimpl->_private_keys[private_key->get_public_key()] = *private_key; - } -} -``` - -### Change 5: Emergency Witness Object — On-Chain (database.cpp) - -When entering emergency mode, ensure the emergency witness account exists in the witness index with the correct signing key: - -```cpp -// When entering emergency mode: -const auto &witness_by_name = get_index().indices().get(); -auto wit_itr = witness_by_name.find(CHAIN_EMERGENCY_WITNESS_ACCOUNT); - -if (wit_itr == witness_by_name.end()) { - // Create emergency witness object if it doesn't exist - create([&](witness_object &w) { - w.owner = CHAIN_EMERGENCY_WITNESS_ACCOUNT; - w.signing_key = CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY; - w.created = head_block_time(); - w.schedule = witness_object::top; - }); -} else { - // Ensure existing emergency witness has the correct key - modify(*wit_itr, [&](witness_object &w) { - w.signing_key = CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY; - w.schedule = witness_object::top; - }); -} -``` - -### Change 6: Emergency Mode Exit — Hybrid Schedule Transition (database.cpp) - -**Problem with the naive exit approach**: During full emergency, ALL schedule slots = "committee". Normal witnesses can't produce blocks → `last_confirmed_block_num` never updates → the exit condition `head_block_num() - w.last_confirmed_block_num < CHAIN_MAX_WITNESS_MISSED_BLOCKS` is always FALSE → **emergency never exits**. - -**Solution: Hybrid schedule** — instead of all-committee or all-real, the emergency schedule is a **mix**. Normal `update_witness_schedule()` runs, and real witnesses get their slots. Committee fills **only** the remaining slots where no real witness is assigned. - -This way: -- Witnesses that are online naturally produce at their assigned slots -- Committee covers slots where witnesses are still offline -- No special re-registration or signaling required -- The system **observes** who is actually producing - -**LIB advancement** is the natural exit signal: -- `update_last_irreversible_block()` needs 75% (16 of 21) witnesses with recent `last_supported_block_num` -- 11 witnesses producing = 52% → LIB stays frozen -- 16 witnesses producing = 76% → LIB starts moving → emergency exits - -In `update_witness_schedule()`, after recalculating the witness schedule: - -```cpp -// After normal schedule update, check emergency mode: -const dynamic_global_property_object &dgp = get_dynamic_global_properties(); - -if (dgp.emergency_consensus_active && has_hardfork(CHAIN_HARDFORK_12)) { - const witness_schedule_object &wso = get_witness_schedule_object(); - - // HYBRID SCHEDULE: Keep the normal computed schedule, but replace - // slots for witnesses that appear offline with committee. - // A witness is considered "potentially offline" if: - // - signing_key is null (disabled), OR - // - no confirmed block since emergency started - // (they haven't produced since we entered emergency) - // - // Witnesses that ARE online will produce at their assigned slots. - // Committee fills only the gaps. - - modify(wso, [&](witness_schedule_object &_wso) { - uint32_t real_witness_slots = 0; - - for (int i = 0; i < _wso.num_scheduled_witnesses; - i += CHAIN_BLOCK_WITNESS_REPEAT) { - const auto &wname = _wso.current_shuffled_witnesses[i]; - if (wname == account_name_type()) { - // Empty slot → assign committee - for (int j = 0; j < CHAIN_BLOCK_WITNESS_REPEAT && - (i+j) < _wso.num_scheduled_witnesses; ++j) { - _wso.current_shuffled_witnesses[i+j] = - CHAIN_EMERGENCY_WITNESS_ACCOUNT; - } - continue; - } - - const auto *w = find_witness(wname); - bool witness_available = w && - w->signing_key != public_key_type() && - // Has the witness produced since emergency started? - // If they have, keep their slot. - // If not, give them the slot anyway — they might be - // coming back. If they miss, it's a normal miss. - // The penalty system handles it. - true; // Always give real witnesses their slots - - if (!witness_available) { - for (int j = 0; j < CHAIN_BLOCK_WITNESS_REPEAT && - (i+j) < _wso.num_scheduled_witnesses; ++j) { - _wso.current_shuffled_witnesses[i+j] = - CHAIN_EMERGENCY_WITNESS_ACCOUNT; - } - } else { - real_witness_slots++; - } - } - - ilog("Emergency hybrid schedule: ${r} real witness slots, " - "${c} committee slots", - ("r", real_witness_slots) - ("c", CHAIN_MAX_WITNESSES - real_witness_slots)); - }); - - // EXIT CONDITION: LIB is advancing again. - // When LIB unfreezes (Change 12), it means 75% of scheduled witnesses - // are producing consistently. That's the strongest possible signal - // that the network has recovered. - // - // We check: has LIB moved past emergency_consensus_start_block? - // If yes → enough witnesses are back → exit emergency. - uint32_t current_lib = dgp.last_irreversible_block_num; - if (current_lib > dgp.emergency_consensus_start_block) { - modify(dgp, [&](dynamic_global_property_object &_dgp) { - _dgp.emergency_consensus_active = false; - }); - - ilog("EMERGENCY CONSENSUS MODE deactivated at block ${b}. " - "LIB has advanced to ${lib}, past emergency start ${start}.", - ("b", head_block_num()) - ("lib", current_lib) - ("start", dgp.emergency_consensus_start_block)); - } -} -``` - -**How the hybrid schedule works step by step**: - -``` -Step 1: Emergency activates (hour 0) - - Penalties reset (Change 7) - - update_witness_schedule() runs normally: top 11 + 10 support - - BUT all 21 witnesses are offline → all miss their slots - - Hybrid: committee fills all 21 slots - - Result: [committee × 21] — same as before - -Step 2: 5 witnesses come back online (hour 1.5) - - update_witness_schedule() computes: top 11 + 10 support - - 5 of these are online, 16 are still offline - - Hybrid: 5 get their real slots, 16 = committee - - Result: [wit1, wit2, wit3, wit4, wit5, committee × 16] - - Those 5 produce real blocks → their last_confirmed_block_num updates - - Committee produces the other 16 slots → chain continues - - LIB: only 5/21 = 24% producing → LIB stays frozen - -Step 3: 16 witnesses back (hour 3) - - Hybrid: 16 real slots + 5 committee - - 16/21 = 76% > 75% threshold - - LIB unfreezes → starts advancing - - LIB passes emergency_consensus_start_block - - Emergency mode exits! - -Step 4: All 21 witnesses back - - Normal operation, all committee slots replaced - - Schedule is 100% real witnesses -``` - -**Key insight**: The witness doesn't need to "re-register" — their `signing_key` is still set from before the emergency. They just need their node to be running and connected to P2P. The hybrid schedule gives them their slot; if they're online, they produce; if not, committee covers. - -### Change 7: Penalty Reset on Emergency Activation - -When entering emergency mode, reset all witness penalties and re-enable shut-down witnesses: - -```cpp -// When entering emergency mode: -const auto &witness_idx = get_index().indices().get(); -for (auto wit_itr = witness_idx.begin(); wit_itr != witness_idx.end(); ++wit_itr) { - modify(*wit_itr, [&](witness_object &w) { - // Re-enable witness if shut down - if (w.signing_key == public_key_type()) { - // The witness must be re-registered with their key via - // witness_update_operation. We cannot restore the key because - // we don't know it. However, we can reset penalties so that - // when they do re-register, they aren't heavily penalized. - } - // Reset all penalties - w.penalty_percent = 0; - w.counted_votes = w.votes; - // Reset miss counters - w.current_run = 0; - }); -} - -// Remove all pending penalty expiration objects -const auto &penalty_idx = get_index().indices().get(); -auto pen_itr = penalty_idx.begin(); -while (pen_itr != penalty_idx.end()) { - const auto ¤t = *pen_itr; - ++pen_itr; - remove(current); -} -``` - -### Change 8: Emergency Key Configuration in config.ini - -```ini -# WIF PRIVATE KEY for emergency consensus block production. -# This key is only used when the network enters emergency consensus mode -# (no blocks for >1 hour since last irreversible block). -# Multiple nodes can safely have this key — only one produces per slot. -# Private key: 5JPYE8UhnxDgURG7mFDcSPQaNjwT5VmCrq3L4QQ2sZSQhbTUZkZ -# Public key: VIZ75CRHVHPwYiUESy1bgN3KhVFbZCQQRA9jT6TnpzKAmpxMPD6Xv -# emergency-private-key = 5JPYE8UhnxDgURG7mFDcSPQaNjwT5VmCrq3L4QQ2sZSQhbTUZkZ -``` - -### Change 9: Automatic Safety Enforcement — Remove Human Factor (witness.cpp) - -**Problem**: Operators set `enable-stale-production=true` and `required-participation=0` to recover the network, then forget to revert. This is the #1 cause of micro-forks. - -**Solution**: After hardfork 12, the witness plugin **automatically enforces safe defaults** when the network is healthy, regardless of what the operator configured. The config values `enable-stale-production` and `required-participation` become irrelevant — the consensus layer decides. - -In `maybe_produce_block()`, replace the existing stale/participation checks with: - -```cpp -// === AUTOMATIC SAFETY ENFORCEMENT (Hardfork 12+) === -// Remove the human factor: when the network is healthy, enforce safe -// defaults regardless of operator config. Operators no longer need to -// manage enable-stale-production or required-participation manually. - -auto &db = database(); -const auto &dgp = db.get_dynamic_global_properties(); - -if (has_hardfork(CHAIN_HARDFORK_12)) { - if (!dgp.emergency_consensus_active) { - // NORMAL MODE: Always enforce safe defaults. - // - Stale check is ALWAYS active (equivalent to enable-stale-production=false) - // - Participation threshold is ALWAYS 33% (required-participation=3300) - // - // This completely removes the human factor. Even if an operator - // has enable-stale-production=true in their config, it's ignored. - - if (!_production_enabled) { - if (db.get_slot_time(1) >= now) { - _production_enabled = true; - } else { - return block_production_condition::not_synced; - } - } - - uint32_t prate = db.witness_participation_rate(); - if (prate < 33 * CHAIN_1_PERCENT) { // Hardcoded 33% — not configurable - capture("pct", uint32_t(prate / CHAIN_1_PERCENT)); - return block_production_condition::low_participation; - } - } else { - // EMERGENCY MODE: Bypass both checks. - // The consensus layer has determined that emergency mode is needed. - // The witness plugin trusts the consensus-level decision and - // produces blocks without the stale/participation checks. - // No manual enable-stale-production=true needed. - _production_enabled = true; - } -} else { - // Pre-hardfork 12: use legacy behavior with config-based overrides - // (existing code for enable-stale-production and required-participation) - if (!_production_enabled) { - if (db.get_slot_time(1) >= now) { - _production_enabled = true; - } else { - return block_production_condition::not_synced; - } - } - uint32_t prate = db.witness_participation_rate(); - if (prate < _required_witness_participation) { - capture("pct", uint32_t(prate / CHAIN_1_PERCENT)); - return block_production_condition::low_participation; - } -} -``` - -**Effect**: After hardfork 12, the lifecycle is fully automatic: - -| Network State | Stale Check | Participation Check | Manual Config Effect | -|---|---|---|---| -| Normal (21 witnesses healthy) | **Always ON** | **Always 33%** | Ignored | -| Emergency mode (LIB timeout) | **Auto-bypassed** | **Auto-bypassed** | Ignored | -| Pre-hardfork 12 | Config-based | Config-based | Honored (legacy) | - -Operators **never** need to touch `enable-stale-production` or `required-participation` again. - -### Change 10: Emergency Multi-Producer Collision Resolution (fork_database.cpp) - -**Problem**: When 5 nodes have the emergency key, ALL of them produce a block for the same slot simultaneously. This creates 5 competing blocks at the same height with different transactions. - -**Root cause analysis**: - -1. **Fork collision check is blind during emergency** — [witness.cpp:457](../../plugins/witness/witness.cpp#L457) checks `eb->data.witness != scheduled_witness`, but during emergency ALL blocks have `witness = "committee"`, so the condition is always `false`. The check never triggers. - -2. **fork_database uses first-seen-wins** — [fork_database.cpp:77](../../libraries/chain/fork_database.cpp#L77): `if (item->num > _head->num)` means same-height blocks don't replace head. Different nodes see different blocks first → persistent parallel chains. - -3. **Timing**: All 5 nodes wake up within milliseconds of slot boundary, check fork_db (empty), produce, broadcast. By the time P2P propagates, all 5 blocks exist. - -**Solution: Deterministic hash-based tie-breaking** - -When two blocks compete at the same height, compare their `block_id` (hash). The block with the **lower hash wins**. Since `block_id` is deterministic (hash of block content), ALL nodes converge to the same winner regardless of P2P arrival order. - -In `fork_database::_push_block()`: - -```cpp -_index.insert(item); -if (!_head || item->num > _head->num) { - _head = item; -} -// CHANGE 10: During emergency mode, deterministic hash tie-breaking. -// When two blocks compete at the same height (multiple emergency -// producers), prefer the lower block_id hash. This ensures all -// nodes converge regardless of P2P arrival order. -// Only applies during emergency — normal mode uses slot-based resolution. -else if (item->num == _head->num && item->id < _head->id && - _emergency_consensus_active) { - _head = item; -} -``` - -> Note: `_emergency_consensus_active` is a flag passed to fork_database (e.g., via `set_emergency_mode(bool)`) from `database` when emergency mode changes state. - -**Why this works**: - -``` -Slot N: 5 nodes produce blocks B1..B5 at height H - │ - ├─ Each block has a unique block_id (hash of content) - ├─ Node A receives: B1, B3, B5, B2, B4 (arrival order varies) - ├─ Node B receives: B3, B1, B4, B5, B2 - ├─ Node C receives: B2, B5, B1, B3, B4 - │ - ├─ All nodes compare hashes: min(B1.id, B2.id, B3.id, B4.id, B5.id) - ├─ All nodes converge to the SAME winner (e.g., B2 has lowest hash) - │ - └─ Result: deterministic convergence within one P2P round - -Slot N+1: fork collision check (updated below) detects the winning - block and prevents redundant production on most nodes. -``` - -**Transactions from losing blocks** (B1, B3, B4, B5) remain in each node's `_pending_tx` pool and are included in subsequent blocks. No transactions are lost. - -### Change 11: Fix Fork Collision Check for Emergency Mode (witness.cpp) - -The existing fork collision check at [witness.cpp:447-471](../../plugins/witness/witness.cpp#L447) doesn't detect emergency-mode collisions because it requires `witness != scheduled_witness`. During emergency, all blocks are from "committee". - -**Fix**: During emergency mode, ANY existing block at the target height is a competing block: - -```cpp -// Check if a competing block already exists in fork_db for this height. -{ - auto existing_blocks = db.get_fork_db().fetch_block_by_number(db.head_block_num() + 1); - if (existing_blocks.size() > 0) { - bool has_competing_block = false; - - if (dgp.emergency_consensus_active) { - // During emergency mode: ANY block at this height is competing. - // Multiple nodes with the emergency key may have produced. - // Defer to the deterministic hash-based resolution in fork_db. - has_competing_block = true; - } else { - // Normal mode: only count blocks from different witnesses - // on different parents as competing (existing logic) - for (const auto &eb : existing_blocks) { - if (eb->data.witness != scheduled_witness && - eb->data.previous != db.head_block_id()) { - has_competing_block = true; - break; - } - } - } - - if (has_competing_block) { - capture("height", db.head_block_num() + 1)("scheduled_witness", scheduled_witness); - wlog("Skipping block production at height ${h} due to existing competing block " - "in fork database (witness ${w} deferring to allow fork resolution)", - ("h", db.head_block_num() + 1)("w", scheduled_witness)); - return block_production_condition::fork_collision; - } - } -} -``` - -**Combined effect of Changes 10 + 11**: - -| Slot | What happens | Competing blocks | -|---|---|---| -| 1st emergency slot | All 5 nodes produce simultaneously (unavoidable) | 5 blocks | -| Hash resolution | All nodes converge to lowest-hash block | 1 winner | -| 2nd emergency slot | Fork collision check fires on nodes that received a block | 1-2 blocks | -| 3rd+ emergency slots | Only 1 node produces (others see block in fork_db first) | 1 block | - -The collision is a **transient startup artifact** that self-resolves within 1-2 slots (~3-6 seconds). - -### Change 12: LIB Freeze During Emergency — Partition Safety (database.cpp) - -**Problem: Competing irreversible emergency chains** - -If the network splits into 3 partitions (e.g., US, EU, Asia), each partition independently activates emergency mode after 1 hour. Each partition produces its own emergency chain with the committee key. The critical flaw: - -`update_last_irreversible_block()` ([database.cpp:4494-4631](../../libraries/chain/database.cpp#L4494)) iterates `wso.current_shuffled_witnesses`, gets each witness's `last_supported_block_num`, and uses `nth_element` at the 75% threshold. During emergency, ALL schedule slots = "committee", so the committee witness's `last_supported_block_num` dominates → **LIB advances on each partition independently**. - -This makes each partition's emergency blocks **irreversible**. When partitions reconnect via P2P, they have irreconcilable chains — the exact same problem we're trying to solve. - -``` -Partition A: blocks 1000→2200 (LIB advanced to 2180) — IRREVERSIBLE -Partition B: blocks 1000→2100 (LIB advanced to 2080) — IRREVERSIBLE -Partition C: blocks 1000→1500 (LIB advanced to 1480) — IRREVERSIBLE - ↑ - P2P reconnects — fork resolution IMPOSSIBLE - All three chains are irreversible past fork point -``` - -**Solution: Freeze LIB during emergency mode** - -Emergency blocks should NEVER become irreversible. They are temporary and must be reversible so that when partitions merge, the losing chain can be unwound. - -In `update_last_irreversible_block()`: - -```cpp -void database::update_last_irreversible_block(uint32_t skip) { - try { - const dynamic_global_property_object &dpo = get_dynamic_global_properties(); - - // === CHANGE 12: EMERGENCY LIB COMPUTATION === - // During emergency mode, compute LIB using ONLY real witnesses - // (exclude committee). This solves two problems: - // - // 1. PARTITION SAFETY: If only committee is producing, LIB stays - // frozen → all emergency blocks remain reversible → partitions - // can merge. - // - // 2. GRADUAL RECOVERY: As real witnesses return via the hybrid - // schedule (Change 6), their last_supported_block_num updates. - // When 75% of real witnesses are producing, LIB advances - // naturally → emergency exits. - // - // Without this, committee's last_supported_block_num would - // dominate, advancing LIB on each partition independently. - if (has_hardfork(CHAIN_HARDFORK_12) && dpo.emergency_consensus_active) { - const witness_schedule_object &wso = get_witness_schedule_object(); - - // Collect ONLY real (non-committee) witnesses from schedule - vector real_wit_objs; - for (int i = 0; i < wso.num_scheduled_witnesses; - i += CHAIN_BLOCK_WITNESS_REPEAT) { - const auto &wname = wso.current_shuffled_witnesses[i]; - if (wname != CHAIN_EMERGENCY_WITNESS_ACCOUNT && - wname != account_name_type()) { - real_wit_objs.push_back( - &get_witness(wname)); - } - } - - if (real_wit_objs.empty()) { - // All committee — LIB stays frozen - // Expand fork_db to accommodate emergency blocks - uint32_t emergency_fork_db_size = std::min( - dpo.head_block_number - - dpo.last_irreversible_block_num + 1, - uint32_t(CHAIN_MAX_UNDO_HISTORY)); - _fork_db.set_max_size(emergency_fork_db_size); - return; - } - - // Compute LIB using real witnesses only. - // Threshold: 75% of REAL witnesses (not total schedule). - // E.g., 16 real witnesses in schedule → need 12 (75% of 16) - // producing consistently for LIB to advance. - size_t offset = - ((CHAIN_100_PERCENT - CHAIN_IRREVERSIBLE_THRESHOLD) * - real_wit_objs.size() / CHAIN_100_PERCENT); - - std::nth_element( - real_wit_objs.begin(), - real_wit_objs.begin() + offset, - real_wit_objs.end(), - [](const witness_object *a, const witness_object *b) { - return a->last_supported_block_num < - b->last_supported_block_num; - }); - - uint32_t new_lib = - real_wit_objs[offset]->last_supported_block_num; - - if (new_lib > dpo.last_irreversible_block_num) { - // Real witnesses have advanced LIB! - // Apply the new LIB and continue with normal commit logic. - ilog("Emergency LIB advance: ${old} → ${new} " - "(${n} real witnesses producing)", - ("old", dpo.last_irreversible_block_num) - ("new", new_lib) - ("n", real_wit_objs.size())); - // ... fall through to normal LIB commit logic with new_lib ... - } else { - // Not enough real witnesses yet — keep LIB frozen - uint32_t emergency_fork_db_size = std::min( - dpo.head_block_number - - dpo.last_irreversible_block_num + 1, - uint32_t(CHAIN_MAX_UNDO_HISTORY)); - _fork_db.set_max_size(emergency_fork_db_size); - return; - } - } - - // ... existing LIB advancement logic ... -``` - -**How LIB recovery works with the hybrid schedule**: - -``` -Hour 0: Emergency activates - Schedule: [committee × 21] - Real witnesses in LIB calc: 0 - LIB: FROZEN (no real witnesses) - -Hour 1.5: 5 witnesses return - Schedule: [wit1..wit5, committee × 16] - Real witnesses: 5, threshold 75% of 5 = 4 - wit1..wit5 producing → last_supported_block_num updates - 4+ witnesses meet threshold → LIB advances for the first time! - But LIB may not yet pass emergency_consensus_start_block... - -Hour 2: 16 witnesses return - Schedule: [wit1..wit16, committee × 5] - Real witnesses: 16, threshold 75% of 16 = 12 - LIB advancing steadily - LIB passes emergency_consensus_start_block → EMERGENCY EXITS - -Hour 2+: Normal operation - Schedule: [wit1..wit21] - Standard LIB computation -``` - -Also freeze LIB in `apply_block_post_validation()` ([database.cpp:4260](../../libraries/chain/database.cpp#L4260)): - -```cpp -void database::apply_block_post_validation(block_id_type block_id, - const account_name_type &witness_account) { - try { - const dynamic_global_property_object &dpo = get_dynamic_global_properties(); - - // Don't advance LIB via post-validation during emergency mode - if (has_hardfork(CHAIN_HARDFORK_12) && dpo.emergency_consensus_active) { - return; - } - // ... existing post-validation logic ... -``` - -**How partition merge works with frozen LIB**: - -**CRITICAL PROBLEM: Different LIBs at partition time** - -The original diagram assumed all partitions have the same LIB. In reality, partitions have **different LIBs** because one partition may have had enough witnesses (75%) to advance LIB further before stalling: - -``` -Partition A: LIB=1000 emergency blocks [1001..2200] (1200 reversible) -Partition B: LIB=1010 real blocks [1001..1010] committed + emergency [1011..2100] -Partition C: LIB=1100 real blocks [1001..1100] committed + emergency [1101..1500] -``` - -The fork switching logic ([database.cpp:1096](../../libraries/chain/database.cpp#L1096)) uses **longest chain wins**: `new_head->data.block_num() > head_block_num()`. This creates a deadlock: - -``` -A receives C's chain: - C's total from fork point (1000): 500 blocks - A's total from fork point (1000): 1200 blocks - A is LONGER → WON'T SWITCH - (even though C has 100 REAL consensus blocks!) - -C receives A's chain: - Fork point: block 1000 - C's LIB = 1100 → can't pop past 1100 (undo data committed) - A's chain diverges at 1001 (before C's LIB) - C CANNOT SWITCH → undo data for blocks 1001-1100 is gone - -RESULT: DEADLOCK — both stay on their own chain -``` - -**Root cause**: Emergency blocks (meaningless filler from committee key) have **equal weight** to real-witness blocks (confirmed by 75% of actual witnesses) in the chain comparison. - -### Change 14: Vote-Weighted Chain Comparison (database.cpp) - -**Problem with simple block counting**: - -Counting "real" (non-emergency) blocks is insufficient. Consider: - -``` -Partition A: 11 top witnesses (big votes, e.g., 100K each) - → Only 11 witnesses, below 75% threshold - → LIB stalls, enters emergency eventually - → 11 real blocks per round + emergency blocks - -Partition B: 30 support witnesses (small votes, e.g., 1K each) - → Enough witnesses to keep producing (>21) - → Penalty system kicks in: missing top witnesses get penalized - → Support witnesses promoted to top 21 - → NO emergency mode → produces a normal longer chain - → 30 real blocks per round - -Block count comparison: B(30) > A(11) → B wins ✘ -But A has 11×100K = 1.1M total stake, B has 30×1K = 30K total stake -A represents FAR more consensus! -``` - -Also vulnerable to **Sybil attack**: one operator registers 10 support witnesses with tiny votes. During a partition, these 10 produce "real" blocks cheaply, inflating the block count. - -**Rule: Use cumulative `votes` (raw, unpenalized stake weight) of unique witnesses per branch.** - -Key design decisions: -- Use `votes` NOT `counted_votes` — because `counted_votes` is affected by penalties accumulated *during* the partition (a partition artifact, not real consensus signal) -- Count each **unique witness once** — prevents a single witness from inflating weight by producing many blocks -- Emergency blocks (`committee` witness) have `votes = 0` — naturally lowest priority - -In `push_block()` ([database.cpp:1089-1161](../../libraries/chain/database.cpp#L1089)): - -```cpp -if (!(skip & skip_fork_db)) { - shared_ptr new_head = _fork_db.push_block(new_block); - _maybe_warn_multiple_production(new_head->num); - - if (new_head->data.previous != head_block_id()) { - bool should_switch = false; - - if (new_head->data.block_num() > head_block_num()) { - should_switch = true; - } - - // === CHANGE 14: VOTE-WEIGHTED CHAIN COMPARISON === - // Use the sum of unique witness votes as the primary fork - // resolution criterion. Applies UNIVERSALLY (not just emergency). - // - // This handles: - // 1. Emergency vs real: committee has 0 votes → always loses - // 2. Top vs support witnesses: top have more votes → their chain wins - // 3. Sybil attack: 10 fake witnesses with tiny votes can't outweigh - // real top witnesses - // 4. Penalty manipulation: uses raw `votes` not `counted_votes`, - // so partition-induced penalties don't affect comparison - if (has_hardfork(CHAIN_HARDFORK_12)) { - auto branches = _fork_db.fetch_branch_from( - new_head->data.id(), head_block_id()); - - // Compute vote weight for each branch: - // Sum raw `votes` of UNIQUE witnesses (each counted once) - auto compute_branch_weight = [&](const branch_type &branch) - -> share_type - { - flat_set seen_witnesses; - share_type total_weight = 0; - - for (const auto &item : branch) { - const auto &witness_name = item->data.witness; - - // Skip emergency witness (votes = 0 anyway) - if (witness_name == CHAIN_EMERGENCY_WITNESS_ACCOUNT) - continue; - - // Count each witness only once - if (seen_witnesses.count(witness_name)) - continue; - seen_witnesses.insert(witness_name); - - // Use raw `votes` — not `counted_votes` — - // to ignore partition-induced penalties - const auto *w = find_witness(witness_name); - if (w) { - total_weight += w->votes; - } - } - return total_weight; - }; - - share_type new_weight = compute_branch_weight(branches.first); - share_type old_weight = compute_branch_weight(branches.second); - - if (new_weight != old_weight) { - // Primary: prefer chain with higher vote weight - should_switch = (new_weight > old_weight); - } else { - // Secondary: same vote weight → prefer longer chain - should_switch = - (new_head->data.block_num() > head_block_num()); - // Tertiary: same length → hash tie-breaking (Change 10) - } - - ilog("Vote-weighted fork comparison: " - "new_weight=${nw} (${nc} witnesses) vs " - "old_weight=${ow} (${oc} witnesses), " - "switch=${s}", - ("nw", new_weight)("nc", branches.first.size()) - ("ow", old_weight)("oc", branches.second.size()) - ("s", should_switch)); - } - - if (should_switch) { - // ... existing fork switching logic (pop + push) ... -``` - -**How this resolves ALL partition scenarios**: - -**Scenario 1: Emergency vs real (different LIBs)** -``` -Partition A (LIB=1000): committee blocks only - unique witnesses: {committee} → votes = 0 - -Partition C (LIB=1100): 11 top witnesses + committee - unique witnesses: {wit1..wit11} → votes = 11 × 100K = 1.1M - -Vote weight: C(1.1M) >> A(0) → C wins ✓ -``` - -**Scenario 2: Top witnesses vs support witnesses (no emergency on B)** -``` -Partition A: 11 top witnesses (100K votes each), enters emergency - unique witnesses: {top1..top11} → votes = 1.1M - -Partition B: 30 support witnesses (1K votes each), NO emergency - unique witnesses: {sup1..sup30} → votes = 30K - (penalty system promoted them to top, but raw votes still small) - -Vote weight: A(1.1M) >> B(30K) → A wins ✓ -Even though B has more blocks and no emergency mode! -``` - -**Scenario 3: Sybil attack (1 operator, 10 witnesses)** -``` -Partition A: 11 top witnesses (100K votes each) - unique witnesses: {top1..top11} → votes = 1.1M - -Partition B: 10 sybil witnesses (500 votes each) + 5 support (2K each) - unique witnesses: {sybil1..sybil10, sup1..sup5} → votes = 15K - -Vote weight: A(1.1M) >> B(15K) → A wins ✓ -Sybil can't manufacture vote weight! -``` - -**Scenario 4: Both partitions have similar vote weight** -``` -Partition A: 11 top witnesses (100K votes each) → 1.1M -Partition B: 10 top witnesses (100K each) + 5 support (20K each) → 1.1M - -Vote weight: A(1.1M) ≈ B(1.1M) → fall through to chain length -Longer chain wins → fall through to hash tie-breaking -``` - -**Why `votes` not `counted_votes`**: -``` -Partition B has 30 support witnesses. After partition: - - Missing top witnesses get penalized (penalty_percent increases) - - Their counted_votes drops - - Support witnesses' counted_votes relatively increases - - This is a PARTITION ARTIFACT, not real consensus - -Using raw `votes` ignores partition-induced penalties: - - top1.votes = 100K (unchanged regardless of penalties) - - sup1.votes = 1K (unchanged regardless of promotions) - - The comparison reflects REAL stake support, not temporary schedule -``` - ---- - -**Updated partition merge diagram**: - -``` -Before merge (3 partitions with DIFFERENT LIBs): - -Partition A: LIB=1000 head=2200 [0 vote weight + emergency blocks] -Partition B: LIB=1010 head=2100 [30K vote weight (support witnesses)] -Partition C: LIB=1100 head=1500 [1.1M vote weight (top witnesses)] - -P2P reconnects: - ├─ Nodes exchange blocks via P2P - ├─ Vote-weighted comparison (Change 14): - │ Primary criterion: sum of unique witness votes - │ C(1.1M) >> B(30K) >> A(0) - ├─ All nodes converge to Partition C's chain - ├─ A unwinds emergency blocks (all reversible, LIB=1000) - ├─ B unwinds its chain past LIB=1010 - ├─ Transactions from A and B return to pending pool - │ - ├─ Normal witnesses reconnect and re-register - ├─ Emergency mode exits (Change 6) - ├─ LIB unfreezes and starts advancing normally - └─ fork_db shrinks back to normal size -``` - -**Emergency duration limit**: With `CHAIN_MAX_UNDO_HISTORY = 10000` blocks and 3-second intervals, emergency mode can sustain ~8.3 hours of frozen LIB before fork_db overflow. If emergency lasts longer, the oldest reversible blocks get pruned and fork resolution scope shrinks. This is acceptable — if the network hasn't recovered after 8+ hours, manual intervention is expected. - -### Change 15: P2P Anti-Spam for Rejected Forks (node.cpp) - -**Problem**: After vote-weighted comparison rejects a peer's fork, the peer continues sending blocks from their losing chain. This wastes bandwidth and processing time, especially after partition merges when many nodes may still be on the old fork. - -**Current behavior** ([node.cpp:3253-3271](../../libraries/network/node.cpp#L3253)): -- `block_older_than_undo_history` → `peer->inhibit_fetching_sync_blocks = true` -- Invalid block → full `disconnect_from_peer()` - -**New behavior**: Add a "soft ban" for peers whose fork loses vote-weighted comparison. Instead of full disconnect, temporarily stop processing blocks from that peer for 1 hour. This allows them to eventually switch to the correct chain and reconnect. - -```cpp -// In send_sync_block_to_node_delegate() and process_block_message(): -// When push_block() rejects a block due to vote-weighted comparison: -catch (const fork_rejected_by_vote_weight &e) { - // Peer is on a losing fork. Don't disconnect fully — - // they might switch to the correct chain soon. - // Instead, temporarily ignore their blocks. - fc_wlog(fc::logger::get("sync"), - "Peer ${peer} sent block from fork rejected by " - "vote-weighted comparison. Ignoring for 1 hour.", - ("peer", originating_peer->get_remote_endpoint())); - - originating_peer->fork_rejected_until = - fc::time_point::now() + fc::seconds(3600); - originating_peer->inhibit_fetching_sync_blocks = true; -} -``` - -In `peer_connection`, add: -```cpp -fc::time_point fork_rejected_until; // soft ban expiry -``` - -In `process_block_message()` and `send_sync_block_to_node_delegate()`, check: -```cpp -if (originating_peer->fork_rejected_until > fc::time_point::now()) { - // Still soft-banned — silently discard block - return; -} -``` - -**Also increase standard `fork_db` max size**: -```cpp -// In fork_database.hpp: -static const uint32_t MAX_BLOCK_REORDERING = 2400; // was 1024 -// ~2 hours at 3s/block, enough for vote-weighted resolution -``` - -This gives the universal vote-weighted comparison enough room to operate in normal mode without emergency fork_db expansion. - -### Change 13: Keep `enable-stale-production` / `required-participation` as Manual Overrides - -Update to Change 9: the config options are NOT deprecated. They remain available as **manual overrides** for operators who want to intervene faster than the 1-hour emergency timeout. - -The auto-enforcement logic (Change 9) applies only when: -- Hardfork 12 is active AND -- The network is healthy (participation ≥ 33%, emergency mode not active) - -When the network is in distress but hasn't yet hit the 1-hour emergency threshold, operators can still manually set `enable-stale-production=true` and `required-participation=0` to accelerate recovery. The difference from the current system: once the network is healthy again, the witness plugin **automatically reverts** to safe defaults, regardless of config. - -```cpp -// Updated Change 9 logic: -if (has_hardfork(CHAIN_HARDFORK_12)) { - if (dgp.emergency_consensus_active) { - // EMERGENCY MODE: auto-bypass (no manual config needed) - _production_enabled = true; - } else { - uint32_t prate = db.witness_participation_rate(); - if (prate >= 33 * CHAIN_1_PERCENT) { - // HEALTHY NETWORK: enforce safe defaults automatically - // Even if operator has enable-stale-production=true in config, - // it's overridden because the network doesn't need it. - if (!_production_enabled) { - if (db.get_slot_time(1) >= now) { - _production_enabled = true; - } else { - return block_production_condition::not_synced; - } - } - if (prate < 33 * CHAIN_1_PERCENT) { - capture("pct", uint32_t(prate / CHAIN_1_PERCENT)); - return block_production_condition::low_participation; - } - } else { - // DISTRESSED NETWORK (participation < 33%, not yet emergency): - // Honor manual config overrides — operator may be trying to - // accelerate recovery before the 1-hour timeout. - // Fall through to legacy enable-stale-production / - // required-participation config-based behavior. - if (!_production_enabled) { - if (_production_skip_flags & database::skip_undo_history_check) { - // enable-stale-production=true → skip sync check - _production_enabled = true; - } else if (db.get_slot_time(1) >= now) { - _production_enabled = true; - } else { - return block_production_condition::not_synced; - } - } - if (prate < _required_witness_participation) { - capture("pct", uint32_t(prate / CHAIN_1_PERCENT)); - return block_production_condition::low_participation; - } - } - } -} -``` - -**Three-state behavior after HF12**: - -| Network State | Participation | `enable-stale-production` | `required-participation` | -|---|---|---|---| -| Healthy | ≥ 33% | **Ignored** (always safe) | **Ignored** (always 33%) | -| Distressed (pre-emergency) | < 33% | **Honored** (manual override) | **Honored** (manual override) | -| Emergency (auto, 1hr+ since LIB) | N/A | **Auto-bypassed** | **Auto-bypassed** | - -This preserves operator agency during the gap between "network struggling" and "full emergency activation", while still auto-reverting when the network recovers. - ---- - -## Emergency Mode Lifecycle - -### Activation - -``` -Normal Operation - │ - ├─ Witnesses start going offline - ├─ Missed blocks accumulate - ├─ Participation rate drops below 33% - ├─ Block production stops - ├─ Witnesses get shut down (signing_key = null after 200 missed blocks) - │ - ├─ 1 hour passes since last irreversible block - │ - └─ EMERGENCY MODE ACTIVATES - ├─ emergency_consensus_active = true - ├─ Witness schedule overridden: all slots → "committee" - ├─ All penalties reset to 0 - ├─ All penalty expiration objects removed - ├─ Nodes with emergency-private-key start producing blocks - └─ LIB frozen (Change 12) — all emergency blocks are reversible -``` - -### Emergency Operation - -``` -Emergency Mode Active - │ - ├─ Only "committee" witness produces blocks - ├─ Blocks signed with CHAIN_EMERGENCY_WITNESS_PUBLIC_KEY - ├─ Any node with the emergency key can produce (only one per slot) - ├─ LIB is FROZEN (Change 12) — all emergency blocks are reversible - ├─ fork_db expanded to CHAIN_MAX_UNDO_HISTORY (10000 blocks ≈ 8.3 hrs) - ├─ Multi-producer collisions resolved by hash tie-breaking (Change 10) - │ - ├─ If network is partitioned: - │ ├─ Each partition produces its own emergency chain - │ ├─ All chains remain reversible (LIB frozen) - │ └─ On reconnection: longest chain wins, losing chains unwind - │ - ├─ Operators re-register their witnesses via CLI: - │ witness_update "account" "url" {"signing_key": "VIZ..."} true - │ - ├─ Witnesses rejoin and start confirming blocks - └─ update_witness_schedule() detects normal witnesses -``` - -### Deactivation - -``` -Normal Witnesses Rejoining - │ - ├─ update_witness_schedule() runs - ├─ Detects non-emergency witnesses with valid signing keys - ├─ Emergency mode deactivates: - │ ├─ emergency_consensus_active = false - │ └─ Normal witness schedule restored - │ - └─ Normal Operation Resumes - ├─ No need to change enable-stale-production - ├─ No need to change required-participation - └─ No micro-fork risk from forgotten emergency settings -``` - ---- - -## Key Advantages Over Current Approach - -| Problem | Current Approach | Emergency Consensus Mode | -|---|---|---| -| Network stalls | Manual `enable-stale-production=true` | Automatic after 1 hour timeout | -| Low participation | Manual `required-participation=0` | Schedule override (all committee) | -| Safety bypass forgotten | Operators forget to revert → micro-forks | Auto-exits when witnesses return | -| Witness shutdown | Manual re-registration via CLI | Penalty reset; still need re-registration | -| Fork_db overflow | Solo production exceeds 1024 blocks | fork_db expanded to 10000 during emergency | -| Multiple producers | Any witness can fork | Single deterministic producer | -| Forgotten settings | enable-stale=true stays forever → micro-forks | Config ignored after HF12 (Change 9) | -| Multi-node emergency | N/A | Hash tie-breaking + collision check + vote-weighted fork comparison (Changes 10+11+14) | - ---- - -## Security Analysis - -### Emergency Private Key Security - -The emergency private key (`5JPYE8UhnxDgURG7mFDcSPQaNjwT5VmCrq3L4QQ2sZSQhbTUZkZ`) is shared among multiple nodes. This is safe because: - -1. **Only used during emergency** — the key is meaningless during normal operation -2. **Deterministic schedule** — only one node produces per slot (based on timing) -3. **Multiple nodes prevent single point of failure** — if one emergency node goes down, another takes the next slot -4. **Short duration** — emergency mode should last minutes to hours, not days -5. **Blocks are validated** — other nodes still validate blocks against consensus rules -6. **Auto-exit** — the more nodes have the key, the faster production resumes, and the faster normal witnesses can rejoin - -### Multi-Producer Collision Safety - -When N nodes have the emergency key, the first emergency slot produces N competing blocks. This is handled by a **three-layer defense**: - -1. **Deterministic hash tie-breaking** (Change 10) — `fork_database::_push_block()` compares `block_id` hashes at the same height: lower hash wins. ALL nodes converge to the same block regardless of P2P arrival order. No transactions are lost — losing blocks' txs stay in `_pending_tx`. - -2. **Emergency-aware fork collision check** (Change 11) — after the first slot, nodes that received any block at the target height skip production entirely, deferring to the hash-based winner. - -3. **Self-resolving** — collision only affects the 1st emergency slot (5 blocks), drops to 1-2 blocks by slot 2, and reaches 1 block by slot 3+. Total extra overhead: ~5 blocks in the first 3-6 seconds. - -### Automatic Safety Enforcement - -After hardfork 12, `enable-stale-production` and `required-participation` config options are **context-dependent** (Change 9 + Change 13): -- **Healthy network** (≥ 33% participation): config ignored, safe defaults enforced automatically -- **Distressed network** (< 33%, not yet emergency): config honored as manual overrides -- **Emergency mode**: auto-bypassed, no config needed - -This eliminates the most common cause of micro-forks (forgotten emergency settings) while preserving operator agency during the pre-emergency window. - -### Attack Vectors - -| Attack | Mitigation | -|---|---| -| Malicious actor with emergency key produces invalid blocks | Consensus validation still applies; invalid blocks are rejected | -| Emergency mode activated during normal operation (time attack) | Only activates if LIB timestamp is >1 hour old; during normal ops, LIB advances every few seconds | -| Emergency key holder censors transactions | Emergency mode is temporary; all transactions are public and will be included when normal witnesses return | -| Emergency mode never exits | Exits automatically when any normal witness with valid key produces a block | -| 5 nodes produce competing emergency blocks | Hash-based tie-breaking ensures deterministic convergence within 1 P2P round; fork collision check reduces to 1 producer by slot 3 | -| Operator forgets to revert enable-stale-production | After HF12, config is ignored in healthy mode — consensus layer controls bypass automatically | -| Partitions with different LIBs enter emergency independently | Vote-weighted chain comparison (Change 14) sums unique witness `votes` per branch; chain with more stake-weighted consensus wins regardless of length | - ---- - -## Implementation Plan - -### Phase 1: Non-Breaking Changes (No Hardfork Required) - -These can be deployed immediately without network-wide upgrade: - -| Change | File | Description | -|---|---|---| -| Emergency private key option | `witness.cpp` | Add `emergency-private-key` config option | -| Emergency key loaded into key map | `witness.cpp` | Load emergency key alongside witness keys | -| Emergency mode logging | `witness.cpp` | Log when emergency witness is scheduled | - -### Phase 2: Hardfork 12 (Requires Network-Wide Upgrade) - -| Change | Breaking | Files Modified | -|---|---|---| -| `emergency_consensus_active` field in DGP | Yes (serialized object) | `global_property_object.hpp`, `database.cpp`, `snapshot/plugin.cpp` | -| `emergency_consensus_start_block` field in DGP | Yes (serialized object) | `global_property_object.hpp`, `database.cpp`, `snapshot/plugin.cpp` | -| Emergency mode activation logic | Yes (consensus behavior) | `database.cpp` `update_global_dynamic_data()` | -| Emergency mode exit logic | Yes (consensus behavior) | `database.cpp` `update_witness_schedule()` | -| Schedule override during emergency | Yes (consensus behavior) | `database.cpp` `update_witness_schedule()` | -| Penalty reset on emergency activation | Yes (consensus behavior) | `database.cpp` | -| Emergency witness object creation | Yes (consensus behavior) | `database.cpp` | -| FC_REFLECT update for DGP | Yes (serialization) | `global_property_object.hpp` | -| Snapshot export/import update | Yes (new fields) | `plugins/snapshot/plugin.cpp` | -| config.ini template update | No | `share/vizd/config/` | -| Auto safety enforcement in witness plugin | Yes (consensus behavior) | `witness.cpp` `maybe_produce_block()` | -| Deterministic hash tie-breaking in fork_db | Yes (fork resolution) | `fork_database.cpp` `_push_block()` | -| LIB freeze + fork_db expansion during emergency | Yes (consensus behavior) | `database.cpp` `update_last_irreversible_block()`, `apply_block_post_validation()` | -| Manual override preservation (enable-stale/required-participation) | No (witness plugin) | `witness.cpp` | -| Emergency-aware chain comparison | Yes (fork resolution) | `database.cpp` `push_block()` — vote-weighted using `witness.votes` (universal) | -| P2P anti-spam for rejected forks | No (P2P layer) | `node.cpp` — soft-ban peers on losing forks for 1 hour | -| fork_db standard size increase to 2400 | Yes (fork resolution) | `fork_database.hpp` `MAX_BLOCK_REORDERING = 2400` | - -### Hardfork 12 Definition - -**File**: `libraries/chain/hardfork.d/12.hf` - -```cpp -// 12 Hardfork — Emergency Consensus Recovery -#ifndef CHAIN_HARDFORK_12 -#define CHAIN_HARDFORK_12 12 -#define CHAIN_HARDFORK_12_TIME // To be determined by witness vote -#define CHAIN_HARDFORK_12_VERSION hardfork_version( version(3, 1, 0) ) -#endif -``` - -All emergency consensus logic should be gated behind: - -```cpp -if (has_hardfork(CHAIN_HARDFORK_12)) { - // Emergency consensus mode logic -} -``` - ---- - -## Alternative Approaches Considered - -### Alternative A: Time-Based Auto-Revert of Emergency Settings - -Instead of on-chain emergency mode, add a timeout to `enable-stale-production` and `required-participation`: - -```ini -enable-stale-production = true -enable-stale-production-timeout = 3600 # Auto-revert after 1 hour -required-participation = 0 -required-participation-timeout = 3600 # Auto-revert after 1 hour -``` - -**Rejected because**: -- Still requires manual activation (operators must set emergency values) -- Auto-revert timing is hard to get right — too short and recovery fails, too long and micro-forks happen -- Doesn't address witness shutdown/penalty issues -- fork_db overflow still possible if revert happens while isolated - -### Alternative B: Increased fork_db Size - -Increase `_max_size` from 1024 to a larger value (e.g., 10000 blocks ≈ 8.3 hours). - -**Rejected because**: -- Only delays the problem, doesn't solve it -- Increases memory usage significantly -- Still doesn't prevent micro-forks from happening -- Doesn't address the root cause (solo production on isolated fork) - -### Alternative C: P2P-Level Fork Detection - -Add P2P-level fork detection where nodes gossip about fork depth and automatically prefer the chain with more witness participation. - -**Rejected because**: -- Complex P2P protocol changes -- Vulnerable to Sybil attacks (fake participation claims) -- Doesn't help if witnesses are genuinely offline -- Doesn't address the need for actual block production during a stall - -### Alternative D: BFT-Style Fallback to Committee Multisig - -Require M-of-N committee signatures for emergency blocks instead of a single key. - -**Rejected because**: -- More complex implementation -- Requires committee members to be online during emergency (defeats the purpose) -- Single-key emergency is simpler and the key is only used during emergencies -- Can be upgraded to multisig later if needed - ---- - -## Test Scenarios - -### Scenario 1: Normal Emergency Activation and Recovery - -1. Start network with 21 witnesses -2. Shut down 15 witnesses (leaving 6 — below 33% participation) -3. Wait for network to stall -4. Wait 1 hour → emergency mode activates -5. Emergency committee produces blocks -6. Restart 15 witnesses, re-register their keys -7. Emergency mode exits → normal production resumes - -**Expected**: No manual intervention beyond restarting witness nodes - -### Scenario 2: Network Partition (Micro-Fork Prevention) - -1. Normal network operation (no emergency settings active) -2. Network partition isolates 5 witnesses from 16 -3. Both sides see participation drop -4. Minority side (5): participation < 33% → production stops -5. Majority side (16): participation still > 33% → continues -6. Partition resolves → minority side syncs from majority - -**Expected**: No micro-forks because emergency settings were never manually activated - -### Scenario 3: Complete Network Stall + Recovery - -1. All 21 witnesses go offline (e.g., datacenter power failure) -2. After 1 hour → emergency mode activates on all nodes with emergency key -3. One node produces blocks with committee key -4. Witnesses come back online one by one -5. Each witness re-registers via CLI -6. After enough witnesses are active → emergency mode exits - -**Expected**: Chain continues without full stall; LIB frozen during emergency, unfreezes when witnesses return - -### Scenario 4: Network Partition During Emergency - -1. All 21 witnesses go offline -2. After 1 hour → emergency mode activates on all partitions independently -3. Network is split into 3 partitions (US, EU, Asia) -4. Each partition produces emergency blocks with committee key -5. LIB is frozen on all partitions (Change 12) -6. After 2 hours, P2P connectivity restores between partitions -7. Longest chain wins, losing partitions unwind -8. Normal witnesses re-register, emergency mode exits - -**Expected**: Partition merge succeeds because all emergency blocks are reversible. No manual intervention needed beyond witness re-registration. - ---- - -## Open Questions - -1. **Should the emergency timeout be configurable on-chain?** **RESOLVED**: No. Keep it hardcoded at 1 hour (`CHAIN_EMERGENCY_CONSENSUS_TIMEOUT_SEC = 3600`). One hour is human-friendly and predictable. Making it on-chain configurable adds complexity without benefit — the timeout is a safety constant, not a tunable parameter. Emergency mode only applies to nodes running the witness plugin. - -2. **Should the emergency witness produce blocks with empty transactions?** **RESOLVED**: Emergency blocks **CAN** be empty but **not forced empty**. Transactions must still be processable because witnesses may need to broadcast `witness_update_operation` to re-register their signing key during the hybrid schedule transition (Change 6). If emergency blocks were forced empty, witnesses couldn't re-activate — creating a deadlock where emergency never exits. In practice, most emergency blocks will be empty (low tx volume during a stall), but the mechanism must not prohibit transactions. - -3. ~~**What happens if multiple nodes with the emergency key produce blocks simultaneously?**~~ **RESOLVED** (Changes 10+11): Deterministic hash tie-breaking in `fork_database::_push_block()` ensures all nodes converge to the same block (lowest `block_id` wins). Emergency-aware fork collision check prevents redundant production after the first slot. Multi-producer collision is a transient 1-2 slot artifact. - -4. **Should the emergency private key be rotated?** **RESOLVED**: No. There's no reason to rotate. The `committee` system account signs emergency blocks only when emergency mode is active. Nodes without the emergency key simply don't produce during emergency — they still validate and accept emergency blocks from peers. The key has no value outside emergency mode, and rotating it would require a hardfork for zero security benefit. - -5. **Should emergency blocks have special extensions?** **RESOLVED**: No. Emergency blocks are already identifiable by `witness = "committee"` (the system account). No additional header extension needed — monitoring tools can simply filter by witness name. - -6. **What is the minimum number of normal witnesses required to exit emergency mode?** **RESOLVED** (Change 6): Emergency exits when **LIB advances past `emergency_consensus_start_block`**. LIB advancement requires 75% (16 of 21) witnesses producing consistently (`CHAIN_IRREVERSIBLE_THRESHOLD = 7500`). So effectively: **16 witnesses** must be online and producing. With 11 witnesses = 52% → LIB stays frozen, emergency continues. The hybrid schedule (Change 6) mixes real witnesses alongside committee: witnesses get their normal slots, committee fills gaps. No special re-registration needed — witnesses just need to be running with their pre-existing `signing_key`. - -7. **Should the deterministic hash tie-breaking apply only during emergency mode?** **RESOLVED**: Yes, gate it behind `emergency_consensus_active` only. In normal mode, witnesses have assigned slots and forks are resolved through the existing scheduling mechanism. Hash tie-breaking is only needed during emergency when multiple nodes produce with the same committee key at the same slot. - -8. **Should `enable-stale-production` and `required-participation` config options be deprecated after HF12?** **RESOLVED** (Change 13): Keep them. They serve as manual overrides during the "distressed" phase (participation < 33% but < 1 hour since LIB). Change 9 auto-reverts to safe defaults when the network is healthy. Three-state behavior: Healthy → ignored, Distressed → honored, Emergency → auto-bypassed. - -9. **What happens when partitions with independent emergency chains reconnect?** **RESOLVED** (Changes 12+14): LIB is frozen during emergency mode. All emergency blocks are reversible. When partitions merge, the chain with higher **cumulative witness vote weight** wins (Change 14), not the longest chain. Uses raw `votes` (not `counted_votes`) to ignore partition-induced penalties. Handles top-vs-support witness splits and sybil attacks. Maximum emergency duration before fork_db overflow: ~8.3 hours (`CHAIN_MAX_UNDO_HISTORY = 10000` blocks). - -11. **What if one operator controls multiple support witnesses (Sybil)?** **RESOLVED** (Change 14): Vote-weighted comparison uses raw `votes` field. A sybil with 10 support witnesses (500 votes each = 5K total) can't outweigh a partition with even 1 top witness (100K votes). The cost of manufacturing vote weight is proportional to real stake — same security assumption as the entire DPoS model. - -12. **Should vote-weighted comparison apply outside of emergency mode?** **RESOLVED**: Yes. Apply universally for all fork resolution (not just emergency). This makes fork resolution stake-aware — forks backed by more stake-weighted witnesses are always preferred. Also increase `fork_db` standard max size from 1024 to 2400 blocks (~2 hours at 3s/block) to give more room for fork resolution. Combined with Change 15 (P2P anti-spam), rejected fork blocks don't flood the network. - -13. **How to prevent P2P spam from peers on rejected forks?** **RESOLVED** (Change 15): When vote-weighted comparison rejects a peer's fork, temporarily ignore blocks from that peer for 1 hour. The P2P layer already disconnects peers sending invalid blocks ([node.cpp:3264-3271](../../libraries/network/node.cpp#L3264)). Change 15 extends this: instead of full disconnect, add a "soft ban" timeout so the peer can reconnect later on the correct chain. - ---- - -## Reference: Key Constants - -| Constant | Value | Meaning | Source | -|---|---|---|---| -| `CHAIN_BLOCK_INTERVAL` | 3 | Seconds per block | [config.hpp:27](../../libraries/protocol/include/graphene/protocol/config.hpp#L27) | -| `CHAIN_MAX_WITNESSES` | 21 | Maximum scheduled witnesses | [config.hpp:41](../../libraries/protocol/include/graphene/protocol/config.hpp#L41) | -| `CHAIN_MAX_TOP_WITNESSES` | 11 | Top voted witnesses | [config.hpp:39](../../libraries/protocol/include/graphene/protocol/config.hpp#L39) | -| `CHAIN_MAX_SUPPORT_WITNESSES` | 10 | Support witnesses | [config.hpp:40](../../libraries/protocol/include/graphene/protocol/config.hpp#L40) | -| `CHAIN_MAX_WITNESS_MISSED_BLOCKS` | 200 | ~10 min before shutdown | [config.hpp:32](../../libraries/protocol/include/graphene/protocol/config.hpp#L32) | -| `CHAIN_IRREVERSIBLE_THRESHOLD` | 7500 (75%) | LIB advancement threshold | [config.hpp:110](../../libraries/protocol/include/graphene/protocol/config.hpp#L110) | -| `CHAIN_IRREVERSIBLE_SUPPORT_MIN_RUN` | 2 | Blocks before supporting LIB | [config.hpp:112](../../libraries/protocol/include/graphene/protocol/config.hpp#L112) | -| `CHAIN_MAX_UNDO_HISTORY` | 10000 | Max head-LIB gap | [config.hpp:108](../../libraries/protocol/include/graphene/protocol/config.hpp#L108) | -| `fork_db._max_size` | 2400 (was 1024) | Fork database depth (~2 hours) | [fork_database.hpp:117](../../libraries/chain/include/graphene/chain/fork_database.hpp#L117) | -| `CONSENSUS_WITNESS_MISS_PENALTY_PERCENT` | 100 (1%) | Miss penalty per block | [config.hpp:127](../../libraries/protocol/include/graphene/protocol/config.hpp#L127) | -| `CONSENSUS_WITNESS_MISS_PENALTY_DURATION` | 1 day | Penalty expiration | [config.hpp:128](../../libraries/protocol/include/graphene/protocol/config.hpp#L128) | diff --git a/@l10n/ru/docs/advanced/hardfork-management.md b/@l10n/ru/docs/advanced/hardfork-management.md index c9298c8edf..0c9ffe8402 100644 --- a/@l10n/ru/docs/advanced/hardfork-management.md +++ b/@l10n/ru/docs/advanced/hardfork-management.md @@ -48,6 +48,8 @@ VIZ Ledger координирует обновления протокола че | 10 | Модель инфляции | | 11 | Изменения модели эмиссии | | 12 | Аварийное восстановление консенсуса (см. ниже) | +| 13 | Длина эпохи распределения (`chain_properties_hf13`) | +| 14 | Прогнозные рынки (Onix): 18 операций + 7 виртуальных, CPMM/LMSR, паримутюэль-расчёт, оракулы, споры, commit-reveal, lazy-пул; chain properties v5 | --- diff --git a/@l10n/ru/docs/governance/chain-properties.md b/@l10n/ru/docs/governance/chain-properties.md index 0358a42f28..1c2b1d2227 100644 --- a/@l10n/ru/docs/governance/chain-properties.md +++ b/@l10n/ru/docs/governance/chain-properties.md @@ -122,8 +122,28 @@ | `chain_properties_hf4` | 1 | HF4 | inflation_validator_percent, inflation_ratio_committee_vs_reward_fund, inflation_recalc_period | | `chain_properties_hf6` | 2 | HF6 | data_operations_cost_additional_bandwidth, validator_miss_penalty_percent, validator_miss_penalty_duration | | `chain_properties_hf9` | 3 | HF9 | create_invite_min_balance, committee_create_request_fee, create_paid_subscription_fee, account_on_sale_fee, subaccount_on_sale_fee, validator_declaration_fee, withdraw_intervals | +| `chain_properties_hf13` | 4 | HF13 | distribution_epoch_length | +| `chain_properties_pm` | 5 | HF14 | ~30 параметров прогнозных рынков + kill-switch `pm_commit_reveal_enabled`, `pm_lazy_pool_enabled` | -Для всех новых публикаций параметров валидатора используйте индекс версии 3 (`chain_properties_hf9`). +Для всех новых публикаций параметров валидатора используйте индекс версии **5** (`chain_properties_pm`). Индекс 4 — `chain_properties_hf13` (`distribution_epoch_length`). + +### Параметры прогнозных рынков (v5, HF14) {#pm-parameters} + +Все медиан-голосуемые; см. [Операции прогнозных рынков](../protocol/operations/prediction-markets.md). + +Все проценты PM — в bp (10000 = 100.00%), как прочие `*_percent`; промилле (‰) нигде нет. + +- **Оракул:** `pm_min_oracle_insurance`, `pm_max_oracle_fee_percent` (**единственный** governance-кэп на фи — на % оракула), `pm_oracle_registration_fee`, `pm_oracle_penalty_percent`, `pm_oracle_dispute_response_sec`, `pm_oracle_accept_window_sec` (по умолчанию 3600 = 1 ч — названный оракул должен принять или отклонить пендинг-рынок в течение этого окна; по истечении крон возвращает создателю сид-ликвидность, но **не** комиссию за создание, и аннулирует рынок → `pm_market_expired`). +- **Риск / покрытие** *(процент от объёма ставок рынка, 100 = 1.0×):* `pm_listing_min_coverage_percent` (250 = 2.5×) — рынки, чья страховка оракула покрывает меньше этой доли их объёма, скрыты из каталога `list_markets` по умолчанию (показываются через `show_risky`); `pm_betting_min_coverage_percent` (150 = 1.5×) — рекомендательный порог, публикуемый для клиентов, чтобы требовать явного подтверждения риска перед ставкой (не форсится on-chain; должен быть `≤ pm_listing_min_coverage_percent`). +- **Рынок:** `pm_min_liquidity`, `pm_market_creation_fee`, `pm_max_outcomes`, `pm_max_market_duration`. *(Агрегатного кэпа фи нет; creator/liquidity-фи без кэпа, самолимитируются; статический инвариант `сумма ≤ 100%`.)* +- **Batch / commit-reveal:** `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_min_batch_bet`, `pm_commit_no_reveal_penalty_percent`, `pm_commit_reveal_enabled`. +- **Споры:** `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent`, `pm_no_contest_penalty_percent`, `pm_dispute_reward_multiplier` (bp-множитель, 10000 = 1×). +- **Time penalty:** `pm_default_time_penalty_percent`, `pm_max_time_penalty`. +- **Lazy-пул:** `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (по умолчанию 200 = 2% — пул отказывается со-предоставлять ликвидность рынку, чей `liquidity_fee_percent` ниже этого порога вознаграждения). +- **Плечо (опц.):** `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent`. +- **Справедливость:** `pm_processing_cap_per_block`. + +Три флага `*_enabled` (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) — живые kill-switch: медиана валидаторов может отключить commit-reveal, lazy-пул или плечо без нового хардфорка. --- diff --git a/@l10n/ru/docs/node/configuration.md b/@l10n/ru/docs/node/configuration.md index c4c5370a48..73fc1e5561 100644 --- a/@l10n/ru/docs/node/configuration.md +++ b/@l10n/ru/docs/node/configuration.md @@ -158,6 +158,11 @@ skip-virtual-ops = false # Разрешить производство при устаревшей цепочке (только для разработки/тестовой сети) enable-stale-production = false +# Отключить обнаружение minority fork (ТОЛЬКО для тестовой сети/форка одного оператора). +# В отличие от enable-stale-production, никогда не сбрасывается автоматически при +# здоровом участии. Никогда не включайте в реальной публичной сети. +disable-minority-fork-detection = false + # Минимальный процент участия для производства блоков (0–99) required-participation = 33 @@ -203,4 +208,4 @@ logger.p2p.appenders = p2p | `plugins/chain/plugin.hpp` | `shared-file-size`, `min-free-shared-file-size`, `inc-shared-file-size`, `block-num-check-free-size`, `single-write-thread`, `enable-plugins-on-push-transaction`, `read-wait-micro`, `max-read-wait-retries`, `write-wait-micro`, `max-write-wait-retries`, `skip-virtual-ops`, `clear-votes-before-block`, `track-account-range`, `history-whitelist-ops`, `history-blacklist-ops`, `history-start-block` | | `plugins/p2p/p2p_plugin.hpp` | `p2p-endpoint`, `p2p-max-connections`, `p2p-seed-node`, `checkpoint` | | `plugins/webserver/webserver_plugin.hpp` | `webserver-http-endpoint`, `webserver-ws-endpoint`, `webserver-thread-pool-size` | -| `plugins/validator/validator.hpp` | `enable-stale-production`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` | +| `plugins/validator/validator.hpp` | `enable-stale-production`, `disable-minority-fork-detection`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` | diff --git a/@l10n/ru/docs/node/validator-node.md b/@l10n/ru/docs/node/validator-node.md index da0cc4b192..7925729ea9 100644 --- a/@l10n/ru/docs/node/validator-node.md +++ b/@l10n/ru/docs/node/validator-node.md @@ -164,6 +164,9 @@ docker run -d \ ### Обнаружение minority fork Если форк-база данных узла показывает 21+ последовательных блоков только от собственных валидаторов, узел автоматически откатывается к LIB и выполняет повторную синхронизацию. Это позволяет обнаружить сетевую изоляцию. +> [!WARNING] Форки одного оператора +> На тестовой сети или форке основной сети, где **один оператор контролирует все валидаторы**, «21 блок подряд от нас» — это нормальное здоровое состояние, поэтому детектор бесконечно откатывается к LIB. `enable-stale-production = true` здесь **не помогает**: при участии ≥33% это переопределение автоматически снимается на каждом блоке. Используйте вместо него `disable-minority-fork-detection = true` — он обходит и стандартный, и DLT-путь обнаружения и никогда не сбрасывается автоматически. **Никогда не включайте его в реальной публичной сети** — это убирает защиту от изоляции. + ### Watchdog производства Если в течение 180 секунд (60 с для экстренного мастера) при активном `should_be_producing` не был произведён ни один блок, watchdog автоматически сбрасывает зависшие флаги (`minority_fork_recovering`, нагон P2P, синхронизация цепочки) и пытается возобновить производство. diff --git a/@l10n/ru/docs/plugins/prediction-market-api.md b/@l10n/ru/docs/plugins/prediction-market-api.md new file mode 100644 index 0000000000..7fa3ac25aa --- /dev/null +++ b/@l10n/ru/docs/plugins/prediction-market-api.md @@ -0,0 +1,192 @@ +# Плагин `prediction_market_api` + +Read-only JSON-RPC доступ к состоянию прогнозных рынков HF14 (рынки, ставки, оракулы, ликвидность, споры, lazy-пул, chain properties v5). Плагин возвращает сырые консенсус-объекты `pm_*` напрямую плюс несколько вычисляемых DTO. + +**Включение:** добавить `prediction_market_api` в список плагинов узла (в `vizd` зарегистрирован по умолчанию). Зависит от `chain` + `json_rpc`. Все list-методы пагинируются через `from` (пропуск) и `limit` (`≤ 1000`). + +## Методы + +### Рынки + +| Метод | Аргументы | Возврат | +|-------|-----------|---------| +| `get_market` | `market_id` | `pm_market_object` | +| `list_markets` | `status, from, limit, [show_risky]` | `pm_market_object[]` | +| `list_markets_by_oracle` | `oracle, from, limit` | `pm_market_object[]` | +| `list_markets_by_creator` | `creator, from, limit` | `pm_market_object[]` | +| `get_market_outcomes` | `market_id` | `pm_outcome_object[]` | +| `get_market_weight_sums` | `market_id` | `pm_market_weight_sums` (вычисляемый) | +| `get_market_bets` | `market_id, from, limit` | `pm_bet_object[]` | +| `get_market_liquidity` | `market_id, from, limit` | `pm_liquidity_object[]` | +| `get_market_full` | `market_id, [account]` | `pm_market_full` (вычисляемый) | + +`status` для `list_markets`: `-1` удалён, `0` ожидание, `1` активен, `2` закрыт, `3` разрешён. По +умолчанию `list_markets` скрывает недострахованные рынки (страховка оракула < **2.5×** объёма ставок); +`show_risky = true` показывает их (рынки только скрываются, ставки on-chain всегда разрешены). + +`get_market_full` — **обогащённое представление за один вызов** для экрана деталей рынка: возвращает рынок + исходы + суммы весов + оракула (с надёжностью) + распарсенные метаданные и — если передан необязательный `account` — ставки, плечевые позиции и LP этого аккаунта **на этом рынке**. Экономит тонкому клиенту несколько round-trip'ов. + +### Метаданные рынка (парсятся off-chain) + +Каждый рынок несёт свободную, консенсус-непрозрачную JSON-строку `metadata`. Плагин парсит индексируемые +ключи (категория / подкатегория / теги / запрещённые юрисдикции) в `pm_market_meta_object` — **только для +отображения/индексации, не консенсус**. + +| Метод | Аргументы | Возврат | +|-------|-----------|---------| +| `get_market_meta` | `market_id` | `pm_market_meta_object` (или ошибка, если нет) | +| `list_markets_by_category` | `category, from, limit, [jurisdiction], [subcategory], [tag], [sort]` | `pm_market_meta_object[]` | +| `get_market_categories` | — | `pm_market_categories` (вычисляемый) | + +`list_markets_by_category` исключает рынки, чьи `banned_jurisdictions` содержат необязательный ISO-код +`jurisdiction` (регулируемый клиент передаёт свою юрисдикцию, чтобы получить только допустимые рынки). +Необязательные `subcategory` (точное совпадение) и `tag` (членство в CSV) сужают набор; `sort` ∈ `newest` +(id рынка по убыв., по умолч.) · `oldest` · `volume` (`bets_sum` по убыв.) · `expiration` +(`betting_expiration` по возр.). `get_market_categories` возвращает живую таксономию — счётчики по +категориям / подкатегориям плюс топ-20 горячих тегов (`jurisdiction-*` исключены) — агрегированную по +проиндексированным сейчас рынкам, чтобы браузинг-UI строил свои фильтр-чипы без захардкоженной таксономии. +Объект: `market`, `category`, `subcategory`, `tags` (через запятую), `banned_jurisdictions` (ISO через +запятую; пусто = разрешено везде), `expiry` (пруна после закрытия окна спора + TTL). + +### Позиции и оракулы + +| Метод | Аргументы | Возврат | +|-------|-----------|---------| +| `get_account_positions` | `account, from, limit` | `pm_position[]` (ставка + `expected_payout`) | +| `get_account_leverage_positions` | `account, from, limit` | `pm_leverage_position_object[]` | +| `get_market_leverage_positions` | `market_id, from, limit` | `pm_leverage_position_object[]` | +| `get_creator_ban` | `account` | `pm_creator_ban_object` (или ошибка, если нет) | +| `get_oracle` | `owner` | `pm_oracle` (объект + `reliability_score`) | +| `list_oracles` | `from, limit` | `pm_oracle_object[]` | + +### Превью плеча (Boost) + +Read-only котировки, вызывающие **ту же внутриузловую математику маржи**, что и эвалуаторы, так что превью совпадает с тем, что вычислила бы соответствующая операция `pm_leverage_*` на головном блоке. Это неконсенсусные оценки (резервы двигаются между чтением и бродкастом — всегда отправляйте on-chain защиту от проскальзывания). + +| Метод | Аргументы | Возврат | +|-------|-----------|---------| +| `get_leverage_quote` | `market_id, outcome_index, collateral` | `pm_leverage_quote` (вычисляемый) | +| `get_leverage_close_preview` | `position_id` | `pm_leverage_close_preview` (вычисляемый) | +| `get_leverage_convert_preview` | `position_id` | `pm_leverage_convert_preview` (вычисляемый) | + +`get_leverage_quote` зеркалит `pm_leverage_open`: возвращает максимальный платёжеспособный заём и итоговое максимальное плечо, кэпы пула/позиции, до 12 стопов слайдера (каждый с токенами, порогом, текущей и худшей стоимостью отмены) и — когда плечо невозможно — `available = false` со списком `failed_constraints[]`. `get_leverage_close_preview` / `get_leverage_convert_preview` зеркалят `pm_leverage_close` / `pm_leverage_convert` при текущих резервах (стоимость отмены, обязательство пула, что получает беттер, закрываемость/конвертируемость и комиссия конвертации при текущей медиане `pm_conversion_profit_cost_percent`). + +> Выплата каждому беттору — виртуальная операция `pm_payout` (стейк, side/outcome, итог; `0` при +> проигрыше); закрытие плечевой позиции — `pm_leverage_resolve` (`outcome_index`, `won`, `leverage`). +> Обе видны в `account_history`; сами объекты позиций — через методы выше. + +### Споры, lazy-пул, governance + +| Метод | Аргументы | Возврат | +|-------|-----------|---------| +| `get_dispute` | `market_id` | `pm_dispute_object` | +| `get_dispute_votes` | `market_id` | `pm_dispute_votes` (голоса + живой подсчёт) | +| `get_lazy_pool` | — | `pm_lazy_pool_object` | +| `get_lazy_deposit` | `account` | `pm_lazy_deposit_object` | +| `get_lazy_allocations` | `from, limit` | `pm_lazy_allocation_object[]` | +| `get_market_lazy_allocation` | `market_id` | `pm_lazy_allocation_object` (или ошибка, если нет) | +| `get_pm_chain_properties` | — | `chain_properties_pm` (медиана, v5) | + +`get_lazy_allocations` перечисляет записи аллокаций lazy-пула по рынкам (для дашборда пула); `get_market_lazy_allocation` берёт запись для конкретного рынка. Штрафные штампы оракула отдельного метода не требуют — они идут на `pm_oracle_object` (`penalty_stamps`, `last_penalty_stamp_time`) через `get_oracle`. + +### Графики — kline / история весов + +Тайм-серия для построения графика изменения веса каждого исхода. Плагин добавляет точку **каждый раз, когда веса исходов рынка меняются** — ставка, отмена, ликвидация, batch-settle, открытие плеча или расчёт плеча — как таймстампированный снимок паримутюэль-веса (сумма ставок) по каждому исходу. Это **неконсенсусное** состояние плагина (хранится в chainbase, undo/redo-безопасно, не входит в хеш состояния); история копится с момента первого включения плагина на узле. + +**Хранение:** kline-история пруна́ется **вместе с метаданными рынка**, по тому же расписанию — `result_expiration` + grace спора + `pmm-ttl-days` (по умолч. 7). Полный график рынка доступен на протяжении его жизни и в окне хранения после расчёта, затем оба индекса очищаются (для очень длинной истории — частями за несколько блоков), чтобы хранилище узла оставалось ограниченным. + +| Метод | Аргументы | Возврат | +|-------|-----------|---------| +| `get_market_kline` | `market_id, [from], [limit]` | `pm_kline[]` (по возрастанию `seq`) | + +Пагинация — **отступ от новейших** (намеренно простая для тонких клиентов): `from` — сколько **новейших** точек пропустить, `limit ≤ 1000` — размер страницы. +- `(market_id, 0, 1000)` → последние ≤ 1000 изменений. +- `(market_id, 1000, 1000)` → предыдущие 1000 (страницей раньше) — повторяй с `from += 1000`, чтобы дозагружать более старую историю. + +График: x = `timestamp` (unix-секунды), по одной линии на исход `i` с y = `weights[i]` (или нормированно `weights[i] / Σweights` — вменённая вероятность). + +## Вычисляемые DTO + +- **`pm_position`** — ставка + `expected_payout` (выплата, если сторона победит, или реализованная после расчёта; байт-в-байт повторяет `settle_market`), `market_status`, `resolved_outcome`. +- **`pm_oracle`** — объект оракула + `reliability_score` (bp `[0..10000]`, неконсенсусная эвристика: смесь доли успешных разрешений и доли выигранных споров минус штраф за баны). +- **`pm_market_weight_sums`** — `bets_sum`/`weight_sum` по сторонам/исходам (веса считаются сканом ставок, т.к. не хранятся). +- **`pm_kline`** — одна точка графика: `seq` (uint32, 0-based, монотонный индекс изменения по рынку), `timestamp` (unix-секунды, x), `reason` (uint8: 0 ставка, 1 отмена, 2 ликвидация, 3 batch settle, 4 открытие плеча, 5 расчёт плеча), `bets_sum` (всего поставлено), `weights[]` (вес по каждому исходу, y; индекс = outcome_index). +- **`pm_dispute_votes`** — голоса + подсчёт finalize. Старые поля (вес = `|vote_percent|`, не стейк): `uphold_weight`/`challenge_weight`/`total_weight`, `challenger_leads` (≥ `pm_dispute_approve_min_percent`), `proposed_outcome`. **Точная stake-взвешенная проекция (зеркалит `pm_dispute_finalize`; все `*_shares` в vesting-shares = `effective_vesting_shares` + стейк lazy-пула→shares):** `participation_shares` (Σ веса проголосовавших), `electorate_shares` (`total_vesting_shares` + NAV пула→shares), `quorum_required_shares`, `quorum_percent_bp` (кворум в bp, 10000 = 100.00%), `quorum_reached` (bool), `oracle_defense_shares`/`change_shares`, `outcome_change_shares[]` (по исходам), `expected_uphold` (останется ли решение оракула), `expected_outcome` (какой исход будет выставлен при резолюции сейчас), `expected_consensus_strength_bp`. Проекция совпадает с тем, что крон применит на `voting_end_time` при текущих голосах (голоса изменяемы до этого момента). + +**`pm_market_full`** — обогащённое представление рынка за один вызов (`oracle`/`meta` = `null`, когда +отсутствуют; массивы `my_*` пусты, если не передан аргумент `account`): +``` +{ market: pm_market_object, + outcomes: pm_outcome_object[], // пусто для бинарных рынков + weight_sums: pm_market_weight_sums, + oracle: pm_oracle | null, + meta: pm_market_meta_object | null, + my_positions: pm_position[], // ставки аккаунта на ЭТОМ рынке + my_leverage_positions: pm_leverage_position_object[], + my_liquidity: pm_liquidity_object[] } +``` + +**`pm_leverage_quote`** — превью открытия плеча (из `pm::leverage::*`, та же математика, что и в эвалуаторе): +``` +{ available: bool, outcome_index, collateral, + max_loan, max_leverage_x100, // 100 = 1.00× + pool_free_amount, fund_available, per_position_cap, market_position_cap, + pool_profit_percent, safety_margin_percent, max_slippage_percent, m_factor_percent, + expiration_buffer_sec, auto_close_time, // betting_expiration − buffer + stops: [ { leverage_x100, loan, total_bet, expected_tokens, pool_profit, + liquidation_threshold, current_cancel_value, worst_case_cancel_value } ], + failed_constraints: [ { constraint, reason } ] } // заполнено, когда !available +``` +**`pm_leverage_close_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, bettor_receives, collateral, loan, pool_profit_charge, closeable: bool, loss_vs_collateral, loss_percent_bp }`. +**`pm_leverage_convert_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, current_profit, conversion_profit_cost_percent, conversion_fee, total_user_payment, convertible: bool }`. + +**`pm_market_categories`** — браузинг-таксономия с живыми счётчиками: +``` +{ categories: [ { category, count, subcategories: [ { subcategory, count } ] } ], // сортировка по count убыв. + hot_tags: [ { tag, count } ] } // топ-20 (jurisdiction-* исключены) +``` + +## Пример + +Последние 1000 точек графика для рынка `42`, затем предыдущие 1000: +```bash +# новейшая страница +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_market_kline",[42,0,1000]]}' http://127.0.0.1:8090 +# страницей старше +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_market_kline",[42,1000,1000]]}' http://127.0.0.1:8090 +``` + +Тонкий клиент (дозагрузка старой истории при прокрутке назад) — каждая точка в серию `{ x: unixtime, y: weight }` по исходам: +```js +async function call(method, params) { + const r = await fetch('http://127.0.0.1:8090', { method: 'POST', + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'call', + params: ['prediction_market_api', method, params] }) }); + return (await r.json()).result; +} + +// Страницы по 1000 от новейших назад, пока не наберём `want` точек (или не кончится история). +async function loadKline(marketId, want = 3000) { + const points = []; + for (let from = 0; points.length < want; from += 1000) { + const page = await call('get_market_kline', [marketId, from, 1000]); + if (!page.length) break; // дошли до начала истории + points.unshift(...page); // страницы по возрастанию; старые — в начало + if (page.length < 1000) break; + } + return points; +} + +// По одной серии {x,y} на исход — напрямую в любую библиотеку графиков. +function toSeries(points, outcomeCount) { + const series = Array.from({ length: outcomeCount }, () => []); + for (const p of points) + for (let i = 0; i < outcomeCount; i++) + series[i].push({ x: p.timestamp, y: Number(p.weights[i]) }); + return series; +} +``` + +См. [Операции прогнозных рынков](../protocol/operations/prediction-markets.md) и [Chain Properties](../governance/chain-properties.md#pm-parameters). diff --git a/@l10n/ru/docs/plugins/validator.md b/@l10n/ru/docs/plugins/validator.md index 7666743682..566b700431 100644 --- a/@l10n/ru/docs/plugins/validator.md +++ b/@l10n/ru/docs/plugins/validator.md @@ -24,6 +24,7 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin | `private-key` | — | WIF-ключ(и) для подписи; может повторяться | | `emergency-private-key` | — | WIF-ключ для экстренного консенсуса; автоматически добавляет `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` в набор валидаторов | | `enable-stale-production` | `false` | Обход проверок участия и синхронизации (только для тестовой сети / восстановления сети) | +| `disable-minority-fork-detection` | `false` | Полностью пропустить обнаружение minority fork (только для тестовой сети/форка одного оператора). Никогда не сбрасывается автоматически при здоровом участии — см. [Обнаружение minority fork](#обнаружение-minority-fork) | | `required-participation` | `3300` | Минимальное участие валидаторов в **базисных пунктах** (3300 = 33%) | | `fork-collision-timeout-blocks` | `21` | Количество последовательных отсрочек при коллизии форков перед принудительным производством (один полный раунд валидаторов) | @@ -116,7 +117,8 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin Перед каждой попыткой производства (после проверок безопасности HF12) плагин просматривает последние 21 блок в `fork_db`. Если все 21 были произведены собственными настроенными валидаторами узла — узел изолирован на minority fork. - **Действие по умолчанию:** Вызов `p2p().resync_from_lib()` — откат блоков к LIB, сброс fork DB, повторная инициация синхронизации P2P, переподключение к начальным узлам. Возвращает `minority_fork`. -- **С `enable-stale-production=true`:** Запись предупреждения, продолжение производства. +- **С `enable-stale-production=true`:** Запись предупреждения, продолжение производства. **Примечание:** при участии ≥33% это переопределение автоматически снимается на каждом блоке, поэтому на форке одного оператора оно *не* останавливает детектор — используйте `disable-minority-fork-detection`. +- **С `disable-minority-fork-detection=true`:** И стандартный, и DLT-путь обнаружения полностью пропускаются, а флаг никогда не сбрасывается автоматически. Для тестовой сети/форков одного оператора, где «21 блок подряд от нас» — это нормальное устойчивое состояние. **Никогда не включайте в реальной публичной сети** — это убирает защиту от изоляции. - **Пропускается при:** Активном экстренном консенсусе (блоки комитета всегда соответствовали бы нашему настроенному набору). В экстренном режиме вместо него используется специфичная для DLT проверка изоляции ведомого. --- @@ -217,7 +219,7 @@ validator[skip_flags=0x0 catching_up=0 head=#79881136 last_prod=45s_ago minority | `no_private_key` | В конфиге отсутствует `private-key` для ключа подписи, зарегистрированного в блокчейне | | `low_participation` | Участие сети < 33%; проверьте подключение к пирам или установите `enable-stale-production=true` | | `fork_collision` | Конкурирующий блок на следующей высоте; ждать разрешения по весу голосов или таймаута 21 отсрочки | -| `minority_fork` | Изолирован; плагин автоматически пересинхронизируется с LIB | +| `minority_fork` | Изолирован; плагин автоматически пересинхронизируется с LIB. На форке одного оператора зацикливается — установите `disable-minority-fork-detection=true` | | Watchdog срабатывает повторно | Флаг синхронизации или нагона завис; watchdog сбросит автоматически при продвижении головы | | Логи `SLOT-HIJACK` | Экстренный мастер обнулил наш ключ; восстановите через `validator_update_operation` | diff --git a/@l10n/ru/docs/prediction-markets/concepts-analysis.md b/@l10n/ru/docs/prediction-markets/concepts-analysis.md new file mode 100644 index 0000000000..13bb38ed28 --- /dev/null +++ b/@l10n/ru/docs/prediction-markets/concepts-analysis.md @@ -0,0 +1,244 @@ +--- +title: Анализ концептов — Onix против 90 концептов прогнозных рынков +description: Как живая on-chain реализация на VIZ (протокол Onix, клиент Forecaster) ложится на 90 теоретических концептов прогнозных рынков — что решено, имманентно, не нужно или в роадмапе. +--- + +# Анализ концептов — Onix против 90 теоретических концептов + +> **Forecaster** — это тонкий клиент к on-chain прогнозному рынку VIZ, слой доступа, позволяющий людям +> со всего мира участвовать, подписывая операции `pm_*` (см. [обзор раздела](./)). +> **Onix** — протокол, к которому он обращается. Эта страница отображает каждый из **90 концептов +> прогнозных рынков PM Atlas** на **то, как живая on-chain реализация на VIZ его закрывает**, и **нужен +> ли он** для этой архитектуры. +> +> Опорные документы: [whitepaper](./whitepaper), [спецификация](./specification), +> [воркфлоу и диспуты](./workflows). + +## Легенда + +| Метка | Значение | +|------|---------| +| ✅ **Решено** | Дизайн Onix напрямую решает/обрабатывает это | +| ⚪ **Имманентно** | Свойство, которое Onix демонстрирует/наследует by construction (доп. работа не нужна) | +| ➖ **Не нужно** | Архитектурно избыточно в Onix | +| 🟡 **Частично / Roadmap** | Частично решено сегодня; остальное — в роадмапе VIZ | +| 🏛 **Слой клиента** | Решается на уровне юрисдикционного клиента, не протокола | +| 🔴 **Открыто / Риск** | Остаётся живой проблемой; не решено полностью | + +Главное структурное отличие от всех остальных платформ: **принципал LP структурно гарантирован (победителям платят только из проигранных ставок), ценообразование — CPMM для бинарных и LMSR-softmax + parimutuel-сеттлмент для мульти, и нет ордербука.** Большая часть концептов «ликвидность и трейдинг» существует именно для управления инвентарным риском маркет-мейкера CLOB/LMSR — и поэтому **не применима** к Onix, где нет инвентарь-несущего мейкера. + +> **Актуализация on-chain (HF14 / live).** Эта таблица изначально писалась по whitepaper/спецификации. Ряд пунктов, помеченных тогда как *roadmap*, теперь **реализованы как consensus-операции** и проверены в `consensus_sim`: +> - **Батч-аукционы + commit-reveal ставки** — `pm_commit_bet` / `pm_reveal_bet` / `pm_batch_settle`, per-market `allow_batch` / `allow_instant_bet`, median kill-switch `pm_commit_reveal_enabled`. **Только бинарные** (мульти форсит `allow_instant_bet` — LMSR-батча пока нет). +> - **Опциональный leverage-сабсистем** — `pm_leverage_open/close/convert`, фондируется из Lazy Pool, kill-switch `pm_leverage_enabled` (по умолчанию off). Прямо закрывает **Position Collateralization (#30)**. +> - **Сам Lazy Pool** — синглтон, auto-allocation, MasterChef-учёт, leverage-займы, graduated recall. +> - Поле рынка **`endogeneity_tier`**; on-chain **баны создателей** (`pm_creator_ban_object`); расширенные settlement-vop (`pm_payout` на каждого беттора, `pm_leverage_resolve`, `pm_market_accepted`, `pm_auto_payout`) + методы плагина. +> - **Новое относительно спеки:** **стейк в lazy pool учитывается как вес в голосовании** — и в PM-диспутах, *и* в голосовании за заявки DAO-комитета (конвертация в vesting-shares, гейт HF14). +> +> **Намеренно НЕ делаем:** commit-reveal голосование в *диспутах* — диспуты комитета это **открытые публичные слушания by design** (голоса остаются публичными через `pm_dispute_vote`, и бюллетень можно менять до закрытия). **Остаётся roadmap:** автоматические экзогенные data-оракулы. Строки и таблица смягчений ниже приведены к этому live-состоянию. + +--- + +## 1. Информационная теория (Information Theory) + +| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли | +|---|---------|---------|--------------------------------------------------| +| 1 | **Brier Score** | ⚪ Имманентно | Не механизм протокола, а *метрика* оценки рынков Onix. На VIZ каждая ставка/резолюция — consensus-validated событие, значит истории цен и исходы полностью on-chain → Brier-скоринг платформы (и оракулов) считается кем угодно. Нужен как вход для аналитики/репутации, не как ядро. | +| 2 | **Calibration** | ⚪ Имманентно | Цены Onix — настоящие вероятности (CPMM `P(A)=reserve_b/(reserve_a+reserve_b)`, LMSR-softmax суммируется в 1). Калибровка — emergent-свойство для *измерения*, косвенно улучшается time-penalty (давит no-info ставки в последнюю секунду) и глубокой LP-ликвидностью. Протокол её не навязывает. | +| 3 | **Credibility Markets** | 🟡 Частично | Bonded-oracle + 14-метричная репутация + composite trust score — это по сути credibility-рынок для *резолверов*. Ставка репутацией против исхода — нативна. Общий продукт «стейкай репутацию на заявлениях» — возможная сборка на слое клиента, не ядро. | +| 4 | **Distribution Markets** | 🟡 Roadmap | Onix Multi (3–10 дискретных исходов) аппроксимирует распределение через бакеты. Настоящие непрерывные distribution-рынки (CDF/скаляр) **не** в скоупе сегодня; нужна скалярная операция исхода. Примыкает к roadmap-пункту «category-level AMMs». | +| 5 | **Endogeneity** | 🟡 Частично (смягчено) | Риск, что рынок меняет то, что предсказывает. Категориально-специфичен (эконом-данные чисто, политика/соц рискованно). Смягчается **живым полем `endogeneity_tier`** (тег оракула 1/2/3) и **опциональными commit-reveal/батч ставками (теперь on-chain)**, которые не дают публичной цене «течь» во время приёма (канал «термостата»); экзогенная резолюция через automated data oracles — **всё ещё roadmap**. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). | +| 6 | **Forecasting Accuracy** | ⚪ Имманентно | Вся ценностная гипотеза. Onix улучшает её косвенно: risk-free LP → глубже книги → меньше слиппедж → больше информированного участия → лучше цены. Точность — это выход для измерения, не фича. | +| 7 | **Info Finance** | ⚪ Имманентно | Onix *и есть* инфо-финансовый инструмент: consensus-level операции превращают информацию в priced, settle-able позиции. Миграция на VIZ делает информационный слой цензуроустойчивым и композируемым. | +| 8 | **Information Aggregation** | ✅ Решено | Основная функция. CPMM/LMSR агрегируют разрозненные ставки в единую вероятность; глубокая risk-free LP-ликвидность — именно тот рычаг, которым Onix заставляет агрегацию работать (флайвил §7.3 whitepaper). | +| 9 | **Information Asymmetry** | 🟡 Частично | Дизайн Onix (parimutuel/CPMM) означает, что информированные трейдеры извлекают прибыль из *других проигравших бетторов*, а не из LP — поэтому асимметрия не банкротит ликвидность (в отличие от CLOB/LMSR-мейкеров). **Опциональные commit-reveal + батч ставки теперь live (бинарные):** закоммиченные ставки сеттлятся по единой батч-цене, убирая утечку направления через mempool; per-market `allow_batch` + median kill-switch оставляют это опциональным. | +| 10 | **Legibility** | ✅ Решено | Каждое финансовое действие — consensus-validated VIZ-операция с audit trail `market_log` (до/после резервов). Полностью читаемо/аудируемо любым узлом — строго легибельнее централизованного бэкенда или непрозрачного CLOB. Новые settlement-vop (**`pm_payout`** на каждого беттора, **`pm_leverage_resolve`**, **`pm_market_accepted`**) + методы плагина делают per-bettor исходы и leverage-резолюции напрямую запрашиваемыми. | +| 11 | **Longshot Bias** | 🟡 Частично | CPMM/LMSR всё ещё могут давать favorite-longshot bias из поведения бетторов; Onix не правит это напрямую. Time penalty и глубокая ликвидность гасят искажение, но это поведенческий выход, не устранён. | +| 12 | **Noise Decomposition** | ⚪ Имманентно | Аналитическая линза, не фича протокола. On-chain ряды цен/объёмов на VIZ делают разложение «сигнал-шум» возможным для аналитиков. В ядре не нужно. | +| 13 | **Nowcasting** | ⚪ Имманентно | Цены Onix обновляются на каждую ставку (~3с блоки VIZ), давая real-time nowcast-оценки. Имманентно любому живому AMM-рынку; доп. механизма нет. | +| 14 | **Price Discovery** | ✅ Решено | CPMM и LMSR-softmax — непрерывные движки price discovery; когерентность цен (`Σ price = 1`) держится *by construction*, без слоя арбитража/split-merge. | +| 15 | **Probability Infrastructure** | ✅ Решено | Это по сути тезис Onix на VIZ: prediction markets как **first-class consensus операции** (`pm_*`), не смарт-контракты — base-layer вероятностный примитив. Прямая цель миграции. | +| 16 | **Superforecasting** | ⚪ Имманентно | Концепт индивидуального скилла; Onix награждает точных бетторов через payout losers→winners. Трансфер позиций + репутация могут поддержать идентичность суперфорекастера, но это черта участника, не логика протокола. | +| 17 | **Wisdom of Crowds** | ✅ Решено | Механизм, который Onix монетизирует. Risk-free LP снижает барьер, чтобы участвовала бóльшая часть толпы, заостряя агрегат. Ядро дизайнерского обоснования. | +| 18 | **Yes Bias** | 🟡 Частично | Поведенческий перекос к «Yes». Симметричный CPMM и profit-only time penalty структурно не благоприятствуют Yes, но и не правят человеческий bias. Смягчается глубиной ликвидности; вопрос измерения. | + +--- + +## 2. Дизайн механизмов (Mechanism Design) + +| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли | +|---|---------|---------|--------------------------------------------------| +| 19 | **Binary Contracts** | ✅ Решено | Onix Binary = CPMM (`x·y=k`) на двух исходах. Доказательство AM-GM гарантирует `reserve_a+reserve_b ≥ L`, значит принципал LP покрыт. Основной тип рынка. | +| 20 | **Combinatorial Prediction Markets** | ➖ Не нужно (сегодня) | LMSR естественно раскладывается по комбинаторным пространствам, но Onix Multi ограничен 3–10 *независимыми* исходами и намеренно опускает CTF split/merge. Комбинаторные/conditional-бандлы явно вне скоупа; для модели LP-гарантии не требуются. | +| 21 | **Incentive Compatibility** | ✅ Решено | LMSR наследует IC от log scoring rule (truth-telling доминантна). Onix добавляет согласование стимулов через bonded-оракулов (insurance > прибыли манипуляции), сеттлмент losers-fund-winners и time-weighted LP-награды. | +| 22 | **Keynesian Beauty Contest** | ✅ Решено (рынок) / ⚪ (слой диспутов — принято by design) | KBC — патология *относительного/peer-скоринга*. **Рыночный** слой Onix платит бетторам против внешней истины (parimutuel), значит он структурно анти-KBC — ты зарабатываешь, *отклоняясь* от цены толпы, когда она неправа. Единственная экспозиция KBC — **stake-weighted голосование комитета в диспуте** (peer-механизм). **Решение: commit-reveal в диспутах внедряться НЕ будет** — диспут комитета это **открытые публичные слушания**, и доверие к ДАО держится на максимально прозрачном разрешении споров; сокрытие голосов это доверие подорвало бы. Остаточный риск KBC принят и структурно мал: голосующим **не платят** за совпадение с большинством (нет bandwagon-бонуса), а **бюллетень можно менять** до закрытия (честные апдейты на новые доводы ожидаемы, а не подавляются). Участники пула также **с правом голоса** (стейк lazy pool → vesting-shares, HF14). См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). | +| 23 | **LMSR** | ✅ Решено (ключевая инновация) | Файл концепта отмечает, что LMSR *провалился на бинарных* (перманентный убыток на границе 0/1). Ответ Onix: **CPMM для бинарных, а LMSR только для мульти, где мейкер НЕ контрагент** — parimutuel-сеттлмент платит победителям из проигравших, поэтому LMSR-субсидия никогда не под риском (`макс. убыток LP = 0` против `b·ln(N)`). Центральный ход дизайна. | +| 24 | **LOX (Log-Odds Excess Lateness)** | ➖ Не нужно | Специализированная метрика скоринга/запаздывания. Onix вместо этого использует **квадратичный time penalty на прибыль** для стимулов поздних ставок — проще, на этапе сеттлмента. LOX-скоринг не часть модели. | +| 25 | **Market Manipulation** | 🟡 Частично | Bonded-оракул (бонд > прибыли манипуляции), DPoS-валидируемые операции и арбитраж комитета повышают стоимость манипуляции. Манипуляция ценой большими ставками ограничена глубиной и — на **батч/commit-reveal рынках (теперь live)** — единой клиринговой ценой, нейтрализующей speed-снайпинг («налог снайпера»); активный surveillance — всё ещё roadmap. | +| 26 | **Market Scoring Rules** | ✅ Решено | Onix Multi — реализация market scoring rule (LMSR), переиспользованная с parimutuel-сеттлментом. Используется напрямую. | +| 27 | **Multi-Outcome Markets** | ✅ Решено | Onix Multi обрабатывает N=3–10 через LMSR-softmax + parimutuel payout, с `b = S/ln(N)`. First-class тип рынка. | +| 28 | **Parimutuel Markets** | ✅ Решено (фундамент) | Сеттлмент в *обоих* типах рынков — parimutuel: проигранные ставки формируют `winners_pool`, распределяемый по доле токенов. Именно это делает гарантию LP структурной, а не страховой. | +| 29 | **Peer Prediction** | ➖ Не нужно | Схемы truth-telling без ground-truth. Onix опирается на bonded-оракулов + комитет, не на peer-prediction скоринг. Могло бы помочь резолюции субъективных рынков, но не используется. | +| 30 | **Position Collateralization** | ✅ Решено (+ опц. leverage, live) | По умолчанию каждая ставка полностью предоплачена (вся сумма входит в резервы; нет комиссий при ставке) — тотальная коллатерализация by construction. Теперь Onix **также** реализует «next level» концепта: **опциональный leverage-сабсистем** (`pm_leverage_open/close/convert`, kill-switch `pm_leverage_enabled`, по умолчанию off). Маржа — это **займ из Lazy Pool** (без эмиссии токенов — zero-sum сохранён), поэтому позиция остаётся полностью обеспеченной *с точки зрения системы*. Бинарный «jump risk», ломающий liquidation-движки CLOB (по этому концепту), решается **ликвидацией по pre-bet резервам**: opposing-bet / settlement force-close возвращает `min(cancel_value, obligation) ≥ loan`, так что пул получает заём + проценты; **единственный** ограниченный путь bad-debt — same-side `pm_cancel_bet` (Case B). Каскад ликвидации намеренно **не** гейтится kill-switch'ем, поэтому выключение leverage никогда не снимает защиту с открытых позиций. | +| 31 | **Proper Scoring Rules** | ✅ Решено | LMSR — cost-function дуал log proper scoring rule; Onix Multi наследует его truthful-elicitation свойство. | +| 32 | **Reflexivity** | 🟡 Частично (смягчено) | Родитель endogeneity. Смягчается тем же набором — **commit-reveal/батч ставки + `endogeneity_tier` теперь live**, экзогенная резолюция всё ещё roadmap — **плюс on-chain баны создателей** (`pm_creator_ban_object`) для вредной рефлексивности (рынки убийств/«hit», пропаганда-рынки, создающие «конституенцию за исход»); сам *список* запрещённых категорий остаётся на слое клиента. Глубокая risk-free LP-ликвидность также повышает стоимость newsworthy-манипуляции ценой. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). | + +--- + +## 3. Ликвидность и трейдинг (Liquidity & Trading) + +> **Заголовок:** в Onix **нет ордербука и нет инвентарь-несущего маркет-мейкера**. Большой класс этих концептов существует именно для управления инвентарным риском CLOB/мейкера, поэтому **не применим** к Onix. + +| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли | +|---|---------|---------|--------------------------------------------------| +| 33 | **Adverse Selection** | ✅ Решено (переосмыслено) | Классическая проблема (информированный поток банкротит мейкера) **не может обанкротить LP Onix**: победителям платят из проигравших, не из принципала LP (гарантия AM-GM / parimutuel). Информированные извлекают из других *бетторов*, не из LP. Устраняет ключевой режим отказа LP. | +| 34 | **Arbitrage** | ⚪ Имманентно | Внутрирыночный арбитраж не нужен: когерентность (`Σ price = 1`) держится by construction в CPMM и LMSR-softmax. Слой split/merge-арбитража не нужен. | +| 35 | **Batched Auctions** | ✅ Решено (опц., live) | Реализовано как **per-market uniform-price батч** (`mode=1` ставки + commit-reveal → `pm_batch_settle` на каждой эпохе `pm_batch_epoch_blocks`); AMM двигает только **нетто-остаток**, поэтому все same-side филлы клирятся по одной цене, а speed-снайпинг («налог снайпера») нейтрализуется. Per-market `allow_batch` + median kill-switch `pm_commit_reveal_enabled`; **только бинарные** сегодня (мульти форсит `allow_instant_bet`). Инвариант LP `Σreserve ≥ L` не тронут — каждый батч это один валидный CPMM-переход. | +| 36 | **Bid-Ask Spread** | ➖ Не нужно | Нет ордербука → нет котируемого спреда. «Стоимость торговли» проявляется как слиппедж CPMM/LMSR, зависящий от глубины, не от спредов мейкера. Концепт не мапится. | +| 37 | **Bonding Trades** | ⚪ Имманентно | Ставки *и есть* bonded-сделки: капитал внесён в резервы и высвобождается только при резолюции (или через отмену/трансфер). Нативное поведение. | +| 38 | **Continuous Double Auction** | ➖ Не нужно | CDA — модель CLOB, которую Onix явно отвергает в пользу AMM. Не используется. | +| 39 | **Covariance Markets** | ➖ Не нужно | Торговля корреляцией событий требует комбинаторной/conditional структуры, которую Onix опускает. Вне скоупа. | +| 40 | **Cross-Platform Arbitrage** | 🟡 Частично | Цены Onix могут расходиться с Polymarket/Kalshi; арбитраж между платформами возможен, но внешний для протокола. Открытый API VIZ + headless-клиент делают данные доступными; нативного моста нет. | +| 41 | **Execution Quality** | ✅ Решено (переосмыслено) | Нет частичных филлов/очереди. Качество исполнения = детерминированный слиппедж + опц. `min_tokens`/`min_return`-гард, валидируемый на консенсусе. Предсказуемо by construction. | +| 42 | **Gap Risk** | ✅ Решено (для LP) | Gap-риск (резкий скачок к 0/1, выносящий мейкера) — режим отказа, который структурная гарантия LP Onix устраняет: LP никогда не держит терминальный риск проигравшей стороны. Бетторы по-прежнему несут свой риск исхода (как и задумано). | +| 43 | **Hedging** | 🟡 Частично | Бетторы могут хеджироваться встречными позициями, трансфером (`pm_transfer_position`), отменой ставки (если разрешена) через reverse CPMM и теперь **опциональным leverage** (`pm_leverage_open/convert`) для капиталоэффективного оффсета. Нативных мульти-leg деривативов нет; базовый + leveraged хедж возможен. | +| 44 | **Implied Correlation** | ➖ Не нужно | Требует мульти-событийных/комбинаторных рынков, которые Onix опускает. Вне скоупа. | +| 45 | **Insider Trading** | 🟡 Частично / 🏛 Клиент | Протокол не может детектить инсайд; смягчается time penalty (поздние инфо-ставки дают меньше прибыли) и bonded-резолюцией. KYC/surveillance для полиции инсайдеров — ответственность **слоя клиента** (регулируемые клиенты). | +| 46 | **Kelly Criterion** | ⚪ Имманентно | Стратегия сайзинга беттора, не фича протокола. Onix отдаёт чистые вероятности и полную коллатерализацию, так что Kelly-сайзинг считается участниками; новый **опциональный leverage** позволяет беттору отыгрывать fractional-Kelly эдж с маржой (фондируется из пула, ограничено ликвидацией). Ядро не вовлечено сверх предоставления примитивов. | +| 47 | **Liquidity Fragmentation** | 🟡 Roadmap | Per-market пулы фрагментируют ликвидность сегодня. Топ-приоритет роадмапа — **shared/category-level AMM-пулы** — архитектурный фикс. Lazy Pool уже мьютуализирует *депозиты* между рынками. | +| 48 | **Liquidity Provision** | ✅ Решено (ключевой дифференциатор) | Risk-free LP — заголовок: принципал структурно гарантирован, time-weighted награды, Lazy Pool auto-allocation + MasterChef-учёт. Решает проблему «LP теряют деньги», ради которой и создан протокол. | +| 49 | **Market Making** | ✅ Решено (переосмыслено) | Активный мейкер не нужен — AMM + LP-пул *и есть* мейкер, и он не несёт инвентарного риска. «Market making» схлопывается в пассивное risk-free предоставление ликвидности. | +| 50 | **Minimum Viable Liquidity** | ✅ Решено | Принудительный пол: мин. начальная ликвидность 100 VIZ; Lazy Pool авто-засевает каждый новый рынок `free_balance × allocation_%`. MVL структурно бутстрапится, не оставлен на случай. | +| 51 | **Order Book** | ➖ Не нужно | Onix на AMM; ордербука нет by design. | +| 52 | **Orderflow Arbitrage** | ➖ Не нужно | Нет ордербука / нет PFOF-роутинга потока → неприменимо. | +| 53 | **Relative Value Trading** | ➖ Не нужно | Кросс-инструментный RV требует коррелированных/комбинаторных рынков, которые Onix опускает. Вне скоупа. | +| 54 | **Retail Flow** | ✅ Решено (переосмыслено) | В CLOB ритейл-поток субсидирует убытки мейкера от toxic flow. В Onix мейкера, которого надо защищать, нет — ритейл и информированные бетторы платят в один parimutuel-пул; LP безразличен. Напряжение «ритейл vs toxic» растворяется на слое LP. | +| 55 | **Semantic Tick Size** | ➖ Не нужно | Гранулярность тика — концепт CLOB. Цены Onix — непрерывные AMM-функции; точность — фикс. mVIZ-единица (1/1000). Дизайн тика не нужен. | +| 56 | **Temporal Arbitrage** | 🟡 Частично | Ставка раньше vs позже несёт разный риск; **time penalty на прибыль** в Onix — именно тот механизм, что закладывает в цену запаздывание, демпфируя «жди-определённости» арбитраж. Не устранён, но явно дестимулирован. | +| 57 | **Time Arbitrage** | 🟡 Частично | То же семейство, что #56 — эксплуатация тайминга информации. Квадратичный time penalty + ~3с такт блоков снижают, но не убирают edge. Учтён по дизайн-намерению. | +| 58 | **Toxic Flow** | ✅ Решено (для LP) | Определяющая проблема CLOB/LMSR (снайпер выкупает книгу по 10¢ на исходе-99¢, мейкер ест 80¢) **не бьёт по LP Onix** — payouts из ставок проигравших, а субсидия LP возвращается безусловно. Toxic flow здесь просто значит, что информированные бетторы выигрывают parimutuel-пул, как и задумано. Крупный структурный выигрыш. | +| 59 | **Wash Trading** | 🟡 Частично / 🏛 Клиент | Отсутствие комиссий при ставке убирает один стимул wash, но накрутка объёма возможна; трансферы — чистая переуступка (там фарм комиссий невозможен). Детекция/surveillance — забота клиента + роадмапа. | + +--- + +## 4. Оракул и резолюция (Oracle & Resolution) + +| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли | +|---|---------|---------|--------------------------------------------------| +| 60 | **Corruption Value Multiple (CVM)** | ✅ Решено (принципом дизайна) | Явный security-инвариант протокола: **бонд страховки оракула должен превышать потенциальную прибыль манипуляции**. Risk factor (insurance/bets) питает composite trust score. CVM — напрямую обоснование бондинга. | +| 61 | **Dispute Resolution** | ✅ Решено | Полная система: 12ч grace, `dispute_fee`, обязательный ответ оракула, per-market резолвер (`dispute_mode==0` stake-weighted голосование комитета / `==1` именованный резолвер), slashing страховки, 3-исходные no-contest диспуты, 14-дневный auto-close против заморозки, on-chain баны создателя/оракула. Одна из наиболее проработанных частей. **Дополнение HF14:** депозитчики lazy pool сохраняют вес голоса в диспуте (NAV пула → vesting-shares, добавляется к `effective_vesting_shares`). | +| 62 | **Oracle Design** | ✅ Решено | Bonded-модель: рег-комиссия, ≥5000 VIZ страховки, явный акцепт, резолюция с доказательствами, 14-метричная репутация, freshness decay, механика банов. Ядровая подсистема. | +| 63 | **Resolution Criteria** | 🟡 Частично / 🏛 Клиент | Вопрос/критерии рынка в `url`/описании (custom_json, display-only). Протокол навязывает *процесс* (кто резолвит, диспуты), но не *качество* критерия — двусмысленные критерии — ответственность создателя/клиента, полиция ретроспективно через диспуты + **on-chain баны создателя** (`pm_creator_ban_object`, теперь live). | +| 64 | **Self-Resolving Markets** | ➖ Не нужно (сегодня) | Резолюция Onix — oracle-driven, не алгоритмическая. Automated data oracles (Chainlink-style фиды) — высокоприоритетный roadmap для объективных рынков, что аппроксимировало бы self-resolution. | +| 65 | **UMA Protocol** | ➖ Не нужно (заменено) | Оптимистичный оракул UMA (у Polymarket) функционально заменён bonded-оракулом Onix + диспут-моделью комитета VIZ. Та же проблема, нативное VIZ-решение — без внешней зависимости от оракула. | + +--- + +## 5. Governance и решения (Governance & Decisions) + +| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли | +|---|---------|---------|--------------------------------------------------| +| 66 | **Attention Markets** | ➖ Не нужно | Торговля вниманием/виральностью — отдельный продукт; Onix фокусируется на резолюции событий. Возможна категория на слое клиента, не ядро. | +| 67 | **Conditional Tokens** | ➖ Не нужно (явно) | Whitepaper §7.2 доказывает, что CTF split/merge **архитектурно не нужен** — когерентность цен математическая, не навязанная токенами. *Единственную* полезную фичу CTF (трансфер позиций) реализуют нативно как `pm_transfer_position` с шифрованными memo. Намеренно опущено. | +| 68 | **Decision Markets** | 🟡 Возможно | Onix Multi мог бы выразить decision-рынки, но conditional-структура «если-политика-то-метрика» не нативна (нет conditional tokens). Собираемо на слое клиента; не ядровый примитив. | +| 69 | **Futarchy** | 🟡 Возможно (клиент) | Файл концепта: футархия = decision-рынки на conditional futures. У Onix нет нативных conditional-рынков, поэтому полная футархия в ядре не поддержана. Stake-weighted комитет VIZ уже управляет *параметрами*; governance-by-market — конструкция клиента/роадмапа. | +| 70 | **Hyperstition Markets** | ➖ Не нужно (намеренно) | Рефлексивность-как-фича (координировать, не предсказывать). Это *дизайн-выбор, а не баг для фикса*: требование Onix, чтобы исход был **внешне верифицируем bonded-оракулом**, структурно исключает hyperstition-рынки по умолчанию. Может существовать как отдельный «coordination market» продукт на слое клиента с milestone-резолюцией, но не ядровая цель. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). | +| 71 | **Impact Markets** | ➖ Не нужно | Retrospective-funding/impact-сертификаты — отдельный домен. Возможна категория на слое клиента; не ядро. | +| 72 | **No-Loss Prediction Markets** | 🟡 Смежно | Onix не no-loss для *бетторов* (проигравшие теряют ставки — это финансирует победителей). Но он **no-loss для *LP*** (принципал гарантирован). Yield-funded no-loss вариант (стейкаешь yield, принципал возвращается) — другая модель; no-loss на стороне LP уже реализован. | +| 73 | **Opportunity Markets** | ⚪ Имманентно (смежно) | **Opportunity-cost protection** Lazy Pool (graduated recall, active-market penalty, fault stamps) напрямую адресует opportunity-cost капитала — хотя «opportunity markets» как категория продукта вне скоупа. | + +--- + +## 6. Бизнес и платформы (Business & Platforms) + +| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли | +|---|---------|---------|--------------------------------------------------| +| 74 | **AI agents** | 🟡 Roadmap | Headless-клиент + открытые VIZ-операции делают программных агентов (бетторы, LP, автоматические оракулы) простыми. AI-driven ликвидность/резолюция — естественное расширение, ещё не специфицировано. | +| 75 | **Cross-subsidization** | ⚪ Имманентно | Lazy Pool кросс-субсидирует ликвидность по многим рынкам с одного депозита; MasterChef `reward_per_share` шарит fee-yield. Кросс-субсидирование встроено в экономику пула. | +| 76 | **Demand markets** | ➖ Не нужно | Рынки замера/агрегации спроса — категория продукта; не ядровый примитив Onix. Слой клиента. | +| 77 | **Distribution moat** | 🟡 Стратегия | Моат Onix — risk-free LP-yield + VIZ-нативная инфраструктура (флайвил). Дистрибуция (Telegram WebApp сегодня → headless web-клиент) — go-to-market, частично решается платформонезависимостью после миграции. | +| 78 | **Election markets** | 🏛 Клиент | Поддержаны как обычные бинарные/мульти рынки; их *легальность* — вопрос юрисдикционного клиента (whitelisted оракулы, фильтры категорий). Протокол нейтрален. | +| 79 | **Event contracts** | ⚪ Имманентно | Каждый рынок Onix *и есть* event contract. Регуляторная классификация — вопрос клиента/права, не логика протокола. | +| 80 | **Federal preemption** | 🏛 Клиент (N/A протоколу) | Файл концепта: зависит от того, «swaps» ли US event contracts. VIZ DLT — **инфраструктура, не оператор** (whitepaper §6.2) — как Bitcoin это леджер. Правовые обязательства привязаны к клиентам, не к консенсусу. Не забота протокола. | +| 81 | **Long-tail markets** | ✅ Решено | Именно ниша, которую включает bounded-loss LMSR — и Onix делает её *risk-free* для засева через Lazy Pool auto-allocation + пол ликвидности. Жизнеспособность long-tail — ядровый аргумент продажи. | +| 82 | **Market structure** | ✅ Решено (определено) | Onix определяет чёткую структуру: AMM-ценообразование, parimutuel-сеттлмент, bonded-оракулы, DPoS-управляемые параметры, consensus-level операции. Связная, новая рыночная структура против CLOB-платформ. | +| 83 | **Market surveillance** | 🟡 Roadmap / 🏛 Клиент | Полный on-chain audit trail (`market_log`, каждая операция consensus-validated) делает surveillance *возможным* для любого. Активный surveillance/энфорсмент — забота клиента + роадмапа. | +| 84 | **Network effects** | 🟡 Стратегия | Флайвил (risk-free LP → глубина → бетторы → комиссии → ещё LP) — задуманный сетевой эффект. Shared liquidity pools (roadmap) усиливают его. Go-to-market, не механика протокола. | +| 85 | **Parlays** | ➖ Не нужно | Многоногие комбо-ставки требуют conditional/комбинаторной структуры, которую Onix опускает. Вне скоупа (возможна сборка клиента поверх независимых рынков). | +| 86 | **Platform competition** | 🟡 Стратегия | Конкурирует уникальным «passive yield without impermanent loss» против Polymarket/Kalshi (таблица сравнения whitepaper §7.1). Стратегическое позиционирование, не логика протокола. | +| 87 | **Polymarket** | ⚪ Референс | Главный бенчмарк. Onix отличается по всем осям: CPMM/LMSR vs CLOB, ноль LP-риска vs инвентарный риск, нативные операции vs контракты Polygon, bonded-оракул vs UMA, без CTF. Используется для сравнения, не перенят. | +| 88 | **Regulatory arbitrage** | 🏛 Клиент | Модель юрисдикционного клиента: каждый регион строит свой compliant (или permissionless) клиент на нейтральных рельсах VIZ. Регуляторное позиционирование живёт целиком на слое клиента. | +| 89 | **Regulatory classification** | 🏛 Клиент | Являются ли рынки swaps/gaming/securities — решается per-юрисдикция на слое клиента; протокол классификационно-нейтрален (те же `pm_*` для permissionless и регулируемых клиентов). Не забота протокола. | + +--- + +## Итог — что реально меняет дизайн Onix-на-VIZ + +**Решено структурно (ключевые выигрыши):** +- LP-сторонние **adverse selection, toxic flow, gap risk, impermanent loss, инвентарный риск мейкера** → все устранены, т.к. победителям платят только из проигранных ставок, а принципал LP возвращается безусловно (доказательство AM-GM для CPMM; parimutuel-сеттлмент для LMSR). +- **Провал LMSR на бинарных рынках** → обойдён использованием CPMM для бинарных и ограничением LMSR мульти-рынками, где мейкер не контрагент. +- **Liquidity provision, minimum viable liquidity, жизнеспособность long-tail** → risk-free LP + Lazy Pool auto-allocation. +- **Oracle design, dispute resolution, CVM** → bonded-оракул + 14-метричная репутация + stake-weighted диспуты комитета. +- **Price discovery, arbitrage, conditional tokens** → когерентность цен математическая (`Σ price = 1`), значит не нужны ордербук, split/merge, внутренний слой арбитража. + +**Не нужно / намеренно опущено:** ордербук, CDA, bid-ask spread, semantic tick size, orderflow arbitrage, CTF split/merge, комбинаторные/covariance/correlation/relative-value/parlay рынки, UMA, peer prediction, LOX. + +**Вытолкнуто на слой юрисдикционного клиента:** federal preemption, regulatory classification/arbitrage, легальность election-рынков, KYC/энфорсмент инсайда, surveillance. + +**Реализовано с момента написания таблицы (теперь live on-chain, HF14):** опциональные батч-аукционы + commit-reveal ставки (бинарные), опциональный leverage-сабсистем (position collateralization), Lazy Pool, `endogeneity_tier`, on-chain баны создателей, settlement-vop на беттора/leverage + методы плагина, и вес стейка lazy pool в PM-диспутах + голосовании за заявки DAO-комитета. + +**В роадмапе VIZ (частично сегодня):** shared/category liquidity pools (фикс фрагментации), automated data oracles (→ self-resolving объективные рынки), distribution markets, AI agents. *(Прим.: commit-reveal в **диспутах** в этом списке **нет** — он намеренно отклонён; слушания диспутов остаются публичными. Commit-reveal для **ставок** уже live.)* + +**Остаётся открытым / поведенческое (смягчено, не устранено):** longshot/yes bias, манипуляция ценой через глубину, кросс-платформенный арбитраж. У семейства рефлексивности (endogeneity, reflexivity, KBC, hyperstition) есть конкретный план смягчения — см. ниже. + +--- + +## Смягчения для семейства рефлексивности + +Endogeneity, reflexivity, Keynesian beauty contest (KBC) и hyperstition — это **одна корневая проблема на разных слоях**: рынок/цена влияет на исход, который он измеряет. Один небольшой набор примитивов адресует все четыре. + +### Карта корневой причины + +| Слой | Концепт | Канал | +|------|---------|-------| +| Уровень форекастера | **Keynesian Beauty Contest** | стадность к видимому консенсусу в *относительном/peer-скоринге* | +| Уровень рынка | **Endogeneity** | *существование/видимость* рынка меняет поведение (категориально) | +| Уровень рынка | **Reflexivity** | общая обратная связь цена↔реальность; манипуляция-как-пропаганда | +| По дизайну | **Hyperstition** | рефлексивность используется *намеренно* для координации исхода | + +### Примитивы смягчения + +| Примитив | Статус | Что чинит | Заметки | +|----------|--------|-----------|---------| +| **Commit-reveal голосование в диспутах** | **отклонено (делать НЕ будем)** | KBC | Диспут комитета это **открытые публичные слушания**: `pm_dispute_vote` — публичный бюллетень, и **остаётся таким by design** — доверие к ДАО держится на прозрачном разрешении споров. Бюллетень **можно менять** до закрытия (повтор перезаписывает), чтобы голосующие честно обновляли решение на новые доводы; остаточный KBC принят (за совпадение с большинством не платят). | +| **Commit-reveal ставки (батчем)** | **live (опц., бинарные)** | endogeneity, reflexivity, инфо-асимметрия | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` скрывают направление/размер in-flight потока, чтобы публичная цена не «текла» во время приёма (убивает канал термостата). Сеттлится единой ценой **батчем** (см. ниже). | +| **Поле рынка `endogeneity_tier`** | **живое поле** | endogeneity | Оракул тегирует tier 1 (эконом-данные — чисто), 2 (спорт/расписание), 3 (политика/соц — рискованно); UI показывает уровень рефлексивного риска; клиенты могут ограничивать tier-3. | +| **Экзогенная резолюция (automated data oracles)** | roadmap (high) | endogeneity, reflexivity | Резолюция привязана к внешнему фиду (BLS/ФРС/спорт-API), на который рынок не влияет → чистый термометр. | +| **Список запрещённых категорий + бан создателя** | **бан создателя live on-chain**; список — слой клиента | вредная рефлексивность, hyperstition | Блокировать рынки, где YES создаёт «конституенцию за исход» (рынки убийств/«hit»/терактов, пропаганда-рынки). Через on-chain бан создателя (`pm_creator_ban_object`) + фильтр категорий клиента. | +| **Глубокая risk-free LP-ликвидность** | ядро сегодня | манипуляционная рефлексивность | Флайвил делает книгу глубокой, поэтому двигать цену ради «newsworthy» манипулированного заголовка — дорого. | + +### KBC: почему рыночный слой уже безопасен + +KBC — патология **относительного скоринга** (платят за близость к peers → стадность к peers). Рыночный слой Onix платит бетторам против **внешней истины через parimutuel-сеттлмент** — тебя награждают за *отклонение* от неправильной цены толпы, а не за совпадение. Значит слой бетторов структурно анти-KBC. Единственный относительный/peer-механизм в протоколе — **stake-weighted голосование комитета в диспуте**, и остаточный риск KBC там **принят by design** — диспут оставлен открытыми публичными слушаниями (без commit-reveal), потому что доверие к ДАО держится на прозрачном разрешении споров; риск ограничивает другое — голосующим не платят за совпадение с большинством, а бюллетень можно менять по мере поступления доводов. + +### Commit-reveal против инварианта CPMM `a·b=k` + +CPMM **path-dependent** (токены зависят от резервов в момент исполнения), поэтому commit-reveal *нельзя* делать ставка-за-ставкой против живой кривой — порядок раскрытия вернул бы MEV и слил бы цену. Фикс (и почему §8.3 ставит commit-reveal рядом с batch-auction): **перестать обновлять кривую per-bet; обновлять её раз в эпоху через uniform-price батч-сеттлмент.** + +1. **Commit:** прислать `hash(side, amount, salt, min_tokens)` и заэскроить `amount`. +2. **Reveal:** раскрыть `(side, amount, salt)`; раскрытия собираются, но **не применяются** до конца эпохи (видеть чужие reveal бесполезно — своё уже закоммичено). +3. **Сеттлмент раз:** встречный поток (`A_in` vs `B_in`) неттится между бетторами по единой клиринговой цене `p*`; только **нетто-остаток** двигает AMM, поэтому `k` пересчитывается **один раз**. Все A-филлы получают `p*`, все B-филлы — `1−p*` → нет внутрибатчевого преимущества по порядку. +4. **Флор `min_tokens`:** поскольку цена невидима в момент commit, per-bet флор по токенам обязателен; если `tokens < min_tokens` на сеттлменте, ставка отклоняется и возвращается из эскроу. +5. **Анти-griefing:** нераскрытие → штраф из эскроу в LP-fee/DAO-пул, убивая атаку «закоммить опциональность, раскрой только победителей». + +**Гарантия LP не тронута:** каждый батч-сеттлмент — валидный CPMM-переход, значит AM-GM `reserve_a + reserve_b ≥ L` держится. Меняется только *гранулярность* обновления кривой (per-bet → per-epoch). Onix Multi аналогично через агрегированную LMSR cost-функцию. + +**Фазировка:** (1) сначала uniform-price батч-аукционы — уже дёшево убивают ordering-MEV/фронт-раннинг; (2) поверх — commit-reveal сокрытие — добавляет in-flight конфиденциальность, включается выборочно для tier-3 (endogeneity-чувствительных) рынков. **И (1), и (2) теперь реализованы on-chain для бинарных рынков** (мульти всё ещё форсит instant-ставки — LMSR-батча пока нет). + +### Изменение вердиктов + +| Концепт | Было | Стало | +|---------|------|-------| +| Keynesian Beauty Contest | 🔴 Открыто | ✅ рынок / ⚪ диспут — голоса публичны **by design** (без commit-reveal; бюллетень изменяем, голосующим не платят, участники пула с правом голоса) | +| Endogeneity | 🔴 Открыто | 🟡 смягчено — `endogeneity_tier` + commit-reveal/батч **live**; экзогенные оракулы roadmap | +| Reflexivity | 🔴 Открыто | 🟡 смягчено — бан создателя **live on-chain**; список категорий — слой клиента | +| Hyperstition | 🔴 Открыто | ➖ исключено по дизайну (опц. продукт клиента) | diff --git a/@l10n/ru/docs/prediction-markets/index.md b/@l10n/ru/docs/prediction-markets/index.md new file mode 100644 index 0000000000..64621de0ee --- /dev/null +++ b/@l10n/ru/docs/prediction-markets/index.md @@ -0,0 +1,53 @@ +--- +title: Прогнозные рынки (Onix) — обзор и карта +description: Стек on-chain прогнозных рынков VIZ — протокол Onix и клиент Forecaster — с полным деревом документации от whitepaper до спецификации, объектов, операций, воркфлоу и анализа концептов. +--- + +# Прогнозные рынки на VIZ + +VIZ Ledger исполняет прогнозные рынки как **операции консенсуса первого класса** (`pm_*`), live с HF14. +Два имени, которые встречаются повсюду: + +- **Onix** — **протокол**: on-chain движок рынка (CPMM binary + LMSR multi, parimutuel zero-sum + расчёт, bonded-оракулы, lazy-пул, опциональное плечо, batch / commit-reveal ставки). +- **Forecaster** — **тонкий клиент** к этому протоколу в VIZ Ledger. Это headless, платформонезависимый + фронтенд, позволяющий **людям со всего мира участвовать в on-chain прогнозном рынке** — создавать + рынки, ставить, давать ликвидность, быть оракулом и оспаривать — подписывая операции `pm_*` напрямую + против публичных узлов VIZ. Протокол нейтрален; Forecaster (и любой юрисдикционный клиент, + построенный так же) — это слой доступа. + +## Карта документации + +```mermaid +flowchart TD + ROOT["Прогнозные рынки (Onix)"] + ROOT --> OV["Обзор — одностраничный питч (весь стек кратко)"] + ROOT --> WP["Whitepaper — тезис: почему risk-free LP, два типа рынков, флайвил"] + ROOT --> SP["Спецификация — формальная механика + §17 On-chain модель объектов"] + ROOT --> OPS["Операции — подписанные consensus-операции pm_*"] + ROOT --> VOPS["Виртуальные операции — детерминированные vop на расчёте / по дедлайну"] + ROOT --> API["API плагина — read-методы prediction_market_api"] + ROOT --> WF["Воркфлоу и диаграммы — один канонический рынок через все роли"] + ROOT --> CA["Анализ концептов — 90 концептов PM-теории против живой реализации на VIZ"] +``` + +## С чего начать + +| Страница | Что это | +|------|-----------| +| [Обзор](./onix) | Одностраничное позиционирование: parimutuel с AMM-ценой и структурно безрисковой ликвидностью. | +| [Whitepaper](./whitepaper) | Индустриальный тезис — гарантия LP, Onix Binary (CPMM) + Onix Multi (LMSR), оракулы, lazy-пул, плечо, управление. | +| [Спецификация](./specification) | Формальная спека: параметры, машина состояний, ценообразование, расчёт, споры, lazy-пул, плечо и **[On-chain модель объектов](./specification)** (каждый `pm_*_object` и его индекс поиска). | +| [Операции](../protocol/operations/prediction-markets) | 21 подписанная consensus-операция (`pm_create_market`, `pm_place_bet`, …). | +| [Виртуальные операции](../protocol/virtual-operations) | Детерминированные vop (`pm_payout`, `pm_market_accepted`, `pm_leverage_resolve`, `pm_batch_settle`, …). | +| [API плагина](../plugins/prediction-market-api) | `prediction_market_api` — read-доступ к рынкам, ставкам, оракулам, спорам, lazy-пулу и медиана-голосуемым параметрам. | +| [Воркфлоу и диаграммы](./workflows) | Один канонический бинарный рынок через всех участников, с zero-sum мастер-леджером для нормального и спорного разрешения. | +| [Анализ концептов (Onix против 90)](./concepts-analysis) | Как on-chain реализация ложится на атлас теории прогнозных рынков — что решено, имманентно, не нужно или в роадмапе. | + +## Управление + +Все экономические параметры делегат-**медиана-голосуемы** и живут в структуре `chain_properties_pm` — +см. [Параметры цепи → Параметры прогнозных рынков](../governance/chain-properties#pm-parameters). +Хардфорк для настройки комиссий, штрафов, lazy-пула, плеча или тайминга batch/commit-reveal не нужен; три +живых kill-switch (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) позволяют +медиане валидаторов отключить целую подсистему без форка. diff --git a/@l10n/ru/docs/prediction-markets/onix.md b/@l10n/ru/docs/prediction-markets/onix.md new file mode 100644 index 0000000000..118329978b --- /dev/null +++ b/@l10n/ru/docs/prediction-markets/onix.md @@ -0,0 +1,89 @@ +--- +title: Onix — прогнозные рынки с AMM-ценой и тотализаторными выплатами +description: "Onix кладёт непрерывное AMM-ценообразование поверх parimutuel-выплат — цена движется как в AMM, а ликвидность несёт риск-профиль тотализатора: маркет-мейкера нельзя обанкротить." +--- + +# Onix — прогнозные рынки: AMM-цена, тотализаторные выплаты + +> Непрерывное **AMM-ценообразование** поверх **parimutuel-выплат** — цена движется как в AMM, а +> ликвидность несёт риск-профиль тотализатора: **маркет-мейкера нельзя обанкротить.** + +::: info Onix и Forecaster +**Onix** — это on-chain протокол. **Forecaster** — тонкий клиент к нему в VIZ Ledger: headless, +платформонезависимый слой доступа, позволяющий людям со всего мира участвовать в on-chain прогнозном +рынке, подписывая операции `pm_*` напрямую против публичных узлов VIZ. Полное дерево документации — +[обзор и карта раздела](./). +::: + +## Одна идея + +Onix **разделяет цену и выплату**: + +- **Цена (discovery)** — кривая CPMM (binary) или LMSR-softmax (multi) обновляет живую вероятность на + каждой ставке и присваивает ставке **вес** (её «билет» на долю). +- **Выплата (settlement)** — победителям платят **только** из проигранных ставок, делёж по весу: чистый + **parimutuel**, строго zero-sum (протокол никогда не печатает токен). + +Всё отличительное в Onix вытекает из этого разделения. + +## Почему это важно — три вещи + +::: tip 1 · Ликвидность, которую нельзя «слить» +Поскольку победителям платят из проигравших, а не из принципала LP, поставщика ликвидности **нельзя +обанкротить** — нет impermanent loss, нет инвентарного риска, нет «смерти от снайпера». Гарантировано *by +construction* (AM–GM для CPMM, сохранение для LMSR), а не страховкой. +::: + +::: tip 2 · Пассивный доход без IL — Lazy Pool +Один депозит сам растекается тихой ликвидностью по многим рынкам и фондирует опциональное плечо, учёт +наград в стиле MasterChef. Доход от ликвидности ПМ **без** выбора рынков и без impermanent loss. +::: + +::: tip 3 · Нативность к цепи, zero-sum +Рынки — это операции консенсуса первого класса (`pm_*`), а не смарт-контракты: цензуроустойчиво, +композируемо, блоки ~3 секунды, без оракул-моста. Протокол не эмитит токены — только перераспределяет. +::: + +## Как работает ставка + +1. **Ты ставишь** `X` на исход. `X` входит в кривую; кривая возвращает твой **вес** — больше веса, если + зашёл раньше, до сдвига цены. +2. **Борд обновляется.** Живой коэффициент стороны = `1 + чужой_пул × (1 − комиссия) / свой_пул`, причём + комиссия (оракул + создатель + LP) уже зашита. +3. **На резолюции** ставки проигравших (минус комиссия) делятся между победителями по весу. Твоя выплата + = возврат ставки **+** твоя доля проигравшего пула. Принципал LP возвращается нетронутым. + +## Сравнение + +| | CLOB / AMM (Polymarket, Kalshi) | Голый parimutuel (тотализатор) | **Onix** | +|---|---|---|---| +| Живая цена | да | нет (только соотношение пулов) | **да (CPMM / LMSR)** | +| Кэф зафиксирован при ставке | да | нет | нет (честный parimutuel) | +| LP / мейкер может обанкротиться | **да** (IL, снайперы, gap) | n/a | **нет (структурно)** | +| Доходный слой ликвидности | хрупкий | нет | **Lazy Pool, без IL** | +| Где живёт | контракты / бэкенд | бэкенд | **консенсус (`pm_*`)** | +| Эмиссия токенов | бывает | нет | **нет (zero-sum)** | + +## Честный компромисс + +::: warning Кэфы — parimutuel: они плывут до закрытия +Onix **не** фиксирует твой коэффициент в момент ставки. Борд движется по мере притока денег, и финальный +кэф известен только на закрытии — ровно как в тотализаторе. Это не баг для заплатки: *единственный* способ +зафиксировать кэф — чтобы риск нёс контрагент (букмекер или AMM-LP, который может потерять). Drift в Onix — +прямая цена его LP-гарантии: риск живёт **между беттерами**, поэтому ничья ликвидность не может сгореть. +::: + +## Что новаторского + +- **AMM-вес + parimutuel-сеттлмент** в одном движке — непрерывный price discovery *без* инвентарного + риска мейкера. +- **Структурная, доказуемая безопасность LP** вместо застрахованной или субсидируемой ликвидности. +- **Мьютуализированный доходный слой ликвидности** (Lazy Pool), который ещё и фондирует опциональное + плечо — ликвидация идёт по pre-bet резервам, поэтому пул всегда остаётся целым. +- **Опциональный анти-MEV** (batch / commit-reveal ставки) и **прозрачное управление** (bonded-оракулы, + публичные слушания споров с изменяемыми голосами) — всё поверх безопасной базы, не трогая LP-гарантию. + +## Подробнее + +- Операции протокола — [Прогнозные рынки](../protocol/operations/prediction-markets) +- API плагина — [API прогнозных рынков](../plugins/prediction-market-api) diff --git a/@l10n/ru/docs/prediction-markets/specification.md b/@l10n/ru/docs/prediction-markets/specification.md new file mode 100644 index 0000000000..8b74bc0a5f --- /dev/null +++ b/@l10n/ru/docs/prediction-markets/specification.md @@ -0,0 +1,1013 @@ +--- +title: Onix Protocol — Спецификация +description: Формальная техническая спецификация протокола Onix, реализованного как операции консенсуса на VIZ DLT (HF14). +--- + +# Спецификация Onix Protocol + +**Версия:** 2.0 (on-chain / HF14) +**Статус:** Формальная техническая спецификация — реализована как операции консенсуса на VIZ DLT + +--- + +> **On-chain (HF14).** Реализовано как операции консенсуса первого класса (`pm_*`) на VIZ DLT и проверено +> в `consensus_sim`. **Все проценты — базисные пункты (bp): 10000 = 100.00%**; все длительности — +> governance-параметры в **секундах / блоках**. Медиана-голосуемые параметры живут в структуре +> `chain_properties_pm` (§3); per-market поля — операция `pm_create_market`; всё состояние — в +> chainbase-объектах §17. Комиссии оракула — offer→quote (потолок создателя → оракул фиксирует котировку +> при акцепте, эмитя `pm_market_accepted`). У споров два режима — комитет (`dispute_mode = 0`, по +> умолчанию: stake-weighted **публичный** `pm_dispute_vote`, изменяем до закрытия, стейк Lazy-Pool +> считается) и аккаунт (`dispute_mode = 1`: именованный `dispute_resolver`). + +## Оглавление + +1. Определения и роли +2. Валюта и точность +3. Системные параметры +4. Машина состояний рынка +5. Onix Binary: Constant Product Market Maker +6. Onix Multi: LMSR с parimutuel-расчётом +7. Структура комиссий +8. Time penalty для поздних ставок +9. Предоставление ликвидности +10. Резолюция и выплаты +11. Отмена ставки +12. Система споров +13. Штраф оракула за пропуск резолюции +14. Скоринг репутации оракула +15. Трансфер позиций +16. Lazy-пул ликвидности +16a. Опциональное плечо +16b. Batch / Commit-Reveal ставки +17. On-chain модель объектов + +--- + +## 1. Определения и роли + +| Роль | Определение | +|------|-----------| +| **Создатель рынка** | Платит `pm_market_creation_fee` (`pm_create_market`); задаёт вопрос, исходы, ликвидность, потолки комиссий и параметры тайминга | +| **Оракул** | Регистрируется (комиссия: `pm_oracle_registration_fee`), вносит страховку (мин.: `pm_min_oracle_insurance`), котирует свои условия комиссии в **базисных пунктах** (≤ потолка создателя) + фиксированную комиссию при акцепте, принимает/отклоняет рынки, даёт решения по исходам | +| **Беттер** | Ставит на исходы; получает токены пропорционально стейку и текущим резервам | +| **Поставщик ликвидности (LP)** | Поставляет капитал в пулы рынка; зарабатывает time-weighted долю комиссий ликвидности + штрафной пул | +| **Поставщик Lazy Pool** | Вносит VIZ в Lazy-пул ликвидности с lock-периодом; пул авто-аллоцирует в рынки и распределяет награды через аккумулятор `reward_per_share` | +| **Резолвер споров** | Только режим аккаунта (`dispute_mode = 1`): per-market аккаунт `dispute_resolver` арбитрирует. Режим комитета (`dispute_mode = 0`) резолвера не использует — голосует электорат SHARES | +| **Фонд DAO / комитета** | Существующий фонд комитета цепи. Получает `pm_market_creation_fee` и доп. штрафы оракулов | + +--- + +## 2. Валюта и точность + +Все суммы хранятся как целые с точностью = 1/1000 (милли-VIZ). `1000` внутренних единиц = 1.000 VIZ. + +Значения time penalty используют точность = 1/1 000 000 (микро-единицы). + +--- + +## 3. Системные параметры + +### Медиана-голосуемые параметры (`chain_properties_pm`) + +Все экономические параметры голосуются медианой делегатов (без хардфорка для настройки) и живут в on-chain +структуре `chain_properties_pm`. Каждый делегат публикует свои предпочтительные значения через стандартную +**`versioned_chain_properties_update_operation`** (op ID 46) — `chain_properties_pm` является текущей +(v5, HF14) версией этой versioned-структуры — а сеть применяет **медиану по каждому полю** активных +делегатов. Две ручки риск-покрытия (`pm_listing_min_coverage_percent`, `pm_betting_min_coverage_percent`) +входят в ту же v5-структуру и настраиваются ровно так же. **Все проценты — базисные пункты +(bp, 10000 = 100.00%); длительности — в секундах или блоках** — за исключением двух ручек покрытия, которые +измеряются в проценте от объёма (100 = 1.0×). Точные дефолты и диапазоны — в +[Chain Properties](../governance/chain-properties#pm-parameters); авторитетный источник — сама структура. + +| Группа | Параметры | +|---|---| +| Регистрация и полы | `pm_oracle_registration_fee`, `pm_min_oracle_insurance`, `pm_market_creation_fee`, `pm_min_liquidity`, `pm_max_outcomes`, `pm_max_market_duration` | +| Комиссии и штрафы (bp) | `pm_max_oracle_fee_percent`, `pm_oracle_penalty_percent`, `pm_no_contest_penalty_percent`, `pm_default_time_penalty_percent`, `pm_max_time_penalty` | +| Окно акцепта | `pm_oracle_accept_window_sec` (по умолчанию 3600 = 1 ч; пендинг-рынки, не принятые/отклонённые в этот срок, аннулируются кроном — сид возвращён, комиссия за создание удержана) | +| Риск / покрытие (% от объёма) | `pm_listing_min_coverage_percent` (250 = 2.5×; рынки с покрытием ниже этого скрыты из каталога по умолчанию, показываются через `show_risky`), `pm_betting_min_coverage_percent` (150 = 1.5×; рекомендательный клиентский порог подтверждения риска, `≤` листингового, on-chain не навязывается) | +| Споры | `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_oracle_dispute_response_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent` (bp), `pm_dispute_reward_multiplier` (bp) | +| Lazy-пул | `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (по умолчанию 200 = 2%; пул пропускает рынки, чей `liquidity_fee_percent` ниже этого порога вознаграждения) | +| Плечо | `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent` | +| Batch / commit-reveal | `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_commit_no_reveal_penalty_percent` (bp), `pm_min_batch_bet` | +| Обработка | `pm_processing_cap_per_block` | + +Получатель `pm_market_creation_fee` и доп. штрафов оракулов — существующий фонд комитета/DAO цепи, а не +отдельный PM-аккаунт. + +### Per-market параметры (операция `pm_create_market`) + +Задаются создателем при создании; поля комиссии оракула — это **потолок**, против которого оракул котирует +при акцепте (offer→quote). Полный референс полей: [Операции прогнозных рынков](../protocol/operations/prediction-markets). + +| Поле | Описание | +|---|---| +| `oracle`, `market_type` (0 binary / 1 multi), `outcomes`, `url` | определение рынка | +| `oracle_fee_percent`, `oracle_fixed_fee` | **потолок** комиссии оракула (bp + фикс.); оракул фиксирует котировку ≤ него (и ≤ медианного `pm_max_oracle_fee_percent`) при акцепте | +| `creator_fee_percent`, `liquidity_fee_percent` | комиссии создателя и LP (bp от пула проигравших) | +| `liquidity`, `lmsr_b` | сид-ликвидность; `lmsr_b` для мульти-рынков | +| `betting_expiration`, `result_expiration` | таймеры | +| `time_penalty_type`, `time_penalty_value`, `penalty_curve_type` | форма штрафа за поздние ставки | +| `allow_early_resolution`, `allow_cancellation` | переключатели | +| `allow_batch`, `allow_instant_bet` | режимы ставок (бинарные) | +| `endogeneity_tier` | 1 эконом-данные / 2 спорт / 3 политика (подсказка отображения/риска) | +| `dispute_mode` (0 комитет / 1 аккаунт), `dispute_resolver` | маршрутизация споров | +| `dispute_penalty_percent` | политика штрафа оракула при удовлетворённом споре (bp, со знаком) | +| `metadata` | свободный клиентский JSON (консенсус-непрозрачен; парсится off-chain) | + +--- + +## 4. Машина состояний рынка + +### Состояния + +| Status | Имя | Описание | +|--------|------|-------------| +| -1 | Deleted | Оракул отклонил **или** истекло окно акцепта (`pm_oracle_accept_window_sec`); сид-ликвидность возвращена создателю (комиссия за создание удержана) | +| 0 | Waiting | Ожидает проверки оракула | +| 1 | Active | Принимает ставки до `betting_expiration` | +| 2 | Closed | Приём ставок закончен, ожидает резолюции оракула | +| 3 | Resolved | Исход определён, выплаты рассчитаны | + +### Состояния выплат + +| payout_status | Имя | Описание | +|---------------|------|-------------| +| 0 | Not calculated | До резолюции | +| 1 | Calculated | Выплаты ожидают (grace-период активен) | +| 2 | Paid | Все выплаты обработаны | +| 3 | Disputed | Подан спор, выплаты заморожены | + +### Переходы + +```mermaid +stateDiagram-v2 + direction LR + state "Waiting (0)" as Waiting + state "Active (1)" as Active + state "Closed (2)" as Closed + state "Resolved (3)" as Resolved + state "Deleted (-1)" as Deleted + state "Paid out" as Paid + [*] --> Waiting + Waiting --> Active: оракул принял + Waiting --> Deleted: оракул отклонил + Waiting --> Deleted: окно акцепта истекло (pm_market_expired) + Active --> Closed: betting_expiration + Active --> Resolved: раннее разрешение (если разрешено) + Closed --> Resolved: оракул разрешает + Resolved --> Paid: окно ожидания (12ч), без спора + Deleted --> [*] + Paid --> [*] +``` + +**Предусловия:** + +| Переход | Предусловия | +|-----------|---------------| +| 0 → 1 | Страховка оракула ≥ `min_oracle_insurance`; оракул принимает | +| 0 → 1 (self-oracle) | Создатель = оракул; проверка страховки; авто-одобрение при создании | +| 0 → -1 | Оракул отклоняет; сид-ликвидность возвращена создателю | +| 0 → -1 (экспирация) | `now ≥ created_time + pm_oracle_accept_window_sec` без действия оракула; крон аннулирует рынок, возвращает сид (комиссия за создание удержана), эмитит `pm_market_expired` | +| 1 → 3 | Оракул подаёт резолюцию с исходом (0, 1 или -1 для no-contest); `allow_early_resolution=1` или `time ≥ betting_expiration` | +| 2 → 3 | Оракул подаёт резолюцию; `time ≤ result_expiration` | +| 3 → paid | Grace-период прошёл без спора; крон обрабатывает выплаты | + +### Флоу создания рынка + +1. Списать `market_creation_fee` с создателя → фонд DAO (невозвратно) +2. Записать `oracle_fixed_fee` из профиля оракула на рынок +3. Заблокировать `liquidity` с баланса создателя +4. Инициализировать резервы: `reserve_a = floor(liquidity/2)`, `reserve_b = liquidity − reserve_a` +5. Вычислить `k = reserve_a × reserve_b` +6. Если self-oracle: авто-одобрение до status=1 с проверкой страховки +7. Если внешний оракул: войти в status=0 и установить `accept_deadline = created_time + pm_oracle_accept_window_sec` + +### Флоу акцепта оракула + +Пендинг-рынок должен быть разрешён своим оракулом в течение окна акцепта +(`pm_oracle_accept_window_sec`, по умолчанию 1 ч). Три исхода: + +- **Принятие** (status 0 → 1): (1) перевести `oracle_fixed_fee` с баланса создателя на баланс оракула + (пропускается для self-oracle); (2) инкремент `markets_accepted`; (3) обновить `last_active_time`; + (4) запуск авто-аллокации Lazy Pool (если у пула есть свободный баланс **и** `liquidity_fee_percent + рынка ≥ pm_lazy_min_liquidity_fee_percent`). +- **Отклонение** (status 0 → -1): сид-ликвидность возвращена создателю; без vop. +- **Экспирация** (status 0 → -1): если ничего не произошло к `accept_deadline`, крон каждого блока + аннулирует рынок, возвращает сид-ликвидность (**не** комиссию за создание) и эмитит `pm_market_expired`. + +### Audit Trail + +Каждое state-changing действие — это операция консенсуса или виртуальная операция, навсегда записываемая в +block log и запрашиваемая через `account_history`. Ставки, отмены, добавление/вывод ликвидности, +accept/reject, резолюция, спор, разрешение спора, выплата и штраф появляются как `pm_*`-операции/vop, +вместе с резервами рынка, которых они касаются. + +--- + +## 5. Onix Binary: Constant Product Market Maker + +### Инвариант + +``` +k = reserve_a × reserve_b +``` + +`k` меняется только при операциях добавления/вывода ликвидности. + +### Размещение ставки (сторона A) + +``` +new_reserve_b = reserve_b + amount +new_reserve_a = floor(k / new_reserve_b) +tokens_received = reserve_a − new_reserve_a +price = amount × 1,000,000 / tokens_received +``` + +Симметрично для стороны B (поменять a/b). + +### Защита от слиппеджа + +Опциональный параметр `min_tokens` в `place-bet`. Если `tokens_received < min_tokens`, транзакция +отвергается. + +### Инициализация рынка + +``` +reserve_a = floor(liquidity / 2) +reserve_b = liquidity − reserve_a +k = reserve_a × reserve_b +``` + +Минимальная начальная ликвидность: 100,000 mVIZ (100 VIZ). + +### Семантика веса (токена) + +- `weight` = число токенов исхода, полученных беттером (задаётся CPMM на момент ставки) +- `weight` — это **относительная претензия**, не выплата в VIZ. Расчёт **parimutuel** (идентично Onix + Multi): победители получают назад стейк плюс пропорциональную долю пула проигравших, по весу. +- Если ставка на сторону A и выигрывает исход A: `payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit` +- Если исход A проигрывает: payout = 0 (стейк форфейтится в пул победителей) + +CPMM — это **движок ценообразования** (вероятность + назначение веса); он больше не гейтит выплату. Это +заставляет два типа рынков разделять одну модель расчёта: *AMM назначает веса (CPMM для бинарных, LMSR +для мульти); проигравшие финансируют победителей пропорционально весу.* + +### Отображение цены + +``` +implied_probability_A = reserve_b / (reserve_a + reserve_b) × 100% +implied_probability_B = reserve_a / (reserve_a + reserve_b) × 100% +``` + +### Гарантия принципала LP (доказательство) + +При parimutuel-расчёте гарантия точна и не опирается на геометрию кривой: + +``` +Money OUT = L (LP principal) + Σ(winning bet_amount) + winners_pool + fees + = L + winning_bets + (losers_sum − fees) + fees + = L + winning_bets + losing_bets = L + all_bets = Money IN +``` + +Суммарная выплата ограничена `losers_sum` независимо от весов, поэтому принципал LP `L` возвращается +безусловно, а победители фондируются целиком проигравшими. (Легаси AM-GM граница +`reserve_a + reserve_b ≥ L` больше не нужна для платёжеспособности; она остаётся свойством ценовой +кривой.) + +--- + +## 6. Onix Multi: LMSR с parimutuel-расчётом + +### Функция цены (softmax) + +Для N исходов с параметрами количества q_1, ..., q_N и параметром ликвидности b: + +``` +price(i) = exp(q_i / b) / Σ_j exp(q_j / b) +``` + +**Инвариант:** `Σ_i price(i) = 1` (по определению softmax). + +### Функция стоимости + +``` +C(q) = b × ln(Σ_j exp(q_j / b)) +``` + +Стоимость покупки Δ токенов на исход i: + +``` +cost = C(q + Δ·e_i) − C(q) + = b × [ln(Σ_j exp(q'_j / b)) − ln(Σ_j exp(q_j / b))] +where q'_i = q_i + Δ, all other q'_j = q_j +``` + +Численная устойчивость (трюк log-sum-exp): + +``` +ln(Σ exp(x_j)) = max(x) + ln(Σ exp(x_j − max(x))) +``` + +### Параметр ликвидности + +``` +b = S / ln(N) +``` + +где S = депозит субсидии LP, N = число исходов. + +### Расчёт (на резолюции) + +``` +1. Oracle declares winning outcome +2. losers_sum = Σ bet_amount for all non-winning bets +3. oracle_fee = floor(losers_sum × oracle_fee_percent / 10000) +4. creator_fee = floor(losers_sum × creator_fee_percent / 10000) +5. liq_fee = floor(losers_sum × liquidity_fee_percent / 10000) +6. winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee +7. For each winning bettor: + payout = bet_amount + (their_tokens / total_winning_tokens × winners_pool) − time_penalty +8. LP subsidy returned unconditionally +9. LP earns time-weighted share of liq_fee +``` + +### Гарантия принципала LP (доказательство) + +1. LP вносит S VIZ как субсидию. Это задаёт b = S / ln(N). +2. Во время ставок пользователи платят VIZ → получают токены. VIZ накапливается как пул ставок. +3. На резолюции: проигравшие форфейтят 100% → `losers_sum`. Победителям платят из `losers_sum` (не из + субсидии). +4. Субсидия LP S возвращается **безусловно** — она архитектурно отделена от потока выплат. + +### Граничные случаи + +| Сценарий | Исход | +|----------|---------| +| Все ставки на выигрышный исход | `losers_sum=0`, `winners_pool=0`. Каждый беттер получает назад `bet_amount`. Субсидия LP возвращена. | +| Нет ставок на выигрышный исход | `losers_sum=total_bets`. Нераспределённый `winners_pool` → бонус LP. | +| Рынок с нулевым объёмом | Субсидия LP возвращена. Ни комиссий, ни выплат. | +| Выигрывает единственный беттер | Беттер получает `bet_amount + winners_pool`. Субсидия LP возвращена. | + +### Операции + +| Операция | Описание | +|-----------|-------------| +| `pm_create_market_multi { oracle, outcomes, liquidity, fees, ... }` | Создать рынок с N исходами | +| `pm_place_bet_multi { market, outcome_index, amount, min_tokens }` | Купить токены исхода | +| `pm_cancel_bet_multi { bet_id, min_return }` | Продать токены назад через reverse LMSR | +| `pm_add_liquidity_multi { market, amount }` | Добавить субсидию LP (увеличивает b) | +| `pm_withdraw_liquidity_multi { liquidity_id }` | Вывести субсидию LP (мин. floor навязан) | +| `pm_resolve_multi { market, winning_outcome }` | Оракул объявляет победителя, запускает расчёт | + +Бинарные рынки (N=2) используют Onix Binary (CPMM). LMSR используется только для N > 2. + +--- + +## 7. Структура комиссий + +### Вычисление комиссий на момент резолюции + +Все процентные комиссии считаются на резолюции от **общего объёма проигравшей стороны**: + +``` +losers_sum = Σ bet_amount for all losing bets + +oracle_fee = floor(losers_sum × oracle_fee_percent / 10000) +creator_fee = floor(losers_sum × creator_fee_percent / 10000) +liquidity_fee = floor(losers_sum × liquidity_fee_percent / 10000) +winners_pool = losers_sum − oracle_fee − creator_fee − liquidity_fee +``` + +Комиссии НЕ удерживаются со ставок при размещении. Полная сумма ставки входит в резервы CPMM/LMSR. + +### Фиксированная комиссия оракула + +Разовая комиссия на рынок. Задаётся оракулом в профиле. Платится создателем оракулу при акцепте рынка. +Полностью пропускается для self-oracle рынков (балансовой операции не происходит). + +### Поля учёта комиссий + +- `oracle_fee_earned` — не используется на резолюции; комиссия считается из losers_sum +- `liquidity_fee_earned` — накопленные комиссии LP, уже выплаченные рано вышедшим LP; на резолюции: `LP fee pool = max(0, floor(losers_sum × liquidity_fee_percent / 10000) − liquidity_fee_earned) + penalty_pool` +- Per-bet `oracle_fee` и `liquidity_fee` записываются для аудита; не аккумулируются на рынке + +### Округление + +Все вычисления используют `floor()`. Нераспределённый dust (< 1 mVIZ) отправляется в фонд DAO при +финальной выплате. + +--- + +## 8. Time penalty для поздних ставок + +### Окно штрафа + +| Тип | Вычисление окна | +|------|-------------------| +| Fixed (type=0) | `penalty_window = time_penalty_value` секунд до экспирации | +| Percentage (type=1) | `penalty_window = time_penalty_value / 100 × (betting_expiration − market_creation_time)` | + +### Вычисление штрафа + +``` +time_to_expiration = betting_expiration − current_time + +if time_to_expiration < penalty_window: + ratio = 1 − (time_to_expiration / penalty_window) + + if penalty_curve_type == 1: // quadratic + penalty_ratio = ratio × ratio + else: // linear + penalty_ratio = ratio + + time_penalty = floor(penalty_ratio × max_time_penalty) +else: + time_penalty = 0 +``` + +### Применение при выплате (только к прибыли) + +``` +profit = floor(winners_pool × weight / total_winning_weight) // parimutuel share of losers' pool +penalty_deduction = floor(profit × time_penalty / 1,000,000) +net_payout = bet_amount + profit − penalty_deduction +``` + +**Инвариант:** `net_payout ≥ bet_amount` — штраф применяется только к доле прибыли, поэтому победители +всегда получают не меньше принципала. (Идентично для Onix Binary и Onix Multi.) + +--- + +## 9. Предоставление ликвидности + +### Добавление ликвидности + +``` +add_a = amount × reserve_a / (reserve_a + reserve_b) +add_b = amount − add_a +new_reserve_a = reserve_a + add_a +new_reserve_b = reserve_b + add_b +new_k = new_reserve_a × new_reserve_b +``` + +Записывает `sec_to_expiration = betting_expiration − current_time` на момент депозита. + +### Time-weighted распределение комиссий (на резолюции) + +``` +fee_pool = remaining_liquidity_fee + total_penalty_pool + +weight_i = amount_i × max(1, sec_to_expiration_i) +total_weight = Σ weight_i +fee_share_i = floor(fee_pool × weight_i / total_weight) +lp_payout_i = principal_i + fee_share_i +``` + +Каждый депозит — независимая позиция. Несколько депозитов одного пользователя отслеживаются раздельно. + +### Ранний вывод + +**Предусловия:** market status=1, `time < betting_expiration`, `resulting liquidity_sum ≥ 100,000 mVIZ`. + +``` +// Fractional withdrawal +fraction = withdraw_amount / lp_amount +withdraw_weight_a = floor(weight_a × fraction) +withdraw_weight_b = floor(weight_b × fraction) + +// Reverse reserves +new_reserve_a = reserve_a − withdraw_weight_a +new_reserve_b = reserve_b − withdraw_weight_b +new_k = new_reserve_a × new_reserve_b + +// Time-ratio discount +time_served = current_time − lp_deposit_time +market_duration = betting_expiration − market_creation_time +time_ratio = min(1, time_served / market_duration) + +// Fee share (conservative: min of both sides) +fee_from_a = floor(a_bets_sum × liquidity_fee_percent / 10000) +fee_from_b = floor(b_bets_sum × liquidity_fee_percent / 10000) +estimated_pool = min(fee_from_a, fee_from_b) − already_paid_to_early_lps +lp_tw = withdraw_amount × max(1, sec_to_expiration) +total_tw = Σ (active LP time-weights) +raw_fee_share = floor(estimated_pool × lp_tw / total_tw) +fee_share = floor(raw_fee_share × time_ratio) + +returned = withdraw_amount + fee_share +``` + +**Post-expiration lock:** Вывод LP блокируется при `time ≥ betting_expiration`. Все позиции LP заблокированы +до резолюции. + +### Безопасность принципала при раннем выводе + +Вывод вычитает исходные `weight_a` и `weight_b` (не пропорциональную долю текущих резервов). Если +`reserve_a < weight_a` или `reserve_b < weight_b`, вывод **блокируется**. + +### Создатель как первый LP + +Создатель рынка автоматически первый LP. Его `sec_to_expiration` равен полной длительности рынка, давая +максимальный time-weight. + +--- + +## 10. Резолюция и выплаты + +### Приоритет выплат + +| Приоритет | Тип | Получатель | Сумма | +|----------|------|-----------|--------| +| 1 | Oracle fee (2) | Оракул | `floor(losers_sum × oracle_fee_percent / 10000)` | +| 1.5 | Creator fee (7) | Создатель | `floor(losers_sum × creator_fee_percent / 10000)` | +| 2 | Creator LP (1) | Создатель | `principal + time-weighted fee share` | +| 3 | LP return (1) | LP | `principal + time-weighted fee share` | +| 4 | Winner bets (0) | Победители | `bet_amount + floor(winners_pool × weight / total_winning_weight) − penalty_deduction` (parimutuel) | +| 5 | Dispute refund (5) | Участники спора | (если применимо) | +| 6 | Oracle penalty bonus (6) | Все участники | (если оракул оштрафован) | + +### Проигравшая сторона + +Выплата = 0. Стейки поглощены в резервный пул. + +### Рынки с нулевым объёмом + +LP получают полный принципал. Оракул получает фикс. комиссию (если есть). Все аккумуляторы комиссий +остаются 0. + +--- + +## 11. Отмена ставки + +### Предусловия + +| Условие | Проверка | +|-----------|-------| +| Ставка активна | `bet.status == 0` | +| Пользователь владеет ставкой | `bet.user == current_user.id` | +| Рынок активен | `market.status == 1` | +| Приём открыт | `current_time < market.betting_expiration` | +| Отмена разрешена | `market.allow_cancellation == 1` | + +### Механика reverse CPMM + +Для ставки на сторону A (side=0): + +``` +new_reserve_a = reserve_a + tokens +new_reserve_b = floor(k / new_reserve_a) +amount_returned = reserve_b − new_reserve_b +if amount_returned <= 0: amount_returned = 0 +``` + +Симметрично для стороны B. + +### Защита от слиппеджа + +Опциональный параметр `min_return`. Если `amount_returned < min_return`, транзакция отвергается. + +### Изменения состояния (атомарно) + +1. Статус ставки → 1 (отменена), записан `returned_amount` +2. Резервы рынка обновлены +3. Суммы ставок рынка уменьшены на исходную сумму ставки +4. Баланс пользователя увеличен на `amount_returned`, `bets_balance` уменьшен +5. Запись истории (type=4) +6. Запись market log с резервами до/после + +--- + +## 12. Система споров + +### Предусловия подачи + +- Подающий разместил ставку на рынке +- В течение `pm_dispute_grace_sec` после резолюции +- Маршрутизация по режиму: **комитет** (`dispute_mode = 0`) — аккаунт-резолвер не нужен, электорат SHARES голосует через `pm_dispute_vote` (публично, изменяемо до `voting_end_time`, вес = `effective_vesting_shares` + стейк Lazy-Pool→shares), подсчёт кроном `pm_dispute_finalize`; **аккаунт** (`dispute_mode = 1`) — именованный `dispute_resolver` рынка выносит `pm_dispute_resolve` +- Нет открытого спора на рынке +- Подающий платит `pm_dispute_fee` + +### Ответ оракула + +Обязателен в течение `pm_oracle_dispute_response_sec`. При пропуске `pm_dispute_fee` авто-слешится из +страховки и записывается на объект оракула. + +Оракул публикует свой контраргумент через **`pm_dispute_oracle_respond`** (op ID 98). Поскольку спор — это +открытое публичное слушание, текст хранится **на объекте спора** (`oracle_response` + `oracle_response_time`, +читается через `get_dispute`), чтобы каждый голосующий комитета или аккаунт-резолвер мог взвесить его перед +решением. Отвечать может только оракул рынка, только пока спор открыт и `now ≤ oracle_response_deadline`; +повторная публикация перезаписывает предыдущий ответ. + +### Жизненный цикл спора + +``` +Resolution (T=0) → Grace period (T to T+12h) → Dispute filed (T≤12h) + → Oracle response (12h window) → Resolver decision (up to 14 days) + → After verdict: recalculate or unfreeze → Auto-payout after new grace period + → Auto-close fallback (T+14 days): full refund + oracle penalty +``` + +### Спор удовлетворён (оракул неправ — переворот) + +Награда диспутёру — carve-out из слэша; **остаток слэша финансирует победивших бетторов** (через +`forfeit_pool`), не резолвера и не ДАО. **Ни голосующие комитета, ни аккаунт-резолвер не оплачиваются.** + +``` +reward_target = floor(dispute_fee × pm_dispute_reward_multiplier / 10000) // bp; 30000 = ×3 +bonus = max(0, reward_target − dispute_fee), ограничен слэшем + +1. Disputer ← dispute_fee (escrow назад) + bonus // bonus берётся из слешнутой страховки +2. forfeit_pool += (slash − bonus) // → победителям, через winners_pool на сеттлменте +``` + +Размер слэша: режим комитета масштабирует `dispute_penalty_percent` оракула на `consensus_strength`; +режим аккаунта берёт `penalty_amount` резолвера (оба ограничены остатком страховки). + +### Спор отклонён (оракул прав — подтверждён) + +``` +Disputer отдаёт всю dispute_fee → оракулу (100%, компенсация). // без 50/50 резолвер/ДАО +``` + +### Процесс пересчёта (оракул неправ) + +1. Валидировать штраф (ограничен оставшейся страховкой) +2. Выплачена награда диспутёру (fee + bonus); остаток слэша → `forfeit_pool` (победителям) +3. Слешинг страховки оракула +5. Применены баны (если запрошены) +6. Удалить все существующие невыплаченные payouts +7. Перевернуть выигрышный исход (A↔B) +8. Заново сгенерировать выплаты с исправленным исходом +9. Записан audit trail + +### Полномочия резолвера — баны это compliance/регуляторная функция (только режим аккаунта) + +Санкции ниже — это поля **аккаунт-режимной** `pm_dispute_resolve` (op ID 80), выносимой именованным +`dispute_resolver` рынка. Когда этот резолвер — **регулятор или лицензированный арбитр**, именно так он +принуждает к off-chain-правилам: одним вердиктом он может слешить страховку **и забанить как оракула, так и +создателя рынка** на платформе, временно или навсегда. **Режим комитета/DAO (`dispute_mode = 0`) по замыслу +не имеет полномочий бана** — публичное слушание только слешит страховку (масштабируемую силой консенсуса) и +корректирует репутацию через `pm_dispute_finalize`; оно никогда не банит. + +| Параметр | Тип | Описание | +|-----------|------|-------------| +| `penalty_amount` | mVIZ | Дополнительный слеш страховки (0 до остатка) → фонд DAO | +| `ban_oracle` | 0/1 | Забанить оракула | +| `ban_oracle_until` | unix ts / 0 | 0=навсегда, >0=истекает | +| `ban_creator` | 0/1 | Забанить создателя | +| `ban_creator_until` | unix ts / 0 | 0=навсегда, >0=истекает | + +Бан записывает выносящего `resolver` в поле `banned_by` цели. **Снятие бана:** тот же резолвер может снять +его **досрочно** через **`pm_unban`** (op ID 99, `unban_oracle` / `unban_creator`); иначе он просто истекает +на `banned_until`, после чего per-block крон очищает его и эмитит виртуальную операцию **`pm_ban_expired`** +(ID 100), чтобы история/индексаторы наблюдали снятие. + +### Авто-закрытие (14-дневный fallback) + +| Действие | Описание | +|--------|-------------| +| Истец | Dispute fee возвращена | +| Оракул | `dispute_fee` слешится из страховки | +| Ставки | Все возвращены (исходные суммы) | +| LP | Все возвращены (только принципал) | +| Распределение штрафа | Слешнутая сумма распределена пропорционально всем участникам | +| Статус спора | Установлен в 3 (авто-закрыт) | + +### Объявление No-Contest + +Оракул вызывает `oracle-no-contest` с `market_id` и `reason`. + +1. Все ставки → pending refund payouts (полная исходная сумма) +2. Все позиции LP → pending refund payouts (только принципал) +3. Штраф: `oracle_no_contest_penalty_percent`% от `dispute_fee` из страховки +4. Штраф распределён пропорционально участникам +5. Рынок: `resolved_outcome = -1`, `payout_status = 1` +6. Стартует grace-период (оспариваемый) + +### Резолюция с 3 исходами (спор no-contest) + +Резолвер выбирает одно из: +- `correct_outcome = 0` — A побеждает (пересчёт выплат) +- `correct_outcome = 1` — B побеждает (пересчёт выплат) +- `correct_outcome = -1` — Подтвердить no-contest (сохранить refund-выплаты) + +Если оракул неправ: pending refund payouts удаляются, заменяются корректными выплатами победителям. +Применяются стандартные штрафы спора. + +--- + +## 13. Штраф оракула за пропуск резолюции + +Если оракул не разрешает к `result_expiration`: + +``` +penalty_amount = floor(oracle_insurance × oracle_penalty_percent / 100) +``` + +### Распределение + +``` +stakes[user_id] += bet_amount (for each active bet) +stakes[user_id] += liquidity_amount (for each active LP position) +total_stakes = Σ stakes[user_id] + +bonus_i = floor(penalty_amount × stakes[user_id] / total_stakes) +``` + +Каждый участник получает: полный рефанд (принципал) + пропорциональный бонус. + +Рынок финализирован: status=3, payout_status=2. + +--- + +## 14. Скоринг репутации оракула + +### Сырые метрики (14 счётчиков на оракула) + +| Метрика | Тип | Источник | +|--------|------|--------| +| `markets_accepted` | counter | oracle-accept-market | +| `markets_resolved` | counter | resolve-market | +| `markets_no_contest` | counter | oracle-no-contest | +| `markets_missed` | counter | cron (missed deadline) | +| `disputes_received` | counter | create-dispute | +| `disputes_lost` | counter | resolve-dispute (status=1) | +| `disputes_won` | counter | resolve-dispute (status=2) | +| `disputes_auto_closed` | counter | cron (14-day auto-close) | +| `dispute_responses_missed` | counter | cron (12h response deadline) | +| `total_volume_resolved` | mVIZ | resolve-market (сумма bets_sum) | +| `total_insurance_slashed` | mVIZ | все события штрафов | +| `avg_resolution_time` | seconds | resolve-market | +| `bans_received` | counter | resolve-dispute | +| `active_since` | timestamp | register-oracle | +| `last_active_time` | timestamp | accept/resolve/no-contest | + +### Производные доли + +Знаменатель: `total_outcomes = markets_resolved + markets_no_contest + markets_missed` + +| Доля | Формула | +|------|---------| +| `resolution_rate` | `markets_resolved / total_outcomes` | +| `dispute_loss_rate` | `disputes_lost / disputes_received` | +| `no_contest_rate` | `markets_no_contest / total_outcomes` | +| `deadline_miss_rate` | `markets_missed / total_outcomes` | +| `dispute_response_rate` | `1 − (dispute_responses_missed / disputes_received)` | + +### Reliability Score (0–100) + +``` +reliability_score = clamp(0, 100, + BASE_SCORE + − W_DISPUTE_LOSS × dispute_loss_rate × 100 + − W_NO_CONTEST × excess_no_contest × 100 + − W_DEADLINE_MISS × deadline_miss_rate × 100 + − W_NO_RESPONSE × (1 − dispute_response_rate) × 100 + + W_VOLUME_BONUS × volume_tier + + W_EXPERIENCE × experience_tier × freshness_multiplier + − W_BAN_PENALTY × bans_received +) +``` + +Где `excess_no_contest = max(0, no_contest_rate − 0.10)`. + +### Веса по умолчанию + +| Вес | Значение | +|--------|-------| +| BASE_SCORE | 50 | +| W_DISPUTE_LOSS | 0.40 | +| W_NO_CONTEST | 0.10 | +| W_DEADLINE_MISS | 0.20 | +| W_NO_RESPONSE | 0.15 | +| W_VOLUME_BONUS | 0–25 (тиры: ≥10K→+5, ≥100K→+10, ≥500K→+15, ≥1M→+20, ≥5M→+25) | +| W_EXPERIENCE | 0–25 (тиры: ≥7d→+5, ≥30d→+10, ≥90d→+15, ≥180d→+20, ≥365d→+25) | +| W_BAN_PENALTY | 15 за бан | + +### Freshness Decay + +| Дней с последней активности | Множитель | +|----------------------|-----------| +| ≤ 30 | 1.00 | +| 31–90 | 0.75 | +| 91–180 | 0.50 | +| > 180 | 0.25 | + +### Composite Trust Score + +``` +trust_score = reliability_score × risk_factor +``` + +| Risk score (страховка/ставки) | risk_factor | +|---------------------------|-------------| +| ≥ 3.0× | 1.00 | +| ≥ 2.0× | 0.95 | +| ≥ 1.0× | 0.85 | +| < 1.0× | 0.70 | + +### Детекция нового оракула + +`total_outcomes < 5` → `is_new = true`. Отдельный бейдж в UI. + +Score считается на чтение через `compute_oracle_reliability_score()`, не хранится. + +--- + +## 15. Трансфер позиций + +### Операция + +``` +pm_transfer_position { bet_id, to_user, amount, memo } +``` + +- Перевести все или часть токенов ставки на другой аккаунт +- Переведённые токены сохраняют исходный рынок и исход +- Выплата идёт текущему держателю на резолюции +- Без слиппеджа, без рыночного влияния — чистая переуступка записи +- Работает и для позиций Onix Binary, и Onix Multi + +### Модель приватности memo + +| Режим | Формат | Видимость | +|------|--------|------------| +| Plaintext | Строка, не начинающаяся с `#` | Публично on-chain | +| Encrypted | Строка, начинающаяся с `#` | Приватно — расшифровать могут только отправитель и получатель | + +Шифрование: ECIES shared-secret `ECDH(sender_memo_private, recipient_memo_public)` с использованием +memo-ключей аккаунтов VIZ (стандартная модель Graphene). Шифрование/расшифровка на стороне клиента. + +--- + +## 16. Lazy-пул ликвидности + +### Параметры + +| Медиана-голосуемый параметр | Роль | +|---------|-------------| +| `pm_lazy_pool_enabled` | kill-switch пула | +| `pm_lazy_alloc_percent` | доля свободного баланса, аллоцируемая на рынок (bp) | +| `pm_lazy_max_total_alloc_percent` | кап доли пула на активных рынках (bp) | +| `pm_lazy_recall_step_percent` | шаг graduated-recall на простаивающих рынках (bp) | +| `pm_lazy_lock_sec` | lock-период депозита (секунды) | +| `pm_lazy_emergency_penalty_percent` | штраф на заблокированную прибыль при экстренном выводе (bp) | +| `pm_lazy_min_liquidity_fee_percent` | минимальный `liquidity_fee_percent` рынка (bp) для аллокации пула; ниже него рынок не получает ликвидности пула (порог вознаграждения) | +| `pm_min_liquidity` | мин. аллокация на рынок (он же пол сида рынка) | + +### Депозит + +- Первый депозитор: `shares = amount` +- Последующие: `new_shares = amount × total_shares / free_balance` +- Таймер блокировки: `unlock_time = now + pm_lazy_lock_sec` +- Расчёт наград перед вычислением долей: `pending += shares × (pool.rps − user.snapshot) / PRECISION` + +### Авто-аллокация + +На активации рынка (status → 1): + +``` +alloc_amount = free_balance × allocation_percent / 100 +× (1 − active_market_penalty_pct / 100) ^ oracle_active_market_count +× (1 − fault_penalty_pct / 100) ^ oracle_active_fault_stamps +``` + +Гейт порога вознаграждения (проверяется первым): если у рынка `liquidity_fee_percent < +pm_lazy_min_liquidity_fee_percent`, пул не аллоцирует **ничего** — он субсидирует только рынки, чья +LP-комиссия платит ему достаточно. Единственной ликвидностью остаётся собственный сид создателя. + +Проверки: `alloc_amount ≥ min_market_allocation`, `allocated + alloc_amount ≤ total × max_total_allocation / 100`. + +Pool LP вставляется с `user=0`. Участвует идентично в time-weighted распределении комиссий. + +### Распределение наград (ленивый учёт) + +На резолюции рынка с прибылью pool LP: + +``` +profit = lp_return − allocation_amount +if profit > 0 AND total_shares > 0: + pool.reward_per_share += profit × PRECISION / total_shares +``` + +Награда пользователя (считается на чтение): + +``` +live_reward = pending_rewards + shares × (pool.rps − user.snapshot) / PRECISION +``` + +### Плановый вывод + +Из консолидированной разблокированной записи (полный или частичный): + +``` +1. Run unlock consolidation +2. Settle rewards: pending += shares × (rps − snapshot) / PRECISION +3. Share value = shares_to_burn × free_balance / total_shares +4. Reward portion = pending_rewards × withdraw_percent / 100 +5. Total payout = share value + reward portion +``` + +### Экстренный вывод + +Все депозиты (locked + unlocked): + +``` +1. Settle rewards +2. total_value = shares × free_balance / total_shares + pending_rewards +3. profit = total_value − principal_deposited +4. if profit > 0: penalty = profit × (locked_shares / total_shares) × emergency_penalty / 100 +5. Penalty → pool reward_per_share +6. User receives: total_value − penalty +``` + +### Защита от opportunity-cost + +> **Governance vs хардкод.** Медиана-голосуется только **размер шага** отзыва — +> `pm_lazy_recall_step_percent`. Остальное **захардкожено** (нужен хардфорк): деление на **10 шагов** +> (`window/10`, `check_step ≥ 10`), критерий простоя (шаг простаивает, если *нет новых ставок* с +> прошлой проверки — `bets_sum ≤ bets_sum_at_check`) и **штраф 5% за активный рынок** (`alloc × 95/100`). +> Штраф fault-штампа и его окно истечения тоже захардкожены. + +**A. Graduated Recall:** Длительность рынка делится на **10 фиксированных шагов**. На каждом шаге, если с +прошлой проверки **не пришло новых ставок**, отозвать `pm_lazy_recall_step_percent` (bp, governance) +текущей аллокации в пул. + +**B. Active Market Penalty:** `factor = (1 − 5%) ^ active_market_count` — захардкоженное рекурсивное +снижение на 5% за каждый одновременный активный рынок того же оракула. + +**C. Fault Stamps:** При плохих исходах рынка (no-contest, пропуск дедлайна, нулевой объём, проигрыш +спора, отсутствие ответа, авто-закрытие) оракул получает fault-штамп, авто-истекающий после фиксированного +окна чистой работы; каждый активный штамп дополнительно снижает аллокацию. (Размер штрафа и окно — хардкод.) + +--- + +## 16a. Опциональное плечо (фондируется Lazy-пулом) + +Live с HF14; опционально, управляется медианным kill-switch `pm_leverage_enabled` (по умолчанию off). + +- **Open** (`pm_leverage_open`) — беттер вносит collateral; Lazy-пул **выдаёт займ** маржи из + `free_balance` (ограничено `leverage_fund_used`; проверяется против `pm_leverage_fund_percent`, + `…_max_per_position_bp`, `…_max_position_ratio_percent`, `…_min_market_liquidity` на момент открытия). + Без эмиссии токенов — позиция полностью обеспечена с точки зрения системы. Открытие с плечом **не** + создаёт `pm_bet`; вес кривой держится на `pm_leverage_position_object`. +- **Liquidation** — идёт по **pre-bet резервам**, поэтому пул возвращает `min(cancel_value, + obligation) ≥ loan`: opposing-bet каскад (`pm_place_bet`) и settlement force-close всегда + full-recovery (заём + проценты → пул); **единственный** ограниченный путь bad-debt — same-side + `pm_cancel_bet` (Case B). Каскад **не** гейтится `pm_leverage_enabled` (флаг блокирует только новые + открытия), поэтому выключение плеча никогда не снимает защиту с открытых позиций. +- **Виртуальные операции** — `pm_leverage_resolve` (force-close на расчёте, с исходом + плечом), + `pm_leverage_liquidate` (mid-market, `reason` 0 opposing / 1 cancel). +- **Governance-вес** — депозитчики Lazy-пула сохраняют вес голоса в PM-спорах и DAO-комитете (NAV пула → + vesting-shares через `get_vesting_share_price`, гейт HF14). + +API: `get_account_leverage_positions`, `get_market_leverage_positions`, `get_lazy_pool`. + +## 16b. Batch / Commit-Reveal ставки (анти-MEV) + +Live с HF14 для **бинарных** рынков (мульти форсит `allow_instant_bet`, пока не появится LMSR-батч); +опционально per-market (`allow_batch` / `allow_instant_bet`), медианный kill-switch +`pm_commit_reveal_enabled`. + +- `pm_place_bet` с `mode = 1` ставит **batch**-ставку в очередь; `pm_commit_bet` (commitment hash + + escrow) → `pm_reveal_bet` запускает **commit-reveal** флоу. Нераскрытые commitments форфейтят + `pm_commit_no_reveal_penalty_percent` (bp) через `pm_commit_forfeit`. +- На каждой границе эпохи (`pm_batch_epoch_blocks`, reveal-окно `pm_reveal_window_blocks`) ставки из + очереди сеттлятся по **единой цене** через крон `pm_batch_settle` — AMM двигает только нетто-остаток, + поэтому внутрибатчевый порядок не даёт преимущества, а инвариант `Σ reserve ≥ L` сохраняется. + +## 17. On-chain модель объектов + +Всё состояние живёт в **chainbase-объектах**, зарегистрированных как core-индексы на HF14 — никакой SQL-БД +нет. Определения полей живут в заголовках операций/объектов и доступны +на чтение через [плагин `prediction_market_api`](../plugins/prediction-market-api). Счётчики репутации, +которые прототип держал в `users` table, теперь поля на `pm_oracle_object`. + +| Объект (индекс) | Хранит | Ищется по | +|---|---|---| +| `pm_oracle_object` | регистрацию оракула, страховку, 14 счётчиков репутации, fault-штампы, бан (`banned_until` + `banned_by`) | owner | +| `pm_market_object` | конфиг рынка, CPMM-резервы (`reserve_a/b`, `k`), `*_fee_percent` (bp), `status` / `payout_status`, таймеры, `dispute_mode`, `a_bets_sum` / `b_bets_sum`, заявление оракула о разрешении (`decision_url` / `decision_reason`) | id / creator / oracle / result_expiration | +| `pm_outcome_object` | per-outcome LMSR `q`, `bets_sum`, `bets_count` (мульти-рынки) | market + outcome | +| `pm_bet_object` | ставку — account, `side` / `outcome_index`, `amount`, вес кривой `weight`, `time_penalty`, `status`, `mode` | market / account | +| `pm_liquidity_object` | позицию LP — принципал, время депозита, time-weight; `provider` пуст ⇒ lazy-pool LP | market | +| `pm_commit_object` | commitment-хэш + escrow (batch / commit-reveal) | market / account | +| `pm_dispute_object` | спор — disputer, `proposed_outcome`, escrow комиссии, таймеры, `status`, `dispute_mode`, контраргумент оракула (`oracle_response` / `oracle_response_time`) | market | +| `pm_dispute_vote_object` | один бюллетень комитета — voter, `vote_outcome`, `vote_percent` (изменяем до закрытия) | market + voter | +| `pm_lazy_pool_object` | синглтон-пул — `free_balance` / `allocated_balance` / `earned_balance`, `reward_per_share`, `leverage_fund_used`, `total_shares` | синглтон (id 0) | +| `pm_lazy_deposit_object` | депозитчика — shares, snapshot наград, unlock time | account | +| `pm_lazy_allocation_object` | тихую LP-аллокацию пула в один рынок + состояние graduated-recall (`bets_sum_at_check`, `check_step`, `recalled_amount`) | market | +| `pm_leverage_position_object` | открытую позицию с плечом — collateral, loan, obligation, вес кривой, `status` | account / market + status | +| `pm_creator_ban_object` | забаненного создателя — `banned_until`, `ban_count`, `banned_by` | ban account | + +Метрики репутации считаются на чтение (`compute_oracle_reliability_score()` — §14), не хранятся. Все +процентные поля — базисные пункты (`*_percent`, bp). Аллокации lazy-пула на рынок и состояние +graduated-recall живут на `pm_lazy_allocation_object`; fault-штампы и счётчики репутации оракула — на +`pm_oracle_object`. + +**Только в плагине (не консенсус):** `pm_market_meta_object` — off-chain распарсенные метаданные рынка +(категория / теги / запрещённые юрисдикции) для discovery и фильтрации по юрисдикции; строится плагином +[`prediction_market_api`](../plugins/prediction-market-api) из непрозрачной строки `metadata` рынка и +никогда не участвует в консенсусе. + +Полные определения полей этих объектов см. в +[Операциях прогнозных рынков](../protocol/operations/prediction-markets). diff --git a/@l10n/ru/docs/prediction-markets/whitepaper.md b/@l10n/ru/docs/prediction-markets/whitepaper.md new file mode 100644 index 0000000000..1f54c8e96d --- /dev/null +++ b/@l10n/ru/docs/prediction-markets/whitepaper.md @@ -0,0 +1,807 @@ +--- +title: Onix Protocol — Whitepaper +description: "Индустриальный whitepaper протокола Onix: прогнозные рынки с гарантированной ликвидностью на VIZ DLT." +--- + +# Onix Protocol: прогнозные рынки с гарантией LP на VIZ DLT + +**Индустриальный whitepaper** + +*Анатолий Пискунов (On1x)* +*Версия 2.0 — июнь 2026 (on-chain / HF14)* + +--- + +> **On-chain статус (HF14).** Этот документ изначально писался против централизованного прототипа. +> Теперь протокол работает как **операции консенсуса первого класса (`pm_*`) на VIZ DLT**, проверено в +> `consensus_sim`. Live с HF14: оба типа рынков (CPMM binary + LMSR multi), parimutuel zero-sum +> расчёт, **Lazy Pool**, опциональный сабсистем **плеча**, опциональные **batch / commit-reveal +> ставки** (бинарные), bonded-оракулы и двухрежимная система споров (комитет / аккаунт). Все +> процентные параметры — в **базисных пунктах (bp): 10000 = 100.00%** (прототип использовал permille). +> Разделы ниже снабжены пометками там, где живой дизайн отличается от исходного текста прототипа. + +## Аннотация + +Прогнозные рынки агрегируют рассеянную информацию в цены, давая оценки вероятностей, которые стабильно +превосходят опросы, экспертные панели и статистические модели. Но внедрение упирается в одну структурную +проблему: **поставщики ликвидности теряют деньги.** + +LP в Uniswap v3 страдают от impermanent loss. Маркет-мейкеры LMSR рискуют всей своей субсидией. +Маркет-мейкеры CLOB сталкиваются с adverse selection. Каждая существующая модель просит поставщиков +капитала принять downside-риск в обмен на неопределённую доходность — и данные показывают, что +большинство из них теряет. + +**Onix Protocol** устраняет риск LP полностью. Это архитектура прогнозного рынка, где принципал LP +**структурно гарантирован** — не страховкой, не хеджированием, а самой механикой выплат. Победителям +платят исключительно из проигранных ставок. Капитал LP даёт глубину рынка, но никогда не используется для +расчёта по ставкам. + +Документ описывает два типа рынков Onix — **Onix Binary** (Constant Product Market Maker) и **Onix Multi** +(LMSR-ценообразование с parimutuel-расчётом) — а также архитектуру рынка, систему оракулов и разрешения +споров, lazy-пул ликвидности, опциональное плечо, модель управления и реализацию на VIZ DLT как операций +уровня консенсуса. + +--- + +## 1. Проблема: риск LP в прогнозных рынках + +Любому прогнозному рынку нужна ликвидность. Без неё цены бессмысленны — ставка, двигающая рынок на 20%, +раскрывает капитал беттера, а не мудрость толпы. Фундаментальный вопрос: **кто даёт эту ликвидность и чем +рискует?** + +### 1.1 Текущий ландшафт + +| Платформа | Модель LP | Риск LP | Источник дохода | +|----------|----------|---------|-------------| +| **Uniswap v3** | Концентрированный AMM | Impermanent loss (часто >5% годовых; >50% LP v3 проигрывают buy-and-hold) | Торговые комиссии | +| **Aave / Compound** | Lending-пул | Риск смарт-контракта, каскады ликвидаций | Проценты заёмщика | +| **Curve** | Stableswap AMM | Низкий IL для привязанных активов, риск смарт-контракта | Комиссии + эмиссия CRV | +| **Standard LMSR** | Субсидия маркет-мейкера | Убыток до `b × ln(N)` — вся субсидия | Bid-ask спред | +| **Polymarket (CLOB)** | Активный маркет-мейкинг | Инвентарный риск, adverse selection | Bid-ask спред | +| **Kalshi** | Концепции LP нет | N/A (биржевая модель) | N/A | + +Закономерность очевидна: предоставление ликвидности прогнозным рынкам требует либо навыка активного +управления (CLOB), либо терпимости к потере капитала (LMSR), либо принятия impermanent loss (AMM). Ни +одно из этого не подходит ритейл-участникам. + +### 1.2 Почему это важно + +Прогнозные рынки работают лучше всего, когда они глубокие и ликвидные. Глубокие рынки дают точные цены, +привлекают информированных трейдеров и генерируют информационную ценность, делающую прогнозные рынки +полезными как общественное благо. Но глубина требует капитала, а капитал требует компенсации за риск. + +Результат — проблема курицы и яйца: +- Тонкие рынки → высокий слиппедж → плохой UX → мало бетторов → низкие комиссии → нет стимула для LP → тонкие рынки + +Чтобы разорвать этот цикл, нужно убрать риск со стороны LP. Если предоставление ликвидности безрисковое, +барьер входа падает до нуля, и маховик может раскрутиться. + +--- + +## 2. Onix Protocol + +### 2.1 Принципы дизайна + +Onix Protocol построен на трёх архитектурных инвариантах: + +1. **Принципал LP структурно безопасен.** Это не стратегия снижения риска — это свойство архитектуры + выплат. Капитал LP и расчёт по ставкам берутся из физически раздельных пулов. + +2. **Проигравшие финансируют победителей.** Все выплаты (прибыль победителей, комиссии оракула, комиссии + создателя, комиссии LP) берутся исключительно из проигранных ставок. Комиссии считаются на резолюции + как `floor(losers_sum × fee_bp / 10000)` (bp: 10000 = 100.00%), никогда не удерживаются при ставке. + +3. **Два типа рынков, одна гарантия.** Бинарные рынки (Onix Binary) и мульти-исходные рынки (Onix Multi) + используют разные формулы ценообразования, но разделяют одну модель расчёта и одну гарантию LP. + +### 2.2 Onix Binary (CPMM + parimutuel-расчёт) + +Onix Binary использует формулу Constant Product Market Maker — тот же инвариант `x * y = k`, что и +Uniswap — как **движок ценообразования** для бинарных исходов, с **parimutuel-расчётом** (проигравшие +финансируют победителей пропорционально весу), той же моделью расчёта, что и в Onix Multi. + +**Механика:** + +Рынок поддерживает два резерва, `reserve_a` и `reserve_b`, с постоянным произведением `k`: + +``` +k = reserve_a × reserve_b +``` + +Когда пользователь ставит `amount` на исход A (в реализации side 0 → `reserve_a`), стейк входит в резерв +этой стороны, а токены берутся из **противоположного** резерва: + +``` +new_reserve_a = reserve_a + amount +new_reserve_b = floor(k / new_reserve_a) +tokens_received = reserve_b − new_reserve_b +``` + +`tokens_received` (называемые `weight`) — это **относительная претензия** пользователя на пул победителей, +если выигрывает исход A (расчёт parimutuel — см. ниже, идентично Onix Multi). Подразумеваемая вероятность +растёт для той стороны, на которую ставят (больше денег на A → `reserve_a` растёт → `P(A)` растёт): + +``` +P(A) = reserve_a / (reserve_a + reserve_b) +P(B) = reserve_b / (reserve_a + reserve_b) +``` + +**Расчёт и доказательство безопасности LP (parimutuel):** + +На резолюции победители получают назад свой стейк плюс пропорциональную долю пула проигравших, по весу +(идентично Onix Multi): + +``` +winners_pool = losers_sum − fees +payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit +``` + +Принципал LP `L` возвращается безусловно, и гарантия точна: + +``` +Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN +``` + +Суммарная выплата ограничена `losers_sum` независимо от весов, поэтому капитал LP никогда не используется +для расчёта по ставкам. CPMM — это **движок ценообразования** (вероятность + вес); он не гейтит выплату. +(Соотношение AM-GM `reserve_a + reserve_b ≥ 2√k = L` по-прежнему держится для ценовой кривой, но на него +больше не опираются для платёжеспособности.) + +**Разобранный пример:** + +``` +Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000 +Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%) + +Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%) +Bob bets 80 VIZ on B → receives weight 81.82 + +Resolution: A wins + Losers (Bob): 80 VIZ forfeited → losers_sum = 80 + oracle_fee = floor(80 × 50/10000) = 0.4 VIZ + creator_fee = floor(80 × 50/10000) = 0.4 VIZ + liq_fee = floor(80 × 100/10000) = 0.8 VIZ + winners_pool = 80 − 1.6 = 78.4 VIZ + + Alice (only winner, weight 33.33 of 33.33): + payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit) + LP return: 200 VIZ principal + share of 0.8 VIZ fee pool +``` + +### 2.3 Onix Multi (LMSR + parimutuel-расчёт) + +Onix Multi — инновация протокола для рынков с 3–10 исходами. Он сочетает Logarithmic Market Scoring Rule +Хэнсона (LMSR, 2003) для ценообразования в реальном времени с parimutuel-расчётом ради безопасности LP. + +**Ценообразование (LMSR softmax):** + +Для рынка с исходами {1, 2, ..., N}, каждый со своим параметром количества `q_i`: + +``` +price(i) = exp(q_i / b) / Σ_j exp(q_j / b) +``` + +Это функция softmax — цены всегда суммируются ровно в 1.0 by construction. Никакого механизма арбитража +или операции split/merge не нужно. + +Стоимость покупки Δ токенов на исход i: + +``` +C(q) = b × ln(Σ_j exp(q_j / b)) + +cost = C(q + Δ·e_i) − C(q) +``` + +Параметр `b` управляет чувствительностью цены (выше b = меньше price impact на ставку). Он фондируется +субсидией LP: `b = S / ln(N)`, где S — суммарная субсидия. + +**Инновация — parimutuel-расчёт:** + +В **стандартном LMSR** маркет-мейкер — контрагент всех ставок. Если толпа верно предсказывает исход, +маркет-мейкер теряет до `b × ln(N)` — потенциально всю субсидию. Именно поэтому LMSR мало внедряли вне +корпоративных прогнозных рынков (Microsoft, Inkling), где оператор поглощает убыток. + +**Onix Multi меняет источник выплат.** На резолюции: + +``` +1. Oracle declares the winning outcome +2. Losers forfeit 100% → losers_sum +3. Fees deducted from losers_sum (bp; 10000 = 100.00%): + oracle_fee = floor(losers_sum × oracle_fee_bp / 10000) + creator_fee = floor(losers_sum × creator_fee_bp / 10000) + liq_fee = floor(losers_sum × liquidity_fee_bp / 10000) + winners_pool = losers_sum − fees +4. Winners receive: + payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty +5. LP subsidy returned unconditionally +``` + +Победителям платят проигравшие, а не LP. Субсидия архитектурно отделена от потока расчёта. + +**Доказательство гарантии принципала LP:** + +1. LP вносит `S` VIZ как субсидию, которая фондирует глубину рынка. +2. Во время ставок пользователи платят VIZ → получают токены исходов. VIZ накапливается как пул ставок. +3. На резолюции проигранные ставки фондируют выплаты победителям и комиссии. Субсидия `S` никогда не была + в платёжном пуле. +4. Субсидия возвращается LP безусловно, независимо от исхода. + +**Сравнение:** + +| Измерение | Standard LMSR | Onix Multi | +|-----------|---------------|------------| +| Роль LP | Контрагент всех ставок | Депозит глубины (не контрагент) | +| Макс. убыток LP | `b × ln(N)` (вся субсидия) | **Ноль** | +| Выплата победителю | 1 токен = 1 единица валюты | Токен = пропорциональная претензия на пул проигравших | +| Нужен ли CTF split/merge? | Да (обеспечить сумму цен = 1) | Нет (softmax гарантирует это) | + +**Разобранный пример (выборы с 3 исходами):** + +``` +Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ +Initial: price(A) = price(B) = price(C) = 33.3% + +Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%) +Bob bets 100 VIZ on B → ~88 tokens +Carol bets 30 VIZ on C → ~29 tokens + +Resolution: A wins + Losers: Bob (100) + Carol (30) = 130 VIZ + Fees (200 bp = 2% total): 2.6 VIZ + winners_pool = 127.4 VIZ + + Alice: 50 + (47/47 × 127.4) = 177.4 VIZ + LP: 1000 VIZ returned in full + share of liquidity fees +``` + +### 2.4 Граничные случаи + +| Сценарий | Исход | +|----------|---------| +| Все ставки на победителя | `losers_sum = 0` → каждый беттер получает назад ровно свою ставку. Субсидия LP возвращена. Zero-sum. | +| Нет ставок на победителя | Весь пул проигравших нераспределён → бонус LP. LP в максимальном плюсе. | +| Рынок с нулевым объёмом | Субсидия LP возвращена полностью. Ни комиссий, ни выплат. | +| Выигрывает единственный беттер | Этот беттер получает `bet_amount + winners_pool`. Субсидия LP возвращена. | + +--- + +## 3. Архитектура рынка + +### 3.1 Жизненный цикл рынка + +```mermaid +stateDiagram-v2 + direction LR + state "Waiting (0)" as Waiting + state "Active (1)" as Active + state "Closed (2)" as Closed + state "Resolved (3)" as Resolved + state "Deleted (-1)" as Deleted + state "Paid out" as Paid + [*] --> Waiting + Waiting --> Active: оракул принял + Waiting --> Deleted: оракул отклонил + Active --> Closed: ставки закрылись + Active --> Resolved: раннее разрешение (если разрешено) + Closed --> Resolved: оракул разрешает + Resolved --> Paid: окно ожидания (12ч) + Deleted --> [*] + Paid --> [*] +``` + +Рынки создаёт создатель рынка, проверяет и принимает оракул (ставящий страховку), они открыты для ставок, +разрешаются исходом и выплачиваются после grace-периода для споров. + +### 3.2 Модель комиссий (извлечение только из проигравших) + +Отличительная черта Onix Protocol — **комиссии не удерживаются при ставке**. Полная сумма ставки входит в +резервы рынка. Комиссии считаются только на резолюции, исключительно из проигранных ставок: + +```mermaid +flowchart TD + LS["losers_sum (100% проигранных ставок)"] + LS --> OF["oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)"] + LS --> CF["creator_fee = floor(losers_sum × creator_fee_bp / 10000)"] + LS --> LF["liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)"] + LS --> WP["winners_pool = losers_sum − все комиссии"] +``` + +Это даёт структурную гарантию: комиссии и выплаты победителям берутся из совершенно раздельных источников. +Извлечение комиссий никогда не конкурирует с обязательствами перед победителями. + +**Условия комиссии оракула фиксируются при акцепте (offer→quote).** Создатель публикует *максимум*, +который оракул может взять (потолок `oracle_fee_percent` в bp + потолок `oracle_fixed_fee`); при акцепте +оракул котирует свои фактические условия (≤ потолка создателя и ≤ медианного governance-капа +`pm_max_oracle_fee_percent`), которые замораживаются на рынке, и эмитится виртуальная операция +`pm_market_accepted`. Self-оракул фиксирует условия при создании. **Фиксированная комиссия оракула** +(на рынок) выплачивается из остатка пула проигравших (никогда не печатается). **Комиссия за создание +рынка** идёт в фонд DAO как защита от спама. + +### 3.3 Time-weighted распределение LP + +Доли комиссий LP распределяются пропорционально `amount × max(1, seconds_to_expiration)`: + +``` +weight_i = amount_i × max(1, sec_to_expiration_i) +fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j) +``` + +Ранние LP зарабатывают драматически больше на единицу капитала, чем поздние. В 48-часовом рынке LP, +внёсший на 1-м часу, зарабатывает ~2400× больше на VIZ, чем внёсший на 47-м. + +Каждый депозит отслеживается как независимая позиция — несколько депозитов одного пользователя взвешиваются +и оплачиваются раздельно. Принципал LP всегда возвращается полностью, независимо от исхода рынка. + +### 3.4 Time penalty для поздних ставок + +Чтобы дестимулировать ставки в последнюю минуту (несущие меньше риска неопределённости), к ставкам близ +экспирации применяется настраиваемый time penalty: + +``` +if time_to_expiration < penalty_window: + ratio = 1 − (time_to_expiration / penalty_window) + penalty_ratio = ratio² // quadratic (default) + time_penalty = floor(penalty_ratio × max_penalty) +``` + +Штраф применяется **только к прибыли**, никогда к принципалу. Победивший беттер всегда получает не меньше +своей исходной ставки. Квадратичная кривая мягкая в начале окна штрафа и крутая в конце, награждая +«немного поздно» над «очень поздно». + +### 3.5 Трансфер позиций + +Позиции переводимы между аккаунтами через нативную операцию протокола: + +``` +pm_transfer_position { bet_id, to_user, amount, memo } +``` + +Без слиппеджа, без рыночного влияния — чистая переуступка записи. Поле `memo` поддерживает и открытый, и +зашифрованный режим (ECIES через memo-ключи аккаунтов VIZ), позволяя P2P-сделки, OTC-торговлю и приватные +аннотации. + +Это единственная фича композируемости из Conditional Tokens Framework (Polymarket/Gnosis), дающая реальную +пользу. CTF split/merge архитектурно не нужен — формулы ценообразования Onix гарантируют когерентность цен +by construction. + +--- + +## 4. Оракул и разрешение споров + +### 4.1 Модель bonded-оракула + +Оракулы в Onix Protocol не доверены по умолчанию — они **bonded**. Каждый оракул обязан: + +- Зарегистрироваться с разовой комиссией (по умолчанию 10 VIZ) +- Внести страховку (минимум 5000 VIZ) +- Явно принимать рынки (ставя свою страховку на каждый акцепт) +- Разрешать рынки исходом и подтверждающими доказательствами (decision URL) + +Страховой бонд создаёт подотчётность: оракулы, которые неверно разрешают, пропускают дедлайны или +проигрывают споры, теряют часть страховки (slashing). Бонд должен превышать потенциальную прибыль +манипуляции оракула, чтобы модель экономической безопасности держалась. + +Доход оракула из двух источников: +1. **Фиксированная комиссия** (на рынок) — компенсирует ставку страховки и предоставление резолюции +2. **Процентная комиссия** (из пула проигравших на резолюции) — масштабируется с объёмом рынка + +### 4.2 Арбитраж споров + +Любой беттер может оспорить резолюцию в течение grace-периода, заплатив dispute fee. Во время споров все +выплаты заморожены. + +Разрешение идёт в одном из **двух per-market режимов**, выбираемом при создании: + +- **Режим комитета (`dispute_mode = 0`, по умолчанию)** — *весь электорат SHARES* решает + **stake-weighted голосованием** (`pm_dispute_vote`), детерминированно подсчитываемым кроном + `pm_dispute_finalize` на `voting_end_time`. Это **открытые публичные слушания**: живой тали + запрашиваем, и голоса **не** скрыты за commit-reveal (намеренный, постоянный выбор — DAO разрешает + споры максимально прозрачно). Поскольку по ходу слушаний всплывают новые доводы, **бюллетень изменяем** + до закрытия (повторный голос перезаписывает прежний). Вес голосующего — его `effective_vesting_shares` + **плюс стейк Lazy-Pool, конвертированный в vesting-shares**, поэтому участники DAO, паркующие VIZ в + пуле, сохраняют свой вес в управлении. +- **Режим аккаунта (`dispute_mode = 1`)** — единственный именованный `dispute_resolver` + (рекомендуется multisig) выносит вердикт (`pm_dispute_resolve`). + +Логика вердикта в любом режиме: + +**Если оракул был неправ (переворот):** +- Применяется верный исход, выплаты пересчитываются. +- Диспутёр получает назад свою fee **плюс carve-out награды** из слешнутой страховки, размером + `dispute_fee × pm_dispute_reward_multiplier` (bp; напр. 30000 = ×3), ограниченный слэшем. +- **Остаток слэша добавляется в пул победителей** (через `forfeit_pool`) — он идёт выигравшим бетторам, + **не** резолверу и **не** в ДАО. Ни голосующие комитета, ни аккаунт-резолвер награды **не получают** + (голосование комитета — неоплачиваемая governance-обязанность). +- Страховка оракула слешится (масштаб — по силе консенсуса в режиме комитета или по `penalty_amount` + резолвера в режиме аккаунта), с опциональным баном. + +**Если оракул был прав (подтверждён):** +- Диспутёр **отдаёт всю dispute fee оракулу** (компенсация за недобросовестное оспаривание). +- Исходные выплаты идут без изменений. + +**Предотвращение denial-of-resolution:** Если резолвер не действует в течение 14 дней, споры +авто-закрываются: все ставки и LP возвращаются, оракул штрафуется, fee диспутёра возвращается. Это +гарантирует, что средства никогда не заморожены бессрочно. + +### 4.3 Скоринг репутации оракула + +Протокол отслеживает 14 on-chain метрик на оракула и считает reliability score (0–100): + +``` +reliability_score = clamp(0, 100, + 50 (base) + − 0.40 × dispute_loss_rate × 100 + − 0.10 × excess_no_contest × 100 + − 0.20 × deadline_miss_rate × 100 + − 0.15 × (1 − dispute_response_rate) × 100 + + volume_bonus (0–25) + + experience_bonus × freshness_multiplier (0–25) + − 15 × bans_received +) +``` + +Ключевые дизайн-решения: +- **Доли, а не счётчики** — 1 проигранный спор из 100 (1%) лучше, чем 1 из 2 (50%) +- **Нейтральный старт на 50** — новые оракулы должны заработать репутацию, а не стартовать со 100 +- **Freshness decay** — неактивные оракулы со временем теряют experience-бонус +- **Тиры объёма** — высокообъёмные оракулы получают бонусные очки за доказанный track record + +Reliability score сочетается с risk factor (отношение страховки к ставкам), давая **composite trust +score** — основную метрику, показываемую пользователям. + +### 4.4 No-contest и резолюция с 3 исходами + +Оракул, который не может верифицировать исход, может добровольно объявить **no-contest**, запуская +рефанды с пониженным штрафом (50% dispute fee из страховки — гораздо дешевле, чем проиграть спор). Это +создаёт градиент стимулов: + +| Сценарий | Стоимость для оракула | Риск бана | +|----------|------------|----------| +| Добровольный no-contest | 500 VIZ | Нет | +| Проигрыш спора | 1000+ VIZ + доп. штраф | Постоянный или временный | +| Пропущенный дедлайн | 250 VIZ (авто-штраф) | Нет (но урон репутации) | + +Если пользователи считают, что оракул злоупотребил no-contest, они могут это оспорить. Тогда резолвер +выбирает из **трёх** возможных верных исходов: побеждает A, побеждает B или подтвердить no-contest. Это +не даёт оракулам использовать no-contest, чтобы избежать выплат победившим бетторам. + +--- + +## 5. Lazy-пул ликвидности + +### 5.1 Проблема развёртывания капитала + +Индивидуальное предоставление LP требует активного выбора рынков. Большинство пользователей не станут +вручную оценивать конкретные рынки и вкладываться в них. Итог: большинство рынков стартует только с +начальной ликвидностью создателя, давая тонкие книги и высокий слиппедж. + +### 5.2 Авто-аллокация пул→рынок + +Lazy-пул ликвидности решает это, принимая депозиты и **автоматически аллоцируя** процент свободного +баланса пула в каждый новый рынок при его активации: + +``` +alloc_amount = free_balance × allocation_percent / 100 +``` + +Аллокации считаются от текущего свободного баланса (не от исходной суммы), создавая геометрический спад — +пул никогда нельзя полностью истощить: + +``` +After 50 markets (2% allocation each): ~357 VIZ free from original 1,000 +After 100 markets: ~133 VIZ still free +``` + +Максимальный кап суммарной аллокации (по умолчанию 70%) даёт дополнительную безопасность. + +### 5.3 Распределение наград (один общий аккумулятор) + +Проблема: когда рынок разрешается с прибылью пула, эту прибыль надо разделить между **всеми** текущими +вкладчиками пропорционально их долям — но проходить по каждому вкладчику на каждом рынке было бы O(N) и +неограниченно. Пул избегает этого **одним глобальным бегущим итогом** `reward_per_share` («rps»): + +``` +// When a market resolves with pool LP profit, the per-share value of the pool rises once: +pool.reward_per_share += profit × PRECISION / total_shares + +// A depositor's earnings = their shares × how much rps has risen since they last touched the pool: +live_reward = pending + shares × (pool.reward_per_share − user.snapshot) / PRECISION +``` + +Простыми словами: каждый вкладчик «владеет» долей каждого роста `reward_per_share`, и его награда = +`доли × (текущий rps − rps на момент его последнего депозита/вывода)`. Запись самого вкладчика трогается +**только когда он действует** (депозит/вывод); до тех пор его право копится тихо в глобальном числе. Поэтому +раздача прибыли тысячам вкладчиков — **O(1)** (одно сложение), и ничего не выплачивается до клейма. Это +известный паттерн-аккумулятор из +[контракта MasterChef SushiSwap](https://github.com/sushiswap/masterchef/blob/master/contracts/MasterChef.sol) +(и индекса cToken у Compound); `PRECISION` (1e9) держит целочисленное деление точным. + +### 5.4 Защита от opportunity-cost + +Пул авто-аллоцирует в каждый рынок, создавая вектор атаки: вредоносный оракул мог бы создавать длинные +рынки с нулевым объёмом, чтобы запереть капитал пула. Это адресуют три механизма: + +**Graduated Early Recall:** Длительность рынка делится на 10 шагов. На каждом шаге, если объём ставок ниже +порога (1% аллокации), 10% текущей аллокации отзывается в пул. Полностью простаивающий 30-дневный рынок +теряет ~60% своей аллокации. + +**Active Market Penalty:** Каждый дополнительный активный рынок того же оракула снижает его аллокацию на +5% (рекурсивно). Оракул с 10 активными рынками получает ~60% базовой аллокации на рынок, стимулируя +качество над количеством. + +**Fault Penalty Stamps:** Плохие исходы (пропущенные дедлайны, проигранные споры, резолюции с нулевым +объёмом) генерируют штрафные штампы, дополнительно снижающие будущие аллокации. Штампы авто-истекают после +10 дней чистой работы. + +### 5.5 Опциональное плечо (фондируется Lazy-пулом) + +Lazy-пул играет **две роли из одного `free_balance`**: тихие market-LP аллокации *и* фондирование +сабсистемы **опционального плеча**. Беттер может открыть позицию с плечом (`pm_leverage_open`), где маржа +— это **займ из пула**: без эмиссии токенов, позиция остаётся полностью обеспеченной с точки зрения +системы. Бинарный «jump risk», ломающий liquidation-движки CLOB, решается **ликвидацией по pre-bet +резервам**: opposing-bet или settlement force-close возвращает `min(cancel_value, obligation) ≥ loan`, +поэтому пул получает назад заём плюс проценты; единственный ограниченный путь bad-debt — same-side +`pm_cancel_bet`. Медианный kill-switch (`pm_leverage_enabled`, по умолчанию off) блокирует *новые* +открытия, но защитный каскад ликвидации намеренно **не** гейтится им — выключение плеча никогда не снимает +защиту с открытых позиций. Пул зарабатывает проценты по плечу вдобавок к LP-доходности, учёт тем же +способом MasterChef. Расчёт по займам пула эмитит `pm_leverage_resolve` / `pm_leverage_liquidate`. + +--- + +## 6. Управление + +### 6.1 Делегат-голосуемые параметры цепи + +VIZ использует консенсус Delegated Proof of Stake (DPoS), где избранные делегаты (валидаторы) управляют +параметрами цепи через механизм медианного голоса: + +1. Каждый делегат публикует предпочитаемые значения всех параметров +2. Сеть вычисляет **медиану** голосов всех активных делегатов +3. Параметры меняются автоматически при сдвиге медианы — без хардфорка, без деплоя + +Все параметры прогнозных рынков (комиссии, штрафы, требования к страховке, окна споров, настройки +lazy-пула, ручки плеча, тайминг batch/commit-reveal) делегат-голосуемы. Все процентные параметры — в +**базисных пунктах (bp), 10000 = 100.00%**: + +| Примеры | Управление | +|----------|-----------| +| `pm_dispute_fee`, `pm_max_oracle_fee_percent` (bp) | Медианный голос делегатов | +| `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec` | Медианный голос делегатов | +| `pm_dispute_approve_min_percent`, `pm_dispute_reward_multiplier` (bp) | Медианный голос делегатов | +| `pm_lazy_*` аллокация/recall, `pm_leverage_*` (enabled, fund %, max position) | Медианный голос делегатов | +| `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks` | Медианный голос делегатов | + +Хардфорки нужны только для структурных изменений (новые типы операций, изменения формул), не для +экономической настройки. Kill-switches (`pm_leverage_enabled`, `pm_commit_reveal_enabled`) позволяют +управлению отключить целую подсистему медианным голосом без форка. + +### 6.2 Модель юрисдикционного клиента + +VIZ DLT — инфраструктура, не оператор — аналогично тому, как Bitcoin это леджер, а не money transmitter. +Протокол нейтрален и permissionless. Правовые обязательства привязаны к **клиентским приложениям**, не к +алгоритму консенсуса. + +Любая юрисдикция может построить compliant-клиент на VIZ DLT: + +| Компонент клиента | Реализация | +|-----------------|---------------| +| Предодобренные оракулы | Whitelist клиента из лицензированных, KYC-верифицированных оракулов | +| Предодобренные резолверы | Госорганы разрешения споров | +| KYC/AML | Идентификация на уровне клиента | +| Маршрутизация комиссий как налоговый доход | `dao_fund_account_id` → счёт госказны | +| Ограничения рынков | Клиент фильтрует по разрешённым категориям | +| Лимиты ставок | Лимиты на пользователя, навязанные клиентом | + +Те же операции протокола (`pm_place_bet`, `pm_resolve`, `pm_dispute`) работают одинаково для +permissionless и регулируемых клиентов. Разница целиком на слое клиента. + +--- + +## 7. Конкурентный ландшафт + +### 7.1 Сравнение платформ + +| Измерение | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR | +|-----------|-------------------|------------|--------|---------------| +| **Ценообразование** | CPMM (binary) / LMSR softmax (multi) | CLOB | CLOB | LMSR | +| **Риск LP** | **Ноль** (структурная гарантия) | Инвентарный риск | N/A | До `b × ln(N)` | +| **Знания LP** | Низкие (внёс и зарабатывай) | Высокие (управляй ордерами) | N/A | Средние | +| **Модель комиссий** | % пула проигравших на резолюции | Bid-ask спред | Биржевые комиссии (1-7%) | Спред | +| **Оракул** | Per-market bonded + диспут комитета | UMA Optimistic Oracle | Kalshi (регулятор CFTC) | Оператор | +| **Штраф за поздние ставки** | Квадратичный, настраиваемый | Нет | Нет | Нет | +| **Трансфер позиций** | Нативная операция + зашифрованное memo | CTF (ERC-1155) | Нет | Нет | +| **Управление** | Делегат-голосуемые параметры | Multisig команды | Процесс CFTC | Оператор | +| **Инфраструктура** | VIZ DLT (уровень консенсуса) | Polygon (смарт-контракты) | Проприетарные серверы | Разное | + +### 7.2 Почему CTF split/merge не нужен + +Polymarket использует Gnosis Conditional Tokens Framework (CTF), где позиции — это ERC-1155 токены, +которые можно split и merge для обеспечения когерентности цен (сумма цен = $1). + +В Onix Protocol этот механизм архитектурно не нужен: + +- **Onix Binary (CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` — по определению +- **Onix Multi (LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` — по определению softmax + +Механизм арбитража не нужен. Когерентность цен — математическое свойство формул, а не внешний слой +энфорсмента. + +### 7.3 Маховик + +``` +Risk-free LP → lower barrier for retail LPs + → more liquidity deposited + → deeper markets, less slippage + → better UX for bettors + → more volume + → more fees for LPs + → attracts even more LPs +``` + +«Пассивная доходность без impermanent loss» — ценностное предложение, которого Uniswap, Balancer и Curve +дать не могут. Для crypto-native аудитории это убедительный нарратив: зарабатывай доход, предоставляя +ликвидность прогнозным рынкам, с нулевым риском для принципала. + +--- + +## 8. VIZ DLT: от прототипа к протоколу + +### 8.1 Текущее состояние + +Протокол начинался как Telegram WebApp с централизованным бэкендом (вся логика рынка на сервере) — +рабочий прототип с известными ограничениями: нет sybil-устойчивости сверх Telegram-аккаунтов, нет +цензуроустойчивости, нет композируемости. **Эта миграция теперь выполнена:** полная логика рынка работает +**на VIZ DLT как consensus-validated операции `pm_*`** (HF14), отработана end-to-end в `consensus_sim`. +Остаток раздела описывает эту on-chain архитектуру, теперь реализованную. + +### 8.2 Архитектура миграции + +VIZ DLT — Distributed Ledger Technology с ~3-секундными блоками, DPoS-консенсусом, именованными аккаунтами +(в стиле Graphene) и без general-purpose смарт-контрактов. Операции прогнозного рынка реализованы как +**операции консенсуса первого класса** — не смарт-контракты, не `custom_json`-нагрузки. + +| Слой | Примеры | Validated консенсусом? | +|-------|---------|---------------------| +| **Операции протокола** | `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, `pm_commit_bet`/`pm_reveal_bet`, `pm_resolve_market`, `pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`, `pm_lazy_deposit`/`pm_lazy_withdraw`, `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | Да — валидирует каждый узел | +| **Виртуальные операции** | `pm_payout` (на беттера), `pm_auto_payout`, `pm_market_accepted`, `pm_dispute_finalize`, `pm_dispute_auto_close`, `pm_oracle_missed_penalty`, `pm_lazy_recall`, `pm_batch_settle`, `pm_commit_forfeit`, `pm_leverage_resolve`/`pm_leverage_liquidate` | Да — детерминированы, генерируются на этапе блока | +| **metadata / custom_json** | Комментарии споров, описания рынков, UI-метаданные | Нет — только отображение/индексация | + +Каждое финансовое действие (ставки, добавление ликвидности, резолюция рынков, изъятие страховки) +валидируется каждым валидатором. Невалидные операции отвергаются до включения в блок. Никакого Solidity, +оценки газа, деплоя байткода. + +Фронтенд — **полностью headless web-клиент** — без бэкенд-сервера, без БД, без сессий. Приватные ключи в +браузере (зашифрованы), транзакции подписываются локально и вещаются на публичные узлы VIZ. Нет зависимости +от Telegram; ядро приложения платформонезависимо. + +### 8.3 Реализовано с момента прототипа и оставшаяся дорожная карта + +**Реализовано on-chain (HF14):** + +| Фича | Статус | +|---------|--------| +| Commit-reveal + batch ставки (бинарные, опциональные, медианный kill-switch) | ✅ Live | +| Опциональное плечо (фондируется Lazy-пулом, ликвидация по pre-bet резервам) | ✅ Live | +| Lazy Pool (авто-аллокация, graduated recall, MasterChef-учёт) | ✅ Live | +| Виртуальные операции расчёта на беттера / плечо + API плагина | ✅ Live | +| Стейк lazy-пула как governance-вес (PM-споры + заявки DAO) | ✅ Live | + +**Оставшаяся дорожная карта:** + +| Приоритет | Фича | Влияние | +|----------|---------|--------| +| Высокий | Shared liquidity pools (category-level AMMs) | Решает фрагментацию ликвидности на уровне архитектуры | +| Высокий | Automated data oracles (экзогенные фиды) | Устраняет манипуляцию для объективных рынков | +| Средний | Тиры окон споров (малые vs крупные рынки) | Лучшая калибровка UX | +| Средний | LMSR batch settlement (расширить batch/commit-reveal на multi) | Multi-рынки сейчас форсят instant-ставки | +| — | Commit-reveal голосование в **спорах** | **Намеренно отклонено** — споры остаются публичными слушаниями (см. §4.2) | + +--- + +## 9. Что Onix НЕ заявляет + +Честное раскрытие компромиссов и ограничений: + +- **Прибыль LP не гарантирована.** Если у рынка ноль проигранных ставок, комиссий для распределения нет. + LP получает назад принципал, но не зарабатывает ничего. + +- **Платформенный риск существует.** Баги, эксплойты и атаки на управление отделены от модели + маркет-мейкера. Гарантия LP структурна (архитектура выплат), не застрахована (нет внешнего гарантийного + фонда). + +- **Токены Onix Multi — не инструменты фиксированной стоимости.** В стандартном LMSR 1 выигрышный токен = + 1 единица валюты. В Onix Multi токены — пропорциональные претензии на пул проигравших. Если все беттеры + выбрали победителя, все выходят в ноль. + +- **Доходность LP зависит от объёма, не от глубины.** Рынок с субсидией 100 000 VIZ и рынок с 1000 VIZ + заработают одинаковую абсолютную комиссию, если у обоих идентичные объём ставок и ставки комиссий. + Субсидия даёт глубину, не доходность. + +- **У DPoS-управления есть известные компромиссы.** Меньше валидаторов, чем у PoW/PoS, риски концентрации + делегатов, token-weighted голосование. Это присуще модели DPoS (общей с EOS, Hive, Tron), не специфично + для VIZ. + +- **Ликвидность токена VIZ сейчас низкая.** Экономические гарантии (страховые бонды, dispute fee) + масштабируются с ценой токена. Протокол предполагает, что полезность со временем создаёт спрос — та же + ставка, что делает любой protocol-native токен-проект. + +--- + +## 10. Заключение + +Onix Protocol адресует фундаментальный барьер внедрения прогнозных рынков: риск LP. Структурно отделяя +капитал LP от расчёта по ставкам — и в бинарных (CPMM), и в мульти-исходных (LMSR + parimutuel) рынках — +Onix делает предоставление ликвидности безрисковым и доступным для ритейл-участников. + +Ключевые инновации: + +1. **Гарантия принципала LP** как архитектурный инвариант, а не страховка +2. **Проигравшие-финансируют-победителей** расчёт, устраняющий конкуренцию комиссий с выплатами +3. **LMSR-ценообразование с parimutuel-расчётом** (Onix Multi) — сочетание проверенного price discovery + с безопасностью LP +4. **Time-weighted распределение LP**, награждающее раннюю фиксацию капитала +5. **Квадратичный time penalty** на прибыль (никогда принципал) для поздних ставок +6. **Lazy-пул ликвидности** с авто-аллокацией, graduated recall и MasterChef-учётом — также фондирующий + сабсистему **плеча** (маржа из пула, ликвидация по pre-bet резервам, нет bad-debt вне ограниченного + cancel-bet пути) +7. **Модель bonded-оракула** со скорингом репутации, offer→quote заморозкой комиссии и двухрежимной + системой споров — диспуты комитета это **публичные слушания** с изменяемыми, lazy-pool-взвешенными + голосами +8. **Опциональный анти-MEV** — batch / commit-reveal ставки (бинарные) с медианным kill-switch +9. **Реализация на уровне консенсуса** на VIZ DLT — без смарт-контрактов, газа и внешних keeper'ов; + строго **zero-sum** (протокол никогда не печатает токен) + +Ставка проста: если безрисковый LP привлекает капитал, капитал создаёт глубину, глубина улучшает цены, а +цены привлекают бетторов — то Onix Protocol решает проблему ликвидности прогнозных рынков. Механика +протокола математически проверяема. Экономическую гипотезу проверит рынок. + +--- + +## 11. Автор и раскрытие + +### Автор + +**Анатолий Пискунов** (On1x) — российский IT-инноватор, Web3/DLT-разработчик, создатель блокчейна VIZ. +Его работа охватывает distributed ledger technology, децентрализованные социальные протоколы и +экономические модели цифровых сообществ. + +Ключевые вклады: VIZ Blockchain (Fair DPoS, примитивы социального капитала), Onix Protocol (прогнозные +рынки с гарантией LP), Voice Protocol (цензуроустойчивый обмен сообщениями) и обширные публикации по +экономике блокчейна и Web3-архитектуре. + +Полный список публикаций и проектов: [https://on1x.com](https://on1x.com) + +### Раскрытие + +Автор Forecaster и Onix Protocol также создатель VIZ DLT. Дорожная карта миграции предлагает перенести +платформу на блокчейн, спроектированный и построенный автором. + +Это раскрыто заранее. Это также норма: Polymarket зависит от инфраструктуры Polygon Labs, Kalshi работает +на своих серверах, Augur спроектировал токен REP, на котором работает. Каждая платформа аргументирует за +свою инфраструктуру. Вопрос не в том, есть ли у автора интерес — он всегда есть — а в том, фальсифицируемы +ли технические заявления. Каждая формула, доказательство и механизм в этом документе математически +проверяемы, а кодовая база — открытая. + +--- + +## 12. Ссылки + +1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. — Logarithmic Market Scoring Rule (LMSR). + +2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* — Constant Product Market Maker (`x * y = k`). + +3. Adams, H., et al. (2021). *Uniswap v3 Core.* — Концентрированная ликвидность и анализ impermanent loss. + +4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ — ERC-1155 позиции прогнозного рынка. + +5. UMA Protocol. *Optimistic Oracle Documentation.* — Механизм эскалации споров, используемый Polymarket. + +6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* — Паттерн аккумулятора cToken (основа для reward_per_share). + +7. SushiSwap. *MasterChef Contract.* — Паттерн ленивого учёта для распределения наград. + +8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* — Архитектура VIZ DLT, DPoS-консенсус, именованные аккаунты. + +9. Piskunov, A. (2019). *What is Fair DPoS.* — Инновация управления в delegated proof of stake. + +10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* — Рамка для блокчейн-систем как цифровых государств. diff --git a/@l10n/ru/docs/prediction-markets/workflows.md b/@l10n/ru/docs/prediction-markets/workflows.md new file mode 100644 index 0000000000..85b97c9440 --- /dev/null +++ b/@l10n/ru/docs/prediction-markets/workflows.md @@ -0,0 +1,741 @@ +--- +title: Прогнозные рынки — воркфлоу и диаграммы взаимодействия +description: Один канонический бинарный рынок Onix, прослеженный через каждого участника, с zero-sum мастер-леджером для нормального и спорного разрешения. +--- + +# Воркфлоу и диаграммы взаимодействия + +Один **канонический сценарий**, прослеженный через каждого участника. Каждая роль отправляет конкретные +**подписанные операции**, её затрагивают конкретные **виртуальные операции**, и всё завершается таблицей +**отправлено / получено токенов** для двух исходов: + +- **Нормальное разрешение** — оракул резолвит, проходит grace, `pm_auto_payout` рассчитывает. Без спора. +- **Спорное разрешение** — оракул резолвит **A**, спор **переворачивает на B**, затем идёт расчёт. + +Все суммы — абстрактные **VIZ**. Все проценты — **bp** (10000 = 100.00%). Расчёт строго **zero-sum** — +токены никогда не печатаются, `current_supply` не трогается: + +``` +Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal + == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, в споре) +``` + +## Канонический рынок **M** (binary CPMM, A vs B) + +| Параметр | Значение | +|------|-------| +| Движок | binary CPMM (`x·y=k`), `weight = tokens_out` | +| Сид-ликвидность (marketmaker) | **2000** → резервы A=1000 / B=1000 | +| `oracle_fee_percent` (котировка оракула) | **1000** (10%) | +| `creator_fee_percent` | **500** (5%) | +| `liquidity_fee_percent` | **500** (5%) | +| `oracle_fixed_fee` (котировка оракула) | **10** | +| `dispute_penalty_percent` | **+10000** (слеш до 100% страховки ×consensus) | + +Иллюстративные chain props: `pm_market_creation_fee` 5, `pm_oracle_registration_fee` 10, +`pm_min_oracle_insurance` 5000, `pm_dispute_fee` 1000, `pm_dispute_reward_multiplier` 30000 (**3×**), +`pm_no_contest_penalty_percent` 5000, `pm_oracle_penalty_percent` 500, `pm_lazy_emergency_penalty_percent` +5000, `pm_leverage_pool_profit_percent` **R = 10%**, `pm_lazy_alloc_percent` 2000 (20%). + +### Состав + +| Актор | Роль | Стейк / действие | +|-------|------|----------------| +| **maker** | создатель + первый LP | сид 2000 ликвидности | +| **orac** | внешний оракул | страховка 5000; котирует 10% + фикс 10 | +| **LP1** | поставщик ликвидности в рынке | добавляет 1000 | +| **A** | беттор — победитель | 100 на **A**, рано; вес 100 | +| **C** | беттор — поздний победитель | 100 на **A** при T+85%; вес 100; time-penalty **50%** | +| **B** | беттор — проигравший | 200 на **B**; вес 200 | +| **D** | leverage **×10** победитель | collateral 10 + loan 90 (рынок **L**) | +| **E** | leverage **×5** ликвидирован | collateral 20 + loan 80 (рынок **L**) | +| **LZ1** | депозитчик lazy-пула | вносит 1000 | +| **disp** | диспутёр | escrow dispute fee 1000 | + +> Веса кривой (100 / 100 / 200) выписаны явно, чтобы арифметика parimutuel читалась; реальный CPMM выдаёт +> чуть меньше веса по мере сдвига резервов. + +## Диаграммы взаимодействия + +**Жизненный цикл рынка.** + +```mermaid +flowchart LR + W["Waiting (0)"] -->|оракул принял| A["Active (1)"] + W -->|оракул отклонил| X["Deleted (-1)"] + A -->|betting_expiration| C["Closed (2)"] + A -->|ранняя резолюция| R["Resolved (3)"] + C -->|оракул резолвит| R + R -->|grace, без спора| P["Выплачено"] + R -->|подан спор| D["Disputed"] + D -->|finalize / резолвер| P +``` + +**Расчёт — НОРМАЛЬНО (побеждает A).** Проигравшие финансируют победителей; принципал LP не тронут (zero-sum). + +```mermaid +flowchart TD + B["B проиграл 200 (пул проигравших)"] --> POOL{"делёж 200"} + POOL -->|oracle_fee 20 + fixed 10| OR["оракул +30"] + POOL -->|creator_fee 10| CR["создатель +10"] + POOL -->|liq_fee 10 + штраф 37| LPS["LP +47"] + POOL -->|winners_pool 150 → профит 75| A["A → выплата 175"] + POOL -->|профит 75 − time-penalty 37| C["C → выплата 138"] + MK["maker + LP1 принципал 3000"] -.возвращён полностью.-> MK +``` + +**Спор — оракул сказал A, перевёрнуто на B.** Наказание — это слеш страховки (отдельные деньги). + +```mermaid +sequenceDiagram + participant O as Оракул + participant D as Диспутёр + participant V as Комитет / Резолвер + O->>O: резолвит A + D->>V: pm_dispute_create (escrow dispute_fee) + O-->>V: обязательный ответ (дедлайн) + V->>V: pm_dispute_vote / pm_dispute_resolve → переворот на B + V-->>O: страховка слешнута (5000) + V-->>D: fee назад + награда (2000 из слэша) + V->>V: расчёт перезапускается → побеждает B +``` + +## Мастер-леджер — НОРМАЛЬНОЕ разрешение (A побеждает) + +`losers_sum = 200` (B). Комиссии с пула проигравших: +`oracle_fee = 200×10% = 20`, `creator_fee = 200×5% = 10`, `liq_fee = 200×5% = 10`, `oracle_fixed = 10`. +`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`. `Σ выигрышного веса = 200` (A 100 + C 100). + +- **A**: профит `150×100/200 = 75`, штраф 0 → **выплата 175**. +- **C**: профит 75, time-penalty `75×50% = 37` (→ LP) → **выплата 138**. +- **LP-бонус** = `liq_fee 10 + штрафы 37 = 47`, делёж по времени в рынке: **maker ~31 / LP1 ~16**. +- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`. **creator_take** = `creator_fee 10`. + +| Актор | отправил | получил | нетто (этот рынок) | +|-------|-------|----------|-------------------| +| maker | 2000 ликвидности + 5 creation-fee | 2000 принципал + 10 creator-fee + 31 LP-бонус | **+36** | +| orac | (10 reg-fee, 5000 страховка заблок.) | 30 oracle-take | **+30** | +| LP1 | 1000 ликвидности | 1000 принципал + 16 LP-бонус | **+16** | +| A | 100 | 175 | **+75** | +| C | 100 | 138 | **+38** | +| B | 200 | 0 | **−200** | + +**Zero-sum:** in `= ставки 400 + LP принципал 3000 = 3400`; out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`. ✔ +5 creation-fee + 10 reg-fee уходят в **фонд DAO** (не часть пула рынка). + +## Мастер-леджер — СПОРНОЕ разрешение (оракул сказал A → перевёрнуто на B) + +`disp` эскроит `dispute_fee 1000`. Вердикт переворачивает на **B**; оракула слешат. +При `dispute_penalty_percent = 10000` и силе консенсуса **100%**: `slash = 5000×100%×100% = 5000`. +Carve-out награды: `reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000` (≤ slash). Диспутёр +получает `fee 1000 + bonus 2000 = 3000`. Остаток `slash − bonus = 3000 → forfeit_pool`. + +Теперь **B побеждает**. `losers_sum = 200` (A 100 + C 100). Комиссии 20/10/10 + fixed 10. +`winners_pool = 200 − 50 + forfeit 3000 = 3150`. `Σ выигрышного веса = 200` (B). +- **B**: профит `3150×200/200 = 3150` → **выплата 3350**. +- **oracle_take** всё ещё `30` (*рыночная* комиссия платится из замороженной конфигурации даже при + перевороте — наказание это **слеш страховки**, отдельные деньги). **creator_take** 10. **LP-бонус** = liq 10. + +| Актор | отправил | получил | нетто (этот рынок) | +|-------|-------|----------|-------------------| +| maker | 2000 + 5 | 2000 принципал + 10 creator-fee + ~6 LP-бонус | **+11** | +| orac | страховка −**5000** слешнута | 30 oracle-take | **−4970** | +| LP1 | 1000 | 1000 принципал + ~4 LP-бонус | **+4** | +| A | 100 | 0 | **−100** | +| C | 100 | 0 | **−100** | +| B | 200 | 3350 | **+3150** | +| disp | 1000 dispute-fee | 3000 (fee назад + 2000 награда) | **+2000** | + +**Zero-sum:** in `= ставки 400 + LP принципал 3000 + dispute_fee 1000 + слеш 5000 = 9400`; +out `= B 3350 + оракул 30 + создатель 10 + lp_bonus 10 + LP принципал 3000 + диспутёр 3000 = 9400`. ✔ +Слеш 5000 делится на бонус диспутёра 2000 + forfeit 3000 (→ B через пул победителей). + +## Статус реализации (сверено с кодом) + +**Обычные операции — все 21 присутствуют** в variant `operation` (`operations.hpp`), валидируются + +оцениваются в `pm_evaluator.cpp`: +`pm_oracle_register`, `pm_oracle_update`, `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, +`pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_add_liquidity`, `pm_withdraw_liquidity`, +`pm_resolve_market`, `pm_no_contest`, `pm_dispute_create`, `pm_dispute_vote`, `pm_dispute_resolve`, +`pm_transfer_position`, `pm_lazy_deposit`, `pm_lazy_withdraw`, `pm_leverage_open`, `pm_leverage_close`, +`pm_leverage_convert`. ✔ + +**Виртуальные операции** — эмитятся `database::process_pm_markets()` / эвалюаторами: + +| Виртуальная op | Фаерится? | Триггер (код) | +|------------|--------|----------------| +| `pm_market_accepted` | ✔ | при `pm_oracle_accept_market` **и** self-oracle `pm_create_market` | +| `pm_payout` | ✔ | **на каждую активную ставку** при расчёте — несёт `account`, `market_id`, `bet_id`, `side`/`outcome_index`, `amount` (стейк), `payout` (**0 при проигрыше**) | +| `pm_auto_payout` | ✔ | **раз на рынок** при расчёте — сводный маркер (`bets_sum`) рядом с per-bet `pm_payout` | +| `pm_commit_forfeit` | ✔ | нераскрытый commit после `reveal_deadline` | +| `pm_dispute_finalize` | ✔ | `voting_end_time` комитета | +| `pm_dispute_auto_close` | ✔ | `auto_close_time` (анти-фриз) | +| `pm_oracle_missed_penalty` | ✔ | оракул пропустил `result_expiration` | +| `pm_lazy_recall` | ✔ | шаг graduated recall простаивающей аллокации | +| `pm_batch_settle` | ✔ | граница эпохи | +| `pm_leverage_liquidate` | ✔ | mid-market ликвидация: reason **0** opposing-bet, **1** cancel-bet (`cascade_liquidate`) | +| `pm_leverage_resolve` | ✔ | **расчёт** leverage-позиции: несёт `market_id`, `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` (= `total_bet/collateral`) | + +См. [API плагина](../plugins/prediction-market-api) для read-методов +(`get_account_leverage_positions`, `get_market_leverage_positions`, `get_creator_ban`, `get_dispute_votes`, …); +per-bettor результаты (`pm_payout`) и расчёты плеча (`pm_leverage_resolve`) видны и в `account_history`. + +## Роли в каноническом сценарии + +Каждый участник прослежен через рынок **M** (и leverage-подрынок **L**): его диаграмма взаимодействия, +**подписанные** операции, **виртуальные** операции, которые его касаются, леджер +**отправлено / получено** для обоих исходов и указатель на проверку в коде. Каждый леджер по роли — +срез двух мастер-леджеров выше. + +### Маркет-мейкер (создатель + первый LP) + +Мейкер создаёт рынок M, вносит **2000** ликвидности (становясь первым `pm_liquidity_object`) и +предлагает **потолок-оферту** оракула. Он **не** разрешает (это делает оракул). + +```mermaid +flowchart LR + maker -->|pm_create_market| M[(pm_market_object
status=0)] + maker -->|seed 2000| LP0[(pm_liquidity_object
provider=maker)] + M -. fee 5 .-> DAO[(committee_fund)] + orac -->|pm_oracle_accept_market| M2[(M status=1)] + M2 -. VIRTUAL .-> VA[[pm_market_accepted]] + M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus] + RET --> maker +``` + +- **Отправляет:** `pm_create_market` (задаёт `oracle_fee_percent`/`oracle_fixed_fee` как **потолок-оферту** + плюс свои `creator_fee_percent` 5% и `liquidity_fee_percent` 5%; платит `pm_market_creation_fee` 5 → DAO, + блокирует `liquidity` 2000); опц. `pm_add_liquidity` / `pm_withdraw_liquidity` (принципал-сейф, заблокирован + от `betting_expiration` до резолюции). +- **Касаются:** `pm_market_accepted` (оракул акцептует, либо self-oracle при создании); `pm_auto_payout` + (возвращает принципал LP + взвешенную по времени долю LP-бонуса). + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | 2000 ликвидности + 5 fee-создания (→DAO) | 2000 принципала + **creator_fee 10** + **LP-бонус ~31** | **+36** | +| спорный (→B) | 2000 + 5 | 2000 принципала + creator_fee 10 + LP-бонус ~6 | **+11** | + +Creator fee **всё равно платится** из замороженной конфигурации рынка при перевороте — спор наказывает +**оракула** (слеш страховки), а не мейкера. Принципал LP возвращается безусловно. + +- **Self-oracle:** `oracle == creator` → активен при создании, `pm_market_accepted` с `self_oracle=true`, + и мейкер дополнительно получает `oracle_take`. +- **Проверка:** `pm_create_market_evaluator`; LP через `settle_liquidity`; `committee_fund += pm_market_creation_fee`. + **Наблюдать:** `get_market`, `list_markets_by_creator`, `get_market_liquidity` (`earned_fee`), `get_market_meta`. + +### Оракул (регистрация → акцепт-котировка → резолюция) + +Внешний оракул **orac** вносит страховку, **котирует** свою fee при акцепте (≤ оферты мейкера и ≤ +`pm_max_oracle_fee_percent`) и разрешает. Его рыночная fee платится из пула проигравших; бонд под риском +только при пропуске дедлайна или проигранном споре. + +```mermaid +flowchart LR + orac -->|pm_oracle_register
insurance 5000| O[(pm_oracle_object)] + orac -. reg-fee 10 .-> DAO[(committee_fund)] + orac -->|pm_oracle_accept_market
quote fee 10% + fixed 10| M[(M status=1)] + M -. VIRTUAL .-> VA[[pm_market_accepted]] + orac -->|pm_resolve_market A| M3[(M status=3)] + M3 ==>|grace passes| VP[[pm_auto_payout]] + VP -->|oracle_take 30| orac +``` + +- **Отправляет:** `pm_oracle_register` (блокирует страховку 5000, платит reg-fee 10 → DAO, задаёт advisory + прайс-лист); `pm_oracle_accept_market` (**котирует** fee 10% + fixed 10, каждое ≤ оферты создателя и ≤ + медианного кэпа; замораживает на M); `pm_resolve_market` (задаёт `winning_outcome`, открывает grace); + опц. `pm_oracle_update` / `pm_no_contest`. Стоячий прайс-лист также может авто-акцептовать рынки live + при создании — см. документ операций оракула. +- **Касаются:** `pm_market_accepted`; `pm_auto_payout` (зачисляет `oracle_take`); `pm_oracle_missed_penalty` + (не разрешил → слеш `pm_oracle_penalty_percent` страховки → DAO, возврат всех ставок). + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | страховка 5000 (заблокир.) + reg-fee 10 (→DAO) | **oracle_take 30** = fee 20 + fixed 10 | **+30** | +| спорный (→B) | страховка −**5000 слеш** | oracle_take 30 | **−4970** | + +Даже при перевороте оракул сохраняет небольшую **рыночную fee** (замороженный конфиг); наказание — это +**слеш страховки**, разбиваемый на награду диспутёра и `forfeit_pool` победителей. Котировать **ниже** +оферты можно (цена = репутация); **выше** — отклоняется. + +- **Проверка:** `pm_oracle_register_evaluator`, `pm_oracle_accept_market_evaluator` (≤ оферты, ≤ кэпа, + заморозка), скан пропущенного дедлайна в `process_pm_markets`. **Наблюдать:** `get_oracle`, `list_oracles`, + `get_market` (замороженные условия). + +### Оракул — поддержан в споре (победитель спора) + +Оракул разрешил **A**; диспутёр оспорил, но вердикт **поддерживает** A. Оракул сохраняет рыночную fee +**и** забирает потерянный `dispute_fee`; страховка нетронута, `disputes_won++`. + +```mermaid +flowchart LR + orac -->|pm_resolve_market A| M[(market resolved A)] + disp -->|pm_dispute_create| D[(dispute)] + D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]] + FIN -->|dispute_fee 1000| orac + FIN --> AUTO[[pm_auto_payout settles A]] + AUTO -->|oracle_take 30| orac +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| спор, поддержан | страховка 5000 (**не** слеш) | oracle_take 30 + **dispute_fee 1000** | **+1030** | +| нормальный (без спора) | страховка 5000 (заблокир.) | oracle_take 30 | **+30** | + +Оспаривание оборачивается против диспутёра и **платит оракулу**. Рынок «доброй воли» +(`dispute_penalty_percent < 0`) может даже выдать оракулу бонус к fee при смене исхода — признавая честную +ошибку. **Проверка:** ветка uphold в `pm_dispute_finalize` / `pm_dispute_resolve`. **Наблюдать:** +`get_oracle` (`disputes_won`), `get_dispute`. + +### Оракул — перевёрнут + слеш (проигравший спор) + +Оракул разрешил **A**; спор **переворачивает на B**, страховка **слешится**. Он всё ещё забирает +крошечную замороженную рыночную fee (fee и наказание — разные деньги), но теряет большую долю бонда и +репутацию. + +```mermaid +flowchart LR + orac -->|pm_resolve_market A| M[(resolved A)] + disp -->|pm_dispute_create proposed=B| D[(dispute)] + D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]] + FIN -->|slash 5000| INS[oracle.insurance ↓] + INS --> SPLIT[bonus 2000 → disputer
3000 → forfeit_pool → B] + FIN --> AUTO[[pm_auto_payout settles B]] + AUTO -->|oracle_take 30| orac +``` + +`slash = страховка 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`, +**перераспределяется**, не сжигается: `bonus 2000 →` диспутёр, `3000 → forfeit_pool →` новые победители (B). +Нетто **−4970** против **+30** без спора. Слеш масштабируется с **силой консенсуса** (`winning_rshares / +max_rshares`); `dispute_penalty_percent < 0` (добрая воля) → **нет** слеша. Слешнутый оракул часто ещё и +**забанен** (следующая роль). **Проверка:** ветка overturn `pm_dispute_finalize` / `pm_dispute_resolve`; +fee по-прежнему из `mkt.oracle_fee_percent`. **Наблюдать:** `get_oracle` (`total_insurance_slashed`, +`banned_until`), `get_dispute`. + +### Забаненный оракул (и забаненный создатель) + +Бан — это **статус**, а не перевод: `pm_oracle_object.banned_until` (а для создателей — +`pm_creator_ban_object`) блокирует актора от **новых** рынков, пока не пройдёт таймстамп. Обычно идёт вместе +со слешем переворота, но сам по себе токены не двигает. + +```mermaid +flowchart LR + resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object
banned_until = T)] + orac -->|pm_create_market / accept| CHK{now < banned_until?} + CHK -->|yes| REJ[REJECTED: 'Oracle is banned'] + CHK -->|no, expired| OK[allowed again] + resolver -->|ban_creator| CB[(pm_creator_ban_object)] + maker -->|pm_create_market| CHK2{banned?} + CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned'] +``` + +- **Кто ставит:** account-режим → `pm_dispute_resolve` (`ban_oracle`/`ban_creator` + `…_until`); + committee-режим → `pm_dispute_finalize` масштабирует бан по консенсусу при перевороте. `banned_until = + time_point_sec::maximum()` ⇒ **перманентный**. +- **Токены:** сам бан — **0** (чистый статус); сопутствующий слеш — это случай переворота выше. Страховка + остаётся заблокированной, возвратна после снятия бана и при отсутствии активных рынков. +- Баны переживают снапшоты и ключуются по аккаунту — повторная регистрация бан не стирает. **Проверка:** + `pm_create_market_evaluator` (`"Oracle is banned"` / `"Creator is banned"`). **Наблюдать:** `get_oracle` + (`banned_until`, `bans_received`), **`get_creator_ban(account)`**. + +### Беттор A — ранний победитель + +**A** ставит **100 на сторону A рано** (без штрафа за время) и выигрывает, когда M разрешается в A. +Выплата = ставка + взвешенная по весу доля пула победителей. + +```mermaid +flowchart LR + A -->|pm_place_bet side=A 100| BET[(pm_bet_object
weight 100)] + BET --> M[(market M
reserves shift)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|payout 175| A +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | 100 | **175** | **+75** | +| спорный (→B) | 100 | **0** | **−100** | + +`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`; без штрафа → выплата `100 + 75`. Переворот +делает A **проигравшей** стороной. Выигрыши идут **только** из ставок проигравших (+ forfeit), никогда из +эмиссии. **Отправляет:** `pm_place_bet` (`side=0`, instant); опц. `pm_transfer_position` / `pm_cancel_bet`. +**Проверка:** `pm_place_bet_evaluator`, `settle_market`. **Наблюдать:** `get_account_positions` +(`expected_payout`), `get_market_weight_sums`; реализованный `pm_payout` в `account_history`. + +### Беттор B — проигравший + +**B** ставит **200 на сторону B**. Когда M разрешается в **A**, ставка B финансирует победителей, а B не +получает ничего. В спорном пути B становится победителем. + +```mermaid +flowchart LR + B -->|pm_place_bet side=B 200| BET[(pm_bet_object
status active)] + BET --> M[(market M)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|status=resolved, payout 0| BET +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | 200 | **0** | **−200** | +| спорный (→B) | 200 | **3350** | **+3150** | + +200 от B **и есть** `losers_sum` (платит 40 fee + 150 пул победителей + LP-бонус) — паримутюэль-правило +«проигравшие финансируют победителей». При перевороте B выигрывает, и forfeit оракула 3000 вливается в пул +B (`payout = 200 + 3150`). Проигравшая ставка тоже фиксируется через `pm_payout` с **payout=0**. +**Проверка:** ветка проигравшего в `settle_market`. **Наблюдать:** `get_account_positions`, +`get_market_bets`, `get_dispute`. + +### Беттор C — поздний победитель (штраф за время) + +**C** ставит **100 на сторону A**, но **поздно** (T+85% окна ставок), поэтому **штраф за время** урезает +*только прибыль* (не принципал). Тот же вес, что у A, но получает меньше; урезанное идёт LP. + +```mermaid +flowchart LR + C -->|pm_place_bet side=A 100
at T+85%| BET[(pm_bet_object
weight 100
time_penalty 50%)] + BET --> M[(market M)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|payout 138| C + VP -. penalty 37 .-> LPb[LP bonus] +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | 100 | **138** | **+38** | +| спорный (→B) | 100 | **0** | **−100** | + +`profit = 75`; `penalty = 75 × 50% = 37` (→ LP); `payout = 100 + 75 − 37 = 138` — **−37** против +75 у A +при том же весе. Узел штампует `time_penalty` при размещении из штрафной кривой рынка +(`time_penalty_type/value`, `penalty_curve_type`). Это сдерживает снайпинг в последнюю секунду и +субсидирует ликвидность, а не протокол. **Проверка:** `pm_place_bet_evaluator` (eval кривой), +`compute_settlement`. **Наблюдать:** `get_account_positions` (`time_penalty`), `get_market_bets`. + +### Беттор D — leverage ×10 победитель + +**D** открывает позицию **×10**: **10 коллатерала + 90 займа** из lazy-пула = **100** на стороне A, в +изолированном leverage-рынке **L** (`pm_leverage_enabled=true`, `R = 10%`). Когда A выигрывает, D +сохраняет апсайд на всей сотне после погашения займа + процентов. +`pool_profit = loan 90 × R 10% = 9`; `obligation = 90 × 1.10 = 99`. + +```mermaid +flowchart LR + D -->|pm_leverage_open
collateral 10 + loan 90| POS[(pm_leverage_position
total_bet 100, obligation 99)] + POOL[(lazy pool)] -.loan 90.-> POS + POS --> L[(market L, side A)] + L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]] + VR -->|min(cv,obligation) 99| POOL + VR -->|cv 200 − 99 = 101| D +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | коллатерал **10** | cancel_value 200 − obligation 99 = **101** | **+91** | +| спорный (→B) | коллатерал 10 | 0 | **−10** | + +Прибыльная позиция закрывается по `cancel_value`; пул забирает `obligation 99` (заём 90 + **9 процентов**), +D оставляет остаток на своих 10 → **+91** (пул **+9**). Leverage рассчитывается ликвидацией, **никогда** не +через `pm_auto_payout`. Zero-sum (L): in `10 + 90 + 100 = 200`; out `101 + 99 = 200`. **Отправляет:** +`pm_leverage_open`; опц. `pm_leverage_close` (только при `cv ≥ obligation`) / `pm_leverage_convert`. +**Касается:** `pm_leverage_resolve` (force-close при расчёте, `reason=expiration`). **Проверка:** +`force_close_positions` → `liquidate_position(reason=2)`. **Наблюдать:** +**`get_account_leverage_positions`** / **`get_market_leverage_positions`**, `get_lazy_pool`. + +### Беттор E — leverage ×5 ликвидирован + +**E** открывает позицию **×5**: **20 коллатерала + 80 займа** = **100** на стороне B (рынок **L**, +`R = 10%`). До резолюции **встречная ставка** двигает кривую против B; **каскадная ликвидация** принудительно +закрывает позицию. **E теряет коллатерал, но пул всегда остаётся целым.** `obligation = 80 × 1.10 = 88`. + +```mermaid +flowchart LR + E -->|pm_leverage_open
collateral 20 + loan 80| POS[(pm_leverage_position
obligation 88)] + POOL[(lazy pool)] -.loan 80.-> POS + X -->|pm_place_bet side=A| L[(market L)] + L ==>|cascade at PRE-bet reserves
cv 88 ≤ threshold| VL[[pm_leverage_liquidate
reason=opposing_bet]] + VL -->|pool_received 88 = loan 80 + profit 8| POOL + VL -->|bettor_received 0| E +``` + +E ликвидируется **до** резолюции, поэтому финальный результат A/B (спорный или нет) на неё не влияет: + +| актор | отправляет | получает | нетто | +|-------|------------|----------|-------| +| **E** | коллатерал **20** | **0** | **−20** | +| **пул** | заём 80 | **88** (заём 80 + R% 8) | **+8** | + +Ликвидации по встречной ставке идут на **до-ставочных** резервах, где `cancel_value ≥ loan`, поэтому +`pool_received = min(cv, obligation)` возвращает как минимум заём — пул **никогда** не теряет. + +> **Единственный путь в минус** — same-side **`pm_cancel_bet` (Case B)**: отмена разворачивает *прежнюю, +> крупную* ставку той же стороны (за пределами per-bet слиппедж-кэпа) и ради честности к отменителю +> исполняется **первой** по его цене — так каскад может оказаться `cancel_value < loan`: +> `shortfall = obligation − cancel_value`, `lazy_pool.free_balance −= shortfall`. Этот **bad debt** +> **ограничен** (`≤ cancel_value_before × SL%`) и **редок** (R% пула со всех прочих позиций его перекрывает). +> Покрыт тестом `leverage_cancel_bet_cascade_bad_debt`. + +Защита пула структурна (`max_per_position`, `max_position_ratio`, `safety_margin`, слиппедж-кэп, +`expiration_buffer`). `pm_leverage_enabled=false` блокирует **только новые** открытия — каскад ликвидации +**не** гейтится флагом, поэтому управление не может снять защиту пула на лету +(`leverage_disabled_keeps_liquidation_protection`). **Проверка:** `pm_place_bet` → +`cascade_liquidate(reason=0)`; `pm_cancel_bet` → `cascade_liquidate(reason=1)`; `liquidate_position`. +**Наблюдать:** **`get_account_leverage_positions`** (`status=1`, `pool_received`, `bettor_received`), +`get_lazy_pool`. + +### Поставщик ликвидности в рынке (LP1) + +**LP1** добавляет **1000** ликвидности в активный M (после сида мейкера). Принципал **всегда** возвращается; +сверху он зарабатывает **взвешенную по времени** долю LP-бонуса (liquidity fee + штрафы за время + пыль). +Отличается от поставщика lazy-пула, который депонирует один раз и авто-аллоцируется по многим рынкам. + +```mermaid +flowchart LR + LP1 -->|pm_add_liquidity 1000| L1[(pm_liquidity_object
provider=LP1)] + L1 --> M[(market M reserves)] + M ==>|settle| SL[[settle_liquidity]] + SL -->|principal 1000 + bonus ~16| LP1 + LP1 -->|pm_withdraw_liquidity
after resolution| OUT[principal-safe exit] +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| нормальный (A) | 1000 | **1000 принципала + ~16 бонуса** | **+16** | +| спорный (→B) | 1000 | 1000 принципала + ~4 бонуса | **+4** | + +Пул LP-бонуса = `liq_fee 10 + штрафы 37 = 47`, делится по `principal × секунды-в-рынке` (ранний мейкер ~31, +поздний LP1 ~16). **Гарантия принципала** архитектурна — сид возвращается до выплаты любому победителю; LP +может лишь недополучить бонус, но не потерять принципал. Вывод заблокирован от `betting_expiration` до +резолюции. **Проверка:** `pm_add_liquidity_evaluator` (фиксирует `deposit_time`), `settle_liquidity` → +`distribute_lp`. **Наблюдать:** `get_market_liquidity` (`earned_fee`), `get_market_weight_sums`. + +### Lazy-пул ликвидности (системный объект) + +**Синглтон** `pm_lazy_pool_object` — не аккаунт. Депозитчики финансируют его один раз; пул +**авто-аллоцирует** долю в каждый акцептованный рынок как молчаливый LP (`pm_liquidity_object` с пустым +`provider`), **финансирует leverage-займы** и **отзывает** простаивающие аллокации. Зарабатывает LP-доход + +проценты по плечу, учёт в стиле MasterChef (один глобальный `reward_per_share`, O(1) — см. белую бумагу). +Поля: `total_shares`, `free_balance`, `allocated_balance`, `earned_balance`, `reward_per_share`, +`leverage_fund_used`. + +```mermaid +flowchart TD + LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool
free 1000 / shares 1000)] + POOL ==>|on market accept
alloc 20% = 200| ALLOC[(pm_lazy_allocation
+ pm_liquidity provider=∅)] + ALLOC -->|market settles| YLD[route_pool_lp_return
principal 200 + yield 20] + YLD --> POOL + POOL -->|leverage loan 90| Dpos[D position] + Dpos -->|close/resolve: 90 + interest 9| POOL + POOL -. idle market .-> VR[[pm_lazy_recall]] + VR -->|step back to free| POOL +``` + +| денежный поток пула | эффект | +|---------------------|--------| +| `pm_lazy_deposit` | `free_balance += amount`, минт shares | +| авто-аллокация (при акцепте) | `free → allocated` (молчаливый LP) | +| рынок рассчитывается | `route_pool_lp_return`: принципал + доход → `free`; доход → `earned` & `reward_per_share` | +| leverage open (D/E) | `free −= loan`, `leverage_fund_used += loan` | +| leverage close / resolve / ликвидация по встречной ставке | `min(cv, obligation) → free`; `cv ≥ loan` ⇒ **никогда не убыток** | +| ликвидация cancel-bet (только Case B) | возвращает `cv`, который **может быть < loan** → ограниченный **bad debt** | +| `pm_lazy_recall` (простой рынок) | один шаг 10% простаивающей аллокации → `free` | +| `pm_lazy_withdraw` | сжечь shares → принципал + pending; emergency-штраф остаётся в пуле | + +За канонический сценарий пул в нетто **+37 earned** (доход рынка M +20, проценты leverage D +9, возврат по +встречной ставке leverage E +8). Как рыночный LP принципал возвращается безусловно; с диспутом меняется лишь +*бонусный* доход. + +> Пул выполняет **обе** роли из единого `free_balance`: рыночные LP-аллокации (`maybe_allocate_lazy`) и +> leverage-займы (`leverage_fund_used` ограничивает последние). Все leverage-параметры проверяются **в момент +> `pm_leverage_open`** против текущей медианы, поэтому позднейшие изменения свойств влияют лишь на *новые* +> открытия, а не на уже выданные займы. + +VIZ в пуле **ликвидны**, не vested → **нет** веса для планирования валидаторов или committee-request. +**Исключение (HF14):** для **PM-споров комитета** стейк депозитчика в пуле **учитывается** — конвертируется в +vesting-shares через `get_vesting_share_price()` и добавляется к весу его `pm_dispute_vote` (см. резолвер- +комитет ниже). **Проверка:** `apply_hardfork(CHAIN_HARDFORK_14)` (синглтон), `maybe_allocate_lazy`, +`route_pool_lp_return`. **Наблюдать:** `get_lazy_pool`. + +### Поставщик ликвидности в lazy-пуле (LZ1) + +**LZ1** депонирует **1000** в пул **один раз** и даёт ему распределить по рынкам + leverage-займам. Он +зарабатывает долю агрегированного дохода пула (`reward_per_share`), а не исход одного рынка. Два выхода: +**плановый** (после лока) и **аварийный** (до лока, со штрафом на *прибыль*). + +```mermaid +flowchart LR + LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object
shares 1000, unlock=+7d)] + DEP --> POOL[(lazy pool)] + POOL -. yield accrues .-> RPS[reward_per_share ↑] + LZ1 -->|pm_lazy_withdraw| OUT{planned or
emergency?} + OUT -->|planned, t≥unlock| P[principal 1000 + pending 29] + OUT -->|emergency, t|pm_dispute_create proposed=B
escrow fee 1000| D[(pm_dispute_object
status open)] + D --> VOTE{committee vote
or account resolve} + VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]] + FIN -->|fee 1000 + bonus 2000| disp + FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp
3000 → forfeit_pool → winners] +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| спор, перевёрнут («победа») | dispute_fee **1000** | fee 1000 назад + **bonus 2000** | **+2000** | + +`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`, **ограничен +фактическим слешем**; остаток (3000) → `forfeit_pool` → новые победители. disp рисковал 1000, уходит +**+2000**. (Committee-режим: disp **не** голосует сам — это делает электорат SHARES.) **Проверка:** +`pm_dispute_create_evaluator`, ветка overturn `pm_dispute_finalize`/`pm_dispute_resolve`. **Наблюдать:** +`get_dispute`, `get_dispute_votes`. + +### Диспутёр — fee потеряна (оракул поддержан) + +**disp** оспаривает **A** оракула, но вердикт **поддерживает оракула**. Эскроу-fee **переходит оракулу** как +компенсация, а рынок рассчитывается как изначально (A выигрывает). + +```mermaid +flowchart LR + disp -->|pm_dispute_create proposed=B
escrow fee 1000| D[(pm_dispute_object)] + D --> VOTE{committee vote
or account resolve} + VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]] + FIN -->|dispute_fee 1000| orac[oracle compensation] + FIN -->|market settles as A| AUTO[[pm_auto_payout]] +``` + +| исход | отправляет | получает | нетто | +|-------|------------|----------|-------| +| спор, поддержан («проигрыш») | dispute_fee **1000** | **0** | **−1000** | + +Fee — это skin-in-the-game диспутёра: неверный/легкомысленный спор платит оракулу. Эта асимметрия (потерять +fee при ошибке, выиграть кратное при правоте) держит канал честным. Спор, который так и **не решён** (оракул +молчит / нет кворума), принудительно закрывается, а fee **возвращается** (нетто 0) — см. авто-закрытие спора +ниже, что отличается от проигрыша по существу. **Проверка:** ветка uphold +`pm_dispute_finalize`/`pm_dispute_resolve`. **Наблюдать:** `get_dispute`, `get_oracle` (получает fee, +`disputes_won++`). + +### Резолвер — комитет (взвешенный по стейку, dispute_mode = 0) + +*Весь электорат SHARES* решает **взвешенным по стейку голосованием**; единого резолвер-аккаунта нет. Вердикт +детерминированно подсчитывается `pm_dispute_finalize` в `voting_end_time`. + +**Вес голоса** = живые **`effective_vesting_shares`** (`vesting − delegated + received`) **плюс стейк +lazy-пула, сконвертированный в vesting-shares**, поскольку многие члены DAO держат VIZ в пуле (где они +ликвидны): + +``` +pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares +pool_weight = pool_claim_viz × get_vesting_share_price() +voter_weight = effective_vesting_shares + pool_weight +``` + +Знаменатель кворума участия — `total_vesting_shares + (pool_NAV → vesting-shares)`. 7-дневный лок депозита +предотвращает игру депозит-голос-вывод. + +```mermaid +flowchart LR + V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)] + V2[voter · eff_vshares] -->|pm_dispute_vote| D + D ==>|voting_end_time| FIN[[pm_dispute_finalize]] + FIN -->|argmax rshares,
threshold check| VERDICT{uphold / overturn} + VERDICT -->|consensus_strength
scales slash & bans| OUT[settle] +``` + +- **Отправляют (голосующие):** `pm_dispute_vote` — **auth `regular`**. `vote_outcome = -1` поддерживает, + иначе предлагает верный исход; `vote_percent ∈ [-10000, 10000]`. Голосующий может **пересматривать** + бюллетень сколько угодно раз, пока голосование открыто — повторный голос **перезаписывает** прежний + (побеждает последний, без «Already voted»). +- **Без commit-reveal — намеренно, меняться НЕ будет.** Спор комитета — **открытое публичное слушание**: + текущий подсчёт виден (`get_dispute_votes`), голоса не скрыты. Ценность DAO — разрешать споры максимально + правдиво и прозрачно; новые аргументы всплывают в ходе голосования, и голосующие *должны* обновляться; а + голосующим **не платят** за совпадение с большинством, поэтому обычный анти-стадный довод (beauty contest) + для commit-reveal здесь неприменим. + +| актор | отправляет | получает | +|-------|------------|----------| +| каждый голосующий | 0 | **0** — голосование это управленческий долг, не оплачиваемое действие | + +Голосующие никогда не получают токены; влияние — чистый вес стейка. Экономические потоки приходятся на +диспутёра, оракула и бетторов согласно спорному мастер-леджеру выше. Нишевые рынки могут не пройти порог → +переход к авто-закрытию спора ниже. **Проверка:** `pm_dispute_vote_evaluator` (modify-or-create на +`by_market_voter`); `pm_dispute_finalize` (`lazy_vote_weight`, `get_vesting_share_price`, кворум, argmax, +`consensus_strength`). **Наблюдать:** `get_dispute_votes` (живой подсчёт + проекция finalize: +`quorum_percent_bp`, `expected_uphold`, `expected_outcome`, `expected_consensus_strength_bp`). Тесты: +`committee_dispute_lazy_pool_voting_weight`, `committee_dispute_flips_outcome`. + +### Резолвер — один аккаунт (централизованный, dispute_mode = 1) + +Рынок называет один аккаунт `dispute_resolver` (например, мультисиг регулятора), который решает в одиночку — +**без веса стейка, без голосования DAO**. Задан при создании, должен отличаться и от `oracle`, и от +`creator` (анти-самосуд). Тот же набор операций, что и в комитете; различается лишь *кто решает*. + +```mermaid +flowchart LR + disp -->|pm_dispute_create| D[(dispute, mode=1)] + resolver -->|pm_dispute_resolve
correct_outcome=B
penalty_amount, ban flags| FIN[[verdict]] + FIN -->|slash penalty_amount| orac[oracle.insurance ↓] + FIN -->|fee + reward| disp + FIN --> AUTO[[pm_auto_payout settles B]] +``` + +- **Отправляет:** `pm_dispute_resolve` — **auth `active` названного `dispute_resolver`** только: + `correct_outcome`, `penalty_amount` (страховка к слешу — фиксированная сумма, **не** масштабируется + стейком), `ban_oracle`/`ban_creator` (+ `…_until`). + +| актор | отправляет | получает | +|-------|------------|----------| +| резолвер | 0 | **0** — нейтральный арбитр | + +Пост-вердиктный канон идентичен комитет-режиму; различается лишь размер слеша (заданный резолвером +`penalty_amount`, без масштабирования `consensus_strength`, поскольку решает один). KYC/whitelisting +резолвера — забота **клиентского слоя**. **Проверка:** `pm_dispute_resolve_evaluator` (только названный +резолвер, `dispute_mode==1`). **Наблюдать:** `get_dispute`, `get_oracle`, **`get_creator_ban(account)`**. + +### Спор принудительно завершён (анти-фриз авто-закрытие) + +Спор, который так и **не решён** — оракул молчит и (в комитете) нет кворума — не может заморозить рынок +навсегда. В `auto_close_time` обработчик `pm_dispute_auto_close` принудительно его завершает: **всем +возврат**, fee диспутёра **возвращается**, неотзывчивый оракул наказывается. Победитель не выбирается. + +```mermaid +flowchart LR + disp -->|pm_dispute_create
escrow fee 1000| D[(dispute, status open)] + D -. oracle silent / no quorum .-> WAIT[auto_close_time reached] + WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]] + AC -->|refund all bets| bettors + AC -->|fee 1000 back| disp + AC -->|insurance slash → DAO| orac +``` + +| актор | отправляет | получает | нетто | +|-------|------------|----------|-------| +| A / B / C | ставка | полный возврат | **0** | +| maker / LP1 | ликвидность | принципал назад | **0** (без бонуса) | +| disp | dispute_fee 1000 | **1000 назад** | **0** | +| orac | страховка −слеш → DAO | — | **− слеш** | + +Это **не** «диспутёр проиграл»: возвращённая fee (нетто 0) отличается от потерянной fee (диспутёр-проигравший, +нетто −1000). Никто не зарабатывает; рынок аннулируется, чтобы снять заморозку, издержки падают на не +ответившего оракула. Та же форма аннуляции-и-возврата покрывает `pm_oracle_missed_penalty` и `pm_no_contest`. +Настройте `pm_dispute_auto_close_sec` (14 д) против `pm_dispute_vote_period_sec` (3 д), чтобы честные споры +решались раньше. **Проверка:** скан авто-закрытия в `process_pm_markets` (`refund_all_bets` + +`return_liquidity` + зачёт fee; `disputes_auto_closed++`). **Наблюдать:** `get_dispute` (статус → +авто-закрыт), `get_market`, `get_oracle`. diff --git a/@l10n/ru/docs/protocol/operations/overview.md b/@l10n/ru/docs/protocol/operations/overview.md index e2dcdb3200..dcfc0a81ab 100644 --- a/@l10n/ru/docs/protocol/operations/overview.md +++ b/@l10n/ru/docs/protocol/operations/overview.md @@ -52,6 +52,32 @@ | 58 | `use_invite_balance_operation` | active | [Инвайты](./invites.md) | | 60 | `fixed_award_operation` | regular | [Награды](./awards.md) | | 61 | `target_account_sale_operation` | master | [Рынок аккаунтов](./account-market.md) | +| 64 | `set_reward_sharing_operation` | active | [Валидаторы](./validators.md) | +| 66 | `pm_oracle_register_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 67 | `pm_oracle_update_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 68 | `pm_create_market_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 69 | `pm_oracle_accept_market_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 70 | `pm_place_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 71 | `pm_commit_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 72 | `pm_reveal_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 73 | `pm_cancel_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 74 | `pm_add_liquidity_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 75 | `pm_withdraw_liquidity_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 76 | `pm_resolve_market_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 77 | `pm_no_contest_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 78 | `pm_dispute_create_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 79 | `pm_dispute_vote_operation` | regular | [Прогнозные рынки](./prediction-markets.md) | +| 80 | `pm_dispute_resolve_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 81 | `pm_transfer_position_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 82 | `pm_lazy_deposit_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 83 | `pm_lazy_withdraw_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 91 | `pm_leverage_open_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 92 | `pm_leverage_close_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 93 | `pm_leverage_convert_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 98 | `pm_dispute_oracle_respond_operation` | active | [Прогнозные рынки](./prediction-markets.md) | +| 99 | `pm_unban_operation` | active | [Прогнозные рынки](./prediction-markets.md) | + +> ID — это фиксированный индекс в едином `operation`-варианте цепи (только добавление). Пропуски в этой таблице — **виртуальные** операции (ниже), чередующиеся по ID — например, 62–63, 65, 84–90, 94–97, 100. --- @@ -83,6 +109,19 @@ | 59 | `expire_escrow_ratification_operation` | Истёк дедлайн эскроу | [Виртуальные операции](../virtual-operations.md) | | 62 | `bid_operation` | Сделана ставка на аукционе | [Виртуальные операции](../virtual-operations.md) | | 63 | `outbid_operation` | Перебитая ставка на аукционе | [Виртуальные операции](../virtual-operations.md) | +| 65 | `stakeholder_reward_operation` | Выплата reward-sharing участнику | [Валидаторы](./validators.md) | +| 84 | `pm_batch_settle_operation` | Рассчитана граница batch-эпохи | [Прогнозные рынки](./prediction-markets.md) | +| 85 | `pm_commit_forfeit_operation` | Escrow commit-reveal форфейтнут (не раскрыт) | [Прогнозные рынки](./prediction-markets.md) | +| 86 | `pm_auto_payout_operation` | Рынок рассчитан (порыночный маркер выплаты) | [Прогнозные рынки](./prediction-markets.md) | +| 87 | `pm_dispute_finalize_operation` | Подсчитано голосование комитета | [Прогнозные рынки](./prediction-markets.md) | +| 88 | `pm_dispute_auto_close_operation` | Анти-фриз авто-закрытие спора | [Прогнозные рынки](./prediction-markets.md) | +| 89 | `pm_oracle_missed_penalty_operation` | Оракул пропустил дедлайн разрешения | [Прогнозные рынки](./prediction-markets.md) | +| 90 | `pm_lazy_recall_operation` | Шаг поэтапного отзыва lazy-пула | [Прогнозные рынки](./prediction-markets.md) | +| 94 | `pm_leverage_liquidate_operation` | Плечевая позиция ликвидирована | [Прогнозные рынки](./prediction-markets.md) | +| 95 | `pm_leverage_resolve_operation` | Плечевая позиция рассчитана на резолюции | [Прогнозные рынки](./prediction-markets.md) | +| 96 | `pm_market_accepted_operation` | Рынок запущен (оракул принял / self / авто) | [Прогнозные рынки](./prediction-markets.md) | +| 97 | `pm_payout_operation` | Паримутюэль-выплата на беттера | [Прогнозные рынки](./prediction-markets.md) | +| 100 | `pm_ban_expired_operation` | Истёк временный бан оракула/создателя | [Прогнозные рынки](./prediction-markets.md) | --- diff --git a/@l10n/ru/docs/protocol/operations/prediction-markets.md b/@l10n/ru/docs/protocol/operations/prediction-markets.md index 8d3ca48bc2..0210f0b1d7 100644 --- a/@l10n/ru/docs/protocol/operations/prediction-markets.md +++ b/@l10n/ru/docs/protocol/operations/prediction-markets.md @@ -106,6 +106,8 @@ flowchart TD Оракул принимает (`status → active`) или отклоняет (ликвидность возвращается создателю; `status → deleted`) ожидающий рынок. При принятии оракул **котирует свои фактические условия** через `oracle_fee_percent` + `oracle_fixed_fee` — каждое должно быть `≤` оферты создателя на рынке, а `oracle_fee_percent ≤ pm_max_oracle_fee_percent`. Котировка **замораживается в рынок**, эмитится виртуальная `pm_market_accepted` (чтобы парсеры истории видели запуск + условия). Расчёт позже читает только эти замороженные поля — никогда живую медиану. +Оракул обязан действовать в течение `pm_oracle_accept_window_sec` (по умолчанию 1 ч) от создания. Если он не сделал ни того, ни другого к `accept_deadline` рынка, крон каждого блока аннулирует рынок (`status → deleted`), возвращает создателю сид-ликвидность (**не** невозвратную комиссию за создание) и эмитит `pm_market_expired` (см. Виртуальные операции). + | Поле | Тип | Описание | |------|-----|----------| | `market_id` | `int64` | Ожидающий рынок | @@ -155,7 +157,14 @@ Commit-reveal фаза 2: раскрывает ставку и ставит её ### `pm_resolve_market_operation` (ID 76) **Auth:** `active` аккаунта `oracle` -Оракул разрешает в `winning_outcome`. Открывает окно ожидания спора (`result_expiration + pm_dispute_grace_sec`); по его истечении `pm_auto_payout` рассчитывает. +Оракул разрешает в `winning_outcome`. Открывает окно ожидания спора (`result_expiration + pm_dispute_grace_sec`); по его истечении `pm_auto_payout` рассчитывает. Заявление оракула о разрешении **сохраняется на рынке** (как `rules_url` оракула), чтобы клиент мог прочитать его прямо через `get_market`, не сканируя историю. + +| Поле | Тип | Описание | +|------|-----|----------| +| `market_id` | `int64` | Целевой рынок | +| `winning_outcome` | `int16_t` | Индекс выигравшего исхода | +| `decision_url` | `string` | Ссылка на доказательства, `≤ MAX_PM_DECISION_URL_LEN`; сохраняется на рынке | +| `decision_reason` | `string` | Свободное обоснование, `≤ MAX_PM_DISPUTE_REASON_LEN`; сохраняется на рынке (`decision_reason`) | ### `pm_no_contest_operation` (ID 77) **Auth:** `active` аккаунта `oracle` @@ -177,7 +186,39 @@ Commit-reveal фаза 2: раскрывает ставку и ставит её ### `pm_dispute_resolve_operation` (ID 80) **Auth:** `active` аккаунта `resolver` -Вердикт в режиме аккаунта от заданного рынком `dispute_resolver`. Может слешить `penalty_amount` страховки и банить оракула/создателя до заданных времён. +Вердикт в режиме аккаунта от заданного рынком `dispute_resolver`. Может слешить `penalty_amount` страховки и банить оракула/создателя до заданных времён (`ban_*_until = time_point_sec::maximum()` = навсегда). + +> **Баны — это фича комплаенса/регулятора, эксклюзивная для режима аккаунта.** Когда рынок направляет свои споры на `dispute_resolver` режима аккаунта (например, регулятора или лицензированного арбитра), этот резолвер может санкционировать **и оракула, и создателя рынка** — временно или навсегда — тем же вердиктом, поверх слэша страховки: это позволяет регулятору-резолверу отстранить недобросовестного оракула или создателя-рецидивиста от платформы. **Режим комитета/DAO (`dispute_mode == 0`) не имеет права бана по замыслу** — это прозрачные публичные слушания, которые лишь слешат страховку и корректируют репутацию (`pm_dispute_finalize`), но никогда не банят. Установленный здесь бан записывает выдавшего `resolver` в `banned_by` цели, так что снять его досрочно через `pm_unban` может только этот резолвер; иначе бан истекает на `banned_until` (крон эмитит `pm_ban_expired`). + +| Поле | Тип | Описание | +|------|-----|----------| +| `market_id` | `int64` | Оспариваемый рынок | +| `correct_outcome` | `int16_t` | Финальный верный исход (`-1` = void/no-contest) | +| `penalty_amount` | `asset` (VIZ) | Страховка оракула к слэшу | +| `ban_oracle` / `ban_oracle_until` | `bool` / `time_point_sec` | Забанить оракула до заданного времени | +| `ban_creator` / `ban_creator_until` | `bool` / `time_point_sec` | Запретить создателю создавать рынки до заданного времени | + +### `pm_dispute_oracle_respond_operation` (ID 98) +**Auth:** `active` аккаунта `oracle` + +Оракул рынка публикует **публичное опровержение** на открытый спор. Поскольку спор — это публичные слушания, текст сохраняется на объекте спора (`oracle_response` / `oracle_response_time`, читается через `get_dispute`), чтобы каждый голосующий/резолвер мог его учесть. Разрешено только пока спор открыт и `now ≤ oracle_response_deadline`; повторная публикация перезаписывает предыдущий ответ. + +| Поле | Тип | Описание | +|------|-----|----------| +| `market_id` | `int64` | Оспариваемый рынок | +| `response` | `string` | Текст опровержения, непустой, `≤ MAX_PM_DISPUTE_REASON_LEN` | + +### `pm_unban_operation` (ID 99) +**Auth:** `active` аккаунта `resolver` + +Снимает бан, наложенный `pm_dispute_resolve` режима аккаунта, **досрочно**. Снять его может только аккаунт, записанный в `banned_by` цели (резолвер, установивший бан); хотя бы один из `unban_oracle` / `unban_creator` должен быть задан, а соответствующий бан — быть активным сейчас. Ставит `banned_until` в прошлое и очищает `banned_by`. (Баны, не снятые здесь, просто истекают на `banned_until` — тогда крон эмитит `pm_ban_expired`.) + +| Поле | Тип | Описание | +|------|-----|----------| +| `resolver` | `account_name_type` | Аккаунт, наложивший бан (должен равняться `banned_by` цели) | +| `target` | `account_name_type` | Забаненный оракул / создатель | +| `unban_oracle` | `bool` | Снять бан оракула (`pm_oracle_object.banned_until`) | +| `unban_creator` | `bool` | Снять бан создателя (`pm_creator_ban_object.banned_until`) | ### `pm_transfer_position_operation` (ID 81) **Auth:** `active` аккаунта `from` @@ -213,6 +254,12 @@ Commit-reveal фаза 2: раскрывает ставку и ставит её | 95 | `pm_leverage_resolve_operation` | Расчёт — плечевая позиция принудительно закрыта: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` | | 96 | `pm_market_accepted_operation` | Эвалуатор — рынок запущен: оракул принял, self-oracle или авто-приём; замороженные условия + флаг `self_oracle` | | 97 | `pm_payout_operation` | Расчёт — на каждую активную ставку: `amount` (стейк), `side`/`outcome_index`, `payout` (**0 при проигрыше**) | +| 100 | `pm_ban_expired_operation` | Временный бан оракула/создателя истёк на `banned_until`: крон снял его (поля `account`, `oracle`, `creator`). Досрочное ручное снятие — через подписанный `pm_unban` | +| 101 | `pm_market_expired_operation` | Дедлайн `accept_deadline` ожидающего рынка прошёл: оракул не принял/отклонил в течение `pm_oracle_accept_window_sec` — рынок аннулирован, сид возвращён (комиссия за создание удержана). Поля `oracle`, `creator`, `market_id`, `refunded_liquidity` | + +> ID 91–93 — это *обычные* операции `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` (см. +> спецификацию); ID 98–99 — *обычные* операции `pm_dispute_oracle_respond`/`pm_unban` (выше). Результаты +> по каждому беттору — `pm_payout`; помарочный `pm_auto_payout` остаётся маркером расчёта. --- diff --git a/@l10n/ru/docs/protocol/operations/validators.md b/@l10n/ru/docs/protocol/operations/validators.md index 845b5363c4..bdeca93009 100644 --- a/@l10n/ru/docs/protocol/operations/validators.md +++ b/@l10n/ru/docs/protocol/operations/validators.md @@ -163,4 +163,39 @@ --- +## `set_reward_sharing_operation` (ID 64) + +**Авторизация:** `active` `owner` + +**Разделение награды валидатора (HF13).** Валидатор выбирает пересылать часть своей блочной награды своим **стейкхолдерам** — аккаунтам, проголосовавшим за него, — пропорционально взвешенному по времени весу голоса. `sharing_rate` — эта доля в базисных пунктах; общий пул накапливается и распределяется в конце каждой эпохи через виртуальную операцию `stakeholder_reward`. + +| Поле | Тип | Описание | +|------|-----|---------| +| `owner` | `account_name_type` | Валидатор, устанавливающий свою долю разделения | +| `sharing_rate` | `uint16_t` | Доля блочной награды, пересылаемая стейкхолдерам, в базисных пунктах (0 = ничего, 10000 = 100%) | + +```json +[64, { + "owner": "alice", + "sharing_rate": 2500 +}] +``` + +- `sharing_rate` ограничен сверху значением 10000 (100%). +- Распределение идёт по **взвешенному по времени** весу голоса, поэтому недавно добавленные голоса получают меньшую долю, пока не «созреют». + +--- + +## `stakeholder_reward_operation` (ID 65) — виртуальная + +Эмитируется в каждую эпоху распределения, когда валидатор с ненулевым `sharing_rate` выплачивает стейкхолдеру его долю общей блочной награды. Виртуальная (никогда не подписывается); появляется в `account_history`. + +| Поле | Тип | Описание | +|------|-----|---------| +| `validator` | `account_name_type` | Валидатор, разделивший награду | +| `stakeholder` | `account_name_type` | Голосующий, получающий долю | +| `shares` | `asset` (SHARES) | Сумма, зачисленная стейкхолдеру | + +--- + См. также: [Типы данных](../data-types.md), [Обзор операций](./overview.md), [Свойства цепочки](../../governance/chain-properties.md). diff --git a/@l10n/ru/docs/protocol/virtual-operations.md b/@l10n/ru/docs/protocol/virtual-operations.md index a0d2b158da..909bbf4a06 100644 --- a/@l10n/ru/docs/protocol/virtual-operations.md +++ b/@l10n/ru/docs/protocol/virtual-operations.md @@ -351,4 +351,32 @@ --- +## Прогнозные рынки (HF14) + +Эмитируются логикой консенсуса PM — **не** кроном по часам. Два источника: +- **Эвалуатор подписанной операции**, в момент её применения — `pm_market_accepted` (при accept / self-oracle / авто-приёме) и `pm_leverage_liquidate` (при встречной или отменяющей ставке, толкающей плечевую позицию за порог). +- **Обработчик дедлайнов `process_pm_markets()`**, запускаемый каждый блок: рассчитывает рынки, достигшие **экспирации / дедлайна / окончания окна спора / границы эпохи** (кап `pm_processing_cap_per_block`, старейший дедлайн первым). + +См. [Операции прогнозных рынков](./operations/prediction-markets.md). (ID 91–93 — это *обычные* операции `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`, а ID 98–99 — *обычные* операции `pm_dispute_oracle_respond`/`pm_unban` — см. ту страницу.) + +| ID | Операция | Триггер | +|----|----------|---------| +| 84 | `pm_batch_settle_operation` | Достигнута граница эпохи: очередные ставки по снимку начала эпохи | +| 85 | `pm_commit_forfeit_operation` | `reveal_deadline` без раскрытия: штраф → `forfeit_pool`, остаток возвращён | +| 86 | `pm_auto_payout_operation` | Истекло окно спора (по рынку): паримутюэль-расчёт + возврат принципала LP | +| 87 | `pm_dispute_finalize_operation` | Достигнут `voting_end_time`: подсчёт решает; штраф оракулу; пере-разрешение/поддержка | +| 88 | `pm_dispute_auto_close_operation` | Достигнут `auto_close_time`, оракул не ответил: анти-фриз возврат, слэш страховки → DAO | +| 89 | `pm_oracle_missed_penalty_operation` | Истёк `result_expiration` без разрешения: слэш → DAO, возврат всех ставок | +| 90 | `pm_lazy_recall_operation` | Простаивающая аллокация lazy-пула достигла шага отзыва: один поэтапный шаг возвращён в пул | +| 94 | `pm_leverage_liquidate_operation` | Эвалуатор — ликвидация плеча в ходе рынка (встречная `0` / отменяющая `1` ставка, каскад) | +| 95 | `pm_leverage_resolve_operation` | Расчёт — плечевая позиция принудительно закрыта по `cancel_value`: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` | +| 96 | `pm_market_accepted_operation` | Эвалуатор — рынок запущен: оракул принял, self-oracle или авто-приём; замороженные условия + флаг `self_oracle` | +| 97 | `pm_payout_operation` | Расчёт — на каждую активную ставку: `amount` (стейк), `side`/`outcome_index`, `payout` (**0 при проигрыше**); рядом с порыночной `pm_auto_payout` | +| 100 | `pm_ban_expired_operation` | Временный бан оракула/создателя истёк на `banned_until`: крон очистил его (`account`, `oracle`, `creator`). Досрочные ручные снятия используют подписанную `pm_unban` | +| 101 | `pm_market_expired_operation` | Дедлайн `accept_deadline` пендинг-рынка прошёл: оракул не принял/отклонил в течение `pm_oracle_accept_window_sec` — рынок аннулирован (`status -1`), сид-ликвидность возвращена (`refunded_liquidity`), комиссия за создание **не** возвращена (`oracle`, `creator`, `market_id`, `refunded_liquidity`) | + +Всё движение средств PM строго zero-sum (без эмиссии); расчёт сохраняет `Σ out == Σ ставок + принципал LP + forfeit_pool`. + +--- + См. также: [Обзор операций](./operations/overview.md), [Награды](./operations/awards.md), [Комитет](./operations/committee.md). diff --git a/@l10n/zh-CN/docs/advanced/hardfork-management.md b/@l10n/zh-CN/docs/advanced/hardfork-management.md index a2dbd920a3..47c5017731 100644 --- a/@l10n/zh-CN/docs/advanced/hardfork-management.md +++ b/@l10n/zh-CN/docs/advanced/hardfork-management.md @@ -48,6 +48,8 @@ VIZ Ledger 通过确定性硬分叉系统协调协议升级。硬分叉在编译 | 10 | 通胀模型 | | 11 | 发行模型变更 | | 12 | 紧急共识恢复(见下文) | +| 13 | 分配纪元长度(`chain_properties_hf13`) | +| 14 | 预测市场 (Onix):18 个操作 + 7 个虚拟操作,CPMM/LMSR、同注分彩结算、预言机、争议、承诺-揭示、懒惰池;链属性 v5 | --- diff --git a/@l10n/zh-CN/docs/governance/chain-properties.md b/@l10n/zh-CN/docs/governance/chain-properties.md index 4ca4e23efa..41d7487dfa 100644 --- a/@l10n/zh-CN/docs/governance/chain-properties.md +++ b/@l10n/zh-CN/docs/governance/chain-properties.md @@ -122,8 +122,28 @@ | `chain_properties_hf4` | 1 | HF4 | inflation_validator_percent、inflation_ratio_committee_vs_reward_fund、inflation_recalc_period | | `chain_properties_hf6` | 2 | HF6 | data_operations_cost_additional_bandwidth、validator_miss_penalty_percent、validator_miss_penalty_duration | | `chain_properties_hf9` | 3 | HF9 | create_invite_min_balance、committee_create_request_fee、create_paid_subscription_fee、account_on_sale_fee、subaccount_on_sale_fee、validator_declaration_fee、withdraw_intervals | +| `chain_properties_hf13` | 4 | HF13 | distribution_epoch_length | +| `chain_properties_pm` | 5 | HF14 | ~30 个预测市场参数 + 终止开关 `pm_commit_reveal_enabled`、`pm_lazy_pool_enabled` | -所有新的验证者属性提交请使用版本索引 3(`chain_properties_hf9`)。 +所有新的验证者属性提交请使用版本索引 **5**(`chain_properties_pm`)。索引 4 为 `chain_properties_hf13`(`distribution_epoch_length`)。 + +### 预测市场参数 (v5, HF14) {#pm-parameters} + +均为中位数投票;参见 [预测市场操作](../protocol/operations/prediction-markets.md)。 + +所有 PM 百分比均以 bp 计(10000 = 100.00%),与其他 `*_percent` 一致;不再使用千分比(‰)。 + +- **预言机:** `pm_min_oracle_insurance`、`pm_max_oracle_fee_percent`(**唯一**的费率治理上限——针对预言机 %)、`pm_oracle_registration_fee`、`pm_oracle_penalty_percent`、`pm_oracle_dispute_response_sec`、`pm_oracle_accept_window_sec`(默认 3600 = 1 小时——指定的预言机须在此窗口内接受或拒绝待定市场;超时后 cron 向创建者退还种子流动性,但**不**退还创建费,并作废市场 → `pm_market_expired`)。 +- **风险 / 覆盖率** *(市场下注量的百分比,100 = 1.0×):* `pm_listing_min_coverage_percent`(250 = 2.5×)——预言机保险覆盖低于其下注量此比例的市场,会从默认 `list_markets` 目录中隐藏(经 `show_risky` 显示);`pm_betting_min_coverage_percent`(150 = 1.5×)——建议性阈值,发布供客户端在下注前要求显式风险确认(不在链上强制;须 `≤ pm_listing_min_coverage_percent`)。 +- **市场:** `pm_min_liquidity`、`pm_market_creation_fee`、`pm_max_outcomes`、`pm_max_market_duration`。*(无聚合费率上限;creator/liquidity 费率无上限、自我约束;静态 `总和 ≤ 100%` 偿付不变式。)* +- **批次 / 承诺-揭示:** `pm_batch_epoch_blocks`、`pm_reveal_window_blocks`、`pm_min_batch_bet`、`pm_commit_no_reveal_penalty_percent`、`pm_commit_reveal_enabled`。 +- **争议:** `pm_dispute_fee`、`pm_dispute_grace_sec`、`pm_dispute_vote_period_sec`、`pm_dispute_auto_close_sec`、`pm_dispute_approve_min_percent`、`pm_no_contest_penalty_percent`、`pm_dispute_reward_multiplier`(bp 乘数,10000 = 1×)。 +- **时间惩罚:** `pm_default_time_penalty_percent`、`pm_max_time_penalty`。 +- **懒惰池:** `pm_lazy_pool_enabled`、`pm_lazy_alloc_percent`、`pm_lazy_max_total_alloc_percent`、`pm_lazy_recall_step_percent`、`pm_lazy_lock_sec`、`pm_lazy_emergency_penalty_percent`、`pm_lazy_min_liquidity_fee_percent`(默认 200 = 2%——池拒绝为 `liquidity_fee_percent` 低于此奖励下限的市场共同提供流动性)。 +- **杠杆(可选):** `pm_leverage_enabled`、`pm_leverage_fund_percent`、`pm_leverage_max_per_position_bp`、`pm_leverage_max_position_ratio_percent`、`pm_leverage_min_market_liquidity`、`pm_leverage_safety_margin_percent`、`pm_leverage_max_slippage_percent`、`pm_leverage_m_factor_percent`、`pm_leverage_pool_profit_percent`、`pm_leverage_expiration_buffer_sec`、`pm_conversion_profit_cost_percent`。 +- **公平性:** `pm_processing_cap_per_block`。 + +三个 `*_enabled` 标志(`pm_commit_reveal_enabled`、`pm_lazy_pool_enabled`、`pm_leverage_enabled`)为实时终止开关:验证者中位数可在无需新硬分叉的情况下停用承诺-揭示、懒惰池或杠杆。 --- diff --git a/@l10n/zh-CN/docs/node/configuration.md b/@l10n/zh-CN/docs/node/configuration.md index 5804cc6777..d7d8e1bc21 100644 --- a/@l10n/zh-CN/docs/node/configuration.md +++ b/@l10n/zh-CN/docs/node/configuration.md @@ -158,6 +158,11 @@ skip-virtual-ops = false # 允许在链过期时生产(仅用于开发/测试网) enable-stale-production = false +# 禁用少数派 fork 检测(仅用于单运营者测试网/分叉)。 +# 与 enable-stale-production 不同,它不会在健康参与度下被自动清除。 +# 切勿在真实的公共网络上启用。 +disable-minority-fork-detection = false + # 生产区块所需的最低参与度 % (0–99) required-participation = 33 @@ -203,4 +208,4 @@ logger.p2p.appenders = p2p | `plugins/chain/plugin.hpp` | `shared-file-size`, `min-free-shared-file-size`, `inc-shared-file-size`, `block-num-check-free-size`, `single-write-thread`, `enable-plugins-on-push-transaction`, `read-wait-micro`, `max-read-wait-retries`, `write-wait-micro`, `max-write-wait-retries`, `skip-virtual-ops`, `clear-votes-before-block`, `track-account-range`, `history-whitelist-ops`, `history-blacklist-ops`, `history-start-block` | | `plugins/p2p/p2p_plugin.hpp` | `p2p-endpoint`, `p2p-max-connections`, `p2p-seed-node`, `checkpoint` | | `plugins/webserver/webserver_plugin.hpp` | `webserver-http-endpoint`, `webserver-ws-endpoint`, `webserver-thread-pool-size` | -| `plugins/validator/validator.hpp` | `enable-stale-production`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` | +| `plugins/validator/validator.hpp` | `enable-stale-production`, `disable-minority-fork-detection`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` | diff --git a/@l10n/zh-CN/docs/node/validator-node.md b/@l10n/zh-CN/docs/node/validator-node.md index f59683fce1..a95ea3579b 100644 --- a/@l10n/zh-CN/docs/node/validator-node.md +++ b/@l10n/zh-CN/docs/node/validator-node.md @@ -164,6 +164,9 @@ docker run -d \ ### 少数派 Fork 检测 如果节点的 fork 数据库显示 21+ 个连续区块全部来自此节点自己的验证者,它会自动回滚到 LIB 并重新同步。这可以捕获网络隔离情况。 +> [!WARNING] 单运营者分叉 +> 在**一个运营者控制所有验证者**的测试网或主网分叉上,「连续 21 个区块都是我们的」是正常的健康状态,因此检测器会无限循环地回滚到 LIB。此时 `enable-stale-production = true` **无效**:在参与率 ≥33% 时,该覆盖会在每个区块被自动清除。请改用 `disable-minority-fork-detection = true`——它会完全绕过标准检测路径和 DLT 检测路径,并且永远不会被自动清除。**切勿在真实的公共网络上启用**——它会移除隔离保护。 + ### 生产 Watchdog 如果在 `should_be_producing` 为 true 的情况下 180 秒内(紧急主节点为 60 秒)没有生产区块,watchdog 会自动清除卡住的标志(`minority_fork_recovering`、P2P 追赶、链同步)并尝试恢复生产。 diff --git a/@l10n/zh-CN/docs/plugins/prediction-market-api.md b/@l10n/zh-CN/docs/plugins/prediction-market-api.md new file mode 100644 index 0000000000..6a350cfcd4 --- /dev/null +++ b/@l10n/zh-CN/docs/plugins/prediction-market-api.md @@ -0,0 +1,186 @@ +# `prediction_market_api` 插件 + +对 HF14 预测市场状态的只读 JSON-RPC 访问(市场、下注、预言机、流动性、争议、懒惰池、链属性 v5)。插件直接返回原始共识 `pm_*` 对象,外加少量计算型 DTO。 + +**启用:** 将 `prediction_market_api` 加入节点插件列表(`vizd` 默认注册)。依赖 `chain` + `json_rpc`。所有列表方法通过 `from`(跳过)与 `limit`(`≤ 1000`)分页。 + +## 方法 + +### 市场 + +| 方法 | 参数 | 返回 | +|------|------|------| +| `get_market` | `market_id` | `pm_market_object` | +| `list_markets` | `status, from, limit, [show_risky]` | `pm_market_object[]` | +| `list_markets_by_oracle` | `oracle, from, limit` | `pm_market_object[]` | +| `list_markets_by_creator` | `creator, from, limit` | `pm_market_object[]` | +| `get_market_outcomes` | `market_id` | `pm_outcome_object[]` | +| `get_market_weight_sums` | `market_id` | `pm_market_weight_sums`(计算型) | +| `get_market_bets` | `market_id, from, limit` | `pm_bet_object[]` | +| `get_market_liquidity` | `market_id, from, limit` | `pm_liquidity_object[]` | +| `get_market_full` | `market_id, [account]` | `pm_market_full`(计算型) | + +`list_markets` 的 `status`:`-1` 已删除、`0` 等待、`1` 活跃、`2` 关闭、`3` 已裁定。默认情况下 +`list_markets` 会隐藏保证金不足的市场(预言机保险 < 下注量的 **2.5×**);`show_risky = true` 可显示它们 +(仅隐藏,链上始终允许下注)。 + +`get_market_full` 是市场详情页的**单次调用富集视图**:返回市场 + 结果 + 权重合计 + 预言机(含可靠度)+ 已解析元数据,且——当提供可选 `account` 时——返回该账户**在本市场上**的下注、杠杆头寸与 LP。为瘦客户端省去多次往返。 + +### 市场元数据(链下解析) + +每个市场携带一个自由格式、共识不透明的 `metadata` JSON 字符串。插件将其索引的键(类别 / 子类别 / 标签 / +受禁司法辖区)解析为 `pm_market_meta_object`——**仅用于展示/索引,非共识**。 + +| 方法 | 参数 | 返回 | +|------|------|------| +| `get_market_meta` | `market_id` | `pm_market_meta_object`(无则报错) | +| `list_markets_by_category` | `category, from, limit, [jurisdiction], [subcategory], [tag], [sort]` | `pm_market_meta_object[]` | +| `get_market_categories` | — | `pm_market_categories`(计算型) | + +`list_markets_by_category` 会排除其 `banned_jurisdictions` 含可选 ISO 代码 `jurisdiction` 的市场(受监管 +客户端传入自身辖区即可只获取可列出的市场)。可选的 `subcategory`(精确)与 `tag`(CSV 成员)进一步收窄集合; +`sort` ∈ `newest`(市场 id 降序,默认)· `oldest` · `volume`(`bets_sum` 降序)· `expiration`(`betting_expiration` +升序)。`get_market_categories` 返回实时分类法——每类别 / 每子类别计数,外加前 20 个热门标签(排除 jurisdiction-*)—— +在当前已索引市场上聚合,使浏览 UI 无需硬编码分类法即可构建其筛选标签。对象:`market`、`category`、`subcategory`、`tags`(逗号分隔)、 +`banned_jurisdictions`(逗号分隔 ISO;为空 = 全球允许)、`expiry`(争议窗口关闭 + TTL 后清理)。 + +### 持仓与预言机 + +| 方法 | 参数 | 返回 | +|------|------|------| +| `get_account_positions` | `account, from, limit` | `pm_position[]`(下注 + `expected_payout`) | +| `get_account_leverage_positions` | `account, from, limit` | `pm_leverage_position_object[]` | +| `get_market_leverage_positions` | `market_id, from, limit` | `pm_leverage_position_object[]` | +| `get_creator_ban` | `account` | `pm_creator_ban_object`(无则报错) | +| `get_oracle` | `owner` | `pm_oracle`(对象 + `reliability_score`) | +| `list_oracles` | `from, limit` | `pm_oracle_object[]` | + +> 每位下注者的结算以 `pm_payout` 虚拟操作发出(本金、side/outcome、结果;输则为 `0`);杠杆头寸的结算为 +> `pm_leverage_resolve`(`outcome_index`、`won`、`leverage`)。两者均见于 `account_history`;头寸对象本身可经上述方法查询。 + +### 杠杆预览(Boost) + +只读报价,调用评估器所用的**同一套节点内保证金数学**,因此预览与对应 `pm_leverage_*` 操作在头区块的计算结果一致。它们是非共识估算(读取与广播之间储备会变动——务必发送链上滑点保护)。 + +| 方法 | 参数 | 返回 | +|------|------|------| +| `get_leverage_quote` | `market_id, outcome_index, collateral` | `pm_leverage_quote`(计算型) | +| `get_leverage_close_preview` | `position_id` | `pm_leverage_close_preview`(计算型) | +| `get_leverage_convert_preview` | `position_id` | `pm_leverage_convert_preview`(计算型) | + +`get_leverage_quote` 镜像 `pm_leverage_open`:返回最大偿付贷款与由此得到的最大杠杆、池/头寸上限、至多 12 个滑块档位(每档含代币、阈值、当前及最坏情形取消价值),且——当无法杠杆时——返回 `available = false` 并附 `failed_constraints[]` 列表。`get_leverage_close_preview` / `get_leverage_convert_preview` 在当前储备下镜像 `pm_leverage_close` / `pm_leverage_convert`(取消价值、池义务、下注者所得、是否可平仓/可转换,以及按当前中位数 `pm_conversion_profit_cost_percent` 的转换费)。 + +### 争议、懒惰池、治理 + +| 方法 | 参数 | 返回 | +|------|------|------| +| `get_dispute` | `market_id` | `pm_dispute_object` | +| `get_dispute_votes` | `market_id` | `pm_dispute_votes`(投票 + 实时计票) | +| `get_lazy_pool` | — | `pm_lazy_pool_object` | +| `get_lazy_deposit` | `account` | `pm_lazy_deposit_object` | +| `get_lazy_allocations` | `from, limit` | `pm_lazy_allocation_object[]` | +| `get_market_lazy_allocation` | `market_id` | `pm_lazy_allocation_object`(无则报错) | +| `get_pm_chain_properties` | — | `chain_properties_pm`(中位数, v5) | + +`get_lazy_allocations` 列出懒惰池的每市场分配记录(用于池仪表盘);`get_market_lazy_allocation` 获取给定市场的那一条。预言机罚分戳记无需单独方法——它们随 `pm_oracle_object`(`penalty_stamps`、`last_penalty_stamp_time`)经 `get_oracle` 一并返回。 + +### 图表 —— kline / 权重历史 + +用于绘制每个结果权重随时间变化的时间序列。**每当市场的各结果权重发生变化时**(下注、取消、清算、批次结算、杠杆开仓或杠杆结算),插件追加一个点 —— 即各结果同注分彩权重(下注额)的带时间戳快照。这是**非共识**插件状态(存于 chainbase,undo/redo 安全,不计入状态哈希);历史从插件在节点上首次启用时开始累积。 + +**保留:** kline 历史与市场元数据**一同清理**,遵循同一时间表 —— `result_expiration` + 争议宽限 + `pmm-ttl-days`(默认 7)。市场的完整图表在其存续期间及结算后的保留窗口内均可用,随后两个索引被清理(极长历史会分多个区块逐步清空),以使节点存储保持有界。 + +| 方法 | 参数 | 返回 | +|------|------|------| +| `get_market_kline` | `market_id, [from], [limit]` | `pm_kline[]`(按 `seq` 升序) | + +分页为**从最新偏移**(为瘦客户端刻意保持简单):`from` 为跳过的**最新**点数量,`limit ≤ 1000` 为页大小。 +- `(market_id, 0, 1000)` → 最新 ≤ 1000 个变化。 +- `(market_id, 1000, 1000)` → 再往前一页的 1000 个 —— 以 `from += 1000` 重复以惰性加载更早历史。 + +绘图:x = `timestamp`(unix 秒),每个结果 `i` 一条线,y = `weights[i]`(或归一化 `weights[i] / Σweights`,即隐含概率)。 + +## 计算型 DTO + +- **`pm_position`** —— 下注 + `expected_payout`(若该方获胜的赔付,或结算后已实现值;与 `settle_market` 逐字节一致)、`market_status`、`resolved_outcome`。 +- **`pm_oracle`** —— 预言机对象 + `reliability_score`(bp `[0..10000]`,非共识启发式:裁定成功率与争议胜率的混合,再减去封禁罚分)。 +- **`pm_market_weight_sums`** —— 各方/各结果的 `bets_sum`/`weight_sum`(权重通过扫描下注计算,因其不存储)。 +- **`pm_kline`** —— 一个图表点:`seq`(uint32,0 起、按市场单调递增的变化索引)、`timestamp`(unix 秒,x)、`reason`(uint8:0 下注、1 取消、2 清算、3 批次结算、4 杠杆开仓、5 杠杆结算)、`bets_sum`(总下注额)、`weights[]`(每个结果的权重,y;索引 = outcome_index)。 +- **`pm_dispute_votes`** —— 投票 + finalize 计票。旧字段(权重 = `|vote_percent|`,非质押):`uphold_weight`/`challenge_weight`/`total_weight`、`challenger_leads`(≥ `pm_dispute_approve_min_percent`)、`proposed_outcome`。**精确的按质押加权投影(镜像 `pm_dispute_finalize`;所有 `*_shares` 为 vesting-shares = `effective_vesting_shares` + 懒惰池质押→shares):** `participation_shares`(已投票者权重之和)、`electorate_shares`(`total_vesting_shares` + 池 NAV→shares)、`quorum_required_shares`、`quorum_percent_bp`(法定人数,bp,10000 = 100.00%)、`quorum_reached`(bool)、`oracle_defense_shares`/`change_shares`、`outcome_change_shares[]`(按结果)、`expected_uphold`(预言机裁决是否维持)、`expected_outcome`(当前若裁决将设定的结果)、`expected_consensus_strength_bp`。该投影与定时任务在 `voting_end_time` 按当前投票应用的结果一致(在此之前投票可更改)。 + +**`pm_market_full`** —— 单次调用的富集市场视图(`oracle`/`meta` 缺失时为 `null`;除非提供 `account` 参数,否则 `my_*` 数组为空): +``` +{ market: pm_market_object, + outcomes: pm_outcome_object[], // 二元市场为空 + weight_sums: pm_market_weight_sums, + oracle: pm_oracle | null, + meta: pm_market_meta_object | null, + my_positions: pm_position[], // 账户在本市场的下注 + my_leverage_positions: pm_leverage_position_object[], + my_liquidity: pm_liquidity_object[] } +``` + +**`pm_leverage_quote`** —— 杠杆开仓预览(来自 `pm::leverage::*`,即评估器运行的同一套数学): +``` +{ available: bool, outcome_index, collateral, + max_loan, max_leverage_x100, // 100 = 1.00× + pool_free_amount, fund_available, per_position_cap, market_position_cap, + pool_profit_percent, safety_margin_percent, max_slippage_percent, m_factor_percent, + expiration_buffer_sec, auto_close_time, // betting_expiration − buffer + stops: [ { leverage_x100, loan, total_bet, expected_tokens, pool_profit, + liquidation_threshold, current_cancel_value, worst_case_cancel_value } ], + failed_constraints: [ { constraint, reason } ] } // !available 时填充 +``` +**`pm_leverage_close_preview`** —— `{ position_id, outcome_index, cancel_value, pool_obligation, bettor_receives, collateral, loan, pool_profit_charge, closeable: bool, loss_vs_collateral, loss_percent_bp }`。 +**`pm_leverage_convert_preview`** —— `{ position_id, outcome_index, cancel_value, pool_obligation, current_profit, conversion_profit_cost_percent, conversion_fee, total_user_payment, convertible: bool }`。 + +**`pm_market_categories`** —— 带实时计数的浏览分类法: +``` +{ categories: [ { category, count, subcategories: [ { subcategory, count } ] } ], // 按 count 降序 + hot_tags: [ { tag, count } ] } // 前 20(排除 jurisdiction-*) +``` + +## 示例 + +市场 `42` 最新 1000 个图表点,再取前 1000 个: +```bash +# 最新一页 +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_market_kline",[42,0,1000]]}' http://127.0.0.1:8090 +# 往前一页 +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_market_kline",[42,1000,1000]]}' http://127.0.0.1:8090 +``` + +瘦客户端(向后滚动时惰性加载更早历史)—— 将每个点按结果转成 `{ x: unixtime, y: weight }` 序列: +```js +async function call(method, params) { + const r = await fetch('http://127.0.0.1:8090', { method: 'POST', + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'call', + params: ['prediction_market_api', method, params] }) }); + return (await r.json()).result; +} + +// 从最新向后按每页 1000 拉取,直到凑够 `want` 个点(或历史耗尽)。 +async function loadKline(marketId, want = 3000) { + const points = []; + for (let from = 0; points.length < want; from += 1000) { + const page = await call('get_market_kline', [marketId, from, 1000]); + if (!page.length) break; // 到达历史起点 + points.unshift(...page); // 页内升序;更早的页前置 + if (page.length < 1000) break; + } + return points; +} + +// 每个结果一条 {x,y} 序列 —— 直接喂给任意图表库。 +function toSeries(points, outcomeCount) { + const series = Array.from({ length: outcomeCount }, () => []); + for (const p of points) + for (let i = 0; i < outcomeCount; i++) + series[i].push({ x: p.timestamp, y: Number(p.weights[i]) }); + return series; +} +``` + +参见 [预测市场操作](../protocol/operations/prediction-markets.md) 与 [链属性](../governance/chain-properties.md#pm-parameters)。 diff --git a/@l10n/zh-CN/docs/plugins/validator.md b/@l10n/zh-CN/docs/plugins/validator.md index 1b8ee701de..d6215fe8a2 100644 --- a/@l10n/zh-CN/docs/plugins/validator.md +++ b/@l10n/zh-CN/docs/plugins/validator.md @@ -24,6 +24,7 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin | `private-key` | — | 用于签名的 WIF 私钥;可重复 | | `emergency-private-key` | — | 紧急共识的 WIF 密钥;自动将 `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` 添加到验证者集合 | | `enable-stale-production` | `false` | 绕过参与度和同步检查(仅用于测试网/网络恢复) | +| `disable-minority-fork-detection` | `false` | 完全跳过少数派 fork 检测(仅用于单运营者测试网/分叉)。在健康参与度下永远不会被自动清除——参见[少数派 Fork 检测](#少数派-fork-检测) | | `required-participation` | `3300` | 最低验证者参与度(**基点**,3300 = 33%) | | `fork-collision-timeout-blocks` | `21` | 强制生产前的连续 fork 冲突延迟次数(一个完整的验证者轮次) | @@ -116,7 +117,8 @@ T=6.000s 的槽位: 在每次生产尝试之前(在 HF12 安全检查之后),插件遍历 `fork_db` 中最后 21 个区块。如果所有 21 个都由节点自己配置的验证者生产,则节点被隔离在少数派 fork 上。 - **默认操作:** 调用 `p2p().resync_from_lib()` — 回滚到 LIB,重置 fork DB,重新启动 P2P 同步,重新连接种子节点。返回 `minority_fork`。 -- **使用 `enable-stale-production=true`:** 记录警告,继续生产。 +- **使用 `enable-stale-production=true`:** 记录警告,继续生产。**注意:** 在参与度 ≥33% 时,该覆盖会在每个区块被自动清除,因此在单运营者分叉上它*不会*停止检测器——请改用 `disable-minority-fork-detection`。 +- **使用 `disable-minority-fork-detection=true`:** 完全跳过标准检测路径和 DLT 检测路径,且该标志永远不会被自动清除。适用于「连续 21 个区块都是我们的」为健康稳态的单运营者测试网/分叉。**切勿在真实的公共网络上启用**——它会移除隔离保护。 - **跳过时机:** 紧急共识激活时(committee 区块总会匹配我们配置的集合)。在紧急模式下,DLT 特定的从节点隔离检查取代它。 --- @@ -217,7 +219,7 @@ validator[skip_flags=0x0 catching_up=0 head=#79881136 last_prod=45s_ago minority | `no_private_key` | 配置中缺少链上注册的签名密钥对应的 `private-key` | | `low_participation` | 网络参与度 < 33%;检查节点连接或设置 `enable-stale-production=true` | | `fork_collision` | 下一高度有竞争区块;等待投票权重解决或 21 次延迟超时 | -| `minority_fork` | 已隔离;插件自动重新同步到 LIB | +| `minority_fork` | 已隔离;插件自动重新同步到 LIB。在单运营者分叉上会无限循环——请设置 `disable-minority-fork-detection=true` | | Watchdog 重复触发 | 同步或追赶标志卡住;头部推进时 watchdog 会自动清除 | | `SLOT-HIJACK` 日志 | 紧急主节点清空了我们的密钥;通过 `validator_update_operation` 恢复 | diff --git a/@l10n/zh-CN/docs/prediction-markets/concepts-analysis.md b/@l10n/zh-CN/docs/prediction-markets/concepts-analysis.md new file mode 100644 index 0000000000..c2b271258d --- /dev/null +++ b/@l10n/zh-CN/docs/prediction-markets/concepts-analysis.md @@ -0,0 +1,204 @@ +--- +title: 概念分析 —— Onix 对照 90 个预测市场概念 +description: VIZ 链上实现(Onix 协议、Forecaster 客户端)如何映射到 90 个预测市场理论概念——哪些已解决、固有、不需要或在路线图中。 +--- + +# 概念分析 —— Onix 对照 90 个理论概念 + +> **Forecaster** 是 VIZ 链上预测市场的瘦客户端——让全球用户通过签署 `pm_*` 操作参与的接入层 +> (见[板块总览](./))。**Onix** 是它所对接的协议。本页将 **90 个 PM-Atlas 预测市场概念**逐一映射到 +> **VIZ 链上实现如何处理它**,以及该概念对本架构**是否必要**。 +> +> 依据文档:[白皮书](./whitepaper)、[规范](./specification)、[工作流与争议](./workflows)。 + +## 图例 + +| 标记 | 含义 | +|------|---------| +| ✅ **已解决** | Onix 设计直接解决/处理 | +| ⚪ **固有** | 由构造自然具备(无需额外工作) | +| ➖ **不需要** | 在 Onix 下架构上不必要 | +| 🟡 **部分 / 路线图** | 今日部分解决;其余在 VIZ 路线图 | +| 🏛 **客户端层** | 由司法辖区客户端处理,而非协议 | +| 🔴 **开放 / 风险** | 仍是现实关切;未完全解决 | + +与其他平台最大的结构性差异:**LP 本金结构性保障(赢家只从输家被没收的本金中获得赔付),二元用 CPMM、多元用 +LMSR-softmax + 同注分彩结算,且无订单簿。** 大量「流动性与交易」概念存在的目的就是管理做市商库存风险,因此对 +Onix **不适用**。 + +> **链上实化(HF14 / 上线)。** 本映射最初针对白皮书/规范撰写。若干当时标为*路线图*的项现已**实现为共识操作** +> 并在 `consensus_sim` 验证:批量拍卖 + 提交-揭示下注(`pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle`, +> 二元,中位数 kill-switch `pm_commit_reveal_enabled`);可选杠杆子系统(`pm_leverage_open/close/convert`, +> 懒惰池提供资金,kill-switch `pm_leverage_enabled`);懒惰池本身;`endogeneity_tier` 字段;链上创建者封禁; +> 更丰富的结算虚拟操作(`pm_payout`、`pm_leverage_resolve`、`pm_market_accepted`)+ 配套插件 API;以及懒惰 +> 池质押在 PM 争议与 DAO 提案投票中计为治理权重。**刻意未做:** 争议的提交-揭示投票——委员会争议按设计为公开 +> 听证。**仍是路线图:** 自动外生数据预言机。 + +## 1. 信息论 + +| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 | +|---|---------|---------|--------------------------------------------------| +| 1 | **Brier Score** | ⚪ 固有 | 非协议机制,而是评判 Onix 市场的*指标*。每笔下注/裁定都是共识事件,价格史与结果全在链上 → 任何人可对平台(及预言机)做 Brier 评分。仅作分析/声誉输入,非核心逻辑。 | +| 2 | **Calibration** | ⚪ 固有 | Onix 价格是真实概率(CPMM、LMSR softmax 之和为 1)。校准是可*测量*的涌现属性,由时间惩罚与深度 LP 间接改善;协议不强制。 | +| 3 | **Credibility Markets** | 🟡 部分 | 保证金预言机 + 14 项声誉 + 综合信任分本质上是*裁决者*的信誉市场。「以信誉质押于结果」是原生的。通用「为主张质押信誉」产品可在客户端层构建,非核心。 | +| 4 | **Distribution Markets** | 🟡 路线图 | Onix Multi(3–10 离散结果)以桶近似分布。真正的连续分布市场(CDF/标量)当前**不**在范围;需标量结果操作。 | +| 5 | **Endogeneity** | 🟡 部分(已缓解) | 市场改变其所预测之物的风险。由**已上线的 `endogeneity_tier` 字段**与**可选提交-揭示/批量下注(已上线)**缓解,使公开价格在窗口内不「泄露」;外生裁定仍是路线图。见[缓解](#fanshenxing-jiazu-de-huanjie)。 | +| 6 | **Forecasting Accuracy** | ⚪ 固有 | 整体价值主张。Onix 间接改善:无风险 LP → 更深订单 → 更少滑点 → 更多知情参与 → 更好价格。 | +| 7 | **Info Finance** | ⚪ 固有 | Onix *就是*信息金融工具:共识操作把信息变为可定价、可结算的头寸。迁移到 VIZ 使信息层抗审查、可组合。 | +| 8 | **Information Aggregation** | ✅ 已解决 | 核心功能。CPMM/LMSR 把分散下注聚合为单一概率;无风险 LP 深度正是使聚合奏效的杠杆。 | +| 9 | **Information Asymmetry** | 🟡 部分 | 同注分彩/CPMM 意味着知情者从*其他输家*而非 LP 获利——故不会拖垮流动性。**可选提交-揭示 + 批量下注现已上线(二元):** 承诺下注按统一批价结算,消除 mempool 方向泄露;per-market `allow_batch` + 中位数 kill-switch 保持可选。 | +| 10 | **Legibility** | ✅ 已解决 | 每个金融动作都是共识 VIZ 操作(前/后储备审计)。任何节点可读可审——远比中心化后端或不透明 CLOB 更清晰。新结算虚拟操作(`pm_payout`、`pm_leverage_resolve`、`pm_market_accepted`)+ 插件方法使逐下注者结果与杠杆结算可直接查询。 | +| 11 | **Longshot Bias** | 🟡 部分 | CPMM/LMSR 仍可能因下注行为出现冷门偏差;Onix 不直接纠正。时间惩罚与深度抑制扭曲,但偏差是行为输出,未消除。 | +| 12 | **Noise Decomposition** | ⚪ 固有 | 分析视角,非协议特性。链上价格/成交量序列使「信号 vs 噪声」分解对分析者可行。 | +| 13 | **Nowcasting** | ⚪ 固有 | Onix 价格随每笔下注更新(~3 秒块),给出实时即时预测。对任何活跃 AMM 市场固有。 | +| 14 | **Price Discovery** | ✅ 已解决 | CPMM 与 LMSR-softmax 是连续价格发现引擎;价格一致性(`Σ price = 1`)按构造成立,无需套利/拆合层。 | +| 15 | **Probability Infrastructure** | ✅ 已解决 | 本质上即 Onix 在 VIZ 的论点:预测市场作为**一等公民共识操作**(`pm_*`),而非智能合约——一种底层概率原语。 | +| 16 | **Superforecasting** | ⚪ 固有 | 个体技能概念;Onix 通过输家→赢家赔付奖励准确下注者。是参与者特征,非协议逻辑。 | +| 17 | **Wisdom of Crowds** | ✅ 已解决 | Onix 货币化的机制。无风险 LP 降低门槛,让更多群体参与,锐化聚合。 | +| 18 | **Yes Bias** | 🟡 部分 | 行为上偏向「Yes」。对称 CPMM 与仅作用于利润的时间惩罚结构上不偏 Yes,但也不纠正人类偏差。 | + +## 2. 机制设计 + +| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 | +|---|---------|---------|--------------------------------------------------| +| 19 | **Binary Contracts** | ✅ 已解决 | Onix Binary = 两结果上的 CPMM(`x·y=k`)。AM-GM 证明保证 `reserve_a+reserve_b ≥ L`,覆盖 LP 本金。主市场类型。 | +| 20 | **Combinatorial Prediction Markets** | ➖ 不需要(今日) | LMSR 可在组合空间自然分解,但 Onix Multi 限于 3–10 个*独立*结果并刻意省略 CTF 拆/合。组合/条件捆绑明确不在范围。 | +| 21 | **Incentive Compatibility** | ✅ 已解决 | LMSR 从对数评分规则继承 IC(说真话占优)。Onix 再加保证金预言机(保险 > 操纵利润)、输家资助赢家、时间加权 LP 奖励对齐激励。 | +| 22 | **Keynesian Beauty Contest** | ✅ 已解决(市场)/ ⚪(争议层——按设计接受) | KBC 是*相对/对等评分*的病态。Onix **市场**层让下注者对外部真相(同注分彩)获酬,故结构上反 KBC——价格错时你因*偏离*而获利。唯一暴露是**按质押加权的委员会争议投票**。**决定:争议提交-揭示投票不会实现**——委员会争议是**公开听证**,DAO 信誉依赖透明裁决。残余 KBC 风险被接受且很小:投票者不因与多数一致而获酬,投票可改至关闭。池内 DAO 成员亦有投票权(懒惰池质押 → vesting-shares,HF14)。见[缓解](#fanshenxing-jiazu-de-huanjie)。 | +| 23 | **LMSR** | ✅ 已解决(关键创新) | LMSR 在二元上*失败*(0/1 边界永久损失)。Onix 之解:**二元用 CPMM,LMSR 仅用于多元且做市商非对手方**——同注分彩由输家付赢家,故 LMSR 补贴从不受险(`LP 最大损失 = 0` vs `b·ln(N)`)。核心设计动作。 | +| 24 | **LOX(Log-Odds Excess Lateness)** | ➖ 不需要 | 一种专门的评分/迟到指标。Onix 改用**对利润的二次时间惩罚**处理迟下注激励——更简单的结算期机制。 | +| 25 | **Market Manipulation** | 🟡 部分 | 保证金预言机(保证金须超操纵利润)、DPoS 校验操作、委员会争议提高操纵成本。靠深度限制大额下注操纵;在**批量/提交-揭示市场(已上线)**上由统一价中和速度狙击(「狙击税」);主动监控仍是路线图。 | +| 26 | **Market Scoring Rules** | ✅ 已解决 | Onix Multi 是市场评分规则(LMSR)实现,配以同注分彩结算。直接采用。 | +| 27 | **Multi-Outcome Markets** | ✅ 已解决 | Onix Multi 以 LMSR softmax + 同注分彩处理 N=3–10,`b = S/ln(N)`。一等公民市场类型。 | +| 28 | **Parimutuel Markets** | ✅ 已解决(基础) | *两种*市场类型的结算均为同注分彩:输家被没收的本金形成 `winners_pool`,按代币份额分配。这使 LP 保障是结构性而非保险性的。 | +| 29 | **Peer Prediction** | ➖ 不需要 | 无真相的说真话方案。Onix 依赖保证金预言机 + 委员会争议,不采用对等预测评分。 | +| 30 | **Position Collateralization** | ✅ 已解决(+ 可选杠杆,上线) | 默认每笔下注全额预付(无下注时扣费)——构造上完全抵押。Onix 现**亦**提供该概念的「下一步」:**可选杠杆子系统**(`pm_leverage_open/close/convert`,kill-switch 默认关)。保证金为**来自懒惰池的贷款**(不增发——保持零和),故系统视角持仓足额抵押。会击垮 CLOB 清算引擎的二元「跳空风险」由**按下注前储备清算**处理:对向下注/结算强平回收 `min(cancel_value, obligation) ≥ loan`;唯一有界坏账路径是同侧 `pm_cancel_bet`。清算级联刻意**不**受 kill-switch 门控。 | +| 31 | **Proper Scoring Rules** | ✅ 已解决 | LMSR 是对数恰当评分规则的成本函数对偶;Onix Multi 继承其真实诱导属性。 | +| 32 | **Reflexivity** | 🟡 部分(已缓解) | endogeneity 的母概念。由同一套件缓解——**提交-揭示/批量 + `endogeneity_tier` 已上线**,外生裁定仍路线图——**外加链上创建者封禁**(`pm_creator_ban_object`)应对有害反身性(暗杀/「悬赏」、为结果制造「拥趸」的宣传市场);类别*清单*本身在客户端层。深度无风险 LP 亦抬高制造头条操纵的成本。见[缓解](#fanshenxing-jiazu-de-huanjie)。 | + +## 3. 流动性与交易 + +> **要点:** Onix **无订单簿、无承担库存的做市商**。这一大类概念专为管理 CLOB/做市商库存风险而存在,因此对 Onix **不适用**。 + +| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 | +|---|---------|---------|--------------------------------------------------| +| 33 | **Adverse Selection** | ✅ 已解决(重构) | 经典问题(知情流拖垮做市商)**无法拖垮 Onix LP**:赢家由输家付,从不动 LP 本金。知情者从其他*下注者*获利,非 LP。消除核心 LP 失效模式。 | +| 34 | **Arbitrage** | ⚪ 固有 | 市场内套利不必要:CPMM 与 LMSR-softmax 中价格一致性按构造成立。无需拆/合套利层。 | +| 35 | **Batched Auctions** | ✅ 已解决(可选,上线) | 实现为 **per-market 统一价批量**(`mode=1` + 提交-揭示 → 每 `pm_batch_epoch_blocks` 纪元 `pm_batch_settle`);仅净残量推动 AMM,同侧成交统一定价,中和速度狙击。per-market `allow_batch` + 中位数 kill-switch;今日**仅二元**。LP `Σreserve ≥ L` 不变量不变。 | +| 36 | **Bid-Ask Spread** | ➖ 不需要 | 无订单簿 → 无报价价差。「交易成本」表现为由深度决定的 CPMM/LMSR 滑点。概念不映射。 | +| 37 | **Bonding Trades** | ⚪ 固有 | 下注*就是*绑定交易:资金投入储备,仅在裁定(或撤注/转让)时释放。原生行为。 | +| 38 | **Continuous Double Auction** | ➖ 不需要 | CDA 是 Onix 明确放弃、改用 AMM 定价的 CLOB 模型。 | +| 39 | **Covariance Markets** | ➖ 不需要 | 交易事件间相关性需 Onix 刻意省略的组合/条件结构。范围外。 | +| 40 | **Cross-Platform Arbitrage** | 🟡 部分 | Onix 价格可与 Polymarket/Kalshi 背离;跨平台套利可行但在协议外。VIZ 开放 API + 无头客户端使数据可得;无原生跨平台桥。 | +| 41 | **Execution Quality** | ✅ 已解决(重构) | 无部分成交/排队。执行质量 = 确定性滑点 + 可选 `min_tokens`/`min_return` 保护,共识校验。构造上可预测。 | +| 42 | **Gap Risk** | ✅ 已解决(对 LP) | 跳空风险(突跳 0/1 抹掉做市商)正是 Onix 结构性 LP 保障消除的失效模式——LP 从不持有输家侧的终端风险。下注者仍承担自身结果风险(如设计)。 | +| 43 | **Hedging** | 🟡 部分 | 下注者可通过对冲头寸、转让(`pm_transfer_position`)、撤注(若允许)经反向 CPMM,及现在的**可选杠杆**(`pm_leverage_open/convert`)做资本高效对冲。无原生多腿衍生品;基础 + 杠杆对冲可行。 | +| 44 | **Implied Correlation** | ➖ 不需要 | 需 Onix 省略的多事件/组合市场。范围外。 | +| 45 | **Insider Trading** | 🟡 部分 / 🏛 客户端 | 协议无法侦测内幕;由时间惩罚(迟到的知情下注利润更少)与保证金裁定缓解。KYC/监控由**客户端层**负责(受监管客户端)。 | +| 46 | **Kelly Criterion** | ⚪ 固有 | 下注者下注规模策略,非协议特性。Onix 暴露干净概率与完全抵押,便于参与者计算 Kelly;新的**可选杠杆**让下注者以保证金践行分数 Kelly 优势(池资助、受清算约束)。 | +| 47 | **Liquidity Fragmentation** | 🟡 路线图 | 今日 per-market 池碎片化流动性。白皮书首要路线图项——**共享/类别级 AMM 池**——是架构性修复。懒惰池已跨市场共担*存款*。 | +| 48 | **Liquidity Provision** | ✅ 已解决(核心差异化) | 无风险 LP 是头条:本金结构性保障、时间加权费用奖励、懒惰池自动分配 + MasterChef 记账。解决了「LP 亏钱」这一驱动整个协议的问题。 | +| 49 | **Market Making** | ✅ 已解决(重构) | 无需主动做市商——AMM + LP 池*即*做市商,且不承担库存风险。「做市」坍缩为被动、无风险的流动性提供。 | +| 50 | **Minimum Viable Liquidity** | ✅ 已解决 | 强制下限:最低初始流动性 100 VIZ;懒惰池以 `free_balance × 分配%` 自动为每个新市场播种。MVL 结构性引导而非听天由命。 | +| 51 | **Order Book** | ➖ 不需要 | Onix 基于 AMM;按设计无订单簿。 | +| 52 | **Orderflow Arbitrage** | ➖ 不需要 | 无订单簿 / 无 PFOF 式订单流路由 → 不适用。 | +| 53 | **Relative Value Trading** | ➖ 不需要 | 跨工具相对价值需 Onix 省略的相关/组合市场。范围外。 | +| 54 | **Retail Flow** | ✅ 已解决(重构) | CLOB 模型中零售流补贴做市商对毒性流的亏损。Onix 无做市商需保护——零售与知情下注者同入一个同注分彩池;LP 无所谓。「零售 vs 毒性」张力在 LP 层消解。 | +| 55 | **Semantic Tick Size** | ➖ 不需要 | 最小变动价位是 CLOB 概念。Onix 价格是连续 AMM 函数;精度为固定 mVIZ 单位(1/1000)。无需 tick 设计。 | +| 56 | **Temporal Arbitrage** | 🟡 部分 | 早/晚下注风险不同;Onix 对利润的**时间惩罚**正是把迟到计入价格、抑制「等确定」套利的机制。未消除,但明确不鼓励。 | +| 57 | **Time Arbitrage** | 🟡 部分 | 与 #56 同族——利用信息时机。二次时间惩罚 + ~3 秒块降低但不消除优势。 | +| 58 | **Toxic Flow** | ✅ 已解决(对 LP) | 定义性 CLOB/LMSR 问题(狙击手在 99¢ 结果上以 10¢ 扫单,做市商吃 80¢)**不打到 Onix LP**——赔付来自输家本金,LP 补贴无条件返还。毒性流仅意味着知情下注者赢得同注分彩池,如设计。重大结构性胜利。 | +| 59 | **Wash Trading** | 🟡 部分 / 🏛 客户端 | 下注无即时费用消除了一个 wash 激励,但刷量仍可能;转让纯属重新分配(无费用农场)。侦测/监控是客户端 + 路线图关切。 | + +## 4. 预言机与裁定 + +| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 | +|---|---------|---------|--------------------------------------------------| +| 60 | **Corruption Value Multiple(CVM)** | ✅ 已解决(设计原则) | 协议显式安全不变式:预言机**保险保证金须超潜在操纵利润**。风险因子(保险/下注比)馈入综合信任分。CVM 直接是保证金理据。 | +| 61 | **Dispute Resolution** | ✅ 已解决 | 完整系统:12h 宽限、`dispute_fee`、强制预言机响应、per-market 裁决(`dispute_mode==0` 委员会加权投票 / `==1` 指定裁决者)、保险罚没、3 结果 no-contest、14 天自动关闭防冻结、链上创建者/预言机封禁。**HF14 新增:** 懒惰池存款人保留争议投票权重(池 NAV → vesting-shares)。 | +| 62 | **Oracle Design** | ✅ 已解决 | 保证金预言机模型:注册费、≥5000 VIZ 保险、显式接受、带证据裁定、14 项声誉、新鲜度衰减、封禁机制。核心子系统。 | +| 63 | **Resolution Criteria** | 🟡 部分 / 🏛 客户端 | 市场问题/标准存于 `url`/描述(仅展示)。协议强制*流程*(谁裁、争议)而非标准*质量*——含糊标准是创建者/客户端责任,事后经争议 + **链上创建者封禁**(`pm_creator_ban_object`,已上线)治理。 | +| 64 | **Self-Resolving Markets** | ➖ 不需要(今日) | Onix 裁定由预言机驱动,非算法自裁。自动数据预言机是客观市场的高优路线图,可近似自裁。 | +| 65 | **UMA Protocol** | ➖ 不需要(被替代) | UMA 乐观预言机(Polymarket 使用)由 Onix 保证金预言机 + VIZ 委员会争议模型功能性替代。同一问题,原生 VIZ 之解——无外部预言机依赖。 | + +## 5. 治理与决策 + +| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 | +|---|---------|---------|--------------------------------------------------| +| 66 | **Attention Markets** | ➖ 不需要 | 交易注意力/病毒性是独立产品;Onix 聚焦事件裁定。可为客户端层市场类别,非核心。 | +| 67 | **Conditional Tokens** | ➖ 不需要(明确) | 白皮书 §7.2 论证 CTF 拆/合**架构上不必要**——价格一致性是数学性的,非由代币强制。CTF *唯一*有用特性(可转让头寸)原生重实现为带加密 memo 的 `pm_transfer_position`。刻意省略。 | +| 68 | **Decision Markets** | 🟡 可能 | Onix Multi 可表达决策市场,但条件「若政策则指标」结构非原生(无条件代币)。可在客户端层构建。 | +| 69 | **Futarchy** | 🟡 可能(客户端) | futarchy = 条件期货上的决策市场。Onix 无原生条件市场,故完整 futarchy 核心不支持。VIZ 加权委员会已治理*参数*;按市场治理为客户端/路线图构建。 | +| 70 | **Hyperstition Markets** | ➖ 不需要(刻意) | 反身性即特性(协调而非预测)。这是*设计选择,非待修缺陷*:Onix 要求结果**可由保证金预言机外部核实**,按默认结构性排除 hyperstition 市场。可作客户端层「协调市场」产品存在,但非核心目标。见[缓解](#fanshenxing-jiazu-de-huanjie)。 | +| 71 | **Impact Markets** | ➖ 不需要 | 追溯资助/影响力证书市场是独立领域。可为客户端层类别;非核心。 | +| 72 | **No-Loss Prediction Markets** | 🟡 相邻 | Onix 对*下注者*非无损(输家没收本金以资助赢家)。但对 *LP* **是**「无损」(本金保障)。收益资助的无损变体是另一模型;LP 侧无损已交付。 | +| 73 | **Opportunity Markets** | ⚪ 固有(相邻) | 懒惰池的**机会成本保护**(渐进召回、活跃市场惩罚、故障印记)直接应对资本机会成本——尽管「机会市场」作为产品类别在范围外。 | + +## 6. 商业与平台 + +| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 | +|---|---------|---------|--------------------------------------------------| +| 74 | **AI agents** | 🟡 路线图 | 无头客户端 + 开放 VIZ 操作使程序化代理(下注者、LP、自动预言机)易于实现。AI 驱动的流动性/裁定是自然延伸,尚未规范。 | +| 75 | **Cross-subsidization** | ⚪ 固有 | 懒惰池以一次存款跨多市场交叉补贴流动性;MasterChef `reward_per_share` 共享费用收益。交叉补贴内建于池经济。 | +| 76 | **Demand markets** | ➖ 不需要 | 衡量/聚合需求的市场是产品类别;非核心 Onix 原语。客户端层。 | +| 77 | **Distribution moat** | 🟡 战略 | Onix 护城河是无风险 LP 收益 + VIZ 原生基础设施(飞轮)。分发是 go-to-market 关切,迁移后由平台无关性部分解决。 | +| 78 | **Election markets** | 🏛 客户端 | 作为普通二元/多元市场支持;其*合法性*是司法辖区客户端事务(白名单预言机、类别过滤)。协议中立。 | +| 79 | **Event contracts** | ⚪ 固有 | 每个 Onix 市场*就是*事件合约。其监管分类是客户端/法律问题,非协议逻辑。 | +| 80 | **Federal preemption** | 🏛 客户端(对协议不适用) | 取决于美国事件合约是否「swaps」。VIZ DLT 是**基础设施而非运营方**——如比特币是账本。法律义务附着于客户端,非共识。 | +| 81 | **Long-tail markets** | ✅ 已解决 | 正是 LMSR 有界损失所启用的利基——Onix 经懒惰池自动分配 + 最低流动性下限使其播种*无风险*。长尾可行性是核心卖点。 | +| 82 | **Market structure** | ✅ 已解决(已定义) | Onix 定义清晰结构:AMM 定价、同注分彩结算、保证金预言机、DPoS 治理参数、共识级操作。相对 CLOB 平台的连贯、新颖市场结构。 | +| 83 | **Market surveillance** | 🟡 路线图 / 🏛 客户端 | 完整链上审计轨迹(每操作共识校验)使任何人*可*监控。主动监控/执法是客户端 + 路线图关切。 | +| 84 | **Network effects** | 🟡 战略 | 飞轮(无风险 LP → 深度 → 下注者 → 费用 → 更多 LP)是预期网络效应。共享流动性池(路线图)加强它。 | +| 85 | **Parlays** | ➖ 不需要 | 多腿组合下注需 Onix 省略的条件/组合结构。范围外(可在独立市场上由客户端构建)。 | +| 86 | **Platform competition** | 🟡 战略 | 以「无无常损失的被动收益」独特角度对抗 Polymarket/Kalshi。战略定位,非协议逻辑。 | +| 87 | **Polymarket** | ⚪ 参照 | 主要基准。Onix 在每个维度都不同:CPMM/LMSR vs CLOB、零 LP 风险 vs 库存风险、原生操作 vs Polygon 合约、保证金预言机 vs UMA、无 CTF。 | +| 88 | **Regulatory arbitrage** | 🏛 客户端 | 司法辖区客户端模型意味着每个地区在中立的 VIZ 之上构建其合规(或无许可)客户端。监管定位完全在客户端层。 | +| 89 | **Regulatory classification** | 🏛 客户端 | 市场是 swaps/博彩/证券由各辖区在客户端层决定;协议分类中立(无许可与受监管客户端用同样的 `pm_*` 操作)。 | + +## 摘要 —— Onix-on-VIZ 设计究竟改变了什么 + +**结构性解决(核心优势):** LP 侧的逆向选择、毒性流、跳空风险、无常损失、做市商库存风险 → 全部消除,因为赢家 +只由输家被没收的本金资助、LP 本金无条件返还(CPMM 用 AM-GM 证明;LMSR 用同注分彩)。**LMSR 的二元失败** → +通过二元用 CPMM、将 LMSR 限于做市商非对手方的多元市场而规避。**流动性提供、最小可行流动性、长尾可行性** → +无风险 LP + 懒惰池自动分配。**预言机设计、争议解决、CVM** → 保证金预言机 + 14 项声誉 + 加权委员会争议。 +**价格发现、套利、条件代币** → 价格一致性是数学性的(`Σ price = 1`),故无需订单簿、拆/合或内部套利层。 + +**不需要 / 刻意省略:** 订单簿、CDA、买卖价差、语义最小变动价位、订单流套利、CTF 拆/合、组合/协方差/相关/相对 +价值/连串市场、UMA、对等预测、LOX。 + +**下放到司法辖区客户端层:** 联邦优先、监管分类/套利、选举市场合法性、KYC/内幕交易执法、市场监控。 + +**自规范以来新上线(HF14):** 可选批量拍卖 + 提交-揭示下注(二元)、可选杠杆子系统(头寸抵押)、懒惰池、 +`endogeneity_tier`、链上创建者封禁、逐下注者/杠杆结算虚拟操作 + 插件 API,以及 PM 争议与 DAO 提案中的懒惰池治 +理权重。 + +**在 VIZ 路线图(今日部分):** 共享/类别流动性池(解决碎片化)、自动数据预言机(→ 自裁定客观市场)、分布市场、 +AI 代理。*(注:争议的提交-揭示投票**不在**此列——它被刻意拒绝;争议听证保持公开。下注的提交-揭示已上线。)* + +**仍开放 / 行为性(已缓解,非消除):** 冷门偏差/Yes 偏差、靠深度的价格操纵、跨平台套利。反身性家族有具体缓解 +方案——见下。 + +## 反身性家族的缓解 {#fanshenxing-jiazu-de-huanjie} + +endogeneity、reflexivity、凯恩斯选美(KBC)与 hyperstition 是**同一根因在不同层**:市场/价格影响其所衡量的结 +果。一小套原语应对全部四者。 + +### 缓解原语 + +| 原语 | 状态 | 修复 | 备注 | +|-----------|--------|-------|---------| +| **提交-揭示争议投票** | **已拒绝(不会构建)** | KBC | 委员会争议是**公开听证**:`pm_dispute_vote` 是公开选票,**按设计保持公开**——DAO 信誉依赖透明裁决。投票可改至关闭;KBC 残余被接受(投票不因与多数一致而获酬)。 | +| **提交-揭示下注(批量)** | **上线(可选,二元)** | endogeneity、reflexivity、信息不对称 | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` 隐藏在途订单方向/大小,使公开价格在下注窗口内不「泄露」。按统一价**批量**结算。 | +| **`endogeneity_tier` 字段** | **上线字段** | endogeneity | 预言机标 1 经济数据、2 体育/排定、3 政治/社会;UI 展示反身性风险;客户端可限制 tier-3。 | +| **外生裁定(自动数据预言机)** | 路线图(高) | endogeneity、reflexivity | 裁定绑定到市场无法影响的外部数据源 → 干净温度计。 | +| **禁止类别清单 + 创建者封禁** | **创建者封禁链上上线**;清单在客户端层 | 有害反身性、hyperstition | 封禁 YES 会制造「拥趸」的市场(暗杀/「悬赏」/恐怖、宣传市场)。经链上创建者封禁 + 客户端类别过滤。 | +| **深度无风险 LP 流动性** | 今日核心 | 操纵驱动的反身性 | 飞轮使订单深,故为「头条」操纵抬价代价高。 | + +### 净裁定变化 + +| 概念 | 之前 | 之后 | +|---------|--------|-------| +| Keynesian Beauty Contest | 🔴 开放 | ✅ 市场 / ⚪ 争议——投票**按设计**公开(无提交-揭示;可改选票、投票者不获酬、池内投票者有权) | +| Endogeneity | 🔴 开放 | 🟡 已缓解——`endogeneity_tier` + 提交-揭示/批量**上线**;外生预言机路线图 | +| Reflexivity | 🔴 开放 | 🟡 已缓解——创建者封禁**链上上线**;类别清单客户端层 | +| Hyperstition | 🔴 开放 | ➖ 按设计排除(可选客户端产品) | diff --git a/@l10n/zh-CN/docs/prediction-markets/index.md b/@l10n/zh-CN/docs/prediction-markets/index.md new file mode 100644 index 0000000000..2261354e16 --- /dev/null +++ b/@l10n/zh-CN/docs/prediction-markets/index.md @@ -0,0 +1,49 @@ +--- +title: 预测市场(Onix)—— 总览与地图 +description: VIZ 链上预测市场技术栈——Onix 协议与 Forecaster 客户端——从白皮书到规范、对象、操作、工作流与概念分析的完整文档树。 +--- + +# VIZ 上的预测市场 + +VIZ Ledger 将预测市场作为**一等公民共识操作**(`pm_*`)运行,自 HF14 上线。全文会出现两个名称: + +- **Onix** —— **协议**:链上市场引擎(CPMM 二元 + LMSR 多元、同注分彩零和结算、保证金预言机、懒惰池、可选 + 杠杆、批量 / 提交-揭示下注)。 +- **Forecaster** —— 对接该协议的 VIZ Ledger **瘦客户端**。它是一个无头、与平台无关的前端,让**全球用户参与链上 + 预测市场**——创建市场、下注、提供流动性、做预言机、发起争议——通过直接对公共 VIZ 节点签署 `pm_*` 操作。协议 + 中立;Forecaster(以及任何按其方式构建的司法辖区客户端)是接入层。 + +## 文档地图 + +```mermaid +flowchart TD + ROOT["预测市场(Onix)"] + ROOT --> OV["总览 — 一页式定位(整体一览)"] + ROOT --> WP["白皮书 — 论点:为何 LP 无风险、两种市场类型、飞轮"] + ROOT --> SP["规范 — 形式化机制 + §17 链上对象模型"] + ROOT --> OPS["操作 — 已签名的 pm_* 共识操作"] + ROOT --> VOPS["虚拟操作 — 结算时/到期时发出的确定性 vop"] + ROOT --> API["插件 API — prediction_market_api 只读方法"] + ROOT --> WF["工作流与图 — 一个典型市场贯穿每个角色"] + ROOT --> CA["概念分析 — 90 个 PM 理论概念对照 VIZ 实现"] +``` + +## 从这里开始 + +| 页面 | 是什么 | +|------|-----------| +| [总览](./onix) | 一页式定位:AMM 定价的同注分彩,具备结构性无风险流动性。 | +| [白皮书](./whitepaper) | 产业论点——LP 保障、Onix Binary(CPMM)+ Onix Multi(LMSR)、预言机、懒惰池、杠杆、治理。 | +| [规范](./specification) | 形式化规范:参数、状态机、定价、结算、争议、懒惰池、杠杆,以及 **[链上对象模型](./specification)**(每个 `pm_*_object` 及其查找索引)。 | +| [操作](../protocol/operations/prediction-markets) | 21 个已签名共识操作(`pm_create_market`、`pm_place_bet`、…)。 | +| [虚拟操作](../protocol/virtual-operations) | 确定性 vop(`pm_payout`、`pm_market_accepted`、`pm_leverage_resolve`、`pm_batch_settle`、…)。 | +| [插件 API](../plugins/prediction-market-api) | `prediction_market_api` —— 对市场、下注、预言机、争议、懒惰池及中位数投票参数的只读访问。 | +| [工作流与交互图](./workflows) | 一个典型二元市场贯穿所有参与者,附正常与争议裁定的零和主账本。 | +| [概念分析(Onix 对照 90)](./concepts-analysis) | 链上实现如何映射到预测市场理论图谱——哪些已解决、固有、不需要或在路线图。 | + +## 治理 + +所有经济参数均由代表**中位数投票**,位于 `chain_properties_pm` 结构体——见 +[链参数 → 预测市场参数](../governance/chain-properties#pm-parameters)。调整费用、罚则、懒惰池、杠杆或 +批量/提交-揭示时序无需硬分叉;三个实时终止开关(`pm_commit_reveal_enabled`、`pm_lazy_pool_enabled`、 +`pm_leverage_enabled`)让验证者中位数无需分叉即可停用整个子系统。 diff --git a/@l10n/zh-CN/docs/prediction-markets/onix.md b/@l10n/zh-CN/docs/prediction-markets/onix.md new file mode 100644 index 0000000000..0035492b83 --- /dev/null +++ b/@l10n/zh-CN/docs/prediction-markets/onix.md @@ -0,0 +1,71 @@ +--- +title: Onix —— AMM 定价的同注分彩预测市场 +description: Onix 在同注分彩(parimutuel)结算之上叠加连续的 AMM 价格发现——价格像 AMM 一样波动,而流动性具备彩池的风险特征:做市商永远不会破产。 +--- + +# Onix —— AMM 定价的同注分彩预测市场 + +> 在 **同注分彩(parimutuel)结算** 之上叠加连续的 **AMM 价格发现**——价格像 AMM 一样波动,而流动性具备彩池的风险特征:**做市商永远不会破产。** + +::: info Onix 与 Forecaster +**Onix** 是链上协议。**Forecaster** 是 VIZ Ledger 上对接它的瘦客户端——无头、与平台无关的接入层,让全球用户 +通过直接对公共 VIZ 节点签署 `pm_*` 操作来参与链上预测市场。完整文档树见[板块总览与地图](./)。 +::: + +## 一个核心思想 + +Onix 将**价格与赔付解耦**: + +- **价格(发现)**——CPMM 曲线(二元)或 LMSR-softmax(多元)在每次下注时更新实时概率,并为每笔下注分配一个**权重**(其份额凭证)。 +- **赔付(结算)**——赢家**只**从输家被没收的本金中获得赔付,按权重分配:纯**同注分彩**,严格零和(协议从不增发代币)。 + +Onix 所有的独特之处都源于这一解耦。 + +## 为什么重要 —— 三点 + +::: tip 1 · 无法被抽干的流动性 +由于赢家从输家而非从 LP 本金中获得赔付,流动性提供者**无法破产**——没有无常损失、没有库存风险、不会被狙击致死。这由*构造保证*(CPMM 用 AM–GM,LMSR 用守恒),而非靠保险。 +::: + +::: tip 2 · 无 IL 的被动收益 —— 懒惰池 +一次存入即自动作为隐形流动性铺展到多个市场,并为可选杠杆提供资金,采用 MasterChef 式的奖励记账。**无需**挑选市场、**不**承担无常损失即可赚取预测市场的流动性收益。 +::: + +::: tip 3 · 链原生、零和 +市场是一等公民的共识操作(`pm_*`),而非智能合约:抗审查、可组合、约 3 秒出块、无需预言机桥。协议从不增发代币——只做再分配。 +::: + +## 一笔下注如何运作 + +1. **你下注** `X` 于某结果。`X` 进入曲线;曲线返回你的**权重**——越早下注(在价格移动之前),权重越多。 +2. **赔率板更新。** 某一方的实时系数 = `1 + 对方池 × (1 − 佣金) / 本方池`,佣金(预言机 + 创建者 + LP)已内含其中。 +3. **结算时**,输家本金(扣除佣金)按权重在赢家间分配。你的赔付 = 退回本金 **+** 你在输家池中的份额。LP 本金原封不动地返还。 + +## 对比 + +| | CLOB / AMM(Polymarket、Kalshi) | 纯同注分彩(彩池) | **Onix** | +|---|---|---|---| +| 实时价格 | 是 | 否(仅池比) | **是(CPMM / LMSR)** | +| 下注时锁定赔率 | 是 | 否 | 否(诚实的同注分彩) | +| LP / 做市商可能破产 | **是**(IL、狙击、跳空风险) | 不适用 | **否(结构性)** | +| 带收益的流动性层 | 脆弱 | 无 | **懒惰池,无 IL** | +| 运行于 | 合约 / 后端 | 后端 | **共识(`pm_*`)** | +| 代币增发 | 有时 | 否 | **否(零和)** | + +## 诚实的取舍 + +::: warning 赔率是同注分彩——会漂移到收盘 +Onix **不**在下注时锁定你的系数。赔率板随资金流动而移动,最终系数只有在收盘时才确定——正如彩池。这不是需要打补丁的缺陷:锁定赔率的*唯一*方式是让对手方承担风险(庄家,或可能亏损的 AMM LP)。Onix 的漂移正是其 LP 保障的直接代价——风险存在于**下注者之间**,所以任何人的流动性都不会被烧毁。 +::: + +## 新颖之处 + +- **AMM 加权 + 同注分彩结算** 集成于同一引擎——连续价格发现,*不带*做市商库存风险。 +- **结构性、可证明的 LP 安全**,而非依赖保险或补贴的流动性。 +- **共担的、带收益的流动性层**(懒惰池),同时为可选杠杆提供资金——清算按下注前的储备执行,因此池始终被补足。 +- **可选的抗 MEV**(批量 / 提交-揭示下注)与**透明治理**(保证金预言机、可修改投票的公开听证争议)——全部叠加在安全基座之上,从不触碰 LP 保障。 + +## 了解更多 + +- 协议操作 —— [预测市场](../protocol/operations/prediction-markets) +- 插件 API —— [预测市场 API](../plugins/prediction-market-api) diff --git a/@l10n/zh-CN/docs/prediction-markets/specification.md b/@l10n/zh-CN/docs/prediction-markets/specification.md new file mode 100644 index 0000000000..2e0c9642af --- /dev/null +++ b/@l10n/zh-CN/docs/prediction-markets/specification.md @@ -0,0 +1,974 @@ +--- +title: Onix 协议 —— 规范 +description: Onix 协议的正式技术规范,已实现为 VIZ DLT 上的共识操作(HF14)。 +--- + +# Onix 协议规范 + +**版本:** 2.0(链上 / HF14) +**状态:** 正式技术规范 —— 现已实现为 VIZ DLT 上的共识操作 + +--- + +> **链上(HF14)。** 实现为 VIZ DLT 上的一等公民共识操作(`pm_*`),并在 `consensus_sim` 中验证。**所有 +> 百分比均为基点(bp):10000 = 100.00%**;所有时长为以**秒 / 区块**计的治理参数。中位数投票的参数位于 +> `chain_properties_pm` 结构体(§3);按市场字段为 `pm_create_market` 操作;所有状态位于 §17 的 chainbase +> 对象中。预言机费用采用 offer→quote(创建者上限 → 预言机在接受时冻结其报价,发出 `pm_market_accepted`)。 +> 争议有两种模式 —— 委员会(`dispute_mode = 0`,默认:按质押加权的**公开** `pm_dispute_vote`,关闭前可改, +> 懒惰池质押计入)与账户(`dispute_mode = 1`:指定 `dispute_resolver`)。 + +## 目录 + +1. 定义与角色 +2. 货币与精度 +3. 系统参数 +4. 市场状态机 +5. Onix Binary:恒定乘积做市商 +6. Onix Multi:LMSR 与同注分彩结算 +7. 费用结构 +8. 迟下注的时间惩罚 +9. 流动性提供 +10. 裁定与赔付 +11. 撤注 +12. 争议系统 +13. 预言机错过裁定的惩罚 +14. 预言机声誉评分 +15. 持仓转让 +16. 懒惰流动性池 +16a. 可选杠杆 +16b. 批量 / 提交-揭示下注 +17. 链上对象模型 + +--- + +## 1. 定义与角色 + +| 角色 | 定义 | +|------|-----------| +| **市场创建者** | 支付 `pm_market_creation_fee`(`pm_create_market`);设定问题、结果、流动性、费用上限与时序参数 | +| **预言机** | 注册(费用:`pm_oracle_registration_fee`),存入保险(最低:`pm_min_oracle_insurance`),在接受时以**基点**报出费用条款(≤ 创建者上限)+ 固定费,接受/拒绝市场,提供结果裁定 | +| **下注者** | 对结果下注;按本金与当前储备成比例获得代币 | +| **流动性提供者(LP)** | 向市场池提供资金;赚取流动性费的时间加权份额 + 惩罚池 | +| **懒惰池提供者** | 以锁仓期向懒惰流动性池存入 VIZ;池自动向市场分配并通过 `reward_per_share` 累加器分发奖励 | +| **争议裁决者** | 仅账户模式(`dispute_mode = 1`):按市场的 `dispute_resolver` 账户仲裁。委员会模式(`dispute_mode = 0`)不使用裁决者——由 SHARES 选民投票 | +| **DAO / 委员会基金** | 链上既有的委员会基金。接收 `pm_market_creation_fee` 与额外的预言机罚款 | + +--- + +## 2. 货币与精度 + +所有金额以整数存储,精度 = 1/1000(毫 VIZ)。`1000` 内部单位 = 1.000 VIZ。 + +时间惩罚值使用精度 = 1/1,000,000(微单位)。 + +--- + +## 3. 系统参数 + +### 中位数投票参数(`chain_properties_pm`) + +所有经济参数由代表中位数投票(无需硬分叉即可调参),位于链上 `chain_properties_pm` 结构体。每个代表通过标准 +的 **`versioned_chain_properties_update_operation`**(op ID 46)发布其偏好值 —— `chain_properties_pm` +是该版本化结构体的当前版本(v5,HF14)—— 网络对活跃代表应用**逐字段中位数**。两个风险覆盖率旋钮 +(`pm_listing_min_coverage_percent`、`pm_betting_min_coverage_percent`)属于同一个 v5 结构体,调参方式完全相同。 +**所有百分比为基点(bp,10000 = 100.00%);时长以秒或区块计** —— 但这两个覆盖率旋钮例外,它们为成交量百分比 +(100 = 1.0×)。确切默认值与范围见 +[链参数](../governance/chain-properties#pm-parameters);权威来源是结构体本身。 + +| 分组 | 参数 | +|---|---| +| 注册与下限 | `pm_oracle_registration_fee`、`pm_min_oracle_insurance`、`pm_market_creation_fee`、`pm_min_liquidity`、`pm_max_outcomes`、`pm_max_market_duration` | +| 费用与罚则(bp) | `pm_max_oracle_fee_percent`、`pm_oracle_penalty_percent`、`pm_no_contest_penalty_percent`、`pm_default_time_penalty_percent`、`pm_max_time_penalty` | +| 接受窗口 | `pm_oracle_accept_window_sec`(默认 3600 = 1 小时;未在此期限内被接受/拒绝的待定市场由 cron 作废——种子退还,创建费保留) | +| 风险 / 覆盖率(成交量 %) | `pm_listing_min_coverage_percent`(250 = 2.5×;覆盖率低于此值的市场从默认目录中隐藏,经 `show_risky` 展示)、`pm_betting_min_coverage_percent`(150 = 1.5×;建议性客户端风险确认阈值,`≤` 挂牌阈值,不在链上强制) | +| 争议 | `pm_dispute_fee`、`pm_dispute_grace_sec`、`pm_oracle_dispute_response_sec`、`pm_dispute_vote_period_sec`、`pm_dispute_auto_close_sec`、`pm_dispute_approve_min_percent`(bp)、`pm_dispute_reward_multiplier`(bp) | +| 懒惰池 | `pm_lazy_pool_enabled`、`pm_lazy_alloc_percent`、`pm_lazy_max_total_alloc_percent`、`pm_lazy_recall_step_percent`、`pm_lazy_lock_sec`、`pm_lazy_emergency_penalty_percent`、`pm_lazy_min_liquidity_fee_percent`(默认 200 = 2%;池跳过 `liquidity_fee_percent` 低于此奖励下限的市场) | +| 杠杆 | `pm_leverage_enabled`、`pm_leverage_fund_percent`、`pm_leverage_max_per_position_bp`、`pm_leverage_max_position_ratio_percent`、`pm_leverage_min_market_liquidity`、`pm_leverage_safety_margin_percent`、`pm_leverage_max_slippage_percent`、`pm_leverage_m_factor_percent`、`pm_leverage_pool_profit_percent`、`pm_leverage_expiration_buffer_sec`、`pm_conversion_profit_cost_percent` | +| 批量 / 提交-揭示 | `pm_commit_reveal_enabled`、`pm_batch_epoch_blocks`、`pm_reveal_window_blocks`、`pm_commit_no_reveal_penalty_percent`(bp)、`pm_min_batch_bet` | +| 处理 | `pm_processing_cap_per_block` | + +`pm_market_creation_fee` 与额外预言机罚款的接收方是链上既有的委员会/DAO 基金,而非 PM 专用账户。 + +### 按市场参数(`pm_create_market` 操作) + +由创建者在创建时设定;预言机费用字段为预言机在接受时据以报价的**上限**(offer→quote)。完整字段参考: +[预测市场操作](../protocol/operations/prediction-markets)。 + +| 字段 | 描述 | +|---|---| +| `oracle`、`market_type`(0 二元 / 1 多元)、`outcomes`、`url` | 市场定义 | +| `oracle_fee_percent`、`oracle_fixed_fee` | 预言机费用**上限**(bp + 固定);预言机在接受时冻结其报价 ≤ 此(且 ≤ 中位 `pm_max_oracle_fee_percent`) | +| `creator_fee_percent`、`liquidity_fee_percent` | 创建者与 LP 费用(输家池的 bp) | +| `liquidity`、`lmsr_b` | 种子流动性;`lmsr_b` 用于多元市场 | +| `betting_expiration`、`result_expiration` | 计时器 | +| `time_penalty_type`、`time_penalty_value`、`penalty_curve_type` | 迟下注惩罚形状 | +| `allow_early_resolution`、`allow_cancellation` | 开关 | +| `allow_batch`、`allow_instant_bet` | 下注模式(二元) | +| `endogeneity_tier` | 1 经济数据 / 2 体育 / 3 政治(展示/风险提示) | +| `dispute_mode`(0 委员会 / 1 账户)、`dispute_resolver` | 争议路由 | +| `dispute_penalty_percent` | 争议成立时的预言机罚则策略(bp,带符号) | +| `metadata` | 自由格式客户端 JSON(共识不透明;链下解析) | + +--- + +## 4. 市场状态机 + +### 状态 + +| Status | 名称 | 描述 | +|--------|------|-------------| +| -1 | Deleted | 预言机拒绝,**或**接受窗口(`pm_oracle_accept_window_sec`)到期;种子流动性返还创建者(创建费保留) | +| 0 | Waiting | 等待预言机审阅 | +| 1 | Active | 接受下注至 `betting_expiration` | +| 2 | Closed | 下注结束,等待预言机裁定 | +| 3 | Resolved | 结果确定,赔付已计算 | + +### 赔付状态 + +| payout_status | 名称 | 描述 | +|---------------|------|-------------| +| 0 | Not calculated | 裁定前 | +| 1 | Calculated | 赔付待处理(宽限期活跃) | +| 2 | Paid | 全部赔付已处理 | +| 3 | Disputed | 已提交争议,赔付冻结 | + +### 转移 + +```mermaid +stateDiagram-v2 + direction LR + state "Waiting (0)" as Waiting + state "Active (1)" as Active + state "Closed (2)" as Closed + state "Resolved (3)" as Resolved + state "Deleted (-1)" as Deleted + state "Paid out" as Paid + [*] --> Waiting + Waiting --> Active: 预言机接受 + Waiting --> Deleted: 预言机拒绝 + Waiting --> Deleted: 接受窗口到期 (pm_market_expired) + Active --> Closed: betting_expiration + Active --> Resolved: 提前裁定(若允许) + Closed --> Resolved: 预言机裁定 + Resolved --> Paid: 宽限窗口(12 小时),无争议 + Deleted --> [*] + Paid --> [*] +``` + +**前置条件:** + +| 转移 | 前置条件 | +|-----------|---------------| +| 0 → 1 | 预言机保险 ≥ `min_oracle_insurance`;预言机接受 | +| 0 → 1(自预言机) | 创建者 = 预言机;保险检查;创建时自动批准 | +| 0 → -1 | 预言机拒绝;种子流动性返还创建者 | +| 0 → -1(到期) | `now ≥ created_time + pm_oracle_accept_window_sec` 且预言机无动作;cron 作废市场、退还种子(创建费保留)、发出 `pm_market_expired` | +| 1 → 3 | 预言机提交带结果的裁定(0、1,或 -1 表示 no-contest);`allow_early_resolution=1` 或 `time ≥ betting_expiration` | +| 2 → 3 | 预言机提交裁定;`time ≤ result_expiration` | +| 3 → paid | 宽限期内无争议;定时任务处理赔付 | + +### 市场创建流程 + +1. 从创建者扣除 `market_creation_fee` → DAO 基金(不可退) +2. 在市场上记录来自预言机资料的 `oracle_fixed_fee` +3. 从创建者余额锁定 `liquidity` +4. 初始化储备:`reserve_a = floor(liquidity/2)`,`reserve_b = liquidity − reserve_a` +5. 计算 `k = reserve_a × reserve_b` +6. 若自预言机:经保险检查自动批准为 status=1 +7. 若外部预言机:进入 status=0 并设置 `accept_deadline = created_time + pm_oracle_accept_window_sec` + +### 预言机接受流程 + +待定市场须由其预言机在接受窗口(`pm_oracle_accept_window_sec`,默认 1 小时)内处置。三种结局: + +- **接受**(status 0 → 1):(1) 从创建者向预言机转移 `oracle_fixed_fee`(自预言机则跳过); + (2) 递增 `markets_accepted`;(3) 更新 `last_active_time`;(4) 触发懒惰池自动分配(若池有自由余额 + **且**市场 `liquidity_fee_percent ≥ pm_lazy_min_liquidity_fee_percent`)。 +- **拒绝**(status 0 → -1):种子流动性返还创建者;无 vop。 +- **到期**(status 0 → -1):若到 `accept_deadline` 仍无动作,逐块 cron 作废市场、退还种子流动性 + (**不**含创建费)、发出 `pm_market_expired`。 + +### 审计轨迹 + +每个改变状态的动作都是一个共识操作或虚拟操作,永久记录在区块日志中并可通过 `account_history` 查询。下注、 +撤注、流动性添加/提取、接受/拒绝、裁定、争议、争议裁决、赔付与罚款都作为 `pm_*` 操作/虚拟操作出现,并附带它 +们所触及的市场储备。 + +--- + +## 5. Onix Binary:恒定乘积做市商 + +### 不变量 + +``` +k = reserve_a × reserve_b +``` + +`k` 仅在流动性添加/提取操作时改变。 + +### 下注(A 侧) + +``` +new_reserve_b = reserve_b + amount +new_reserve_a = floor(k / new_reserve_b) +tokens_received = reserve_a − new_reserve_a +price = amount × 1,000,000 / tokens_received +``` + +B 侧对称(交换 a/b)。 + +### 滑点保护 + +`place-bet` 上的可选 `min_tokens` 参数。若 `tokens_received < min_tokens`,交易被拒。 + +### 市场初始化 + +``` +reserve_a = floor(liquidity / 2) +reserve_b = liquidity − reserve_a +k = reserve_a × reserve_b +``` + +最低初始流动性:100,000 mVIZ(100 VIZ)。 + +### 权重(代币)语义 + +- `weight` = 下注者获得的结果代币数量(由 CPMM 在下注时设定) +- `weight` 是**相对索取权**,而非以 VIZ 计价的赔付。结算为**同注分彩**(与 Onix Multi 相同):赢家取回本金 + 外加按权重的输家池比例份额。 +- 若下注 A 侧且结果 A 获胜:`payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit` +- 若结果 A 落败:payout = 0(本金没入赢家池) + +CPMM 是**定价引擎**(概率 + 权重分配);它不再门控赔付。这使两种市场类型共享一个结算模型:*AMM 分配权重 +(二元用 CPMM,多元用 LMSR);输家按权重比例资助赢家。* + +### 价格显示 + +``` +implied_probability_A = reserve_b / (reserve_a + reserve_b) × 100% +implied_probability_B = reserve_a / (reserve_a + reserve_b) × 100% +``` + +### LP 本金保障(证明) + +在同注分彩结算下,保障精确且不依赖曲线几何: + +``` +Money OUT = L (LP principal) + Σ(winning bet_amount) + winners_pool + fees + = L + winning_bets + (losers_sum − fees) + fees + = L + winning_bets + losing_bets = L + all_bets = Money IN +``` + +无论权重如何,总赔付以 `losers_sum` 为上限,故 LP 本金 `L` 无条件返还,赢家完全由输家资助。(旧的 AM-GM 界 +`reserve_a + reserve_b ≥ L` 不再用于偿付能力;它仍是定价曲线的属性。) + +--- + +## 6. Onix Multi:LMSR 与同注分彩结算 + +### 价格函数(softmax) + +对 N 个结果,数量参数 q_1, ..., q_N,流动性参数 b: + +``` +price(i) = exp(q_i / b) / Σ_j exp(q_j / b) +``` + +**不变量:** `Σ_i price(i) = 1`(按 softmax 定义)。 + +### 成本函数 + +``` +C(q) = b × ln(Σ_j exp(q_j / b)) +``` + +在结果 i 上买入 Δ 代币的成本: + +``` +cost = C(q + Δ·e_i) − C(q) + = b × [ln(Σ_j exp(q'_j / b)) − ln(Σ_j exp(q_j / b))] +where q'_i = q_i + Δ, all other q'_j = q_j +``` + +数值稳定性(log-sum-exp 技巧): + +``` +ln(Σ exp(x_j)) = max(x) + ln(Σ exp(x_j − max(x))) +``` + +### 流动性参数 + +``` +b = S / ln(N) +``` + +其中 S = LP 补贴存款,N = 结果数。 + +### 结算(裁定时) + +``` +1. Oracle declares winning outcome +2. losers_sum = Σ bet_amount for all non-winning bets +3. oracle_fee = floor(losers_sum × oracle_fee_percent / 10000) +4. creator_fee = floor(losers_sum × creator_fee_percent / 10000) +5. liq_fee = floor(losers_sum × liquidity_fee_percent / 10000) +6. winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee +7. For each winning bettor: + payout = bet_amount + (their_tokens / total_winning_tokens × winners_pool) − time_penalty +8. LP subsidy returned unconditionally +9. LP earns time-weighted share of liq_fee +``` + +### LP 本金保障(证明) + +1. LP 存入 S VIZ 作补贴。这设定 b = S / ln(N)。 +2. 下注期间,用户支付 VIZ → 取得代币。VIZ 累积为下注池。 +3. 裁定时:输家没收 100% → `losers_sum`。赢家由 `losers_sum` 支付(非补贴)。 +4. LP 补贴 S **无条件**返还 —— 它在架构上与赔付流分离。 + +### 边界情形 + +| 情形 | 结果 | +|----------|---------| +| 全部下注于获胜结果 | `losers_sum=0`、`winners_pool=0`。每个下注者取回 `bet_amount`。LP 补贴返还。 | +| 无人下注于获胜结果 | `losers_sum=total_bets`。未分配的 `winners_pool` → LP 奖励。 | +| 零成交量市场 | LP 补贴返还。无手续费、无赔付。 | +| 单一下注者获胜 | 下注者获得 `bet_amount + winners_pool`。LP 补贴返还。 | + +### 操作 + +| 操作 | 描述 | +|-----------|-------------| +| `pm_create_market_multi { oracle, outcomes, liquidity, fees, ... }` | 创建 N 结果市场 | +| `pm_place_bet_multi { market, outcome_index, amount, min_tokens }` | 为某结果买入代币 | +| `pm_cancel_bet_multi { bet_id, min_return }` | 通过反向 LMSR 卖回代币 | +| `pm_add_liquidity_multi { market, amount }` | 增加 LP 补贴(提升 b) | +| `pm_withdraw_liquidity_multi { liquidity_id }` | 提取 LP 补贴(强制最低下限) | +| `pm_resolve_multi { market, winning_outcome }` | 预言机宣布赢家,触发结算 | + +二元市场(N=2)使用 Onix Binary(CPMM)。LMSR 仅用于 N > 2。 + +--- + +## 7. 费用结构 + +### 裁定时费用计算 + +所有百分比费用在裁定时从**输家侧总成交量**计算: + +``` +losers_sum = Σ bet_amount for all losing bets + +oracle_fee = floor(losers_sum × oracle_fee_percent / 10000) +creator_fee = floor(losers_sum × creator_fee_percent / 10000) +liquidity_fee = floor(losers_sum × liquidity_fee_percent / 10000) +winners_pool = losers_sum − oracle_fee − creator_fee − liquidity_fee +``` + +下注时不从下注扣费。完整下注额进入 CPMM/LMSR 储备。 + +### 预言机固定费 + +每个市场一次性费用。由预言机在资料中设定。市场接受时由创建者付给预言机。自预言机市场完全跳过(不发生余额操 +作)。 + +### 费用追踪字段 + +- `oracle_fee_earned` —— 裁定时不使用;费用从 losers_sum 计算 +- `liquidity_fee_earned` —— 已付给早退 LP 的累计 LP 费;裁定时:`LP fee pool = max(0, floor(losers_sum × liquidity_fee_percent / 10000) − liquidity_fee_earned) + penalty_pool` +- 每笔下注的 `oracle_fee` 与 `liquidity_fee` 记录用于审计;不在市场上累加 + +### 取整 + +所有计算使用 `floor()`。未分配的尘额(< 1 mVIZ)在最终赔付时送入 DAO 基金。 + +--- + +## 8. 迟下注的时间惩罚 + +### 惩罚窗口 + +| 类型 | 窗口计算 | +|------|-------------------| +| Fixed(type=0) | 到期前 `penalty_window = time_penalty_value` 秒 | +| Percentage(type=1) | `penalty_window = time_penalty_value / 100 × (betting_expiration − market_creation_time)` | + +### 惩罚计算 + +``` +time_to_expiration = betting_expiration − current_time + +if time_to_expiration < penalty_window: + ratio = 1 − (time_to_expiration / penalty_window) + + if penalty_curve_type == 1: // quadratic + penalty_ratio = ratio × ratio + else: // linear + penalty_ratio = ratio + + time_penalty = floor(penalty_ratio × max_time_penalty) +else: + time_penalty = 0 +``` + +### 赔付时应用(仅作用于利润) + +``` +profit = floor(winners_pool × weight / total_winning_weight) // parimutuel share of losers' pool +penalty_deduction = floor(profit × time_penalty / 1,000,000) +net_payout = bet_amount + profit − penalty_deduction +``` + +**不变量:** `net_payout ≥ bet_amount` —— 惩罚仅作用于利润份额,故赢家始终至少取回本金。(Onix Binary 与 +Onix Multi 相同。) + +--- + +## 9. 流动性提供 + +### 添加流动性 + +``` +add_a = amount × reserve_a / (reserve_a + reserve_b) +add_b = amount − add_a +new_reserve_a = reserve_a + add_a +new_reserve_b = reserve_b + add_b +new_k = new_reserve_a × new_reserve_b +``` + +存款时记录 `sec_to_expiration = betting_expiration − current_time`。 + +### 时间加权费用分配(裁定时) + +``` +fee_pool = remaining_liquidity_fee + total_penalty_pool + +weight_i = amount_i × max(1, sec_to_expiration_i) +total_weight = Σ weight_i +fee_share_i = floor(fee_pool × weight_i / total_weight) +lp_payout_i = principal_i + fee_share_i +``` + +每笔存款为独立持仓。同一用户的多笔存款分别追踪。 + +### 提前提取 + +**前置条件:** market status=1,`time < betting_expiration`,`resulting liquidity_sum ≥ 100,000 mVIZ`。 + +``` +// Fractional withdrawal +fraction = withdraw_amount / lp_amount +withdraw_weight_a = floor(weight_a × fraction) +withdraw_weight_b = floor(weight_b × fraction) + +// Reverse reserves +new_reserve_a = reserve_a − withdraw_weight_a +new_reserve_b = reserve_b − withdraw_weight_b +new_k = new_reserve_a × new_reserve_b + +// Time-ratio discount +time_served = current_time − lp_deposit_time +market_duration = betting_expiration − market_creation_time +time_ratio = min(1, time_served / market_duration) + +// Fee share (conservative: min of both sides) +fee_from_a = floor(a_bets_sum × liquidity_fee_percent / 10000) +fee_from_b = floor(b_bets_sum × liquidity_fee_percent / 10000) +estimated_pool = min(fee_from_a, fee_from_b) − already_paid_to_early_lps +lp_tw = withdraw_amount × max(1, sec_to_expiration) +total_tw = Σ (active LP time-weights) +raw_fee_share = floor(estimated_pool × lp_tw / total_tw) +fee_share = floor(raw_fee_share × time_ratio) + +returned = withdraw_amount + fee_share +``` + +**到期后锁定:** 当 `time ≥ betting_expiration` 时禁止 LP 提取。所有 LP 持仓锁定至裁定。 + +### 提前提取的本金安全 + +提取减去原始 `weight_a` 与 `weight_b`(而非当前储备的比例份额)。若 `reserve_a < weight_a` 或 +`reserve_b < weight_b`,提取被**阻止**。 + +### 创建者作为首位 LP + +市场创建者自动成为首位 LP。其 `sec_to_expiration` 等于完整市场时长,给予最大时间权重。 + +--- + +## 10. 裁定与赔付 + +### 赔付优先顺序 + +| 优先级 | 类型 | 接收方 | 金额 | +|----------|------|-----------|--------| +| 1 | Oracle fee (2) | 预言机 | `floor(losers_sum × oracle_fee_percent / 10000)` | +| 1.5 | Creator fee (7) | 创建者 | `floor(losers_sum × creator_fee_percent / 10000)` | +| 2 | Creator LP (1) | 创建者 | `principal + time-weighted fee share` | +| 3 | LP return (1) | LP | `principal + time-weighted fee share` | +| 4 | Winner bets (0) | 赢家 | `bet_amount + floor(winners_pool × weight / total_winning_weight) − penalty_deduction`(同注分彩) | +| 5 | Dispute refund (5) | 争议参与者 | (若适用) | +| 6 | Oracle penalty bonus (6) | 全部参与者 | (若预言机被罚) | + +### 输家侧 + +赔付 = 0。本金并入储备池。 + +### 零成交量市场 + +LP 取回全部本金。预言机取得固定费(若有)。所有费用累加器保持为 0。 + +--- + +## 11. 撤注 + +### 前置条件 + +| 条件 | 检查 | +|-----------|-------| +| 下注活跃 | `bet.status == 0` | +| 用户拥有该下注 | `bet.user == current_user.id` | +| 市场活跃 | `market.status == 1` | +| 下注开放 | `current_time < market.betting_expiration` | +| 允许撤注 | `market.allow_cancellation == 1` | + +### 反向 CPMM 机制 + +对 A 侧下注(side=0): + +``` +new_reserve_a = reserve_a + tokens +new_reserve_b = floor(k / new_reserve_a) +amount_returned = reserve_b − new_reserve_b +if amount_returned <= 0: amount_returned = 0 +``` + +B 侧对称。 + +### 滑点保护 + +可选 `min_return` 参数。若 `amount_returned < min_return`,交易被拒。 + +### 状态变更(原子) + +1. 下注状态 → 1(已撤),记录 `returned_amount` +2. 更新市场储备 +3. 市场下注合计按原下注额递减 +4. 用户余额增加 `amount_returned`,`bets_balance` 减少 +5. 历史记录(type=4) +6. 带前/后储备的 market log 记录 + +--- + +## 12. 争议系统 + +### 提交前置条件 + +- 提交者在该市场下过注 +- 在裁定后 `pm_dispute_grace_sec` 之内 +- 按模式路由:**委员会**(`dispute_mode = 0`)—— 无需裁决者账户,SHARES 选民通过 `pm_dispute_vote` 投票(公开、可改至 `voting_end_time`、权重 = `effective_vesting_shares` + 懒惰池质押→shares),由 `pm_dispute_finalize` 定时任务计票;**账户**(`dispute_mode = 1`)—— 市场指定的 `dispute_resolver` 作出 `pm_dispute_resolve` +- 市场无未结争议 +- 提交者支付 `pm_dispute_fee` + +### 预言机响应 + +须在 `pm_oracle_dispute_response_sec` 内。错过则从保险自动罚没 `pm_dispute_fee`,并记录在预言机对象上。 + +预言机以 **`pm_dispute_oracle_respond`**(op ID 98)提交其反驳。由于争议是一场公开听证,该文本存储**在争议对象上** +(`oracle_response` + `oracle_response_time`,可经 `get_dispute` 读取),以便每位委员会投票者或账户裁决者在裁决 +前予以权衡。仅市场的预言机可响应,且仅在争议未结且 `now ≤ oracle_response_deadline` 时;再次提交将覆盖先前的 +响应。 + +### 争议生命周期 + +``` +Resolution (T=0) → Grace period (T to T+12h) → Dispute filed (T≤12h) + → Oracle response (12h window) → Resolver decision (up to 14 days) + → After verdict: recalculate or unfreeze → Auto-payout after new grace period + → Auto-close fallback (T+14 days): full refund + oracle penalty +``` + +### 争议成立(预言机有误 —— 翻转) + +对提出者的奖励是罚没额中切出的一份;**罚没的其余部分资助获胜下注者**(经 `forfeit_pool`),而非裁决者、也非 +DAO。**委员会投票者与账户模式裁决者均不获报酬。** + +``` +reward_target = floor(dispute_fee × pm_dispute_reward_multiplier / 10000) // bp; 30000 = ×3 +bonus = max(0, reward_target − dispute_fee), 以罚没额为上限 + +1. Disputer ← dispute_fee(托管退回)+ bonus // bonus 取自被罚没的保险 +2. forfeit_pool += (slash − bonus) // → 赢家,结算时经 winners_pool +``` + +罚没大小:委员会模式将预言机的 `dispute_penalty_percent` 按 `consensus_strength` 缩放;账户模式取裁决者的 +`penalty_amount`(均以剩余保险为上限)。 + +### 争议被驳回(预言机正确 —— 维持) + +``` +Disputer 将全部 dispute_fee → 预言机(100%,补偿)。 // 无 50/50 裁决者/DAO 拆分 +``` + +### 重算流程(预言机有误) + +1. 校验罚款(以剩余保险为上限) +2. 支付提出争议者奖励(fee + bonus);罚没余额 → `forfeit_pool`(赢家) +3. 罚没预言机保险 +5. 应用封禁(若请求) +6. 删除所有现存未支付的 payouts +7. 翻转获胜结果(A↔B) +8. 以修正后的结果从头重生成赔付 +9. 记录审计轨迹 + +### 裁决者权力 —— 封禁是合规/监管功能(仅账户模式) + +以下制裁是**账户模式** `pm_dispute_resolve`(op ID 80)的字段,由市场指定的 `dispute_resolver` 发出。当该裁决者 +是**监管方或持牌仲裁人**时,这正是其执行链下规则的方式:在单次裁决中即可罚没保险,**并将预言机与市场创建者双方** +从平台上封禁,可临时或永久。**委员会/DAO 模式(`dispute_mode = 0`)在设计上无封禁权**——公开听证只罚没保险 +(按共识强度缩放)并经 `pm_dispute_finalize` 调整声誉;它从不封禁。 + +| 参数 | 类型 | 描述 | +|-----------|------|-------------| +| `penalty_amount` | mVIZ | 额外保险罚没(0 至剩余)→ DAO 基金 | +| `ban_oracle` | 0/1 | 封禁预言机 | +| `ban_oracle_until` | unix ts / 0 | 0=永久,>0=到期 | +| `ban_creator` | 0/1 | 封禁创建者 | +| `ban_creator_until` | unix ts / 0 | 0=永久,>0=到期 | + +封禁会将发出封禁的 `resolver` 记录到目标的 `banned_by`。**解除封禁:** 同一裁决者可以 **`pm_unban`**(op ID 99, +`unban_oracle` / `unban_creator`)**提前**解除;否则封禁在 `banned_until` 到期时自然失效,此时逐块 cron 将其清除 +并发出 **`pm_ban_expired`** 虚拟操作(ID 100),以便历史/索引器观察到该解除。 + +### 自动关闭(14 天回退) + +| 动作 | 描述 | +|--------|-------------| +| 原告 | 退回争议费 | +| 预言机 | 从保险罚没 `dispute_fee` | +| 下注 | 全部退款(原始金额) | +| LP | 全部退款(仅本金) | +| 罚款分配 | 罚没额按比例分配给所有参与者 | +| 争议状态 | 置为 3(自动关闭) | + +### No-Contest 声明 + +预言机以 `market_id` 与 `reason` 调用 `oracle-no-contest`。 + +1. 所有下注 → 待退款赔付(全额原始金额) +2. 所有 LP 持仓 → 待退款赔付(仅本金) +3. 罚款:从保险扣 `dispute_fee` 的 `oracle_no_contest_penalty_percent`% +4. 罚款按比例分配给参与者 +5. 市场:`resolved_outcome = -1`,`payout_status = 1` +6. 宽限期开始(可争议) + +### 3 结果裁定(No-Contest 争议) + +裁决者从以下选其一: +- `correct_outcome = 0` —— A 胜(重算赔付) +- `correct_outcome = 1` —— B 胜(重算赔付) +- `correct_outcome = -1` —— 确认 no-contest(保留退款赔付) + +若预言机有误:待退款赔付被删除,替换为正确的赢家赔付。适用标准争议罚则。 + +--- + +## 13. 预言机错过裁定的惩罚 + +若预言机未在 `result_expiration` 前裁定: + +``` +penalty_amount = floor(oracle_insurance × oracle_penalty_percent / 100) +``` + +### 分配 + +``` +stakes[user_id] += bet_amount (for each active bet) +stakes[user_id] += liquidity_amount (for each active LP position) +total_stakes = Σ stakes[user_id] + +bonus_i = floor(penalty_amount × stakes[user_id] / total_stakes) +``` + +每个参与者获得:全额退款(本金)+ 比例奖励。 + +市场终结:status=3,payout_status=2。 + +--- + +## 14. 预言机声誉评分 + +### 原始指标(每个预言机 14 个计数器) + +| 指标 | 类型 | 来源 | +|--------|------|--------| +| `markets_accepted` | counter | oracle-accept-market | +| `markets_resolved` | counter | resolve-market | +| `markets_no_contest` | counter | oracle-no-contest | +| `markets_missed` | counter | cron(错过截止) | +| `disputes_received` | counter | create-dispute | +| `disputes_lost` | counter | resolve-dispute (status=1) | +| `disputes_won` | counter | resolve-dispute (status=2) | +| `disputes_auto_closed` | counter | cron(14 天自动关闭) | +| `dispute_responses_missed` | counter | cron(12h 响应截止) | +| `total_volume_resolved` | mVIZ | resolve-market(bets_sum 之和) | +| `total_insurance_slashed` | mVIZ | 所有罚款事件 | +| `avg_resolution_time` | seconds | resolve-market | +| `bans_received` | counter | resolve-dispute | +| `active_since` | timestamp | register-oracle | +| `last_active_time` | timestamp | accept/resolve/no-contest | + +### 派生比率 + +分母:`total_outcomes = markets_resolved + markets_no_contest + markets_missed` + +| 比率 | 公式 | +|------|---------| +| `resolution_rate` | `markets_resolved / total_outcomes` | +| `dispute_loss_rate` | `disputes_lost / disputes_received` | +| `no_contest_rate` | `markets_no_contest / total_outcomes` | +| `deadline_miss_rate` | `markets_missed / total_outcomes` | +| `dispute_response_rate` | `1 − (dispute_responses_missed / disputes_received)` | + +### 可靠性分数(0–100) + +``` +reliability_score = clamp(0, 100, + BASE_SCORE + − W_DISPUTE_LOSS × dispute_loss_rate × 100 + − W_NO_CONTEST × excess_no_contest × 100 + − W_DEADLINE_MISS × deadline_miss_rate × 100 + − W_NO_RESPONSE × (1 − dispute_response_rate) × 100 + + W_VOLUME_BONUS × volume_tier + + W_EXPERIENCE × experience_tier × freshness_multiplier + − W_BAN_PENALTY × bans_received +) +``` + +其中 `excess_no_contest = max(0, no_contest_rate − 0.10)`。 + +### 默认权重 + +| 权重 | 值 | +|--------|-------| +| BASE_SCORE | 50 | +| W_DISPUTE_LOSS | 0.40 | +| W_NO_CONTEST | 0.10 | +| W_DEADLINE_MISS | 0.20 | +| W_NO_RESPONSE | 0.15 | +| W_VOLUME_BONUS | 0–25(分层:≥10K→+5、≥100K→+10、≥500K→+15、≥1M→+20、≥5M→+25) | +| W_EXPERIENCE | 0–25(分层:≥7d→+5、≥30d→+10、≥90d→+15、≥180d→+20、≥365d→+25) | +| W_BAN_PENALTY | 每次封禁 15 | + +### 新鲜度衰减 + +| 距上次活跃天数 | 乘数 | +|----------------------|-----------| +| ≤ 30 | 1.00 | +| 31–90 | 0.75 | +| 91–180 | 0.50 | +| > 180 | 0.25 | + +### 综合信任分数 + +``` +trust_score = reliability_score × risk_factor +``` + +| 风险分(保险/下注) | risk_factor | +|---------------------------|-------------| +| ≥ 3.0× | 1.00 | +| ≥ 2.0× | 0.95 | +| ≥ 1.0× | 0.85 | +| < 1.0× | 0.70 | + +### 新预言机检测 + +`total_outcomes < 5` → `is_new = true`。UI 中独立徽章。 + +分数在读取时经 `compute_oracle_reliability_score()` 计算,不存储。 + +--- + +## 15. 持仓转让 + +### 操作 + +``` +pm_transfer_position { bet_id, to_user, amount, memo } +``` + +- 将一笔下注的全部或部分代币转给另一账户 +- 转出的代币保留原市场与结果 +- 赔付在裁定时归当前持有者 +- 无滑点、无市场冲击 —— 纯粹的记录重新分配 +- 对 Onix Binary 与 Onix Multi 持仓均适用 + +### Memo 隐私模型 + +| 模式 | 格式 | 可见性 | +|------|--------|------------| +| 明文 | 不以 `#` 开头的字符串 | 链上公开 | +| 加密 | 以 `#` 开头的字符串 | 私密 —— 仅发送方与接收方可解密 | + +加密:使用 VIZ 账户 memo 密钥的 ECIES 共享密钥 `ECDH(sender_memo_private, recipient_memo_public)`(标准 +Graphene 模型)。客户端加解密。 + +--- + +## 16. 懒惰流动性池 + +### 参数 + +| 中位数投票参数 | 作用 | +|---------|-------------| +| `pm_lazy_pool_enabled` | 池 kill-switch | +| `pm_lazy_alloc_percent` | 每个市场分配的自由余额份额(bp) | +| `pm_lazy_max_total_alloc_percent` | 活跃市场上池占比的上限(bp) | +| `pm_lazy_recall_step_percent` | 闲置市场的渐进式召回步长(bp) | +| `pm_lazy_lock_sec` | 存款锁定期(秒) | +| `pm_lazy_emergency_penalty_percent` | 紧急提取对锁定利润的罚则(bp) | +| `pm_lazy_min_liquidity_fee_percent` | 池进行分配所需的市场最低 `liquidity_fee_percent`(bp);低于它市场得不到池流动性(奖励下限) | +| `pm_min_liquidity` | 每个市场的最低分配(亦为市场种子下限) | + +### 存款 + +- 首位存款者:`shares = amount` +- 后续:`new_shares = amount × total_shares / free_balance` +- 锁定计时器:`unlock_time = now + pm_lazy_lock_sec` +- 计算份额前先结算奖励:`pending += shares × (pool.rps − user.snapshot) / PRECISION` + +### 自动分配 + +在市场激活(status → 1)时: + +``` +alloc_amount = free_balance × allocation_percent / 100 +× (1 − active_market_penalty_pct / 100) ^ oracle_active_market_count +× (1 − fault_penalty_pct / 100) ^ oracle_active_fault_stamps +``` + +奖励下限闸门(最先检查):若市场 `liquidity_fee_percent < pm_lazy_min_liquidity_fee_percent`,池**不**分配任何资金——它只补贴 LP 费用付得起的市场。此时创建者自己的种子是唯一流动性。 + +检查:`alloc_amount ≥ min_market_allocation`,`allocated + alloc_amount ≤ total × max_total_allocation / 100`。 + +Pool LP 以 `user=0` 插入。在时间加权费用分配中等同参与。 + +### 奖励分配(懒惰记账) + +在带池 LP 利润的市场裁定时: + +``` +profit = lp_return − allocation_amount +if profit > 0 AND total_shares > 0: + pool.reward_per_share += profit × PRECISION / total_shares +``` + +用户奖励(读取时计算): + +``` +live_reward = pending_rewards + shares × (pool.rps − user.snapshot) / PRECISION +``` + +### 计划提取 + +从合并的已解锁记录(全部或部分): + +``` +1. Run unlock consolidation +2. Settle rewards: pending += shares × (rps − snapshot) / PRECISION +3. Share value = shares_to_burn × free_balance / total_shares +4. Reward portion = pending_rewards × withdraw_percent / 100 +5. Total payout = share value + reward portion +``` + +### 紧急提取 + +全部存款(锁定 + 未锁定): + +``` +1. Settle rewards +2. total_value = shares × free_balance / total_shares + pending_rewards +3. profit = total_value − principal_deposited +4. if profit > 0: penalty = profit × (locked_shares / total_shares) × emergency_penalty / 100 +5. Penalty → pool reward_per_share +6. User receives: total_value − penalty +``` + +### 机会成本保护 + +> **治理 vs 硬编码。** 仅召回**步长**由中位数投票——`pm_lazy_recall_step_percent`。其余为**硬编码**,更改需 +> 硬分叉:**10 步**划分(`window/10`、`check_step ≥ 10`)、闲置判定(自上次检查起*无新下注*即为闲置—— +> `bets_sum ≤ bets_sum_at_check`),以及**每活跃市场 5% 惩罚**(`alloc × 95/100`)。故障印记的罚则与到期窗口 +> 同为硬编码。 + +**A. 渐进式召回:** 市场时长分为 **10 个固定步**。每步若自上次检查起**无新下注**,则向池召回当前分配的 +`pm_lazy_recall_step_percent`(bp,治理)。 + +**B. 活跃市场惩罚:** `factor = (1 − 5%) ^ active_market_count`——硬编码,同一预言机每个并发活跃市场递归减 5%。 + +**C. 故障印记:** 在不良市场结果(no-contest、错过截止、零成交量、败诉、无响应、自动关闭)时,预言机获得一个 +在固定洁净运营窗口后自动到期的故障印记;每个活跃印记进一步降低其分配。(罚则大小与到期窗口为硬编码。) + +--- + +## 16a. 可选杠杆(懒惰池提供资金) + +自 HF14 上线;可选,由中位数 kill-switch `pm_leverage_enabled`(默认关)治理。 + +- **开仓**(`pm_leverage_open`)—— 下注者提交抵押;懒惰池从 `free_balance` **借出**保证金(受 + `leverage_fund_used` 限制;开仓时对照 `pm_leverage_fund_percent`、`…_max_per_position_bp`、 + `…_max_position_ratio_percent`、`…_min_market_liquidity` 检查)。不增发代币 —— 从系统视角看持仓足额 + 抵押。杠杆开仓**不**创建 `pm_bet`;曲线权重持有在 `pm_leverage_position_object` 上。 +- **清算** —— 按**下注前储备**执行,故池回收 `min(cancel_value, obligation) ≥ loan`:对向下注级联 + (`pm_place_bet`)与结算强平始终全额回收(贷款 + 利息 → 池);**唯一**有界的坏账路径是同侧 + `pm_cancel_bet`(情形 B)。级联**不**受 `pm_leverage_enabled` 门控(该标志仅阻止新开仓),故关闭杠杆永 + 不剥夺已开持仓的保护。 +- **虚拟操作** —— `pm_leverage_resolve`(结算时强平,带结果 + 杠杆)、`pm_leverage_liquidate`(盘中, + `reason` 0 对向 / 1 撤注)。 +- **治理权重** —— 懒惰池存款人保留其在 PM 争议与 DAO 委员会的投票权重(池 NAV → vesting-shares,经 + `get_vesting_share_price`,HF14 门控)。 + +API:`get_account_leverage_positions`、`get_market_leverage_positions`、`get_lazy_pool`。 + +## 16b. 批量 / 提交-揭示下注(抗 MEV) + +自 HF14 起对**二元**市场上线(多元在 LMSR 批量落地前强制 `allow_instant_bet`);按市场可选(`allow_batch` +/ `allow_instant_bet`),中位数 kill-switch `pm_commit_reveal_enabled`。 + +- `pm_place_bet` 带 `mode = 1` 将一笔**批量**下注入队;`pm_commit_bet`(承诺哈希 + 托管)→ + `pm_reveal_bet` 运行**提交-揭示**流程。未揭示的承诺通过 `pm_commit_forfeit` 没收 + `pm_commit_no_reveal_penalty_percent`(bp)。 +- 在每个纪元边界(`pm_batch_epoch_blocks`,揭示窗口 `pm_reveal_window_blocks`)入队下注由 `pm_batch_settle` + 定时任务以**统一价格**结算 —— 只有净残量推动 AMM,故批内排序无优势,且 `Σ reserve ≥ L` 不变量得以保持。 + +## 17. 链上对象模型 + +所有状态都存于在 HF14 注册为核心索引的 **chainbase 对象**中 —— 不存在 SQL 数据库。字段级定义位于操作/对象头文件中,并可通过 [`prediction_market_api` 插件](../plugins/prediction-market-api) +只读查询。原型保存在 `users` 表上的声誉计数器现为 `pm_oracle_object` 的字段。 + +| 对象(索引) | 保存 | 按何查找 | +|---|---|---| +| `pm_oracle_object` | 预言机注册、保险、14 个声誉计数器、故障印记、封禁(`banned_until` + `banned_by`) | owner | +| `pm_market_object` | 市场配置、CPMM 储备(`reserve_a/b`、`k`)、`*_fee_percent`(bp)、`status` / `payout_status`、计时器、`dispute_mode`、`a_bets_sum` / `b_bets_sum`、预言机裁定声明(`decision_url` / `decision_reason`) | id / creator / oracle / result_expiration | +| `pm_outcome_object` | 每结果的 LMSR `q`、`bets_sum`、`bets_count`(多元市场) | market + outcome | +| `pm_bet_object` | 一笔下注 —— account、`side` / `outcome_index`、`amount`、曲线 `weight`、`time_penalty`、`status`、`mode` | market / account | +| `pm_liquidity_object` | 一个 LP 持仓 —— 本金、存款时间、时间权重;`provider` 为空 ⇒ 懒惰池 LP | market | +| `pm_commit_object` | 提交-揭示的承诺哈希 + 托管(批量 / 提交-揭示) | market / account | +| `pm_dispute_object` | 一个争议 —— disputer、`proposed_outcome`、费用托管、计时器、`status`、`dispute_mode`、预言机反驳(`oracle_response` / `oracle_response_time`) | market | +| `pm_dispute_vote_object` | 一张委员会选票 —— voter、`vote_outcome`、`vote_percent`(关闭前可改) | market + voter | +| `pm_lazy_pool_object` | 单例池 —— `free_balance` / `allocated_balance` / `earned_balance`、`reward_per_share`、`leverage_fund_used`、`total_shares` | 单例(id 0) | +| `pm_lazy_deposit_object` | 一位存款人 —— shares、奖励快照、解锁时间 | account | +| `pm_lazy_allocation_object` | 池对某市场的静默 LP 分配 + 渐进式召回状态(`bets_sum_at_check`、`check_step`、`recalled_amount`) | market | +| `pm_leverage_position_object` | 一个已开杠杆持仓 —— collateral、loan、obligation、曲线权重、`status` | account / market + status | +| `pm_creator_ban_object` | 被封禁的创建者 —— `banned_until`、`ban_count`、`banned_by` | ban account | + +声誉指标在读取时计算(`compute_oracle_reliability_score()` —— §14),不存储。所有百分比字段均为基点 +(`*_percent`,bp)。懒惰池的按市场分配与渐进式召回状态位于 `pm_lazy_allocation_object`;预言机故障印记与声誉计数器位于 `pm_oracle_object`。 + +**仅插件(非共识):** `pm_market_meta_object` —— 链下解析的市场元数据(类别 / 标签 / 受禁司法辖区),用于 +发现与辖区过滤;由 [`prediction_market_api`](../plugins/prediction-market-api) 插件从每个市场不透明的 +`metadata` 字符串构建,从不参与共识。 + +这些对象的完整字段定义见[预测市场操作](../protocol/operations/prediction-markets)。 diff --git a/@l10n/zh-CN/docs/prediction-markets/whitepaper.md b/@l10n/zh-CN/docs/prediction-markets/whitepaper.md new file mode 100644 index 0000000000..c17ead3f3a --- /dev/null +++ b/@l10n/zh-CN/docs/prediction-markets/whitepaper.md @@ -0,0 +1,725 @@ +--- +title: Onix 协议 —— 白皮书 +description: Onix 协议产业白皮书:VIZ DLT 上具备流动性保障的预测市场。 +--- + +# Onix 协议:VIZ DLT 上具备 LP 保障的预测市场 + +**产业白皮书** + +*Anatoly Piskunov (On1x)* +*版本 2.0 —— 2026 年 6 月(链上 / HF14)* + +--- + +> **链上状态(HF14)。** 本文最初针对中心化原型撰写。协议现已作为 VIZ DLT 上的**一等公民共识操作 +> (`pm_*`)**运行,并在 `consensus_sim` 中验证。自 HF14 起上线:两种市场类型(CPMM 二元 + LMSR 多元)、 +> 同注分彩零和结算、**懒惰池**、可选**杠杆**子系统、可选**批量 / 提交-揭示下注**(二元)、保证金预言机, +> 以及双模式争议系统(委员会 / 账户)。所有百分比参数均为**基点(bp):10000 = 100.00%**(原型使用千分比 +> permille)。下文在链上设计与原型文本不同之处均有标注。 + +## 摘要 + +预测市场将分散的信息聚合为价格,其概率估计持续优于民调、专家组和统计模型。然而采用仍受一个结构性问题制约: +**流动性提供者会亏钱。** + +Uniswap v3 的 LP 承受无常损失。LMSR 做市商面临全部补贴的风险。CLOB 做市商遭遇逆向选择。每一种现有模型都要求 +资本提供者以不确定的收益换取下行风险——数据显示大多数人亏损。 + +**Onix 协议**彻底消除 LP 风险。这是一种预测市场架构,其中 LP 本金由**结算机制本身结构性地保障**——不是靠 +保险,不是靠对冲。赢家只从输家被没收的本金中获得赔付。LP 资金提供市场深度,但从不用于结算下注。 + +本文描述 Onix 协议的两种市场类型——**Onix Binary**(恒定乘积做市商)与 **Onix Multi**(LMSR 定价 + +同注分彩结算)——以及市场架构、预言机与争议解决系统、懒惰流动性池、可选杠杆、治理模型,及其作为共识级操作在 +VIZ DLT 上的实现。 + +--- + +## 1. 问题:预测市场中的 LP 风险 + +每个预测市场都需要流动性。没有它,价格便毫无意义——一笔使市场移动 20% 的下注暴露的是下注者的资金,而非群体 +的智慧。根本问题是:**谁提供这份流动性,他们承担什么风险?** + +### 1.1 当前格局 + +| 平台 | LP 模型 | LP 风险 | 收益来源 | +|----------|----------|---------|-------------| +| **Uniswap v3** | 集中流动性 AMM | 无常损失(常 >5% 年化;>50% 的 v3 LP 跑不赢买入持有) | 交易手续费 | +| **Aave / Compound** | 借贷池 | 智能合约风险、清算级联 | 借款人利息 | +| **Curve** | Stableswap AMM | 锚定资产低 IL、智能合约风险 | 手续费 + CRV 增发 | +| **Standard LMSR** | 做市商补贴 | 损失高达 `b × ln(N)`——全部补贴 | 买卖价差 | +| **Polymarket (CLOB)** | 主动做市 | 库存风险、逆向选择 | 买卖价差 | +| **Kalshi** | 无 LP 概念 | 不适用(交易所模型) | 不适用 | + +格局清晰:为预测市场提供流动性要么需要主动管理技能(CLOB),要么容忍资本损失(LMSR),要么接受无常损失 +(AMM)。这些都不适合零售参与者。 + +### 1.2 为何重要 + +预测市场在深而具流动性时表现最佳。深度市场产生准确价格、吸引知情交易者,并产生使预测市场成为公共品的信息价 +值。但深度需要资本,资本需要风险补偿。 + +结果是先有鸡还是先有蛋的问题: +- 浅市场 → 高滑点 → 差体验 → 少下注者 → 低手续费 → 无 LP 激励 → 浅市场 + +打破此循环需从等式的 LP 一侧移除风险。若提供流动性是无风险的,进入门槛降为零,飞轮便可开始转动。 + +--- + +## 2. Onix 协议 + +### 2.1 设计原则 + +Onix 协议建立在三条架构不变量之上: + +1. **LP 本金结构性安全。** 这不是风险缓释策略——而是赔付架构的属性。LP 资金与下注结算取自物理上分离的池。 + +2. **输家资助赢家。** 所有赔付(赢家利润、预言机费、创建者费、LP 费)只取自输家被没收的本金。手续费在裁定时 + 计算为 `floor(losers_sum × fee_bp / 10000)`(bp:10000 = 100.00%),从不在下注时扣除。 + +3. **双市场类型,单一保障。** 二元市场(Onix Binary)与多元市场(Onix Multi)使用不同定价公式,但共享同一 + 结算模型与同一 LP 保障。 + +### 2.2 Onix Binary(CPMM + 同注分彩结算) + +Onix Binary 使用恒定乘积做市商公式——与 Uniswap 相同的 `x * y = k` 不变量——作为二元结果的**定价引擎**, +配合**同注分彩结算**(输家按权重比例资助赢家),与 Onix Multi 相同的结算模型。 + +**机制:** + +市场维护两个储备 `reserve_a` 与 `reserve_b`,乘积恒为 `k`: + +``` +k = reserve_a × reserve_b +``` + +当用户对结果 A 下注 `amount`(实现中 side 0 → `reserve_a`)时,本金进入该侧储备,代币从**对侧**储备取出: + +``` +new_reserve_a = reserve_a + amount +new_reserve_b = floor(k / new_reserve_a) +tokens_received = reserve_b − new_reserve_b +``` + +`tokens_received`(称为 `weight`)是用户对赢家池的**相对索取权**(若结果 A 获胜;结算为同注分彩——见下, +与 Onix Multi 相同)。被下注的一侧其隐含概率上升(A 上的钱越多 → `reserve_a` 增长 → `P(A)` 增长): + +``` +P(A) = reserve_a / (reserve_a + reserve_b) +P(B) = reserve_b / (reserve_a + reserve_b) +``` + +**结算与 LP 安全性证明(同注分彩):** + +裁定时,赢家取回本金,外加按权重的输家池比例份额(与 Onix Multi 相同): + +``` +winners_pool = losers_sum − fees +payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit +``` + +LP 本金 `L` 无条件返还,保障精确: + +``` +Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN +``` + +无论权重如何,总赔付以 `losers_sum` 为上限,故 LP 资金从不用于结算下注。CPMM 是**定价引擎**(概率 + +权重);它不门控赔付。(AM-GM 关系 `reserve_a + reserve_b ≥ 2√k = L` 对定价曲线仍成立,但不再用于偿付能力。) + +**示例:** + +``` +Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000 +Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%) + +Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%) +Bob bets 80 VIZ on B → receives weight 81.82 + +Resolution: A wins + Losers (Bob): 80 VIZ forfeited → losers_sum = 80 + oracle_fee = floor(80 × 50/10000) = 0.4 VIZ + creator_fee = floor(80 × 50/10000) = 0.4 VIZ + liq_fee = floor(80 × 100/10000) = 0.8 VIZ + winners_pool = 80 − 1.6 = 78.4 VIZ + + Alice (only winner, weight 33.33 of 33.33): + payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit) + LP return: 200 VIZ principal + share of 0.8 VIZ fee pool +``` + +### 2.3 Onix Multi(LMSR + 同注分彩结算) + +Onix Multi 是协议针对 3–10 个结果市场的创新。它结合 Hanson 的对数市场评分规则(LMSR,2003)做实时定价, +与同注分彩结算来保障 LP 安全。 + +**定价(LMSR softmax):** + +对结果为 {1, 2, ..., N} 的市场,每个结果有数量参数 `q_i`: + +``` +price(i) = exp(q_i / b) / Σ_j exp(q_j / b) +``` + +这是 softmax 函数——价格按构造总是恰好相加为 1.0。无需任何套利机制或 split/merge 操作。 + +在结果 i 上买入 Δ 代币的成本: + +``` +C(q) = b × ln(Σ_j exp(q_j / b)) + +cost = C(q + Δ·e_i) − C(q) +``` + +参数 `b` 控制价格敏感度(b 越高,每笔下注的价格冲击越小)。它由 LP 补贴提供资金:`b = S / ln(N)`,其中 +S 为总补贴。 + +**创新——同注分彩结算:** + +在**标准 LMSR** 中,做市商是所有下注的对手方。若群体正确预测结果,做市商损失高达 `b × ln(N)`——可能是全部 +补贴。这正是 LMSR 在企业预测市场(Microsoft、Inkling,由运营方吸收损失)之外采用有限的原因。 + +**Onix Multi 改变了赔付来源。** 裁定时: + +``` +1. Oracle declares the winning outcome +2. Losers forfeit 100% → losers_sum +3. Fees deducted from losers_sum (bp; 10000 = 100.00%): + oracle_fee = floor(losers_sum × oracle_fee_bp / 10000) + creator_fee = floor(losers_sum × creator_fee_bp / 10000) + liq_fee = floor(losers_sum × liquidity_fee_bp / 10000) + winners_pool = losers_sum − fees +4. Winners receive: + payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty +5. LP subsidy returned unconditionally +``` + +赢家由输家支付,而非 LP。补贴在架构上与结算流分离。 + +**LP 本金保障证明:** + +1. LP 存入 `S` VIZ 作为补贴,用于提供市场深度。 +2. 下注期间,用户支付 VIZ → 取得结果代币。VIZ 累积为下注池。 +3. 裁定时,输家被没收的本金资助赢家赔付与手续费。补贴 `S` 从未进入赔付池。 +4. 无论结果如何,补贴无条件返还给 LP。 + +**对比:** + +| 维度 | Standard LMSR | Onix Multi | +|-----------|---------------|------------| +| LP 角色 | 所有下注的对手方 | 深度存款(非对手方) | +| LP 最大损失 | `b × ln(N)`(全部补贴) | **零** | +| 赢家赔付 | 1 代币 = 1 单位货币 | 代币 = 对输家池的比例索取权 | +| 需要 CTF split/merge? | 是(强制价格之和 = 1) | 否(softmax 保证) | + +**示例(3 结果选举):** + +``` +Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ +Initial: price(A) = price(B) = price(C) = 33.3% + +Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%) +Bob bets 100 VIZ on B → ~88 tokens +Carol bets 30 VIZ on C → ~29 tokens + +Resolution: A wins + Losers: Bob (100) + Carol (30) = 130 VIZ + Fees (200 bp = 2% total): 2.6 VIZ + winners_pool = 127.4 VIZ + + Alice: 50 + (47/47 × 127.4) = 177.4 VIZ + LP: 1000 VIZ returned in full + share of liquidity fees +``` + +### 2.4 边界情形 + +| 情形 | 结果 | +|----------|---------| +| 全部下注于赢家 | `losers_sum = 0` → 每个下注者恰好取回其下注额。LP 补贴返还。零和。 | +| 无人下注于赢家 | 整个输家池未分配 → LP 奖励。LP 利润最大化。 | +| 零成交量市场 | LP 补贴全额返还。无手续费、无赔付。 | +| 单一下注者获胜 | 该下注者获得 `bet_amount + winners_pool`。LP 补贴返还。 | + +--- + +## 3. 市场架构 + +### 3.1 市场生命周期 + +```mermaid +stateDiagram-v2 + direction LR + state "Waiting (0)" as Waiting + state "Active (1)" as Active + state "Closed (2)" as Closed + state "Resolved (3)" as Resolved + state "Deleted (-1)" as Deleted + state "Paid out" as Paid + [*] --> Waiting + Waiting --> Active: 预言机接受 + Waiting --> Deleted: 预言机拒绝 + Active --> Closed: 下注到期 + Active --> Resolved: 提前裁定(若允许) + Closed --> Resolved: 预言机裁定 + Resolved --> Paid: 宽限窗口(12 小时) + Deleted --> [*] + Paid --> [*] +``` + +市场由创建者创建,经预言机(质押保险)审阅并接受,开放下注,以某一结果裁定,并在争议宽限期后赔付。 + +### 3.2 费用模型(仅从输家提取) + +Onix 协议的一个鲜明特征是**下注时不扣任何手续费**。完整下注额进入市场储备。手续费仅在裁定时计算,且只取自输 +家被没收的本金: + +```mermaid +flowchart TD + LS["losers_sum(输家下注的 100%)"] + LS --> OF["oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)"] + LS --> CF["creator_fee = floor(losers_sum × creator_fee_bp / 10000)"] + LS --> LF["liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)"] + LS --> WP["winners_pool = losers_sum − 所有费用"] +``` + +这提供了结构性保障:手续费与赢家赔付取自完全分离的资金来源。费用提取永不与对赢家的义务竞争。 + +**预言机费用条款在接受时冻结(offer→quote)。** 创建者公布预言机可收取的**上限**(bp 计的 +`oracle_fee_percent` 上限 + `oracle_fixed_fee` 上限);预言机接受时报出其实际条款(≤ 创建者上限且 ≤ 中位 +治理上限 `pm_max_oracle_fee_percent`),冻结到市场上并发出 `pm_market_accepted` 虚拟操作。自预言机在创建时 +冻结其条款。**预言机固定费**(每个市场)从输家池余额支付(从不增发)。**市场创建费**进入 DAO 基金作为反垃圾 +保护。 + +### 3.3 时间加权 LP 分配 + +LP 手续费份额按 `amount × max(1, seconds_to_expiration)` 比例分配: + +``` +weight_i = amount_i × max(1, sec_to_expiration_i) +fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j) +``` + +早期 LP 每单位资本的收益远高于后期 LP。在 48 小时市场中,第 1 小时存入的 LP 每 VIZ 收益约为第 47 小时存入者 +的 ~2400 倍。 + +每笔存款作为独立持仓追踪——同一用户的多笔存款分别加权与支付。无论市场结果如何,LP 本金始终全额返还。 + +### 3.4 迟下注的时间惩罚 + +为抑制临近到期的下注(其不确定性风险更小),对临近到期下注施加可配置的时间惩罚: + +``` +if time_to_expiration < penalty_window: + ratio = 1 − (time_to_expiration / penalty_window) + penalty_ratio = ratio² // quadratic (default) + time_penalty = floor(penalty_ratio × max_penalty) +``` + +惩罚**仅作用于利润**,永不作用于本金。获胜下注者始终至少取回其原始本金。二次曲线在惩罚窗口早期平缓、后期陡 +峭,奖励「稍迟」而非「极迟」。 + +### 3.5 持仓转让 + +持仓可通过原生协议操作在账户间转让: + +``` +pm_transfer_position { bet_id, to_user, amount, memo } +``` + +无滑点、无市场冲击——纯粹的记录重新分配。`memo` 字段支持明文与加密两种模式(通过 VIZ 账户 memo 密钥的 +ECIES),可实现 P2P 交易、OTC 交易与私密备注。 + +这是 Conditional Tokens Framework(Polymarket/Gnosis)中唯一带来真实用户价值的可组合性特性。CTF +split/merge 在架构上不必要——Onix 定价公式按构造保证价格一致性。 + +--- + +## 4. 预言机与争议解决 + +### 4.1 保证金预言机模型 + +Onix 协议中的预言机默认不被信任——它们是**有保证金的**。每个预言机必须: + +- 以一次性费用注册(默认 10 VIZ) +- 存入保险(最低 5,000 VIZ) +- 显式接受市场(每次接受都质押其保险) +- 以结果及支撑证据(decision URL)裁定市场 + +保险保证金创造问责:误裁、错过截止或败诉的预言机其保险会被罚没。为使经济安全模型成立,保证金必须超过预言机潜 +在的操纵利润。 + +预言机收入有两个来源: +1. **固定费**(每个市场)——补偿质押保险与提供裁定 +2. **百分比费**(裁定时取自输家池)——随市场成交量扩展 + +### 4.2 争议仲裁 + +任何下注者可在宽限期内支付争议费以质疑裁定。争议期间所有赔付冻结。 + +裁决以两种**按市场选择**的模式之一进行,于创建时选定: + +- **委员会模式(`dispute_mode = 0`,默认)**——*全体 SHARES 选民*通过**按质押加权投票** + (`pm_dispute_vote`)决定,由 `pm_dispute_finalize` 定时任务在 `voting_end_time` 确定性地计票。这是 + **公开听证**:实时计票可查询,投票**不**隐藏于 commit-reveal 之后(一项刻意且永久的选择——DAO 尽可能 + 透明地裁决争议)。由于听证期间会浮现新证据,**投票可修改**至关闭(重复投票覆盖先前的)。投票者权重为其 + `effective_vesting_shares` **加上其转换为 vesting-shares 的懒惰池质押**,因此把 VIZ 存入池中的 DAO + 成员保留其治理权重。 +- **账户模式(`dispute_mode = 1`)**——单一指定的 `dispute_resolver`(建议多签)作出裁决 + (`pm_dispute_resolve`)。 + +任一模式下的裁决逻辑: + +**若预言机有误(翻转):** +- 应用正确结果并重算赔付。 +- 提出争议者取回其费用,**外加从被罚没保险中切出的奖励**,大小为 + `dispute_fee × pm_dispute_reward_multiplier`(bp;如 30000 = ×3),以罚没额为上限。 +- **罚没的余额加入赢家池**(经 `forfeit_pool`)——归获胜下注者,**而非**裁决者或 DAO。委员会投票者与账户 + 模式裁决者**均不获奖励**(委员会投票是无偿的治理职责)。 +- 预言机保险被罚没(委员会模式按共识强度缩放,账户模式按裁决者的 `penalty_amount`),并可附加封禁。 + +**若预言机正确(维持):** +- 提出争议者**将全部争议费没收给预言机**(对恶意挑战的补偿)。 +- 原赔付不变继续。 + +**防拒裁:** 若裁决者 14 天内不作为,争议自动关闭:所有下注与 LP 退款,预言机受罚,提出争议者的费用退回。这 +保证资金永不被无限期冻结。 + +### 4.3 预言机声誉评分 + +协议为每个预言机追踪 14 项链上指标并计算可靠性分数(0–100): + +``` +reliability_score = clamp(0, 100, + 50 (base) + − 0.40 × dispute_loss_rate × 100 + − 0.10 × excess_no_contest × 100 + − 0.20 × deadline_miss_rate × 100 + − 0.15 × (1 − dispute_response_rate) × 100 + + volume_bonus (0–25) + + experience_bonus × freshness_multiplier (0–25) + − 15 × bans_received +) +``` + +关键设计选择: +- **比率而非计数**——100 次中败 1 次(1%)优于 2 次中败 1 次(50%) +- **从 50 中性起步**——新预言机须赢得声誉,而非从 100 起步 +- **新鲜度衰减**——不活跃的预言机随时间失去经验加分 +- **成交量分层**——高成交量预言机因经过验证的记录获得加分 + +可靠性分数与风险因子(保险与下注量之比)结合,产生**综合信任分数**——展示给用户的主要指标。 + +### 4.4 No-Contest 与 3 结果裁定 + +无法核实结果的预言机可自愿宣布 **no-contest**,以较低罚则触发退款(从保险扣除 50% 争议费——远比败诉便宜)。 +这形成激励梯度: + +| 情形 | 预言机成本 | 封禁风险 | +|----------|------------|----------| +| 自愿 no-contest | 500 VIZ | 无 | +| 败诉 | 1,000+ VIZ + 额外罚款 | 永久或临时 | +| 错过截止 | 250 VIZ(自动罚款) | 无(但损害声誉) | + +若用户认为预言机滥用 no-contest,可对其提出争议。裁决者随后从**三个**可能的正确结果中选择:A 胜、B 胜,或 +确认 no-contest。这阻止预言机用 no-contest 来逃避向获胜下注者赔付。 + +--- + +## 5. 懒惰流动性池 + +### 5.1 资本部署问题 + +个体 LP 提供需要主动选择市场。多数用户不会手动评估并存入特定市场。结果:多数市场仅以创建者的初始流动性启动, +产生浅订单簿与高滑点。 + +### 5.2 池到市场的自动分配 + +懒惰流动性池通过接受存款并在每个新市场激活时**自动分配**池自由余额的一个百分比来解决此问题: + +``` +alloc_amount = free_balance × allocation_percent / 100 +``` + +分配按当前自由余额(非原始总额)计算,形成几何衰减——池永不被完全耗尽: + +``` +After 50 markets (2% allocation each): ~357 VIZ free from original 1,000 +After 100 markets: ~133 VIZ still free +``` + +最大总分配上限(默认 70%)提供额外安全。 + +### 5.3 奖励分配(一个共享累加器) + +问题:当市场带池利润裁定时,该利润须按份额比例分给**所有**当前存款人——但在每个市场上遍历每位存款人将是 +O(N) 且无界。池用**一个全局累计值** `reward_per_share`(「rps」)避免这一点: + +``` +// When a market resolves with pool LP profit, the per-share value of the pool rises once: +pool.reward_per_share += profit × PRECISION / total_shares + +// A depositor's earnings = their shares × how much rps has risen since they last touched the pool: +live_reward = pending + shares × (pool.reward_per_share − user.snapshot) / PRECISION +``` + +通俗地说:每位存款人「拥有」每次 `reward_per_share` 上升中的一份,其奖励 = +`份额 × (当前 rps − 上次存取时记录的 rps)`。存款人自己的记录**仅在其行动时**(存入/提取)才被触及;在此之前 +其权益在全局数字中静默累积。因此向成千上万存款人分配利润是 **O(1)**(一次加法),且在领取前不付出任何资金。 +这是出自 +[SushiSwap MasterChef 合约](https://github.com/sushiswap/masterchef/blob/master/contracts/MasterChef.sol) +(及 Compound 的 cToken 指数)的著名累加器模式;`PRECISION`(1e9)保持整数除法精确。 + +### 5.4 机会成本保护 + +池向每个市场自动分配,形成一个攻击面:恶意预言机可创建长期零成交量市场以锁定池资金。三种机制应对: + +**渐进式提前召回:** 市场时长分为 10 步。每步若下注量低于阈值(分配的 1%),则将当前分配的 10% 召回池中。 +完全闲置的 30 天市场会失去约 60% 的分配。 + +**活跃市场惩罚:** 同一预言机每多一个活跃市场,其分配减少 5%(递归)。拥有 10 个活跃市场的预言机每个市场仅获 +约 60% 的基础分配,激励质量而非数量。 + +**故障惩罚印记:** 不良结果(错过截止、败诉、零成交量裁定)产生惩罚印记,进一步降低未来分配。印记在 10 天干 +净运营后自动到期。 + +### 5.5 可选杠杆(由懒惰池提供资金) + +懒惰池**从一个 `free_balance` 承担两种角色**:静默的市场 LP 分配*以及*为**可选杠杆**子系统提供资金。下注者 +可开立杠杆持仓(`pm_leverage_open`),其保证金为**来自池的贷款**——不增发代币,从系统视角看持仓始终足额抵 +押。会击垮 CLOB 清算引擎的二元「跳空风险」通过**按下注前储备清算**处理:对向下注或结算强平回收 +`min(cancel_value, obligation) ≥ loan`,故池取回其贷款外加利息;唯一有界的坏账路径是同侧 +`pm_cancel_bet`。中位数 kill-switch(`pm_leverage_enabled`,默认关)阻止*新*开仓,但保护性清算级联刻意 +**不**受其门控——关闭杠杆永不剥夺已开持仓的保护。池在 LP 收益之外赚取杠杆利息,按相同的 MasterChef 方式记账。 +池贷款的结算发出 `pm_leverage_resolve` / `pm_leverage_liquidate`。 + +--- + +## 6. 治理 + +### 6.1 代表投票的链参数 + +VIZ 使用委托权益证明(DPoS)共识,由当选代表(验证人)通过中位数投票机制治理链参数: + +1. 每位代表公布所有参数的偏好值 +2. 网络计算所有活跃代表投票的**中位数** +3. 中位数移动时参数自动改变——无需硬分叉、无需部署 + +所有预测市场参数(费用、罚则、保险要求、争议窗口、懒惰池设置、杠杆旋钮、批量/提交-揭示时序)均由代表投票。所 +有百分比参数均以**基点(bp),10000 = 100.00%**: + +| 示例 | 治理 | +|----------|-----------| +| `pm_dispute_fee`、`pm_max_oracle_fee_percent`(bp) | 代表中位数投票 | +| `pm_dispute_grace_sec`、`pm_dispute_vote_period_sec` | 代表中位数投票 | +| `pm_dispute_approve_min_percent`、`pm_dispute_reward_multiplier`(bp) | 代表中位数投票 | +| `pm_lazy_*` 分配/召回、`pm_leverage_*`(enabled、fund %、max position) | 代表中位数投票 | +| `pm_commit_reveal_enabled`、`pm_batch_epoch_blocks`、`pm_reveal_window_blocks` | 代表中位数投票 | + +硬分叉仅用于结构性变更(新操作类型、公式改变),而非经济调参。Kill-switch(`pm_leverage_enabled`、 +`pm_commit_reveal_enabled`)让治理可通过中位数投票停用整个子系统而无需分叉。 + +### 6.2 司法辖区客户端模型 + +VIZ DLT 是基础设施,而非运营方——类似于比特币是账本而非货币传输方。协议中立且无需许可。法律义务附着于**客户 +端应用**,而非共识算法。 + +任何司法辖区都可在 VIZ DLT 上构建合规客户端: + +| 客户端组件 | 实现 | +|-----------------|---------------| +| 预先批准的预言机 | 客户端持有许可、经 KYC 验证的预言机白名单 | +| 预先批准的裁决者 | 政府批准的争议解决机构 | +| KYC/AML | 客户端层身份验证 | +| 费用路由为税收 | `dao_fund_account_id` → 国库账户 | +| 市场限制 | 客户端按允许类别过滤 | +| 下注限额 | 客户端强制的人均上限 | + +相同的协议操作(`pm_place_bet`、`pm_resolve`、`pm_dispute`)对无许可与受监管客户端运行一致。差异完全在客 +户端层。 + +--- + +## 7. 竞争格局 + +### 7.1 平台对比 + +| 维度 | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR | +|-----------|-------------------|------------|--------|---------------| +| **定价** | CPMM(二元)/ LMSR softmax(多元) | CLOB | CLOB | LMSR | +| **LP 风险** | **零**(结构性保障) | 库存风险 | 不适用 | 高达 `b × ln(N)` | +| **LP 知识门槛** | 低(存入即赚) | 高(管理订单) | 不适用 | 中 | +| **费用模型** | 裁定时输家池的 % | 买卖价差 | 交易所费用(1-7%) | 价差 | +| **预言机** | 每市场保证金 + 委员会争议 | UMA 乐观预言机 | Kalshi(CFTC 监管) | 运营方 | +| **迟下注惩罚** | 二次、可配置 | 无 | 无 | 无 | +| **持仓转让** | 原生协议操作 + 加密 memo | CTF(ERC-1155) | 无 | 无 | +| **治理** | 代表投票参数 | 团队多签 | CFTC 流程 | 运营方 | +| **基础设施** | VIZ DLT(共识级) | Polygon(智能合约) | 专有服务器 | 各异 | + +### 7.2 为何不需要 CTF Split/Merge + +Polymarket 使用 Gnosis Conditional Tokens Framework(CTF),其持仓为可拆分与合并的 ERC-1155 代币以强制价 +格一致性(价格之和 = $1)。 + +在 Onix 协议下,此机制在架构上不必要: + +- **Onix Binary(CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` —— 按定义 +- **Onix Multi(LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` —— 按 softmax 定义 + +无需套利机制。价格一致性是公式的数学属性,而非外部强制层。 + +### 7.3 飞轮 + +``` +Risk-free LP → lower barrier for retail LPs + → more liquidity deposited + → deeper markets, less slippage + → better UX for bettors + → more volume + → more fees for LPs + → attracts even more LPs +``` + +「无无常损失的被动收益」是 Uniswap、Balancer 与 Curve 无法提供的价值主张。对加密原生受众而言,这是一个引人 +注目的叙事:通过为预测市场提供流动性赚取收益,本金零风险。 + +--- + +## 8. VIZ DLT:从原型到协议 + +### 8.1 当前状态 + +协议起步为带中心化后端的 Telegram WebApp(所有市场逻辑在服务端)——一个有已知限制的可用原型:除 Telegram +账户外无女巫抵抗、无抗审查、无可组合性。**该迁移现已完成:** 完整市场逻辑作为 **VIZ DLT 上经共识验证的 +`pm_*` 操作**运行(HF14),在 `consensus_sim` 中端到端验证。本节其余部分描述这一现已实现的链上架构。 + +### 8.2 迁移架构 + +VIZ DLT 是一种分布式账本技术,约 3 秒出块、DPoS 共识、命名账户(Graphene 风格),且无通用智能合约。预测市 +场操作实现为**一等公民、经共识验证的操作**——非智能合约,非 `custom_json` 负载。 + +| 层 | 示例 | 经共识验证? | +|-------|---------|---------------------| +| **协议操作** | `pm_create_market`、`pm_oracle_accept_market`、`pm_place_bet`、`pm_commit_bet`/`pm_reveal_bet`、`pm_resolve_market`、`pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`、`pm_lazy_deposit`/`pm_lazy_withdraw`、`pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | 是——每个节点验证 | +| **虚拟操作** | `pm_payout`(每下注者)、`pm_auto_payout`、`pm_market_accepted`、`pm_dispute_finalize`、`pm_dispute_auto_close`、`pm_oracle_missed_penalty`、`pm_lazy_recall`、`pm_batch_settle`、`pm_commit_forfeit`、`pm_leverage_resolve`/`pm_leverage_liquidate` | 是——确定性,在出块时生成 | +| **metadata / custom_json** | 争议评论、市场描述、UI 元数据 | 否——仅展示/索引 | + +每个金融动作(下注、添加流动性、裁定市场、扣押保险)都由每个验证人验证。无效操作在入块前被拒。无 Solidity、 +无 gas 估算、无字节码部署。 + +前端是**完全无头的 Web 客户端**——无后端服务器、无数据库、无会话。私钥存于浏览器(加密),交易本地签名并广播 +到公共 VIZ 节点。不依赖 Telegram;核心应用与平台无关。 + +### 8.3 自原型以来已交付,及剩余路线图 + +**已链上交付(HF14):** + +| 特性 | 状态 | +|---------|--------| +| 提交-揭示 + 批量下注(二元、可选、中位数 kill-switch) | ✅ 上线 | +| 可选杠杆(懒惰池提供资金、下注前储备清算) | ✅ 上线 | +| 懒惰池(自动分配、渐进式召回、MasterChef 记账) | ✅ 上线 | +| 每下注者 / 杠杆结算虚拟操作 + 插件 API | ✅ 上线 | +| 懒惰池质押作为治理权重(PM 争议 + DAO 提案) | ✅ 上线 | + +**剩余路线图:** + +| 优先级 | 特性 | 影响 | +|----------|---------|--------| +| 高 | 共享流动性池(类别级 AMM) | 在架构层面解决流动性碎片化 | +| 高 | 自动数据预言机(外生数据源) | 消除客观市场的操纵 | +| 中 | 分层争议窗口(小市场 vs 大市场) | 更好的 UX 校准 | +| 中 | LMSR 批量结算(将批量/提交-揭示扩展到多元) | 多元市场目前强制即时下注 | +| — | **争议**的提交-揭示投票 | **刻意拒绝**——争议保持公开听证(见 §4.2) | + +--- + +## 9. Onix 不主张什么 + +对取舍与局限的诚实披露: + +- **LP 利润不被保证。** 若某市场零输家下注,则无手续费可分配。LP 取回本金但无收益。 + +- **存在平台风险。** 漏洞、攻击与治理攻击独立于做市商模型。LP 保障是结构性的(赔付架构),并非保险(无外部担保 + 基金)。 + +- **Onix Multi 代币并非固定面值工具。** 在标准 LMSR 中,1 个获胜代币 = 1 单位货币。在 Onix Multi 中,代币是 + 对输家池的比例索取权。若所有下注者都选中赢家,则人人保本。 + +- **LP 收益取决于成交量,而非深度。** 补贴 100,000 VIZ 的市场与补贴 1,000 VIZ 的市场,若成交量与费率相同, + 赚取相同的绝对手续费。补贴提供深度,而非收益。 + +- **DPoS 治理有已知取舍。** 验证人少于 PoW/PoS、代表集中风险、按代币加权投票。这些是 DPoS 模型固有的(EOS、 + Hive、Tron 共有),非 VIZ 特有。 + +- **VIZ 代币流动性目前较低。** 经济保障(保险保证金、争议费)随代币价格扩展。协议假设效用随时间驱动需求——这 + 是每个协议原生代币项目所下的同一注。 + +--- + +## 10. 结论 + +Onix 协议针对预测市场采用的根本障碍:LP 风险。通过在二元(CPMM)与多元(LMSR + 同注分彩)市场中结构性地将 +LP 资金与下注结算分离,Onix 使流动性提供变为无风险且对零售参与者可及。 + +关键创新: + +1. **LP 本金保障** 作为架构不变量,而非保险 +2. **输家资助赢家** 结算,消除手续费与赢家赔付的竞争 +3. **LMSR 定价 + 同注分彩结算**(Onix Multi)——将经验证的价格发现与 LP 安全结合 +4. **时间加权 LP 分配**,奖励早期资本投入 +5. **二次时间惩罚** 作用于利润(永不本金),用于迟下注 +6. **懒惰流动性池**,具自动分配、渐进式召回与 MasterChef 记账——同时为可选**杠杆**子系统提供资金(池提供保 + 证金、下注前储备清算、除有界 cancel-bet 路径外无坏账) +7. **保证金预言机模型**,具声誉评分、offer→quote 费用冻结与双模式争议系统——委员会争议为**公开听证**,投票 + 可修改且按懒惰池加权 +8. **可选抗 MEV**——批量 / 提交-揭示下注(二元),带中位数 kill-switch +9. **共识级实现** 于 VIZ DLT——无智能合约、无 gas、无外部 keeper;严格**零和**(协议从不增发代币) + +赌注很简单:若无风险 LP 吸引资本、资本创造深度、深度改善价格、价格吸引下注者,那么 Onix 协议便解决了预测市场 +的流动性问题。协议机制可数学验证。经济假设将由市场检验。 + +--- + +## 11. 作者与披露 + +### 作者 + +**Anatoly Piskunov**(On1x)—— 俄罗斯 IT 创新者、Web3/DLT 开发者、VIZ 区块链的创建者。其工作横跨分布式账 +本技术、去中心化社交协议与数字社区的经济模型。 + +主要贡献包括:VIZ 区块链(Fair DPoS、社会资本原语)、Onix 协议(具 LP 保障的预测市场)、Voice 协议(抗审查 +消息)以及关于区块链经济学与 Web3 架构的大量出版物。 + +完整出版物与项目列表:[https://on1x.com](https://on1x.com) + +### 披露 + +Forecaster 与 Onix 协议的作者同时是 VIZ DLT 的创建者。迁移路线图提议将平台迁至作者设计并构建的区块链。 + +这一点已预先披露。这也是常态:Polymarket 依赖 Polygon Labs 的基础设施,Kalshi 运行在自有服务器上,Augur +设计了其运行所依赖的 REP 代币。每个平台都为自身基础设施辩护。问题不在于作者是否有利益——他们总有——而在于技 +术主张是否可证伪。本文中的每一条公式、证明与机制都可数学验证,且代码库开源。 + +--- + +## 12. 参考文献 + +1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. —— 对数市场评分规则(LMSR)。 + +2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* —— 恒定乘积做市商(`x * y = k`)。 + +3. Adams, H., et al. (2021). *Uniswap v3 Core.* —— 集中流动性与无常损失分析。 + +4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ —— ERC-1155 预测市场持仓。 + +5. UMA Protocol. *Optimistic Oracle Documentation.* —— Polymarket 使用的争议升级机制。 + +6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* —— cToken 累加器模式(reward_per_share 的基础)。 + +7. SushiSwap. *MasterChef Contract.* —— 奖励分配的懒惰记账模式。 + +8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* —— VIZ DLT 架构、DPoS 共识、命名账户。 + +9. Piskunov, A. (2019). *What is Fair DPoS.* —— 委托权益证明中的治理创新。 + +10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* —— 区块链系统作为数字政体的框架。 diff --git a/@l10n/zh-CN/docs/prediction-markets/workflows.md b/@l10n/zh-CN/docs/prediction-markets/workflows.md new file mode 100644 index 0000000000..25119631ce --- /dev/null +++ b/@l10n/zh-CN/docs/prediction-markets/workflows.md @@ -0,0 +1,687 @@ +--- +title: 预测市场 —— 工作流与交互图 +description: 一个典型的 Onix 二元市场贯穿每个参与者,附正常与争议裁定的零和主账本。 +--- + +# 工作流与交互图 + +一个**典型场景**贯穿每个参与者。每个角色发送特定的**已签名操作**,被特定的**虚拟操作**触及,并以两种结果的 +**代币 发送 / 接收** 表收尾: + +- **正常裁定** —— 预言机裁定,宽限期过去,`pm_auto_payout` 结算。无争议。 +- **争议裁定** —— 预言机裁定 **A**,争议**翻转为 B**,随后进行结算。 + +所有金额为抽象 **VIZ**。所有百分比为 **bp**(10000 = 100.00%)。结算严格**零和**——从不增发代币, +`current_supply` 不变: + +``` +Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal + == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, 争议中) +``` + +## 典型市场 **M**(binary CPMM,A vs B) + +| 项 | 值 | +|------|-------| +| 引擎 | binary CPMM(`x·y=k`),`weight = tokens_out` | +| 种子流动性(marketmaker) | **2000** → 储备 A=1000 / B=1000 | +| `oracle_fee_percent`(预言机报价) | **1000**(10%) | +| `creator_fee_percent` | **500**(5%) | +| `liquidity_fee_percent` | **500**(5%) | +| `oracle_fixed_fee`(预言机报价) | **10** | +| `dispute_penalty_percent` | **+10000**(罚没至多 100% 保险 ×consensus) | + +示例链参数:`pm_market_creation_fee` 5、`pm_oracle_registration_fee` 10、`pm_min_oracle_insurance` 5000、 +`pm_dispute_fee` 1000、`pm_dispute_reward_multiplier` 30000(**3×**)、`pm_no_contest_penalty_percent` +5000、`pm_oracle_penalty_percent` 500、`pm_lazy_emergency_penalty_percent` 5000、 +`pm_leverage_pool_profit_percent` **R = 10%**、`pm_lazy_alloc_percent` 2000(20%)。 + +### 角色表 + +| 角色 | 职责 | 质押 / 动作 | +|-------|------|----------------| +| **maker** | 创建者 + 首位 LP | 种子 2000 流动性 | +| **orac** | 外部预言机 | 保险 5000;报价 10% + 固定 10 | +| **LP1** | 市场内流动性提供者 | 增加 1000 | +| **A** | 下注者 —— 赢家 | 100 于 **A**,早期;权重 100 | +| **C** | 下注者 —— 迟到赢家 | T+85% 时 100 于 **A**;权重 100;时间惩罚 **50%** | +| **B** | 下注者 —— 输家 | 200 于 **B**;权重 200 | +| **D** | 杠杆 **×10** 赢家 | collateral 10 + loan 90(市场 **L**) | +| **E** | 杠杆 **×5** 被清算 | collateral 20 + loan 80(市场 **L**) | +| **LZ1** | 懒惰池存款人 | 存入 1000 | +| **disp** | 争议提出者 | 托管争议费 1000 | + +> 曲线权重(100 / 100 / 200)显式写出以便同注分彩的算术可读;真实 CPMM 随储备移动会给出略少的权重。 + +## 交互图 + +**市场生命周期。** + +```mermaid +flowchart LR + W["Waiting (0)"] -->|预言机接受| A["Active (1)"] + W -->|预言机拒绝| X["Deleted (-1)"] + A -->|betting_expiration| C["Closed (2)"] + A -->|提前裁定| R["Resolved (3)"] + C -->|预言机裁定| R + R -->|宽限期,无争议| P["已赔付"] + R -->|提交争议| D["Disputed"] + D -->|finalize / 裁决者| P +``` + +**结算 —— 正常裁定(A 获胜)。** 输家资助赢家;LP 本金不变(零和)。 + +```mermaid +flowchart TD + B["B 输 200(输家池)"] --> POOL{"拆分 200"} + POOL -->|oracle_fee 20 + fixed 10| OR["预言机 +30"] + POOL -->|creator_fee 10| CR["创建者 +10"] + POOL -->|liq_fee 10 + 惩罚 37| LPS["LP +47"] + POOL -->|winners_pool 150 → 利润 75| A["A → 赔付 175"] + POOL -->|利润 75 − 时间惩罚 37| C["C → 赔付 138"] + MK["maker + LP1 本金 3000"] -.全额返还.-> MK +``` + +**争议 —— 预言机裁 A,翻转为 B。** 惩罚是保险罚没(独立资金)。 + +```mermaid +sequenceDiagram + participant O as 预言机 + participant D as 提出者 + participant V as 委员会 / 裁决者 + O->>O: 裁定 A + D->>V: pm_dispute_create(托管 dispute_fee) + O-->>V: 强制响应(截止) + V->>V: pm_dispute_vote / pm_dispute_resolve → 翻转为 B + V-->>O: 保险罚没(5000) + V-->>D: 退费 + 奖励(从罚没中 2000) + V->>V: 重新结算 → B 获胜 +``` + +## 主账本 —— 正常裁定(A 获胜) + +`losers_sum = 200`(B)。从输家池扣费: +`oracle_fee = 200×10% = 20`、`creator_fee = 200×5% = 10`、`liq_fee = 200×5% = 10`、`oracle_fixed = 10`。 +`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`。`Σ 获胜权重 = 200`(A 100 + C 100)。 + +- **A**:利润 `150×100/200 = 75`,惩罚 0 → **赔付 175**。 +- **C**:利润 75,时间惩罚 `75×50% = 37`(→ LP)→ **赔付 138**。 +- **LP 奖励** = `liq_fee 10 + 惩罚 37 = 47`,按在市场中的时间拆分:**maker ~31 / LP1 ~16**。 +- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`。**creator_take** = `creator_fee 10`。 + +| 角色 | 发送 | 接收 | 净额(本市场) | +|-------|-------|----------|-------------------| +| maker | 2000 流动性 + 5 创建费 | 2000 本金 + 10 创建者费 + 31 LP 奖励 | **+36** | +| orac | (10 注册费,5000 保险锁定) | 30 oracle-take | **+30** | +| LP1 | 1000 流动性 | 1000 本金 + 16 LP 奖励 | **+16** | +| A | 100 | 175 | **+75** | +| C | 100 | 138 | **+38** | +| B | 200 | 0 | **−200** | + +**零和:** in `= 下注 400 + LP 本金 3000 = 3400`;out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`。✔ +5 创建费 + 10 注册费进入 **DAO 基金**(不属于市场池)。 + +## 主账本 —— 争议裁定(预言机裁 A → 翻转为 B) + +`disp` 托管 `dispute_fee 1000`。裁决翻转为 **B**;预言机被罚没。 +当 `dispute_penalty_percent = 10000` 且共识强度 **100%**:`slash = 5000×100%×100% = 5000`。 +奖励切分:`reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000`(≤ slash)。提出者获得 +`fee 1000 + bonus 2000 = 3000`。余额 `slash − bonus = 3000 → forfeit_pool`。 + +现在 **B 获胜**。`losers_sum = 200`(A 100 + C 100)。费用 20/10/10 + fixed 10。 +`winners_pool = 200 − 50 + forfeit 3000 = 3150`。`Σ 获胜权重 = 200`(B)。 +- **B**:利润 `3150×200/200 = 3150` → **赔付 3350**。 +- **oracle_take** 仍为 `30`(即使翻转,*市场*费用仍按冻结配置支付——惩罚是**保险罚没**,独立资金)。 + **creator_take** 10。**LP 奖励** = liq 10。 + +| 角色 | 发送 | 接收 | 净额(本市场) | +|-------|-------|----------|-------------------| +| maker | 2000 + 5 | 2000 本金 + 10 创建者费 + ~6 LP 奖励 | **+11** | +| orac | 保险 −**5000** 罚没 | 30 oracle-take | **−4970** | +| LP1 | 1000 | 1000 本金 + ~4 LP 奖励 | **+4** | +| A | 100 | 0 | **−100** | +| C | 100 | 0 | **−100** | +| B | 200 | 3350 | **+3150** | +| disp | 1000 争议费 | 3000(退费 + 2000 奖励) | **+2000** | + +**零和:** in `= 下注 400 + LP 本金 3000 + dispute_fee 1000 + 罚没 5000 = 9400`; +out `= B 3350 + 预言机 30 + 创建者 10 + lp_bonus 10 + LP 本金 3000 + 提出者 3000 = 9400`。✔ +罚没 5000 拆为提出者奖励 2000 + forfeit 3000(→ 经赢家池归 B)。 + +## 实现状态(已与代码核对) + +**常规操作 —— 全部 21 个**存在于 `operation` variant(`operations.hpp`),在 `pm_evaluator.cpp` 校验 + 求值: +`pm_oracle_register`、`pm_oracle_update`、`pm_create_market`、`pm_oracle_accept_market`、`pm_place_bet`、 +`pm_commit_bet`、`pm_reveal_bet`、`pm_cancel_bet`、`pm_add_liquidity`、`pm_withdraw_liquidity`、 +`pm_resolve_market`、`pm_no_contest`、`pm_dispute_create`、`pm_dispute_vote`、`pm_dispute_resolve`、 +`pm_transfer_position`、`pm_lazy_deposit`、`pm_lazy_withdraw`、`pm_leverage_open`、`pm_leverage_close`、 +`pm_leverage_convert`。✔ + +**虚拟操作** —— 由 `database::process_pm_markets()` / 求值器发出: + +| 虚拟操作 | 触发? | 触发点(代码) | +|------------|--------|----------------| +| `pm_market_accepted` | ✔ | `pm_oracle_accept_market` **以及**自预言机 `pm_create_market` | +| `pm_payout` | ✔ | 结算时**每笔活跃下注**——携带 `account`、`market_id`、`bet_id`、`side`/`outcome_index`、`amount`(本金)、`payout`(**输则 0**) | +| `pm_auto_payout` | ✔ | 结算时**每个市场一次**——汇总标记(`bets_sum`),与逐笔 `pm_payout` 并列 | +| `pm_commit_forfeit` | ✔ | 超过 `reveal_deadline` 未揭示的承诺 | +| `pm_dispute_finalize` | ✔ | 委员会 `voting_end_time` | +| `pm_dispute_auto_close` | ✔ | `auto_close_time`(防冻结) | +| `pm_oracle_missed_penalty` | ✔ | 预言机错过 `result_expiration` | +| `pm_lazy_recall` | ✔ | 闲置分配的渐进式召回步 | +| `pm_batch_settle` | ✔ | 纪元边界 | +| `pm_leverage_liquidate` | ✔ | 盘中清算:reason **0** 对向下注、**1** 撤注(`cascade_liquidate`) | +| `pm_leverage_resolve` | ✔ | 杠杆头寸的**结算**:携带 `market_id`、`outcome_index`、`won`、`pool_received`/`bettor_received`、`leverage`(= `total_bet/collateral`) | + +读取方法见[插件 API](../plugins/prediction-market-api) +(`get_account_leverage_positions`、`get_market_leverage_positions`、`get_creator_ban`、`get_dispute_votes`、…); +逐下注者结果(`pm_payout`)与杠杆结算(`pm_leverage_resolve`)亦见于 `account_history`。 + +## 典型场景中的角色 + +每个参与者都被贯穿市场 **M**(及杠杆子市场 **L**)来追踪:其交互图、它发送的**签名**操作、触及它的**虚拟** +操作、两种结果的**发送 / 接收**账本,以及代码核对指引。每个按角色的账本都是上述两份主账本的切片。 + +### 做市商(创建者 + 首个 LP) + +做市商创建市场 M,注入 **2000** 流动性(成为首个 `pm_liquidity_object`),并提出预言机的**报价上限**。它 +**不**裁定(那是预言机的事)。 + +```mermaid +flowchart LR + maker -->|pm_create_market| M[(pm_market_object
status=0)] + maker -->|seed 2000| LP0[(pm_liquidity_object
provider=maker)] + M -. fee 5 .-> DAO[(committee_fund)] + orac -->|pm_oracle_accept_market| M2[(M status=1)] + M2 -. VIRTUAL .-> VA[[pm_market_accepted]] + M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus] + RET --> maker +``` + +- **发送:** `pm_create_market`(将 `oracle_fee_percent`/`oracle_fixed_fee` 设为**报价上限**,加上自身的 + `creator_fee_percent` 5% 与 `liquidity_fee_percent` 5%;支付 `pm_market_creation_fee` 5 → DAO,锁定 + `liquidity` 2000);可选 `pm_add_liquidity` / `pm_withdraw_liquidity`(本金安全,从 `betting_expiration` + 到裁定锁定)。 +- **被触及:** `pm_market_accepted`(预言机接受,或自预言机在创建时);`pm_auto_payout`(返还 LP 本金 + + 按时间加权的 LP 奖励份额)。 + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 2000 流动性 + 5 创建费(→DAO) | 2000 本金 + **creator_fee 10** + **LP 奖励 ~31** | **+36** | +| 争议(→B) | 2000 + 5 | 2000 本金 + creator_fee 10 + LP 奖励 ~6 | **+11** | + +翻转时创建费仍从冻结的市场配置**照付**——争议惩罚的是**预言机**(保证金罚没),而非做市商。LP 本金无条件返还。 + +- **自预言机:** `oracle == creator` → 创建即生效,`pm_market_accepted` 带 `self_oracle=true`,做市商另外 + 赚取 `oracle_take`。 +- **核对:** `pm_create_market_evaluator`;LP 经 `settle_liquidity`;`committee_fund += pm_market_creation_fee`。 + **观察:** `get_market`、`list_markets_by_creator`、`get_market_liquidity`(`earned_fee`)、`get_market_meta`。 + +### 预言机(注册 → 接受-报价 → 裁定) + +外部预言机 **orac** 质押保证金,在接受时**报价**其费用(≤ 做市商报价且 ≤ `pm_max_oracle_fee_percent`)并裁定。 +其市场费从输家池支付;保证金仅在错过截止或争议败诉时承压。 + +```mermaid +flowchart LR + orac -->|pm_oracle_register
insurance 5000| O[(pm_oracle_object)] + orac -. reg-fee 10 .-> DAO[(committee_fund)] + orac -->|pm_oracle_accept_market
quote fee 10% + fixed 10| M[(M status=1)] + M -. VIRTUAL .-> VA[[pm_market_accepted]] + orac -->|pm_resolve_market A| M3[(M status=3)] + M3 ==>|grace passes| VP[[pm_auto_payout]] + VP -->|oracle_take 30| orac +``` + +- **发送:** `pm_oracle_register`(锁定保证金 5000,支付 reg-fee 10 → DAO,设定建议价目表); + `pm_oracle_accept_market`(**报价** fee 10% + fixed 10,各 ≤ 创建者报价且 ≤ 中位上限;冻结到 M); + `pm_resolve_market`(设 `winning_outcome`,开启宽限);可选 `pm_oracle_update` / `pm_no_contest`。常驻价目表 + 亦可在创建时自动接受市场使其上线——见预言机操作文档。 +- **被触及:** `pm_market_accepted`;`pm_auto_payout`(计入 `oracle_take`);`pm_oracle_missed_penalty` + (从未裁定 → 罚没 `pm_oracle_penalty_percent` 保证金 → DAO,全额退还下注)。 + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 保证金 5000(锁定)+ reg-fee 10(→DAO) | **oracle_take 30** = fee 20 + fixed 10 | **+30** | +| 争议(→B) | 保证金 −**5000 罚没** | oracle_take 30 | **−4970** | + +即便翻转,预言机仍保留小额**市场费**(冻结配置);惩罚是**保证金罚没**,拆分为争议者奖励与赢家 `forfeit_pool`。 +报价**低于**报价允许(价格=声誉);**高于**则拒绝。 + +- **核对:** `pm_oracle_register_evaluator`、`pm_oracle_accept_market_evaluator`(≤ 报价、≤ 上限、冻结)、 + `process_pm_markets` 错过截止扫描。**观察:** `get_oracle`、`list_oracles`、`get_market`(冻结条款)。 + +### 预言机 —— 争议中被维持(争议胜方) + +预言机裁定 **A**;争议者挑战但裁决**维持** A。预言机保留市场费**并**收取被没收的 `dispute_fee`;保证金不动, +`disputes_won++`。 + +```mermaid +flowchart LR + orac -->|pm_resolve_market A| M[(market resolved A)] + disp -->|pm_dispute_create| D[(dispute)] + D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]] + FIN -->|dispute_fee 1000| orac + FIN --> AUTO[[pm_auto_payout settles A]] + AUTO -->|oracle_take 30| orac +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 争议,维持 | 保证金 5000(**不**罚没) | oracle_take 30 + **dispute_fee 1000** | **+1030** | +| 正常(无争议) | 保证金 5000(锁定) | oracle_take 30 | **+30** | + +挑战反噬争议者并**支付给预言机**。善意市场(`dispute_penalty_percent < 0`)甚至能在改变结果时给预言机费用 +奖励——承认诚实错误。**核对:** `pm_dispute_finalize` / `pm_dispute_resolve` 的 uphold 分支。**观察:** +`get_oracle`(`disputes_won`)、`get_dispute`。 + +### 预言机 —— 被翻转 + 罚没(争议败方) + +预言机裁定 **A**;争议**翻转为 B**,保证金被**罚没**。它仍收取微小的冻结市场费(费用与惩罚是两笔钱),但损失 +大部分保证金与声誉。 + +```mermaid +flowchart LR + orac -->|pm_resolve_market A| M[(resolved A)] + disp -->|pm_dispute_create proposed=B| D[(dispute)] + D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]] + FIN -->|slash 5000| INS[oracle.insurance ↓] + INS --> SPLIT[bonus 2000 → disputer
3000 → forfeit_pool → B] + FIN --> AUTO[[pm_auto_payout settles B]] + AUTO -->|oracle_take 30| orac +``` + +`slash = 保证金 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`,是**再分配**而非 +销毁:`bonus 2000 →` 争议者,`3000 → forfeit_pool →` 新赢家(B)。净 **−4970** 对比无争议的 **+30**。罚没随 +**共识强度**(`winning_rshares / max_rshares`)缩放;`dispute_penalty_percent < 0`(善意)→ **无**罚没。被 +罚没的预言机往往也被**封禁**(下一角色)。**核对:** `pm_dispute_finalize` / `pm_dispute_resolve` 的 overturn +分支;费用仍取自 `mkt.oracle_fee_percent`。**观察:** `get_oracle`(`total_insurance_slashed`、`banned_until`)、 +`get_dispute`。 + +### 被封禁的预言机(及被封禁的创建者) + +封禁是**状态**,非转账:`pm_oracle_object.banned_until`(创建者则是 `pm_creator_ban_object`)阻止该角色承接 +**新**市场直到时间戳过去。它通常伴随翻转罚没,但本身不移动代币。 + +```mermaid +flowchart LR + resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object
banned_until = T)] + orac -->|pm_create_market / accept| CHK{now < banned_until?} + CHK -->|yes| REJ[REJECTED: 'Oracle is banned'] + CHK -->|no, expired| OK[allowed again] + resolver -->|ban_creator| CB[(pm_creator_ban_object)] + maker -->|pm_create_market| CHK2{banned?} + CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned'] +``` + +- **谁设定:** 账户模式 → `pm_dispute_resolve`(`ban_oracle`/`ban_creator` + `…_until`);委员会模式 → + `pm_dispute_finalize` 在翻转时按共识缩放封禁。`banned_until = time_point_sec::maximum()` ⇒ **永久**。 +- **代币:** 封禁本身为 **0**(纯状态);伴随的罚没是上面的翻转情形。保证金仍锁定,封禁解除且无活跃市场后可退。 +- 封禁在快照中存续并按账户为键——重新注册无法抹除。**核对:** `pm_create_market_evaluator`(`"Oracle is + banned"` / `"Creator is banned"`)。**观察:** `get_oracle`(`banned_until`、`bans_received`)、 + **`get_creator_ban(account)`**。 + +### 下注者 A —— 早期赢家 + +**A** **早期在 A 侧下注 100**(无时间惩罚),M 裁定为 A 时获胜。赔付 = 本金 + 按权重比例的赢家池份额。 + +```mermaid +flowchart LR + A -->|pm_place_bet side=A 100| BET[(pm_bet_object
weight 100)] + BET --> M[(market M
reserves shift)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|payout 175| A +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 100 | **175** | **+75** | +| 争议(→B) | 100 | **0** | **−100** | + +`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`;无惩罚 → 赔付 `100 + 75`。翻转使 A 成为**输**侧。 +赢利**仅**来自输家下注(+ forfeit 池),绝不来自增发。**发送:** `pm_place_bet`(`side=0`,instant);可选 +`pm_transfer_position` / `pm_cancel_bet`。**核对:** `pm_place_bet_evaluator`、`settle_market`。**观察:** +`get_account_positions`(`expected_payout`)、`get_market_weight_sums`;已实现的 `pm_payout` 见 `account_history`。 + +### 下注者 B —— 输家 + +**B** **在 B 侧下注 200**。M 裁定为 **A** 时,B 的下注资助赢家,B 一无所获。在争议路径中 B 成为赢家。 + +```mermaid +flowchart LR + B -->|pm_place_bet side=B 200| BET[(pm_bet_object
status active)] + BET --> M[(market M)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|status=resolved, payout 0| BET +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 200 | **0** | **−200** | +| 争议(→B) | 200 | **3350** | **+3150** | + +B 的 200 **就是** `losers_sum`(支付 40 费用 + 150 赢家池 + LP 奖励)——同注分彩的「输家资助赢家」规则。翻转时 +B 获胜,预言机 forfeit 3000 注入 B 池(`payout = 200 + 3150`)。输的下注仍由 `pm_payout` 以 **payout=0** 记录。 +**核对:** `settle_market` 输家分支。**观察:** `get_account_positions`、`get_market_bets`、`get_dispute`。 + +### 下注者 C —— 迟到赢家(时间惩罚) + +**C** **在 A 侧下注 100** 但**迟**(下注窗口 T+85%),故**时间惩罚**仅扣其*利润*(非本金)。权重与 A 相同但 +所得更少;被扣部分流向 LP。 + +```mermaid +flowchart LR + C -->|pm_place_bet side=A 100
at T+85%| BET[(pm_bet_object
weight 100
time_penalty 50%)] + BET --> M[(market M)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|payout 138| C + VP -. penalty 37 .-> LPb[LP bonus] +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 100 | **138** | **+38** | +| 争议(→B) | 100 | **0** | **−100** | + +`profit = 75`;`penalty = 75 × 50% = 37`(→ LP);`payout = 100 + 75 − 37 = 138`——同权重下比 A 的 +75 少 +**−37**。节点在下注时按市场惩罚曲线(`time_penalty_type/value`、`penalty_curve_type`)盖上 `time_penalty`。它 +遏制最后一秒抢狙并补贴流动性,而非协议。**核对:** `pm_place_bet_evaluator`(曲线求值)、`compute_settlement`。 +**观察:** `get_account_positions`(`time_penalty`)、`get_market_bets`。 + +### 下注者 D —— 杠杆 ×10 赢家 + +**D** 开 **×10** 仓:**10 抵押 + 90 贷款**(来自懒惰池)= 在 A 侧 **100**,于隔离杠杆市场 **L** +(`pm_leverage_enabled=true`,`R = 10%`)。A 获胜时,D 在偿还贷款 + 利息后保留全部 100 的上行。 +`pool_profit = loan 90 × R 10% = 9`;`obligation = 90 × 1.10 = 99`。 + +```mermaid +flowchart LR + D -->|pm_leverage_open
collateral 10 + loan 90| POS[(pm_leverage_position
total_bet 100, obligation 99)] + POOL[(lazy pool)] -.loan 90.-> POS + POS --> L[(market L, side A)] + L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]] + VR -->|min(cv,obligation) 99| POOL + VR -->|cv 200 − 99 = 101| D +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 抵押 **10** | cancel_value 200 − obligation 99 = **101** | **+91** | +| 争议(→B) | 抵押 10 | 0 | **−10** | + +盈利仓按其 `cancel_value` 平仓;池收回 `obligation 99`(贷款 90 + **9 利息**),D 在自有 10 上保留余下 → +**+91**(池 **+9**)。杠杆按清算结算,**绝不**经 `pm_auto_payout`。零和(L):in `10 + 90 + 100 = 200`;out +`101 + 99 = 200`。**发送:** `pm_leverage_open`;可选 `pm_leverage_close`(仅当 `cv ≥ obligation`)/ +`pm_leverage_convert`。**被触及:** `pm_leverage_resolve`(结算强制平仓,`reason=expiration`)。**核对:** +`force_close_positions` → `liquidate_position(reason=2)`。**观察:** **`get_account_leverage_positions`** / +**`get_market_leverage_positions`**、`get_lazy_pool`。 + +### 下注者 E —— 杠杆 ×5 被清算 + +**E** 开 **×5** 仓:**20 抵押 + 80 贷款** = 在 B 侧 **100**(市场 **L**,`R = 10%`)。裁定前一笔**对向下注**将 +曲线推向不利于 B;**级联清算**强制平仓。**E 失去抵押,但池始终被补足。** `obligation = 80 × 1.10 = 88`。 + +```mermaid +flowchart LR + E -->|pm_leverage_open
collateral 20 + loan 80| POS[(pm_leverage_position
obligation 88)] + POOL[(lazy pool)] -.loan 80.-> POS + X -->|pm_place_bet side=A| L[(market L)] + L ==>|cascade at PRE-bet reserves
cv 88 ≤ threshold| VL[[pm_leverage_liquidate
reason=opposing_bet]] + VL -->|pool_received 88 = loan 80 + profit 8| POOL + VL -->|bettor_received 0| E +``` + +E 在裁定**之前**被清算,故最终 A/B 结果(是否争议)不改变它: + +| 角色 | 发送 | 接收 | 净额 | +|------|------|------|------| +| **E** | 抵押 **20** | **0** | **−20** | +| **池** | 贷款 80 | **88**(贷款 80 + R% 8) | **+8** | + +对向下注清算在**下注前**储备执行,此时 `cancel_value ≥ loan`,故 `pool_received = min(cv, obligation)` 至少返 +还贷款——池**绝不**亏损。 + +> **唯一可能为负的路径**是同侧 **`pm_cancel_bet`(Case B)**:取消会撤回一笔*先前的大额*同侧下注(超过单笔滑点 +> 上限),为对取消者公平,它**先**按其提交价执行——故级联可能落到 `cancel_value < loan`: +> `shortfall = obligation − cancel_value`,`lazy_pool.free_balance −= shortfall`。此**坏账**是**有界的** +> (`≤ cancel_value_before × SL%`)且**罕见**(池在其余所有仓位上的 R% 足以抵消)。由 +> `leverage_cancel_bet_cascade_bad_debt` 测试覆盖。 + +池保护是结构性的(`max_per_position`、`max_position_ratio`、`safety_margin`、滑点上限、`expiration_buffer`)。 +`pm_leverage_enabled=false` **仅**阻止**新**开仓——清算级联**不**受该标志门控,故治理永远无法在途中剥夺池的保护 +(`leverage_disabled_keeps_liquidation_protection`)。**核对:** `pm_place_bet` → `cascade_liquidate(reason=0)`; +`pm_cancel_bet` → `cascade_liquidate(reason=1)`;`liquidate_position`。**观察:** +**`get_account_leverage_positions`**(`status=1`、`pool_received`、`bettor_received`)、`get_lazy_pool`。 + +### 市场内流动性提供者(LP1) + +**LP1** 向活跃 M 添加 **1000** 流动性(在做市商种子之后)。本金**始终**返还;之上赚取**按时间加权**的 LP 奖励 +份额(流动性费 + 时间惩罚 + 零头)。与懒惰池提供者不同,后者一次存入并被自动分配到多个市场。 + +```mermaid +flowchart LR + LP1 -->|pm_add_liquidity 1000| L1[(pm_liquidity_object
provider=LP1)] + L1 --> M[(market M reserves)] + M ==>|settle| SL[[settle_liquidity]] + SL -->|principal 1000 + bonus ~16| LP1 + LP1 -->|pm_withdraw_liquidity
after resolution| OUT[principal-safe exit] +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 正常(A) | 1000 | **1000 本金 + ~16 奖励** | **+16** | +| 争议(→B) | 1000 | 1000 本金 + ~4 奖励 | **+4** | + +LP 奖励池 = `liq_fee 10 + 时间惩罚 37 = 47`,按 `本金 × 在场秒数` 分配(早期做市商 ~31,较晚的 LP1 ~16)。 +**本金保证**是架构性的——种子在任何赢家获付之前返还;LP 只会放弃奖励,绝不损失本金。提取从 +`betting_expiration` 到裁定锁定。**核对:** `pm_add_liquidity_evaluator`(记录 `deposit_time`)、 +`settle_liquidity` → `distribute_lp`。**观察:** `get_market_liquidity`(`earned_fee`)、`get_market_weight_sums`。 + +### 懒惰流动性池(系统对象) + +**单例** `pm_lazy_pool_object`——不是账户。存款人一次性注资;池**自动分配**一片给每个已接受市场作为静默 LP +(`pm_liquidity_object`,`provider` 为空)、**资助杠杆贷款**并**回收**闲置分配。它赚取 LP 收益 + 杠杆利息,按 +MasterChef 方式记账(一个全局 `reward_per_share`,O(1)——见白皮书)。字段:`total_shares`、`free_balance`、 +`allocated_balance`、`earned_balance`、`reward_per_share`、`leverage_fund_used`。 + +```mermaid +flowchart TD + LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool
free 1000 / shares 1000)] + POOL ==>|on market accept
alloc 20% = 200| ALLOC[(pm_lazy_allocation
+ pm_liquidity provider=∅)] + ALLOC -->|market settles| YLD[route_pool_lp_return
principal 200 + yield 20] + YLD --> POOL + POOL -->|leverage loan 90| Dpos[D position] + Dpos -->|close/resolve: 90 + interest 9| POOL + POOL -. idle market .-> VR[[pm_lazy_recall]] + VR -->|step back to free| POOL +``` + +| 池资金流 | 效果 | +|----------|------| +| `pm_lazy_deposit` | `free_balance += amount`,铸造 shares | +| 自动分配(接受时) | `free → allocated`(静默 LP) | +| 市场结算 | `route_pool_lp_return`:本金 + 收益 → `free`;收益 → `earned` 与 `reward_per_share` | +| 杠杆开仓(D/E) | `free −= loan`,`leverage_fund_used += loan` | +| 杠杆平仓 / 结算 / 对向下注清算 | `min(cv, obligation) → free`;`cv ≥ loan` ⇒ **绝不亏损** | +| cancel-bet 清算(仅 Case B) | 回收 `cv`,**可能 < loan** → 有界**坏账** | +| `pm_lazy_recall`(闲置市场) | 闲置分配的一个 10% 步 → `free` | +| `pm_lazy_withdraw` | 销毁 shares → 本金 + pending;紧急惩罚留在池中 | + +在典型场景中池净赚 **+37 earned**(市场 M 收益 +20,杠杆 D 利息 +9,杠杆 E 对向下注回收 +8)。作为市场 LP 其 +本金无条件返还;只有*奖励*收益随争议变化。 + +> 池从单一 `free_balance` **同时**承担两种角色:市场 LP 分配(`maybe_allocate_lazy`)与杠杆贷款 +> (`leverage_fund_used` 限制后者)。所有杠杆参数都在 **`pm_leverage_open` 时**对当前中位值检查,故此后属性波动 +> 只影响*新*开仓,绝不影响已放出的贷款。 + +池中 VIZ 为**流动**而非 vested → **无**验证人调度或 committee-request 权重。**例外(HF14):** 对 **PM 委员会 +争议**,存款人的池权益**被计入**——经 `get_vesting_share_price()` 转为 vesting-shares 并加入其 `pm_dispute_vote` +权重(见下文委员会裁决者)。**核对:** `apply_hardfork(CHAIN_HARDFORK_14)`(单例)、`maybe_allocate_lazy`、 +`route_pool_lp_return`。**观察:** `get_lazy_pool`。 + +### 懒惰池中的流动性提供者(LZ1) + +**LZ1** 一次性向池存入 **1000**,任其在市场 + 杠杆贷款间分散。它赚取池聚合收益的份额(`reward_per_share`),而 +非任一市场的结果。两种退出:**计划**(锁定后)与**紧急**(锁定前,对*利润*罚一笔)。 + +```mermaid +flowchart LR + LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object
shares 1000, unlock=+7d)] + DEP --> POOL[(lazy pool)] + POOL -. yield accrues .-> RPS[reward_per_share ↑] + LZ1 -->|pm_lazy_withdraw| OUT{planned or
emergency?} + OUT -->|planned, t≥unlock| P[principal 1000 + pending 29] + OUT -->|emergency, t|pm_dispute_create proposed=B
escrow fee 1000| D[(pm_dispute_object
status open)] + D --> VOTE{committee vote
or account resolve} + VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]] + FIN -->|fee 1000 + bonus 2000| disp + FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp
3000 → forfeit_pool → winners] +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 争议,翻转(「胜」) | dispute_fee **1000** | fee 1000 退回 + **bonus 2000** | **+2000** | + +`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`,**以实际罚没 +为上限**;余下(3000)→ `forfeit_pool` → 新赢家。disp 冒险 1000,最终 **+2000**。(委员会模式:disp **不**为自己 +投票——由 SHARES 选民投。)**核对:** `pm_dispute_create_evaluator`、`pm_dispute_finalize`/`pm_dispute_resolve` +的 overturn 分支。**观察:** `get_dispute`、`get_dispute_votes`。 + +### 争议者 —— 没收费用(预言机被维持) + +**disp** 争议预言机的 **A**,但裁决**维持预言机**。托管费用**没收给预言机**作为补偿,市场按原裁定结算(A 胜)。 + +```mermaid +flowchart LR + disp -->|pm_dispute_create proposed=B
escrow fee 1000| D[(pm_dispute_object)] + D --> VOTE{committee vote
or account resolve} + VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]] + FIN -->|dispute_fee 1000| orac[oracle compensation] + FIN -->|market settles as A| AUTO[[pm_auto_payout]] +``` + +| 结果 | 发送 | 接收 | 净额 | +|------|------|------|------| +| 争议,维持(「负」) | dispute_fee **1000** | **0** | **−1000** | + +费用是争议者的利益绑定:错误/轻率的争议向预言机付费。这种不对称(错则失费、对则赢数倍)保持渠道诚实。一个始终 +**未决**的争议(预言机沉默 / 无法定人数)被强制关闭并**退还**费用(净 0)——见下文争议自动关闭,与按实质败诉不同。 +**核对:** `pm_dispute_finalize`/`pm_dispute_resolve` 的 uphold 分支。**观察:** `get_dispute`、`get_oracle` +(获得费用,`disputes_won++`)。 + +### 裁决者 —— 委员会(按权益加权,dispute_mode = 0) + +*整个 SHARES 选民*以**按权益加权投票**裁决;无单一裁决者账户。裁决在 `voting_end_time` 由 `pm_dispute_finalize` +确定性计票。 + +**投票权重** = 实时 **`effective_vesting_shares`**(`vesting − delegated + received`)**加上转为 vesting-shares +的懒惰池权益**,因为许多 DAO 成员把 VIZ 停在池里(在那里它是流动的): + +``` +pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares +pool_weight = pool_claim_viz × get_vesting_share_price() +voter_weight = effective_vesting_shares + pool_weight +``` + +参与法定人数分母同样是 `total_vesting_shares + (pool_NAV → vesting-shares)`。7 天存款锁防止存款-投票-提取博弈。 + +```mermaid +flowchart LR + V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)] + V2[voter · eff_vshares] -->|pm_dispute_vote| D + D ==>|voting_end_time| FIN[[pm_dispute_finalize]] + FIN -->|argmax rshares,
threshold check| VERDICT{uphold / overturn} + VERDICT -->|consensus_strength
scales slash & bans| OUT[settle] +``` + +- **发送(投票者):** `pm_dispute_vote` —— **auth `regular`**。`vote_outcome = -1` 维持,否则提议正确结果; + `vote_percent ∈ [-10000, 10000]`。投票者在投票开放期间可**任意次修改**选票——重复投票**覆盖**先前的(最新者 + 胜,无「Already voted」)。 +- **无 commit-reveal —— 刻意,且不会改变。** 委员会争议是**公开听证**:实时计票可见(`get_dispute_votes`),票 + 不隐藏。DAO 的价值在于尽可能真实透明地裁决争议;投票中会浮现新论据,投票者*被期望*更新;且投票者**不因**与多数 + 一致而**获酬**,故 commit-reveal 通常的反羊群(选美博弈)理由在此不适用。 + +| 角色 | 发送 | 接收 | +|------|------|------| +| 每位投票者 | 0 | **0** —— 投票是治理职责,非有偿行为 | + +投票者从不收代币;影响力是纯权益权重。经济流向按上文争议主账本归于争议者、预言机与下注者。小众市场可能不达门槛 → +落入下文争议自动关闭。**核对:** `pm_dispute_vote_evaluator`(在 `by_market_voter` 上 modify-or-create); +`pm_dispute_finalize`(`lazy_vote_weight`、`get_vesting_share_price`、法定人数、argmax、`consensus_strength`)。 +**观察:** `get_dispute_votes`(实时计票 + finalize 投影:`quorum_percent_bp`、`expected_uphold`、 +`expected_outcome`、`expected_consensus_strength_bp`)。测试:`committee_dispute_lazy_pool_voting_weight`、 +`committee_dispute_flips_outcome`。 + +### 裁决者 —— 单账户(中心化,dispute_mode = 1) + +市场指名一个 `dispute_resolver` 账户(如监管多签)独自裁决——**无权益权重、无 DAO 投票**。在创建时设定,须与 +`oracle` 和 `creator` 都不同(防自裁)。操作集与委员会模式相同;只是*由谁裁决*不同。 + +```mermaid +flowchart LR + disp -->|pm_dispute_create| D[(dispute, mode=1)] + resolver -->|pm_dispute_resolve
correct_outcome=B
penalty_amount, ban flags| FIN[[verdict]] + FIN -->|slash penalty_amount| orac[oracle.insurance ↓] + FIN -->|fee + reward| disp + FIN --> AUTO[[pm_auto_payout settles B]] +``` + +- **发送:** `pm_dispute_resolve` —— **仅指名 `dispute_resolver` 的 `active` auth**:`correct_outcome`、 + `penalty_amount`(待罚没保证金——固定额,**不**按权益缩放)、`ban_oracle`/`ban_creator`(+ `…_until`)。 + +| 角色 | 发送 | 接收 | +|------|------|------| +| 裁决者 | 0 | **0** —— 中立仲裁者 | + +裁决后的规范与委员会模式相同;只是罚没规模不同(裁决者设定的 `penalty_amount`,无 `consensus_strength` 缩放, +因只有一个决策者)。裁决者的 KYC/白名单是**客户端层**事务。**核对:** `pm_dispute_resolve_evaluator`(仅指名裁 +决者,`dispute_mode==1`)。**观察:** `get_dispute`、`get_oracle`、**`get_creator_ban(account)`**。 + +### 争议被强制结束(防冻结自动关闭) + +一个始终**未决**的争议——预言机沉默且(委员会)无法定人数——不能永远冻结市场。在 `auto_close_time`, +`pm_dispute_auto_close` 处理器强制结束它:**全员退款**,争议者费用**退还**,无响应的预言机受罚。不选赢家。 + +```mermaid +flowchart LR + disp -->|pm_dispute_create
escrow fee 1000| D[(dispute, status open)] + D -. oracle silent / no quorum .-> WAIT[auto_close_time reached] + WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]] + AC -->|refund all bets| bettors + AC -->|fee 1000 back| disp + AC -->|insurance slash → DAO| orac +``` + +| 角色 | 发送 | 接收 | 净额 | +|------|------|------|------| +| A / B / C | 下注 | 全额退款 | **0** | +| maker / LP1 | 流动性 | 本金退回 | **0**(无奖励) | +| disp | dispute_fee 1000 | **退回 1000** | **0** | +| orac | 保证金 −罚没 → DAO | — | **− 罚没** | + +这**不是**「争议者输了」:退回的费用(净 0)不同于被没收的费用(争议者输家,净 −1000)。无人获利;市场作废以打破 +冻结,代价落在未响应的预言机上。同样的作废-退款形态也覆盖 `pm_oracle_missed_penalty` 与 `pm_no_contest`。调校 +`pm_dispute_auto_close_sec`(14 天)对 `pm_dispute_vote_period_sec`(3 天),使诚实争议先行解决。**核对:** +`process_pm_markets` 自动关闭扫描(`refund_all_bets` + `return_liquidity` + 费用计入;`disputes_auto_closed++`)。 +**观察:** `get_dispute`(状态 → 自动关闭)、`get_market`、`get_oracle`。 diff --git a/@l10n/zh-CN/docs/protocol/operations/overview.md b/@l10n/zh-CN/docs/protocol/operations/overview.md index 602109cc12..31dcf15306 100644 --- a/@l10n/zh-CN/docs/protocol/operations/overview.md +++ b/@l10n/zh-CN/docs/protocol/operations/overview.md @@ -52,6 +52,32 @@ VIZ Ledger 操作是包含在交易中的原子状态变更动作。每个操作 | 58 | `use_invite_balance_operation` | active | [邀请](./invites.md) | | 60 | `fixed_award_operation` | regular | [奖励](./awards.md) | | 61 | `target_account_sale_operation` | master | [账户市场](./account-market.md) | +| 64 | `set_reward_sharing_operation` | active | [验证者](./validators.md) | +| 66 | `pm_oracle_register_operation` | active | [预测市场](./prediction-markets.md) | +| 67 | `pm_oracle_update_operation` | active | [预测市场](./prediction-markets.md) | +| 68 | `pm_create_market_operation` | active | [预测市场](./prediction-markets.md) | +| 69 | `pm_oracle_accept_market_operation` | active | [预测市场](./prediction-markets.md) | +| 70 | `pm_place_bet_operation` | active | [预测市场](./prediction-markets.md) | +| 71 | `pm_commit_bet_operation` | active | [预测市场](./prediction-markets.md) | +| 72 | `pm_reveal_bet_operation` | active | [预测市场](./prediction-markets.md) | +| 73 | `pm_cancel_bet_operation` | active | [预测市场](./prediction-markets.md) | +| 74 | `pm_add_liquidity_operation` | active | [预测市场](./prediction-markets.md) | +| 75 | `pm_withdraw_liquidity_operation` | active | [预测市场](./prediction-markets.md) | +| 76 | `pm_resolve_market_operation` | active | [预测市场](./prediction-markets.md) | +| 77 | `pm_no_contest_operation` | active | [预测市场](./prediction-markets.md) | +| 78 | `pm_dispute_create_operation` | active | [预测市场](./prediction-markets.md) | +| 79 | `pm_dispute_vote_operation` | regular | [预测市场](./prediction-markets.md) | +| 80 | `pm_dispute_resolve_operation` | active | [预测市场](./prediction-markets.md) | +| 81 | `pm_transfer_position_operation` | active | [预测市场](./prediction-markets.md) | +| 82 | `pm_lazy_deposit_operation` | active | [预测市场](./prediction-markets.md) | +| 83 | `pm_lazy_withdraw_operation` | active | [预测市场](./prediction-markets.md) | +| 91 | `pm_leverage_open_operation` | active | [预测市场](./prediction-markets.md) | +| 92 | `pm_leverage_close_operation` | active | [预测市场](./prediction-markets.md) | +| 93 | `pm_leverage_convert_operation` | active | [预测市场](./prediction-markets.md) | +| 98 | `pm_dispute_oracle_respond_operation` | active | [预测市场](./prediction-markets.md) | +| 99 | `pm_unban_operation` | active | [预测市场](./prediction-markets.md) | + +> ID 是链上单一 `operation` 变体中的固定索引(仅追加)。本表中的空缺为按 ID 交错的**虚拟**操作(见下文)—— 例如 62–63、65、84–90、94–97、100。 --- @@ -83,6 +109,19 @@ VIZ Ledger 操作是包含在交易中的原子状态变更动作。每个操作 | 59 | `expire_escrow_ratification_operation` | 托管截止日期错过 | [虚拟操作](../virtual-operations.md) | | 62 | `bid_operation` | 拍卖出价 | [虚拟操作](../virtual-operations.md) | | 63 | `outbid_operation` | 拍卖被超价 | [虚拟操作](../virtual-operations.md) | +| 65 | `stakeholder_reward_operation` | 向利益相关者的收益分成结算 | [验证者](./validators.md) | +| 84 | `pm_batch_settle_operation` | 批量纪元边界结算 | [预测市场](./prediction-markets.md) | +| 85 | `pm_commit_forfeit_operation` | 提交-揭示托管被没收(未揭示) | [预测市场](./prediction-markets.md) | +| 86 | `pm_auto_payout_operation` | 市场结算(按市场赔付标记) | [预测市场](./prediction-markets.md) | +| 87 | `pm_dispute_finalize_operation` | 委员会投票计票完成 | [预测市场](./prediction-markets.md) | +| 88 | `pm_dispute_auto_close_operation` | 争议防冻结自动关闭 | [预测市场](./prediction-markets.md) | +| 89 | `pm_oracle_missed_penalty_operation` | 预言机错过裁定截止 | [预测市场](./prediction-markets.md) | +| 90 | `pm_lazy_recall_operation` | 懒惰池渐进式召回步 | [预测市场](./prediction-markets.md) | +| 94 | `pm_leverage_liquidate_operation` | 杠杆头寸被清算 | [预测市场](./prediction-markets.md) | +| 95 | `pm_leverage_resolve_operation` | 杠杆头寸在裁定时结算 | [预测市场](./prediction-markets.md) | +| 96 | `pm_market_accepted_operation` | 市场上线(预言机接受 / 自预言机 / 自动) | [预测市场](./prediction-markets.md) | +| 97 | `pm_payout_operation` | 每下注者的同注分彩赔付 | [预测市场](./prediction-markets.md) | +| 100 | `pm_ban_expired_operation` | 临时预言机/创建者封禁失效 | [预测市场](./prediction-markets.md) | --- diff --git a/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md b/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md index 25501781d3..03ec881778 100644 --- a/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md +++ b/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md @@ -106,6 +106,8 @@ flowchart TD 预言机接受(`status → active`)或拒绝(流动性退还创建者;`status → deleted`)一个待定市场。接受时预言机通过 `oracle_fee_percent` + `oracle_fixed_fee` **报出其实际条款**——各须 `≤` 该市场创建者的报价,且 `oracle_fee_percent ≤ pm_max_oracle_fee_percent`。报价被**冻结入市场**并发出 `pm_market_accepted` 虚拟操作(使历史解析器看到上线 + 条款)。此后结算只读这些冻结字段——绝不读实时中位值。 +预言机须在创建后的 `pm_oracle_accept_window_sec`(默认 1 小时)内行动。若到市场的 `accept_deadline` 仍未接受或拒绝,逐块 cron 将作废市场(`status → deleted`),向创建者退还种子流动性(**不**含不可退还的创建费),并发出 `pm_market_expired`(见虚拟操作)。 + | 字段 | 类型 | 说明 | |------|------|------| | `market_id` | `int64` | 待定市场 | @@ -155,7 +157,14 @@ flowchart TD ### `pm_resolve_market_operation`(ID 76) **Auth:** `oracle` 的 `active` -预言机裁定为 `winning_outcome`。开启争议宽限窗口(`result_expiration + pm_dispute_grace_sec`);其过后由 `pm_auto_payout` 结算。 +预言机裁定为 `winning_outcome`。开启争议宽限窗口(`result_expiration + pm_dispute_grace_sec`);其过后由 `pm_auto_payout` 结算。预言机的裁定声明**存储在市场上**(如同预言机的 `rules_url`),因此客户端可直接通过 `get_market` 读取,无需扫描历史。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `market_id` | `int64` | 目标市场 | +| `winning_outcome` | `int16_t` | 获胜结果索引 | +| `decision_url` | `string` | 证据链接,`≤ MAX_PM_DECISION_URL_LEN`;存储在市场上 | +| `decision_reason` | `string` | 自由文本理由,`≤ MAX_PM_DISPUTE_REASON_LEN`;存储在市场上(`decision_reason`) | ### `pm_no_contest_operation`(ID 77) **Auth:** `oracle` 的 `active` @@ -177,7 +186,39 @@ flowchart TD ### `pm_dispute_resolve_operation`(ID 80) **Auth:** `resolver` 的 `active` -由市场配置的 `dispute_resolver` 做出账户模式裁决。可罚没 `penalty_amount` 的保证金,并将预言机/创建者封禁至给定时间。 +由市场配置的 `dispute_resolver` 做出账户模式裁决。可罚没 `penalty_amount` 的保证金,并将预言机/创建者封禁至给定时间(`ban_*_until = time_point_sec::maximum()` = 永久)。 + +> **封禁是一项合规/监管功能,仅限账户模式。** 当市场将其争议路由到账户模式的 `dispute_resolver`(例如监管机构或持牌仲裁者)时,该解析者可在同一裁决中对**预言机与市场创建者双方**施加处罚——临时或永久——在保证金罚没之外:这让担任解析者的监管机构得以将恶意预言机或屡犯的创建者逐出平台。**委员会/DAO 模式(`dispute_mode == 0`)按设计没有封禁权**——它是透明的公开听证,只罚没保证金并调整声誉(`pm_dispute_finalize`),绝不封禁。此处设定的封禁会将发起的 `resolver` 记入目标的 `banned_by`,因此只有该解析者可通过 `pm_unban` 提前解除;否则封禁在 `banned_until` 时失效(cron 发出 `pm_ban_expired`)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `market_id` | `int64` | 争议市场 | +| `correct_outcome` | `int16_t` | 最终正确结果(`-1` = 作废/无争议) | +| `penalty_amount` | `asset`(VIZ) | 罚没的预言机保证金 | +| `ban_oracle` / `ban_oracle_until` | `bool` / `time_point_sec` | 将预言机封禁至给定时间 | +| `ban_creator` / `ban_creator_until` | `bool` / `time_point_sec` | 将创建者封禁至给定时间(禁止创建市场) | + +### `pm_dispute_oracle_respond_operation`(ID 98) +**Auth:** `oracle` 的 `active` + +市场的预言机在开放的争议上发布**公开反驳**。由于争议是公开听证,文本存储在争议对象上(`oracle_response` / `oracle_response_time`,可通过 `get_dispute` 读取),以便每位投票者/解析者权衡。仅在争议开放且 `now ≤ oracle_response_deadline` 时允许;重新发布会覆盖先前响应。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `market_id` | `int64` | 争议市场 | +| `response` | `string` | 反驳文本,非空,`≤ MAX_PM_DISPUTE_REASON_LEN` | + +### `pm_unban_operation`(ID 99) +**Auth:** `resolver` 的 `active` + +**提前**解除由账户模式 `pm_dispute_resolve` 施加的封禁。仅目标的 `banned_by` 中记录的账户(施加封禁的解析者)可解除;`unban_oracle` / `unban_creator` 中至少须设定一个,且对应封禁当前须处于活跃状态。将 `banned_until` 设为过去时间并清空 `banned_by`。(未在此解除的封禁只会在 `banned_until` 时到期——届时 cron 发出 `pm_ban_expired`。) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `resolver` | `account_name_type` | 施加封禁的账户(须等于目标的 `banned_by`) | +| `target` | `account_name_type` | 被封禁的预言机 / 创建者 | +| `unban_oracle` | `bool` | 清除预言机封禁(`pm_oracle_object.banned_until`) | +| `unban_creator` | `bool` | 清除创建者封禁(`pm_creator_ban_object.banned_until`) | ### `pm_transfer_position_operation`(ID 81) **Auth:** `from` 的 `active` @@ -213,6 +254,12 @@ flowchart TD | 95 | `pm_leverage_resolve_operation` | 结算——杠杆头寸被强制关闭:`outcome_index`、`won`、`pool_received`/`bettor_received`、`leverage` | | 96 | `pm_market_accepted_operation` | 求值器——市场上线:预言机接受、自预言机或自动接受;冻结条款 + `self_oracle` 标志 | | 97 | `pm_payout_operation` | 结算——每个有效下注:`amount`(本金)、`side`/`outcome_index`、`payout`(**输则为 0**) | +| 100 | `pm_ban_expired_operation` | 临时的预言机/创建者封禁在 `banned_until` 时失效:cron 将其清除(字段 `account`、`oracle`、`creator`)。提前手动解除改用已签名的 `pm_unban` | +| 101 | `pm_market_expired_operation` | 待定市场的 `accept_deadline` 到期:预言机未在 `pm_oracle_accept_window_sec` 内接受/拒绝——市场作废,种子退还(创建费保留)。字段 `oracle`、`creator`、`market_id`、`refunded_liquidity` | + +> ID 91–93 是*常规*操作 `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`(见规范); +> ID 98–99 是*常规*操作 `pm_dispute_oracle_respond`/`pm_unban`(见上)。每位下注者的结果是 +> `pm_payout`;每市场的 `pm_auto_payout` 仍为结算标记。 --- diff --git a/@l10n/zh-CN/docs/protocol/operations/validators.md b/@l10n/zh-CN/docs/protocol/operations/validators.md index 7c83b51228..d7eb913e1d 100644 --- a/@l10n/zh-CN/docs/protocol/operations/validators.md +++ b/@l10n/zh-CN/docs/protocol/operations/validators.md @@ -163,4 +163,39 @@ --- +## `set_reward_sharing_operation`(ID 64) + +**授权:** `owner` 的 `active` + +**HF13 验证者收益分成。** 验证者选择将其区块奖励的一部分转发给其**利益相关者**——即为其投票的账户——按时间加权的投票权重比例分配。`sharing_rate` 为该比例,以基点表示;分成池累积后在每个纪元结束时通过 `stakeholder_reward` 虚拟操作分配。 + +| 字段 | 类型 | 描述 | +|------|------|------| +| `owner` | `account_name_type` | 设置分成比例的验证者 | +| `sharing_rate` | `uint16_t` | 转发给利益相关者的区块奖励比例,以基点表示(0 = 无,10000 = 100%) | + +```json +[64, { + "owner": "alice", + "sharing_rate": 2500 +}] +``` + +- `sharing_rate` 上限为 10000(100%)。 +- 分配按**时间加权**的投票权重进行,因此新添加的投票在成熟前获得较小的份额。 + +--- + +## `stakeholder_reward_operation`(ID 65)— 虚拟 + +在每个分配纪元,当 `sharing_rate` 非零的验证者向利益相关者支付其应得的分成区块奖励时发出。虚拟操作(从不签名);出现在 `account_history` 中。 + +| 字段 | 类型 | 描述 | +|------|------|------| +| `validator` | `account_name_type` | 分享奖励的验证者 | +| `stakeholder` | `account_name_type` | 接收份额的投票者 | +| `shares` | `asset`(SHARES) | 记入利益相关者的金额 | + +--- + 参见:[数据类型](../data-types.md)、[操作概述](./overview.md)、[链属性](../../governance/chain-properties.md)。 diff --git a/@l10n/zh-CN/docs/protocol/virtual-operations.md b/@l10n/zh-CN/docs/protocol/virtual-operations.md index 843380b33d..9c22fd7ff3 100644 --- a/@l10n/zh-CN/docs/protocol/virtual-operations.md +++ b/@l10n/zh-CN/docs/protocol/virtual-operations.md @@ -351,4 +351,32 @@ --- +## 预测市场 (HF14) + +由 PM 共识逻辑发出 —— **并非**按时钟的 cron。两个来源: +- **已签名操作的求值器**,在其应用的瞬间 —— `pm_market_accepted`(接受 / 自预言机 / 自动接受)与 `pm_leverage_liquidate`(对向或取消下注将杠杆头寸推过阈值时)。 +- **截止处理器 `process_pm_markets()`**,每块运行:结算已达到**到期 / 截止 / 争议宽限结束 / 纪元边界**的市场(上限 `pm_processing_cap_per_block`,最早截止优先)。 + +参见 [预测市场操作](./operations/prediction-markets.md)。(ID 91–93 是*常规*操作 `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`,ID 98–99 是*常规*操作 `pm_dispute_oracle_respond`/`pm_unban` —— 均见该页。) + +| ID | 操作 | 触发 | +|----|------|------| +| 84 | `pm_batch_settle_operation` | 到达纪元边界:排队下注按纪元开盘快照执行 | +| 85 | `pm_commit_forfeit_operation` | `reveal_deadline` 未揭示:罚金 → `forfeit_pool`,其余退还 | +| 86 | `pm_auto_payout_operation` | 争议宽限到期(按市场):同注分彩结算 + 返还 LP 本金 | +| 87 | `pm_dispute_finalize_operation` | 到达 `voting_end_time`:计票裁定;预言机罚金;重判/维持 | +| 88 | `pm_dispute_auto_close_operation` | 到达 `auto_close_time` 且预言机未响应:防冻结退款,罚没保证金 → DAO | +| 89 | `pm_oracle_missed_penalty_operation` | `result_expiration` 已过仍未裁决:罚没 → DAO,全额退还下注 | +| 90 | `pm_lazy_recall_operation` | 闲置的懒惰池分配到达回收步:一个分阶段步返还入池 | +| 94 | `pm_leverage_liquidate_operation` | 求值器 —— 市场进行中的杠杆清算(对向 `0` / 取消 `1` 下注级联) | +| 95 | `pm_leverage_resolve_operation` | 结算 —— 杠杆头寸按 `cancel_value` 强制关闭:`outcome_index`、`won`、`pool_received`/`bettor_received`、`leverage` | +| 96 | `pm_market_accepted_operation` | 求值器 —— 市场上线:预言机接受、自预言机或自动接受;冻结条款 + `self_oracle` 标志 | +| 97 | `pm_payout_operation` | 结算 —— 每个有效下注:`amount`(本金)、`side`/`outcome_index`、`payout`(**输则为 0**);与按市场的 `pm_auto_payout` 并列 | +| 100 | `pm_ban_expired_operation` | 临时预言机/创建者封禁在 `banned_until` 失效:cron 将其清除(`account`、`oracle`、`creator`)。提前手动解除改用已签名的 `pm_unban` | +| 101 | `pm_market_expired_operation` | 待定市场的 `accept_deadline` 到期:预言机未在 `pm_oracle_accept_window_sec` 内接受/拒绝——市场作废(`status -1`),种子流动性退还(`refunded_liquidity`),创建费**不**退还(`oracle`、`creator`、`market_id`、`refunded_liquidity`) | + +所有 PM 资金流动严格零和(无增发);结算守恒 `Σ out == Σ 下注 + LP 本金 + forfeit_pool`。 + +--- + 参见:[操作概述](./operations/overview.md)、[奖励](./operations/awards.md)、[委员会](./operations/committee.md)。 diff --git a/CMakeLists.txt b/CMakeLists.txt index de3e41e15a..3ced54bbfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,20 @@ endif() cmake_minimum_required(VERSION 3.16) +# Default to a Release build when the caller doesn't specify one. Without this, +# a bare `cmake ..` (or an IDE that doesn't pass -DCMAKE_BUILD_TYPE) configures +# with empty optimization flags, producing an unoptimized node. Single-config +# generators only; multi-config generators (MSVC, Ninja Multi-Config) pick the +# type at build time and leave CMAKE_BUILD_TYPE empty by design. +get_property(_is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT _is_multi_config AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Choose the build type (Debug, Release, RelWithDebInfo, MinSizeRel)" FORCE) + message(STATUS "CMAKE_BUILD_TYPE not set; defaulting to Release") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "RelWithDebInfo" "MinSizeRel") +endif() + set(CHAIN_NAME "VIZ") @@ -53,6 +67,16 @@ option(Boost_USE_STATIC_LIBS "Build with Boost static libraries usage" TRUE) option(BUILD_SHARED_LIBRARIES "Build shared libraries" FALSE) +# Single source of truth for the library linkage type. Each library/plugin uses +# add_library( ${VIZ_LIBRARY_TYPE} ...) instead of duplicating the full +# source list across if(BUILD_SHARED_LIBRARIES)/else() branches. Set at root +# scope so it propagates into every add_subdirectory below. +if(BUILD_SHARED_LIBRARIES) + set(VIZ_LIBRARY_TYPE SHARED) +else() + set(VIZ_LIBRARY_TYPE STATIC) +endif() + option(BUILD_TESTNET "Build source for test network (ON OR OFF)" FALSE) message(STATUS "BUILD_TESTNET: ${BUILD_TESTNET}") if(BUILD_TESTNET) @@ -114,10 +138,36 @@ if(CCACHE_FOUND) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif(CCACHE_FOUND) +# Precompiled headers (opt-in, default OFF). When ON, the heaviest libraries +# (chain, protocol, wallet) precompile a curated set of stable third-party +# headers (Boost.MultiIndex, FC reflection/serialization, std) once per target +# instead of re-parsing them in every translation unit. See each library's +# pch.hpp for the header list and rationale. +# +# Left OFF by default so it cannot affect existing/CI builds until a maintainer +# opts in and measures. If ccache is in use, PCH only helps when ccache is told +# not to hash the (large, stable) PCH timestamp: export +# CCACHE_SLOPPINESS=pch_defines,time_macros +# before building, otherwise ccache conservatively disables its own cache for +# PCH-using compilations and you lose the ccache win without gaining the PCH one. +option(ENABLE_PCH "Enable precompiled headers for heavy libraries" OFF) +if(ENABLE_PCH) + message(STATUS "ENABLE_PCH: ON (precompiling stable third-party headers for chain/protocol/wallet)") + if(CCACHE_FOUND AND NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "pch_defines") + message(WARNING + "ENABLE_PCH is ON and ccache is active, but CCACHE_SLOPPINESS does not " + "include pch_defines,time_macros. ccache will likely disable caching for " + "PCH-using compilations. Export " + "CCACHE_SLOPPINESS=pch_defines,time_macros before building.") + endif() +else() + message(STATUS "ENABLE_PCH: OFF") +endif() + if(WIN32) message(STATUS "Configuring VIZ on WIN32") - set(PLATFORM_SPECIFIC_LIBS) # Păstrăm inițializarea pentru alte biblioteci + set(PLATFORM_SPECIFIC_LIBS) # Keep the initialization for other libraries set(DB_VERSION 60) set(BDB_STATIC_LIBS 1) @@ -164,7 +214,7 @@ if(WIN32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc") endif(FULL_STATIC_BUILD) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") - # bcrypt trebuie sa fie dupa toate librariile Boost statice + # bcrypt must come after all the static Boost libraries list(APPEND PLATFORM_SPECIFIC_LIBS bcrypt) # We remove the explicit addition of bcrypt to PLATFORM_SPECIFIC_LIBS, as it is now linked globally endif(MSVC) @@ -229,6 +279,7 @@ add_subdirectory(programs) if(BUILD_CONSENSUS_TESTS) enable_testing() add_subdirectory(tests/consensus_sim) + add_subdirectory(tests/pm) endif() if(ENABLE_INSTALLER) diff --git a/README.md b/README.md index c28f49c9ea..edcf96eae4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ VIZ is a Graphene blockchain with a Fair-DPOS consensus algorithm (vote weight s ## Building -See [documentation/building.md](documentation/building.md) for detailed build instructions, including +See [docs/node/building.md](docs/node/building.md) for detailed build instructions, including compile-time options, and specific commands for Linux (Ubuntu LTS) or macOS X. ## Running in docker @@ -16,7 +16,7 @@ Auto-built image [vizblockchain/vizd](https://hub.docker.com/r/vizblockchain/viz Docker image tags: * **latest** - built from master branch, used to run production VIZ network -* **testnet** - built from master branch, could be used to run [local test network](documentation/testnet.md) +* **testnet** - built from master branch, could be used to run [local test network](docs/node/docker.md#testnet-node) Example run: diff --git a/build_mingv.sh b/build_mingw.sh similarity index 100% rename from build_mingv.sh rename to build_mingw.sh diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 11cb8fd882..9da57a101f 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,4 +1,5 @@ import { defineConfig, type DefaultTheme } from 'vitepress'; +import { withMermaid } from 'vitepress-plugin-mermaid'; const githubRepo = 'https://github.com/VIZ-Blockchain/viz-cpp-node'; @@ -51,6 +52,15 @@ interface SidebarLabels { awards: string; subscriptions: string; accountMarket: string; + predictionMarkets: string; + predictionMarketApi: string; + predictionMarketsSection: string; + onixOverview: string; + onixWhitepaper: string; + onixSpecification: string; + pmHub: string; + pmWorkflows: string; + pmConcepts: string; proposals: string; storage: string; sharedMemory: string; @@ -126,6 +136,15 @@ const en: SidebarLabels = { awards: 'Awards', subscriptions: 'Subscriptions', accountMarket: 'Account Market', + predictionMarkets: 'Prediction Markets', + predictionMarketApi: 'Prediction Market API', + predictionMarketsSection: 'Prediction Markets (Onix)', + onixOverview: 'Onix Overview', + onixWhitepaper: 'Whitepaper', + onixSpecification: 'Specification', + pmHub: 'Overview & map', + pmWorkflows: 'Workflows & diagrams', + pmConcepts: 'Concept analysis', proposals: 'Proposals', storage: 'Storage', sharedMemory: 'Shared Memory', @@ -201,6 +220,15 @@ const ru: SidebarLabels = { awards: 'Награды', subscriptions: 'Подписки', accountMarket: 'Рынок аккаунтов', + predictionMarkets: 'Прогнозные рынки', + predictionMarketApi: 'API прогнозных рынков', + predictionMarketsSection: 'Прогнозные рынки (Onix)', + onixOverview: 'Обзор Onix', + onixWhitepaper: 'Whitepaper', + onixSpecification: 'Спецификация', + pmHub: 'Обзор и карта', + pmWorkflows: 'Воркфлоу и диаграммы', + pmConcepts: 'Анализ концептов', proposals: 'Предложения', storage: 'Хранилище', sharedMemory: 'Разделяемая память', @@ -276,6 +304,15 @@ const zhCN: SidebarLabels = { awards: '奖励', subscriptions: '订阅', accountMarket: '账户市场', + predictionMarkets: '预测市场', + predictionMarketApi: '预测市场 API', + predictionMarketsSection: '预测市场(Onix)', + onixOverview: 'Onix 概览', + onixWhitepaper: '白皮书', + onixSpecification: '规范', + pmHub: '总览与地图', + pmWorkflows: '工作流与图', + pmConcepts: '概念分析', proposals: '提案', storage: '存储', sharedMemory: '共享内存', @@ -317,6 +354,20 @@ function buildSidebar(t: SidebarLabels, prefix: string): DefaultTheme.SidebarIte { text: t.keyConcepts, link: p('/introduction/key-concepts') }, ], }, + { + text: t.predictionMarketsSection, + items: [ + { text: t.pmHub, link: p('/prediction-markets/') }, + { text: t.onixOverview, link: p('/prediction-markets/onix') }, + { text: t.onixWhitepaper, link: p('/prediction-markets/whitepaper') }, + { text: t.onixSpecification, link: p('/prediction-markets/specification') }, + { text: t.predictionMarkets, link: p('/protocol/operations/prediction-markets') }, + { text: t.virtualOperations, link: p('/protocol/virtual-operations') }, + { text: t.predictionMarketApi, link: p('/plugins/prediction-market-api') }, + { text: t.pmWorkflows, link: p('/prediction-markets/workflows') }, + { text: t.pmConcepts, link: p('/prediction-markets/concepts-analysis') }, + ], + }, { text: t.runANode, items: [ @@ -358,6 +409,7 @@ function buildSidebar(t: SidebarLabels, prefix: string): DefaultTheme.SidebarIte { text: t.snapshot, link: p('/plugins/snapshot') }, { text: t.chain, link: p('/plugins/chain') }, { text: t.databaseApi, link: p('/plugins/database-api') }, + { text: t.predictionMarketApi, link: p('/plugins/prediction-market-api') }, { text: t.webserver, link: p('/plugins/webserver') }, ], }, @@ -382,6 +434,7 @@ function buildSidebar(t: SidebarLabels, prefix: string): DefaultTheme.SidebarIte { text: t.awards, link: p('/protocol/operations/awards') }, { text: t.subscriptions, link: p('/protocol/operations/subscriptions') }, { text: t.accountMarket, link: p('/protocol/operations/account-market') }, + { text: t.predictionMarkets, link: p('/protocol/operations/prediction-markets') }, { text: t.proposals, link: p('/protocol/operations/proposals') }, ], }, @@ -451,6 +504,7 @@ function localizedNav( const p = (path: string) => `${prefix}${path}`; return [ { text: introLabel, link: p('/introduction/what-is-viz') }, + { text: 'Onix', link: p('/prediction-markets/') }, { text: nodeLabel, link: p('/node/getting-started') }, { text: protocolLabel, link: p('/protocol/data-types') }, { text: apiLabel, link: p('/api/json-rpc') }, @@ -460,7 +514,7 @@ function localizedNav( // ─── Config ───────────────────────────────────────────────────────────────── -export default defineConfig({ +export default withMermaid(defineConfig({ title: 'VIZ Ledger Documentation', description: 'Official documentation for VIZ Ledger — hybrid DLT with Fair-DPOS consensus', base: '/viz-cpp-node/', @@ -505,4 +559,4 @@ export default defineConfig({ }, }, }, -}); +})); diff --git a/docs/TECH_DEBT_AUDIT.md b/docs/TECH_DEBT_AUDIT.md index b29b96102b..0b756ed44b 100644 --- a/docs/TECH_DEBT_AUDIT.md +++ b/docs/TECH_DEBT_AUDIT.md @@ -1,229 +1,157 @@ # Tech Debt Audit — viz-cpp-node -Generated: 2026-05-01 -Branch audited: `chore/dead-code-tier-a` -Scope: source under `libraries/`, `plugins/`, `programs/`, root `CMakeLists.txt`, `documentation/`, `share/vizd/`. Excludes `thirdparty/` (vendored submodules: fc, chainbase, appbase). - -> ## Refresh — 2026-06-17 (verified against working tree) -> -> The May findings below are kept as a historical log. The current state of each verified item: -> -> **Fixed since May:** -> - **F008** — `CHAIN_INTERNAL_PLUGINS` lines removed from `plugins/CMakeLists.txt`. -> - **F009** — `test_api_plugin` no longer registered in `programs/vizd/main.cpp`. -> - **F017 / F029** — `chain_test` reference removed from `documentation/building.md`. -> - **F046 / F047 / F048** — `documentation/plugin.md` no longer references the phantom dirs, `newplugin.py`, or the `CHAIN_INTERNAL_PLUGINS` claim. -> - **F051** — `cat_parts.py` now uses `return False` / `return True`. -> -> **Partially addressed:** -> - **F002** — `libraries/network/node.cpp` was split; the file is gone, replaced by `dlt_p2p_node.cpp` (4,113 LOC) + `dlt_p2p_messages.cpp` (26 LOC). The message extraction was trivial — the 4,113-LOC `dlt_p2p_node.cpp` is still a god file. F039's line refs (`node.cpp:4192-4395`) now point into `dlt_p2p_node.cpp`. -> - **F019** — `remote_node_api.hpp` reduced from 10 `using namespace plugins::*` to 5. Still present. -> -> **Still open (line/LOC drift):** -> - **F001** — `database.cpp` grew to **7,975 LOC** (+1,470, much of it stall-monitor work). Still #1 split priority. F036/F037 TODO line refs have shifted. -> - **F003** — `plugins/snapshot/plugin.cpp` grew to **4,345 LOC** (+1,068). Not split. (Note: path is `plugins/snapshot/`, not `libraries/chain/snapshot/`.) -> - **F004** wallet.cpp 2,796 · **F005** chain_evaluator.cpp 2,350 · **F006** wallet.hpp 1,554 — roughly unchanged. -> - **F019** — `remote_node_api.hpp` reduced to 5 `using namespace plugins::*`. **Deferred:** narrowing them needs a local compiler — dozens of bare types resolve through those usings (incl. bare `set<>` with no `using std::set`), so a blind edit would break the build across consumers. -> -> **Fixed in working tree (pending CI build):** -> - **F018** — `using namespace std;` in `wallet.hpp` → targeted `using std::{string,vector,map,pair};`. -> - **F041** — `std::exit(0)` ×2 at `plugins/chain/plugin.cpp:672,698` → `appbase::app().quit()`. (Fixes the bypassed-shutdown bug; quit() likely still exits 0, so not an exit-code change.) -> -> **⚠️ Obsolete / now backwards — DO NOT ACT ON:** -> - **F012** — The original `std::cerr` targets (`database.cpp:398,509`, reindex progress) are gone. The 5 remaining `std::cerr` calls (lines 329, 701, 828, 1160, 1174) are the **deliberate stall-watchdog** stderr — written to `cerr` precisely *so the monitor can never block on node locks*. Converting them to `ilog`/`wlog` would defeat the watchdog. **This recommendation is now wrong; leave the watchdog stderr as-is.** F011/F038/F045 snapshot `cerr` line refs have also drifted (file is now 4,345 LOC). -## Executive summary - -- **God-file concentration is the single biggest structural problem.** Five files (`libraries/chain/database.cpp` 6,505 LOC; `libraries/network/node.cpp` 5,759 LOC; `plugins/snapshot/plugin.cpp` 3,277 LOC; `libraries/wallet/wallet.cpp` 2,886 LOC; `libraries/chain/chain_evaluator.cpp` 2,347 LOC) hold ~20.8k LOC, roughly 40% of the real code surface. Three of these five are also the highest-churn files in the last 6 months (snapshot 55, database 38, node 25 commits). Size × churn = where bugs concentrate. -- **Zero unit tests.** No `tests/` directory anywhere. No `BOOST_AUTO_TEST_CASE` callsites in non-thirdparty code. `documentation/building.md:374` advertises `chain_test` as a build target but no such target exists. `documentation/testing.md` describes a testing process for a system that isn't there. -- **Documentation drift is systemic, not episodic.** `documentation/plugin.md` references three directories that don't exist (`libraries/plugins`, `external_plugins`, `example_plugins`) and a file that doesn't exist (`programs/util/newplugin.py`). It also describes runtime behavior (`CHAIN_INTERNAL_PLUGINS` "used by argument parsing") that is fictional — the variable is set but never read. -- **`test_api_plugin` is registered into production `vizd`** (`programs/vizd/main.cpp:72`). Its `test_api_a` / `test_api_b` JSON-RPC methods return hardcoded `"A"` and `"B"`. It's gated on `enable-plugin` config, but live in the production binary's plugin registry. -- **`using namespace` in public headers, including `using namespace std;`** in `libraries/wallet/include/graphene/wallet/wallet.hpp:16`. Pollutes every consumer of the wallet API. `libraries/wallet/include/graphene/wallet/remote_node_api.hpp:28-38` adds 10 more — a wallet-side dependency rats-nest. -- **Two implementations of the hardfork concatenator** (`programs/build_helpers/cat-parts.cpp` for MSVC, `programs/build_helpers/cat_parts.py` for everything else). The Python version has latent NameError bugs (`return false` lowercase on lines 14, 16); they are unreachable today only because `hardfork.d` happens to contain only regular `.hf` files. -- **Plugin glob with dead state.** `plugins/CMakeLists.txt:2-12` walks subdirectories and appends each to `CHAIN_INTERNAL_PLUGINS`, but no other CMake or C++ file ever reads that variable. Pure ceremony. -- **Console I/O leaks across the logging boundary.** ~75 direct `std::cerr` / `std::cout` / `printf` callsites in libraries+plugins despite `fc::ilog/wlog/elog` being the project standard. `plugins/snapshot/plugin.cpp` has 40+ `std::cerr` callsites alone, with bespoke ANSI-color macros (CLOG_GREEN/ORANGE/etc., lines 50-54) that bypass fc entirely. -- **Submodules track branches, not tags.** All three (`fc`, `chainbase`, `appbase` in `.gitmodules`) point at moving branches. Reproducible builds from an old commit can silently change. -- **Layered cleanup work already in flight.** PR #100 (`chore/dead-code-tier-a`) is removing orphan files, dead CMake plumbing, and `#if 0` blocks. This audit complements it: many of the medium-effort architectural items below are out of scope for that PR but are good Tier B candidates. - -## Architectural mental model - -VIZ is a Graphene-derived blockchain (sister to Steem/Hive/BitShares lineage) with a Fair-DPOS consensus tweak. The codebase has a clean four-layer shape on paper: +**Generated:** 2026-08-11 (full re-audit against `master` @ `d4fe3334`) +**Scope:** source under `libraries/`, `plugins/`, `programs/`, `tests/`, root `CMakeLists.txt`, `.github/workflows/`, `share/vizd/`. Excludes `thirdparty/` (vendored submodules: fc, chainbase, appbase). -1. **`thirdparty/`** — vendored as submodules: `fc` (FC framework: serialization, logging, exceptions, async), `chainbase` (memory-mapped object database), `appbase` (plugin lifecycle / appbase pattern). All are VIZ-Blockchain forks tracking `update` / `lib-boost-1.71` branches. -2. **`libraries/`** — `protocol` (operations, types, asset), `chain` (state machine, evaluators, hardforks), `network` (P2P, peer_connection), `api` (RPC API helpers), `wallet` (cli_wallet model + remote node API), `time`, `utilities` (BIP39 wordlist, key derivation, plain_keys). -3. **`plugins/`** — ~25 plugins (account_history, snapshot, witness, p2p, json_rpc, webserver, mongo_db, etc.), all loaded via appbase. Two flavors: data plugins that index chain state, and API plugins that expose JSON-RPC methods. -4. **`programs/`** — `vizd` (production binary, registers ~20 plugins in `main.cpp:62-88`), `cli_wallet` (interactive wallet CLI), `util` (sign_digest, test_block_log, test_shared_mem — operator/dev tools), `build_helpers` (cat-parts hardfork concatenator, configure_build.py for Win cross-compile). +> This is a from-scratch re-audit that **replaces** the 2026-05-01 audit and its +> 2026-06-17 refresh. Those described a tree that has since drifted materially — +> e.g. they claimed "zero tests / no `tests/` directory" (a `consensus_sim` +> harness now exists), referenced `libraries/network/node.cpp` (renamed to +> `dlt_p2p_node.cpp`), and audited a `documentation/` tree that a pending PR +> removes. The old finding IDs (F001–F051) are **not** carried forward; this +> document uses a fresh `D###` numbering. Where a current item maps to prior +> work it is called out inline. -**Where the model breaks down:** - -The clean four-layer story is undermined by god-file concentration. `database.cpp` is the actual heart of the system — block push, fork resolution, undo session management, snapshot integration, hardfork application — all in one 6,500-line file. `network/node.cpp` is similarly omnibus: peer discovery, message routing, sync logic, and peer database all live together in 5,759 lines. Logical separation exists (peer_connection.cpp, message_oriented_connection.cpp are split out) but the bulk of behavior pools in the giants. +## Executive summary -The plugin layer has two architectural inconsistencies. First, `plugins/CMakeLists.txt` uses a glob loop and an env var (`CHAIN_INTERNAL_PLUGINS`) that nothing reads — the system claims plugins are auto-discovered but `programs/vizd/main.cpp:62-88` actually hardcodes the registration list. Second, `test_api_plugin` is in that hardcoded list and ships in production. +- **God-file concentration is still the dominant structural risk, and it now + coincides exactly with the highest churn.** The three biggest hand-written + translation units are also the three most-changed files in the last six + months: `libraries/chain/database.cpp` (7,991 LOC, 130 commits/6mo), + `plugins/snapshot/plugin.cpp` (4,657 LOC, 125 commits/6mo), and + `libraries/network/dlt_p2p_node.cpp` (4,380 LOC, 125 commits/6mo). Size × + churn is where consensus bugs concentrate. Together with `wallet.cpp` (2,796) + and `chain_evaluator.cpp` (2,350) these five hold ~22k LOC. +- **Tests now exist but are not enforced.** `tests/consensus_sim/` is a real + deterministic multi-node harness (32 `BOOST_AUTO_TEST_CASE` scenarios across 10 + files: determinism replay, equivocation, wedge predicate, smoke) built under + ASAN + UBSAN. **But it is gated `OFF` by default (`BUILD_CONSENSUS_TESTS`) and + no CI workflow builds or runs it.** The correctness gate is written but not + wired in. This is the single highest-value open item. +- **The build system does no optimization by default.** There is no default + `CMAKE_BUILD_TYPE`, and `-O3/-O2` are set only inside the MinGW branch. A bare + `cmake ..` (or an IDE configure) produces an unoptimized node. *(Addressed in + open PR #146.)* +- **No precompiled headers despite a 3.16 minimum.** Every TU re-parses the same + heavy Boost.MultiIndex / FC reflection headers. *(Opt-in PCH proposed in open + PR #148.)* +- **Massive CMake duplication.** 26 library/plugin `CMakeLists.txt` carry the + full source list twice, once per `SHARED`/`STATIC` branch. *(Collapsed in open + PR #147, which also fixes a real drift in the chain lib's two branches.)* +- **`using namespace` in public headers is widespread — 51 occurrences**, + including `using namespace std;` in `plugins/account_by_key/.../account_by_key_objects.hpp` + and 6 stacked usings in `wallet/remote_node_api.hpp`. These leak into every + consumer TU. +- **Console I/O bypasses the fc logging boundary**, concentrated in + `plugins/snapshot/plugin.cpp` (81 `cerr`/`cout`/`printf` callsites with + bespoke ANSI-color output). Note: a handful of `std::cerr` calls in + `database.cpp` are the deliberate stall-watchdog and must stay. +- **Submodules track no pinned commit discipline.** `.gitmodules` lists three + moving forks with no `branch=`/tag; `submodule update --remote` can silently + advance them. +- **Repo hygiene:** `.qoder/` (168 tracked files, ~6.6 MB of AI-tool scratch) + and a stale `documentation/` tree are still tracked. *(Both addressed in open + PRs #144 and #145.)* -The chain evolution model is hardfork-driven. `libraries/chain/hardfork.d/` holds 13 `.hf` files (0-preamble, 1.hf through 12.hf) that get concatenated at build time into `hardfork.hpp`. There are 126 `has_hardfork(HARDFORK_*)` conditionals in `libraries/chain/`. Most are necessary for historical replay correctness — removing them would break sync from genesis. This is intrinsic complexity, not debt. +## Architectural mental model -The wallet/CLI layer is the most aggressive offender for header pollution: 12+ `using namespace` directives in public headers, including the canonical `using namespace std;` anti-pattern in `wallet.hpp:16`. +VIZ is a Graphene-derived blockchain (Steem/Hive/BitShares lineage) with a +Fair-DPOS consensus tweak, mid-migration to a "VIZ Ledger" DLT positioning +(snapshot-assisted state storage). Four layers: + +1. **`thirdparty/`** — `fc` (serialization, logging, exceptions, async), + `chainbase` (mmap object DB), `appbase` (plugin lifecycle). Vendored as + submodules. +2. **`libraries/`** — `protocol` (operations, types, asset), `chain` (state + machine, evaluators, hardforks), `network` (P2P — note the recent + `node.cpp` → `dlt_p2p_node.cpp` DLT redesign), `api`, `wallet`, `time`, + `utilities` (BIP39 wordlist, key derivation). +3. **`plugins/`** — ~20 appbase plugins registered explicitly in + `programs/vizd/main.cpp` `register_plugins()`. Data plugins index chain + state; API plugins expose JSON-RPC. `mongo_db` has moved out to + `examples-plugins/`. +4. **`programs/`** — `vizd`, `cli_wallet`, `util`, `build_helpers`. + +**Where it breaks down:** the clean layering is undercut by god-file +concentration in `database.cpp` (block apply, fork resolution, undo sessions, +snapshot integration, hardfork application, a push-block stall monitor) and by +`dlt_p2p_node.cpp` / `snapshot/plugin.cpp` each pooling many responsibilities in +one high-churn TU. An in-flight witness→validator rename is visible in the churn +history (`witness.cpp`, `witness_guard.cpp` → `validator*`). ## Findings -| ID | Category | File:Line | Severity | Effort | Description | Recommendation | -|-------|---------------------|---------------------------------------------------------------------|----------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| -| F001 | Architectural decay | `libraries/chain/database.cpp:1` | High | L | 6,505-LOC god file: block push, fork resolution, undo state, snapshot import, hardfork apply, p2p side-effects. 38 commits in 6 months — highest churn in chain/. | Extract `apply_block_path` (push/pop/apply), `snapshot_integration`, and `undo_session_lifecycle` into separate translation units. Keep `database.hpp` API stable. | -| F002 | Architectural decay | `libraries/network/node.cpp:1` | High | L | 5,759-LOC god file in p2p layer. Peer discovery, message routing, fetch coordination, sync state — all in one TU. 25 commits in 6mo. | Split `node_impl` along the boundaries already implicit in member groupings: `peer_db_node`, `sync_node`, `fetch_node`. Mechanical extraction. | -| F003 | Architectural decay | `plugins/snapshot/plugin.cpp:1` | High | L | 3,277 LOC and **55 commits in 6 months** — highest churn in the whole repo. Single-file plugin handles import, export, P2P transfer, peer query, ANSI logging. | Split into `snapshot_import.cpp` / `snapshot_export.cpp` / `snapshot_peer_query.cpp`. The `detail::set_*` helpers (lines 60-90+) deserve their own header. | -| F004 | Architectural decay | `libraries/wallet/wallet.cpp:1` | Medium | L | 2,886 LOC. Wallet API impl mixes account ops, key management, transaction signing, paid-subscription, social-network forwarding into one class. | Extract `wallet_keys_impl`, `wallet_transaction_impl`, `wallet_account_impl` partial classes. Header (1,568 LOC) similarly bloated. | -| F005 | Architectural decay | `libraries/chain/chain_evaluator.cpp:1` | Medium | M | 2,347-LOC monolith of operation evaluators. New operations are appended; no logical grouping. | Group by operation category (account_*, content_*, witness_*, asset_*) into 4-5 evaluator TUs. Each evaluator is independent — low merge risk. | -| F006 | Architectural decay | `libraries/wallet/include/graphene/wallet/wallet.hpp:1` | Medium | M | 1,568-LOC public header. Includes too much, drags `private_message`, `social_network`, `tags`, `database_api` types into wallet TU. | Forward-declare and move impls to `.cpp`. Reduces compile fan-out for cli_wallet. | -| F007 | Architectural decay | `libraries/protocol/include/graphene/protocol/chain_operations.hpp:1` | Medium | M | 1,188-LOC header with all operation structs. Every consumer pays the include cost for every op type. | Group ops into per-category headers; keep umbrella `chain_operations.hpp` for compatibility. | -| F008 | Architectural decay | `plugins/CMakeLists.txt:2,8` | Low | S | `set(ENV{CHAIN_INTERNAL_PLUGINS} …)` glob-appends every plugin subdir to an env var. **Never read anywhere** in CMake or source. | Delete the env-var lines; keep the glob if `add_subdirectory` is doing useful work. (Tier A candidate.) | -| F009 | Architectural decay | `programs/vizd/main.cpp:72` | High | S | `test_api_plugin` registered unconditionally in production `vizd`. Plugin's API returns hardcoded `"A"`/`"B"` (`plugins/test_api/test_api_plugin.cpp:25-37`). | Wrap registration in `#ifdef BUILD_TESTNET` (or remove the plugin entirely — it has no real consumers). | -| F010 | Architectural decay | `programs/build_helpers/cat-parts.cpp:1` | Low | S | 68-LOC C++ tool used **only on MSVC** (`libraries/chain/CMakeLists.txt:3`). Linux/macOS use `cat_parts.py` (line 8). Compiled into every build regardless. | Either delete cat-parts.cpp and use Python on MSVC too, or delete cat_parts.py and use the C++ binary everywhere. One implementation, one place. | -| F011 | Consistency rot | `plugins/snapshot/plugin.cpp:50-54` | High | M | Bespoke ANSI-color logging macros (`CLOG_GREEN`, `CLOG_ORANGE`, etc.) that pipe to `std::cerr`. ~40 callsites between lines 1100-2500 use this instead of fc::*log.| Replace with fc logging at appropriate levels. fc supports structured fields; cerr-with-ANSI doesn't survive log aggregators. | -| F012 | Consistency rot | `libraries/chain/database.cpp:398,509` | Medium | S | `std::cerr` used directly in chain core (during reindex progress reporting). Same path elsewhere uses `ilog`/`wlog`. | Replace with `ilog("…")`. Keep behavior identical; gain log-config compliance. | -| F013 | Consistency rot | repo-wide | Medium | M | 75 direct console-I/O callsites (`std::cout`, `std::cerr`, `printf`, `fprintf`) across libraries+plugins despite fc-logging being standard. | Sweep with `rg "(std::c(out\|err)\|printf\|fprintf)" libraries/ plugins/`; convert to fc::*log. Most are progress messages or warnings. | -| F014 | Consistency rot | `programs/build_helpers/` | Medium | S | Two implementations of "concatenate `.d` files" (`cat-parts.cpp`, `cat_parts.py`). Drift risk: changes in one not reflected in the other. | Pick one. See F010. | -| F015 | Consistency rot | repo-wide | Low | - | Threading uses both `std::thread`/`std::mutex` (9 callsites) and `fc::thread`/`fc::mutex`/`fc::scoped_lock` (56 callsites). | Document when each is preferred (fc:: integrates with fc::future and the cooperative scheduler; std:: is for one-shot OS threads). Add a short note in CLAUDE.md or developer.md. | -| F016 | Consistency rot | `share/vizd/config/` | Low | S | 7 config templates (`config.ini`, `config_debug.ini`, `config_debug_mongo.ini`, `config_mongo.ini`, `config_stock_exchange.ini`, `config_testnet.ini`, `config_witness.ini`). Unclear which is canonical. | Add `share/vizd/config/README.md` describing what each template is for. Or delete the ones not exercised by Docker images. | -| F017 | Consistency rot | `documentation/building.md:374` | Low | S | Lists `chain_test` as a build target. No such target exists in any `CMakeLists.txt`. | Remove the line. (Will collapse to F033 once test debt is addressed.) | -| F018 | Type & contract | `libraries/wallet/include/graphene/wallet/wallet.hpp:16` | High | S | `using namespace std;` in a public header. Leaks std::* into every TU including this header. | Delete the line. Qualify uses with `std::` (mostly `std::string`, `std::vector`). | -| F019 | Type & contract | `libraries/wallet/include/graphene/wallet/remote_node_api.hpp:28-38` | High | S | 10 `using namespace plugins::*` directives at namespace scope in a public header. Names from condenser_api, database_api, follow, social_network, etc. all leak. | Move usings to .cpp, or replace with `using ns::Type` declarations for only the names actually needed. | -| F020 | Type & contract | `libraries/chain/include/graphene/chain/fork_database.hpp:15` | Medium | S | `using namespace boost::multi_index;` in public header. | Move into .cpp. | -| F021 | Type & contract | `libraries/chain/include/graphene/chain/chain_object_types.hpp:16` | Medium | S | `using namespace boost::multi_index;` in public header. | Move into .cpp. | -| F022 | Type & contract | `libraries/protocol/include/graphene/protocol/operation_util_impl.hpp:9` | Medium | S | `using namespace graphene::protocol;` in a header that gets transitively included by every operation visitor. | Use qualified names. | -| F023 | Type & contract | `libraries/chain/include/graphene/chain/dlt_block_log.hpp:9` | Low | S | `using namespace graphene::protocol;` in header. | Move to .cpp. | -| F024 | Type & contract | `libraries/chain/include/graphene/chain/block_log.hpp:9` | Low | S | Same pattern. | Move to .cpp. | -| F025 | Type & contract | repo-wide (libraries+plugins) | Medium | M | 14 raw `new T(…)` callsites and 24 raw `delete` callsites in `.cpp` files. C++14 has `make_unique`; the project is C++14. | Sweep: replace `new X(...)` paired with `delete` by `std::unique_ptr`/`std::make_unique`. `new char[N]` + `delete[]` → `std::vector` or `make_unique(N)`. | -| F026 | Type & contract | `libraries/network/message_oriented_connection.cpp:262` | Low | S | `std::unique_ptr padded_message(new char[size_with_padding])` — uses raw `new` even though it's wrapped in unique_ptr. | `auto padded_message = std::make_unique(size_with_padding);` | -| F027 | Test debt | repo-wide | Critical | XL | **Zero unit tests.** No `tests/` dir, no inline `BOOST_AUTO_TEST_CASE`. The chain handles money and consensus and has no automated correctness gate. | Start with golden-replay regression tests for the highest-risk paths: block push, fork resolution, snapshot import. Even one harness that replays a known mainnet block range would close the largest correctness gap. | -| F028 | Test debt | `documentation/testing.md` | Low | S | File describes a testing system that doesn't exist. | Either build the system the doc describes, or rewrite the doc to describe testnet-replay-based validation. | -| F029 | Test debt | `documentation/building.md:374` | Low | S | Lists `chain_test` as a `make` target. No such target. | Delete the line until F027 is addressed. | -| F030 | Test debt | `plugins/snapshot/plugin.cpp` (3,277 LOC, 55 commits/6mo) | High | XL | Highest-churn file in the repo. Zero tests. Snapshot import is consensus-adjacent (chain replays from snapshot state). | Property tests for the `detail::set_shared_*` helpers (pure functions, easy to test). Round-trip test: export → import → state-equivalent. | -| F031 | Test debt | `libraries/chain/database.cpp` (6,505 LOC, 38 commits/6mo) | Critical | XL | Highest-churn file in chain core. Zero tests. Behavior changes here are validated only by mainnet/testnet observation. | Same approach as F030 but on smaller leaf functions first. `validate_block_header_signature`, `apply_hardfork_*`, undo-session lifecycle are testable in isolation. | -| F032 | Dependency & config | `.gitmodules` | Medium | S | All three submodules track moving branches (`update`, `lib-boost-1.71`), not pinned commits/tags. Reproducible builds at an old commit can silently shift. | Pin to commit SHAs. The submodule directory entry already records the SHA — but tracking a branch means a fresh `submodule update --remote` would silently advance it. Audit `.gitmodules` and add `update = none` or pin via tag. | -| F033 | Dependency & config | `plugins/mongo_db/` | Medium | S | `ENABLE_MONGO_PLUGIN` (`CMakeLists.txt:71`) gates a 5-file plugin with no CI coverage. `Dockerfile-mongo` exists but no workflow exercises it. | Either add a CI smoke build or mark the plugin deprecated and remove. The risk is silent bit-rot. | -| F034 | Dependency & config | `share/vizd/docker/Dockerfile-lowmem` | Low | S | Lowmem image documented in `building.md:33` but no workflow builds it. Same bit-rot risk as F033. | Add a CI build matrix entry, or consolidate the variant set. | -| F035 | Dependency & config | `CMakeLists.txt:42` | Low | M | `BUILD_SHARED_LIBRARIES` option exists but Docker forces FALSE. Many CMakeLists carry dual `if(BUILD_SHARED_LIBRARIES)` branches (e.g., `libraries/chain/CMakeLists.txt:16-71` vs :73-128) — 50+ lines of duplicated source lists per branch. | If shared builds aren't supported in production, drop the option and the duplicated `add_library(... SHARED)` branches. | -| F036 | Performance | `libraries/chain/database.cpp:1141` | Low | - | TODO: "no easy way to catch boost::interprocess::bad_alloc" — long-standing issue. Not blocking, but a known cliff for shared-mem exhaustion. | Track in an issue. Note it in `documentation/shared-memory.md` if user-visible. | -| F037 | Performance | `libraries/chain/database.cpp:2755` | Low | M | TODO: "this method can be skipped for validation-only nodes" — known optimization gap on the validation hot path. | Add a `skip_*` flag to the apply path; benchmark on a low-mem replay before/after. | -| F038 | Performance | `plugins/snapshot/plugin.cpp:1100-1471` | Low | S | Snapshot import logs progress via `std::cerr` line-by-line; on slow stdouts (e.g. journald with rate limiting) this can throttle a multi-GB import. | Use buffered fc logging or rate-limit progress messages. | -| F039 | Error handling | `libraries/network/node.cpp:4192-4395` | Low | M | 17 `catch(...)` handlers in this range. Most log via fc; pattern is consistent. But a couple (sample audit needed) may continue silently. | Audit each handler: if "log + continue" is the intent, factor into a `LOG_AND_CONTINUE(reason)` helper. Naming makes intent explicit. | -| F040 | Error handling | `libraries/chain/proposal_evaluator.cpp:75` | Medium | S | `} catch (...) {` inside a proposal-validation path with no handler body shown in the snippet. Silent failure is consensus-relevant. | Verify the handler body. If it swallows, replace with explicit fc::exception conversion + propagate. | -| F041 | Error handling | `plugins/chain/plugin.cpp:506,520` | Medium | S | `std::exit(0)` from inside plugin code. Bypasses appbase shutdown sequence. Marked TODO. | Replace with `appbase::app().quit()` per the existing TODO. Behavior delta should be small. | -| F042 | Error handling | repo-wide | Low | - | 23 TODO/FIXME markers in source, several pre-hardfork-2 (`chain_evaluator.cpp:1168` "Remove after hardfork 2"). Hardfork 12 has shipped per `hardfork.d/`. | Sweep: which TODOs reference hardforks ≤ HF12? Those are dead conditionals — delete the gate, keep the post-fork branch. | -| F043 | Security | repo-wide | Low | - | No hardcoded secrets found via simple regex sweep on `password=`, `secret=`, `private_key=`, `api_key=`, `token=` patterns. Passes basic check. | n/a — keep this finding to mark that the check was performed. | -| F044 | Security | `libraries/wallet/include/graphene/wallet/wallet.hpp:16` | Low | S | `using namespace std;` in cli_wallet's transitive include set. Increases ADL surprise surface in JSON-RPC dispatch (where `std::string` and project string types coexist). | Same fix as F018. | -| F045 | Security | `plugins/snapshot/plugin.cpp:2425+` | Medium | M | Snapshot peer-query path downloads multi-GB blobs from "trusted" peers. Trust model is encoded in config (`trusted_snapshot_peers`). Verify signature/hash chain on import path is mandatory and not skippable by config. | Audit the import path: confirm `validating snapshot checksum` (line 1165) covers the entire payload and uses a key derived from chain consensus, not a static cert. | -| F046 | Documentation drift | `documentation/plugin.md:5-9` | Medium | S | References `libraries/plugins`, `external_plugins`, `example_plugins`, and `hello_api`. None exist. The actual plugin layout is `plugins//`. | Rewrite the section to describe the real layout. Either do that, or rebuild the directories the doc promises (former is cheaper). | -| F047 | Documentation drift | `documentation/plugin.md:24` | Low | S | References `programs/util/newplugin.py`. Doesn't exist (`programs/util/` contains only sign_digest, test_block_log, test_shared_mem, sign_transaction, get_dev_key). | Remove the autogeneration section, or add the script back if it's wanted. | -| F048 | Documentation drift | `documentation/plugin.md:5` | Medium | S | Claims `CHAIN_INTERNAL_PLUGINS` is "used to create a runtime-accessible list of available plugins used by the argument parsing." It isn't — see F008. | Delete that claim. Plugins are registered explicitly in `programs/vizd/main.cpp:62-88`. | -| F049 | Documentation drift | `documentation/building.md` | Low | S | `Dockerfile-lowmem` and `Dockerfile-mongo` documented in the table at line 30-34 but no CI workflow builds them — see F033, F034. | Drop them from the doc table, or add CI coverage so the doc reflects reality. | -| F050 | Documentation drift | `README.md` | Low | S | "Pre-populated seed node entries can be found in the config templates under `share/vizd/config/`" — true, but doesn't say which template to use. Combined with F016, users don't know which `config_*.ini` to start from. | Same fix as F016 (a README in `share/vizd/config/`). | -| F051 | Build hygiene | `programs/build_helpers/cat_parts.py:14,16` | Medium | S | `return false` and `return true` (lowercase) — `NameError` in Python. Unreachable today only because `hardfork.d/` happens to contain only regular `.hf` files (the buggy branches require subdirs or empty filter). | Fix to `return False` / `return True`. Two-character change. | - -## Top 5 — if you fix nothing else, fix these - -### 1. **F027 — Add at least one regression test harness** -The combination of (a) zero tests, (b) handling of money / consensus, and (c) high-churn god files (snapshot, database, node) is the largest correctness risk in the repo. You don't need full coverage to materially reduce risk; you need *one* fast harness that replays a known block range and asserts on the resulting state hash. - -**Concrete starting point:** -``` -programs/util/test_block_log.cpp (already exists, deferred per project notes) -``` -Don't delete it — extend it. Add: -- A `replay_to_height` driver that opens block_log, applies blocks up to N, prints final state hash. -- A CI job that runs `replay_to_height 1000000` against a checked-in snapshot + block_log slice. -- A failing test means a consensus-breaking change. Even one such test catches the worst class of bugs. - -This is the kind of test debt that compounds; every week without this is a week where every database.cpp change is rolled out without a safety net. - -### 2. **F009 — Stop registering `test_api_plugin` in production `vizd`** -`programs/vizd/main.cpp:72` currently does: -```cpp -appbase::app().register_plugin(); -``` - -Change to: -```cpp -#ifdef BUILD_TESTNET -appbase::app().register_plugin(); -#endif -``` -Or remove the plugin entirely (its API methods return literal `"A"` / `"B"` strings — no production consumer would want them). One line, no behavioral risk on the production path. - -### 3. **F018 + F019 — Strip `using namespace` from public headers, starting with wallet** -`libraries/wallet/include/graphene/wallet/wallet.hpp:16` has `using namespace std;`. `remote_node_api.hpp:28-38` has 10 more across plugin namespaces. These leak into every consumer of the wallet API. - -**Sketch:** -```cpp -// wallet.hpp:16 — DELETE -- using namespace std; - -// .cpp files that depended on it pick up `using std::string;` etc. -// or qualify call sites. -``` -Mechanical, but careful work — expect compile errors at every unqualified `string`, `vector`, `pair`, `map` callsite in the wallet TU. Run as one PR; reviewers can scan the diff for surprises. - -### 4. **F003 — Split `plugins/snapshot/plugin.cpp` along its natural seams** -3,277 LOC, 55 commits in 6 months — not a refactor candidate, a refactor *necessity*. The file has clean internal sections already: -- import path: lines ~1100-1471 (`load_snapshot_from_*`, `import_state_into_db`) -- export path: separate functions -- peer query path: lines 2425-2530 (`query_trusted_peers_for_snapshot`) -- import detail helpers: namespace `detail` at line 60+ - -**Sketch:** -``` -plugins/snapshot/ - plugin.cpp — entry, init/startup/shutdown, glue - snapshot_import.cpp — load + apply - snapshot_export.cpp — serialize + write - snapshot_peer_query.cpp — P2P peer queries - detail.hpp — set_shared_string, set_buffer, set_shared_authority helpers -``` -No public-API change; pure file-level decomposition. Reviewer cost is low because each callsite stays the same. - -### 5. **F008 + F046 + F048 — Consolidate the `plugin.md` / `CHAIN_INTERNAL_PLUGINS` lie** -Three findings with the same root cause: `documentation/plugin.md` describes a system that doesn't exist (auto-discovered plugins via env var). The CMake glob produces dead state. Pick one of two paths: - -**Path A (cheap, recommended):** Rewrite the doc to describe the actual system (plugins registered in `programs/vizd/main.cpp:62-88`, headers under `plugins//include/graphene/plugins//`, manual `add_subdirectory` in `plugins/CMakeLists.txt`). Delete `set(ENV{CHAIN_INTERNAL_PLUGINS}…)`. Both are 1-PR changes. - -**Path B (expensive):** Build the system the doc describes. Probably not worth it — the existing static registration list is fine. - -## Quick wins - -Low-effort × Medium-or-higher severity. Each takes <30 min and has near-zero risk. - -- [x] **F008** — ~~Delete `set(ENV{CHAIN_INTERNAL_PLUGINS}…)` lines in `plugins/CMakeLists.txt:2,8`.~~ Done (2026-06-17). -- [x] **F009** — ~~Wrap `test_api_plugin` registration in `#ifdef BUILD_TESTNET` or remove (`programs/vizd/main.cpp:72`).~~ Done — no longer registered (2026-06-17). -- [ ] ~~**F012** — Replace `std::cerr` with `ilog`/`wlog` at `libraries/chain/database.cpp:398,509`.~~ **OBSOLETE — do not act.** Target lines gone; remaining `cerr` is the intentional stall-watchdog (see 2026-06-17 refresh banner). -- [x] **F017** — ~~Remove `chain_test` reference from `documentation/building.md:374`.~~ Done (2026-06-17). -- [x] **F018** — ~~Delete `using namespace std;` at `wallet.hpp:15`.~~ Done (2026-06-17): replaced with `using std::{string,vector,map,pair};`. *(Pending CI build verification.)* -- [x] **F029** — ~~Same as F017.~~ Done (2026-06-17). -- [x] **F041** — ~~Replace `std::exit(0)` at `plugins/chain/plugin.cpp:672,698` with `appbase::app().quit()`.~~ Done (2026-06-17). *(Pending CI build verification.)* -- [x] **F046** — ~~Rewrite `documentation/plugin.md` opening section to match real layout.~~ Done (2026-06-17). -- [x] **F047** — ~~Remove the `programs/util/newplugin.py` reference at `documentation/plugin.md:24`.~~ Done (2026-06-17). -- [x] **F048** — ~~Remove the `CHAIN_INTERNAL_PLUGINS` claim from `documentation/plugin.md:5`.~~ Done (2026-06-17). -- [x] **F051** — ~~Fix `false` → `False`, `true` → `True` at `programs/build_helpers/cat_parts.py:14,16`.~~ Done (2026-06-17). +| ID | Category | Location | Sev | Effort | Description | Recommendation | +|------|---------------------|-------------------------------------------------------------|------|--------|-------------|----------------| +| D001 | Test enforcement | `.github/workflows/*`, `CMakeLists.txt:229` | High | M | `tests/consensus_sim/` (32 ASAN/UBSAN test cases across 10 scenario files) exists but `BUILD_CONSENSUS_TESTS` defaults OFF and **no CI workflow builds or runs it**. `docker-pr-build.yml` builds only `vizd`. The correctness gate is written but not enforced. | Add a CI job: `cmake -DBUILD_CONSENSUS_TESTS=ON` + run `consensus_sim_tests` on PRs. This is the highest-leverage change in the repo. | +| D002 | Architectural decay | `libraries/chain/database.cpp` (7,991 LOC, 130 commits/6mo) | High | L | Highest size × churn in the repo. Block apply, fork resolution, undo lifecycle, snapshot import, hardfork apply, stall monitor in one TU. Longest-to-compile file; MSVC needs `/bigobj`. | Extract along functional seams. *(Started in open PR #149 — hardfork cluster → `database_hardfork.cpp`.)* Continue with apply-block path, snapshot integration, undo-session lifecycle. | +| D003 | Architectural decay | `plugins/snapshot/plugin.cpp` (4,657 LOC, 125 commits/6mo) | High | L | Highest-churn plugin. Import, export, P2P transfer, peer query, ANSI logging in one file. Also holds 81 console-I/O callsites (D008) and 43 `catch(...)` handlers. | Split into `snapshot_import` / `snapshot_export` / `snapshot_peer_query` + a detail header. | +| D004 | Architectural decay | `libraries/network/dlt_p2p_node.cpp` (4,380 LOC, 125 commits/6mo) | High | L | God file in the P2P layer (product of the DLT redesign). Peer discovery, routing, fetch coordination, sync state in one TU. | Split `node_impl` along member groupings: peer DB, sync, fetch. Mechanical once the redesign settles. | +| D005 | Architectural decay | `libraries/wallet/wallet.cpp` 2,796 · `wallet.hpp` 1,557 | Med | L | Wallet impl and its 1,557-LOC public header mix account ops, keys, signing, paid-subscription, forwarding. Header drags many plugin types into every cli_wallet TU. | Extract partial-class impls; forward-declare in the header, move bodies to `.cpp`. | +| D006 | Architectural decay | `libraries/chain/chain_evaluator.cpp` (2,350 LOC) | Med | M | Monolith of operation evaluators; new ops appended with no grouping. Low churn (9/6mo) so lower priority than D002–D004. | Group by category (account/content/validator/asset) into separate evaluator TUs. | +| D007 | Type & contract | 51 `using namespace` in public headers | High | M | Leaks into every consumer TU. Worst: `using namespace std;` in `plugins/account_by_key/include/.../account_by_key_objects.hpp:14`; 6 stacked usings in `wallet/include/graphene/wallet/remote_node_api.hpp:19-25`; `using namespace boost::multi_index;` in `chain_object_types.hpp` and `account_by_key_objects.hpp`. | Move usings into `.cpp`, or replace with targeted `using ns::Type;`. Start with `std;` — mechanical, high value. | +| D008 | Consistency rot | `plugins/snapshot/plugin.cpp` (~81), `plugins/chain/plugin.cpp` (11), others | Med | M | ~100 direct `std::cerr`/`cout`/`printf` callsites across libs+plugins despite fc logging being standard; snapshot uses bespoke ANSI-color macros. | Convert to `fc::*log`. **Exception:** the ~5 `std::cerr` calls in `database.cpp` are the intentional stall-watchdog (written to cerr precisely so the monitor never blocks on node locks) — leave them. | +| D009 | Type & contract | 28 raw `new`/`delete` in `database.cpp`, 14 in `chain_evaluator.cpp`, others | Low | M | Project is C++14; `make_unique` available. Consensus-core files, so verify each pairing before converting. | Sweep `new X()`+`delete` → `unique_ptr`; `new char[N]` → `vector`/`make_unique`. Low priority; needs a compiler to verify. | +| D010 | Build config | `CMakeLists.txt` — no default `CMAKE_BUILD_TYPE` | Med | S | Bare `cmake ..` yields empty optimization flags (unoptimized node); `-O3/-O2` set only in the MinGW branch. | *(Open PR #146.)* Default to Release when unset on single-config generators. | +| D011 | Build speed | No `target_precompile_headers` anywhere (min CMake 3.16) | Med | M | Every TU re-parses heavy Boost.MultiIndex / FC reflection headers. | *(Open PR #148 — opt-in `ENABLE_PCH` for chain/protocol/wallet.)* | +| D012 | Build maintenance | 26 `CMakeLists.txt` with dual `SHARED`/`STATIC` source lists | Med | M | Full source list duplicated per library/plugin; already drifted in `libraries/chain` (SHARED branch listed `invite_evaluator.cpp` twice, missing `invite_objects.hpp`). | *(Open PR #147 — single `VIZ_LIBRARY_TYPE` var; also fixes the chain drift.)* | +| D013 | Build hygiene | `programs/build_helpers/` — two hardfork concatenators | Low | S | `cat-parts.cpp` (MSVC) and `cat_parts.py` (everyone else); the C++ binary was compiled on every platform. Drift risk. | *(Open PR #146 gates `cat-parts` to MSVC/MinGW.)* Longer term, pick one implementation. | +| D014 | Dependency & config | `.gitmodules` — 3 submodules, no pinned discipline | Med | S | fc/chainbase/appbase track moving forks with no `branch=`/tag. `submodule update --remote` can silently advance them; reproducible builds from an old commit can shift. | Pin to tags or document that the recorded SHA is authoritative and `--remote` is not to be used. Maintainer decision on which commit is canonical. | +| D015 | Dependency & config | `examples-plugins/mongo_db/` (5 files) | Low | S | Moved out of `plugins/` (good), but no CI smoke build; silent bit-rot risk. | Add a compile-only CI check or mark explicitly unmaintained. | +| D016 | Documentation | `share/vizd/config/` — 5 templates, no README | Low | S | `config.ini`, `config_debug.ini`, `config_stock_exchange.ini`, `config_testnet.ini`, `config_witness.ini` with no guide to which to use; README points users here without saying which. | Add `share/vizd/config/README.md` with a one-line purpose per template. | +| D017 | Repo hygiene | `.qoder/` (168 files, ~6.6MB) + `documentation/` (8 files) | Low | S | AI-tool scratch dir and a legacy doc tree still tracked; the `documentation/` tree overlaps the current `docs/` VitePress site and has drifted. | *(Open PRs #144 untrack `.qoder/`; #145 consolidates `documentation/` into `docs/`.)* | +| D018 | Consistency rot | Root build scripts: `build_mingv.sh` (typo), mixed dash/underscore naming | Low | S | `build_mingv.sh` is misspelled; `documentation/building.md` tells users to run `build_mingw.sh` which doesn't exist. | *(Open PR #144 renames to `build_mingw.sh`.)* | +| D019 | Dead conditional | `chain_evaluator.cpp:1168` "TODO: Remove after hardfork 2" | Low | S | 19 TODO/FIXME markers total; this one gates pre-HF4 behavior and is long past. | Verify replay-safety, then delete the gate and keep the post-fork branch. Treat all other hardfork conditionals as append-only (D-note below). | + +## Top 5 — if you fix nothing else + +1. **D001 — Wire `consensus_sim` into CI.** The tests are already written (ASAN + + UBSAN, deterministic multi-node). Turning them from "exists" into "runs on + every PR" is the biggest correctness win available and needs no new test + code — just a workflow job with `-DBUILD_CONSENSUS_TESTS=ON`. +2. **D002 — Keep splitting `database.cpp`.** 130 commits in six months into one + 7,991-LOC file with no per-file test isolation. PR #149 starts it; continue + seam by seam. +3. **D007 — Strip `using namespace` from public headers**, starting with the + `std;` in `account_by_key_objects.hpp`. Mechanical, high blast-radius win. +4. **D010 + D011 + D012 — Land the build PRs (#146/#147/#148).** Default-Release, + collapsed CMake, and opt-in PCH together cut both wall-clock build time and + the drift surface, at near-zero risk. +5. **D003 / D004 — Split the two other high-churn god files.** Snapshot and + dlt_p2p_node each see ~125 commits/6mo; both are prime bug territory. ## Things that look bad but are actually fine -This section exists because half the trap of doing an audit on a Graphene-derived blockchain is over-flagging consensus-critical code. - -- **`catch(...)` handlers in `libraries/network/peer_connection.cpp:118,130,142,290` and `libraries/network/message_oriented_connection.cpp:222,298`.** These are inside an event loop where any uncaught exception would tear down a peer connection thread. They all log via `dlog`/`wlog`/`elog` and rethrow or set a flag for the connection-closed handler. This is the correct pattern for a fc::thread-driven network layer. Don't "clean up" by removing them. -- **126 `has_hardfork(HARDFORK_*)` checks in `libraries/chain/`.** These are not "stale migration code." They are required for replay correctness — a node syncing from genesis must reproduce the exact behavior at every height. A hardfork conditional from HF1 still matters because someone, somewhere, is replaying a node from block 1. Only the very oldest pre-genesis-launch branches (e.g., `chain_evaluator.cpp:1168` "Remove after hardfork 2") can be cleaned, and even then very carefully. Treat hardfork code as append-only. -- **`programs/util/sign_digest.cpp`, `test_block_log.cpp`, `test_shared_mem.cpp`.** These look like dead dev utilities, but per the project's own deferred-cleanup note, they have plausible operator value. `sign_digest` lets witnesses sign things offline; `test_block_log` is the closest thing to a regression-test driver in the repo (and is the natural home for F027). Don't delete in a Tier B sweep — promote them. -- **`libraries/utilities/words.cpp` at 49,787 lines.** This is a BIP39 wordlist embedded as a C++ array. The size is intrinsic to the data, not a code-quality issue. Don't add it to "god file" findings. -- **Boost coroutine being found separately from other Boost components** (`CMakeLists.txt:85-89`). Looks like duplication, but this is a known workaround for a Boost CMake config quirk where `coroutine` isn't pulled in by `find_package(Boost ... COMPONENTS thread …)` cleanly across all distributions. -- **Mixed `std::thread` and `fc::thread`.** `fc::thread` is required for cooperative scheduling with `fc::future`/`fc::async` and the message-bus pattern (see VERIFY_CORRECT_THREAD usage in network code). `std::thread` is correct for one-shot OS threads that don't interact with fc. The mix is intentional, not rot. (F015 is a doc finding, not a code finding.) -- **Two CMake branches per library — `BUILD_SHARED_LIBRARIES` ON vs OFF.** Looks like 2× maintenance, but the duplication is in source-list arrays; the `add_library(SHARED)` vs `add_library(STATIC)` is a real and load-bearing distinction for some downstream consumers. Removal (F035) is a judgment call on whether shared-lib builds are still supported. -- **The 6,505-line `database.cpp` is a god file but is not "rotten."** It's huge because the chain state machine has lots of facets — block apply, undo session lifecycle, snapshot integration, hardfork application — and they share a lot of `_db.session()` / object-index access. Splitting (F001) is good debt reduction, but this isn't the same as a 6,500-line god file in a typical web service. Don't recommend a rewrite. Recommend extraction along functional seams. +- **`libraries/utilities/words.cpp` (49,787 lines).** BIP39 wordlist as a + `const char* word_list[]` in `.rodata`. Size is intrinsic to the data — not a + god file. +- **132 `has_hardfork(HARDFORK_*)` checks in `libraries/chain/`.** Required for + replay correctness — a node syncing from genesis must reproduce behavior at + every height. Treat as append-only; only the very oldest pre-launch gates + (D019) can be removed, and only carefully. +- **`catch(...)` in `peer_connection.cpp` / `message_oriented_connection.cpp` + and the validator/p2p event loops.** These sit in fc::thread-driven loops + where an uncaught exception would tear down a connection thread; they log via + `dlog`/`wlog`/`elog` and set a flag. Correct pattern — do not "clean up." +- **The ~5 `std::cerr` calls in `database.cpp`.** Deliberate stall-watchdog + output, written to cerr so the monitor can never block on node locks. + Converting them to fc logging would defeat the watchdog. +- **Mixed `std::thread`/`fc::thread`.** Intentional: `fc::thread` integrates with + `fc::future` and the cooperative scheduler; `std::thread` is for one-shot OS + threads. +- **Boost coroutine found separately in root CMake.** Known workaround for a + Boost CMake config quirk, not duplication. ## Open questions for the maintainer -These are things I couldn't classify as definitely-debt vs. intentional without domain context. - -1. **`test_api_plugin` registration in production vizd (F009).** Is there a downstream tool that probes for this plugin's presence as a node-version sentinel? If yes, removing it is breaking. If no (likely), the fix is a one-liner. -2. **Snapshot peer-trust model (F045).** Is the snapshot checksum (`plugin.cpp:1165, 1172`) a content hash, or is it tied to chain state at the snapshot height? The difference matters for whether a malicious "trusted_snapshot_peer" can serve a forged-but-consistent snapshot. -3. **`BUILD_SHARED_LIBRARIES` (F035).** Are there real-world consumers of the shared-lib build, or is it dead-but-unproven? If the latter, F035 becomes a quick-win deletion of half of every library's CMakeLists. -4. **Mongo plugin status (F033).** Is mongo_db plugin still a supported deployment target, or is it kept around for one specific operator? If the latter, capture that in a comment and CI; if the former, it's actively bit-rotting without coverage. -5. **Submodule branches vs commits (F032).** Is the team intentionally tracking `update`/`lib-boost-1.71` branches so that `submodule update --remote` follows them, or is the branch entry vestigial and the SHA pin is what's authoritative? Different fixes apply. -6. **`programs/util/` operator value.** Per the project notes, `sign_digest`, `test_block_log`, `test_shared_mem` are deferred for operator audit. Useful for F27 (regression test starter). Are operators actively using them today, or has tooling moved on? -7. **`config_stock_exchange.ini`.** Why does this exist as a top-level config template in `share/vizd/config/`? Either it documents a real deployment shape (capture in a README) or it's vestigial. +1. **D001 CI cost.** `consensus_sim` builds at `-O1 -g -fsanitize=address,undefined`. + Is the CI runner budget OK with an ASAN build per PR, or should it run on a + schedule/label instead of every PR? +2. **D014 submodules.** Is tracking moving forks intentional (so `--remote` + follows them), or is the recorded SHA authoritative? Different fixes apply. +3. **D015 mongo_db.** Is the `examples-plugins/mongo_db` plugin a supported + deployment target or a reference example? Determines whether it needs CI. +4. **D005 wallet header.** Is the cli_wallet compile-time fan-out from + `wallet.hpp` a felt pain, or tolerable? Drives priority of the header split. +5. **`config_stock_exchange.ini`.** Does this document a real deployment shape + worth keeping, or is it vestigial? (Feeds D016.) diff --git a/docs/advanced/hardfork-management.md b/docs/advanced/hardfork-management.md index e4fffae7ec..f1a00b3dee 100644 --- a/docs/advanced/hardfork-management.md +++ b/docs/advanced/hardfork-management.md @@ -48,6 +48,8 @@ When the head block time passes `next_hardfork_time` and sufficient validators s | 10 | Inflation model | | 11 | Emission model changes | | 12 | Emergency consensus recovery (see below) | +| 13 | Distribution epoch length (`chain_properties_hf13`) | +| 14 | Prediction Markets (Onix): 18 ops + 7 virtual ops, CPMM/LMSR pricing, parimutuel settlement, oracles, disputes, commit-reveal, lazy pool; chain properties v5 | --- diff --git a/docs/governance/chain-properties.md b/docs/governance/chain-properties.md index 52ae9beb4e..5517dfa254 100644 --- a/docs/governance/chain-properties.md +++ b/docs/governance/chain-properties.md @@ -122,8 +122,28 @@ Properties were introduced in hardfork stages: | `chain_properties_hf4` | 1 | HF4 | inflation_validator_percent, inflation_ratio_committee_vs_reward_fund, inflation_recalc_period | | `chain_properties_hf6` | 2 | HF6 | data_operations_cost_additional_bandwidth, validator_miss_penalty_percent, validator_miss_penalty_duration | | `chain_properties_hf9` | 3 | HF9 | create_invite_min_balance, committee_create_request_fee, create_paid_subscription_fee, account_on_sale_fee, subaccount_on_sale_fee, validator_declaration_fee, withdraw_intervals | +| `chain_properties_hf13` | 4 | HF13 | distribution_epoch_length | +| `chain_properties_pm` | 5 | HF14 | ~30 prediction-market parameters + kill-switches `pm_commit_reveal_enabled`, `pm_lazy_pool_enabled` | -Use version index 3 (`chain_properties_hf9`) for all new validator property submissions. +Use version index **5** (`chain_properties_pm`) for all new validator property submissions. (Index 4 is `chain_properties_hf13`, which added `distribution_epoch_length`.) + +### Prediction-market parameters (v5, HF14) {#pm-parameters} + +All median-voted; see [Prediction Market Operations](../protocol/operations/prediction-markets.md). + +All PM percentages are **bp (10000 = 100.00%)**, like the other `*_percent` properties — no permille (‰) anywhere. + +- **Oracle:** `pm_min_oracle_insurance`, `pm_max_oracle_fee_percent` (the *only* governed fee cap — on the oracle %), `pm_oracle_registration_fee`, `pm_oracle_penalty_percent`, `pm_oracle_dispute_response_sec`, `pm_oracle_accept_window_sec` (default 3600 = 1h — the named oracle must accept or reject a pending market within this window; on expiry the cron refunds the creator's seed liquidity, but **not** the creation fee, and voids the market → `pm_market_expired`). +- **Risk / coverage** *(percent of a market's betting volume, 100 = 1.0×):* `pm_listing_min_coverage_percent` (250 = 2.5×) — markets whose oracle insurance covers less than this share of their volume are hidden from the default `list_markets` catalog (revealed via `show_risky`); `pm_betting_min_coverage_percent` (150 = 1.5×) — advisory threshold, published for clients to require an explicit risk confirmation before betting (not enforced on-chain; must be `≤ pm_listing_min_coverage_percent`). +- **Market:** `pm_min_liquidity`, `pm_market_creation_fee`, `pm_max_outcomes`, `pm_max_market_duration`. *(There is no aggregate fee cap; creator/liquidity fees are uncapped and self-limiting, with a static `sum ≤ 100%` solvency bound.)* +- **Batch / commit-reveal:** `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_min_batch_bet`, `pm_commit_no_reveal_penalty_percent`, `pm_commit_reveal_enabled`. +- **Disputes:** `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent`, `pm_no_contest_penalty_percent`, `pm_dispute_reward_multiplier` (bp multiplier, 10000 = 1×). +- **Time penalty:** `pm_default_time_penalty_percent`, `pm_max_time_penalty`. +- **Lazy pool:** `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (default 200 = 2% — the pool refuses to co-provide liquidity to a market whose `liquidity_fee_percent` is below this reward floor). +- **Leverage (opt-in):** `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent`. +- **Fairness:** `pm_processing_cap_per_block`. + +The three `*_enabled` flags (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) are live kill-switches: the validator median can disable commit-reveal, the lazy pool, or leverage without a new hardfork. --- diff --git a/docs/node/configuration.md b/docs/node/configuration.md index 2121d967b8..7a892d090e 100644 --- a/docs/node/configuration.md +++ b/docs/node/configuration.md @@ -158,6 +158,11 @@ Leave these unset on non-validator nodes. # Allow production even if chain is stale (development/testnet only) enable-stale-production = false +# Disable minority-fork detection (single-operator testnet/fork ONLY). +# Unlike enable-stale-production, this is never auto-cleared by healthy +# participation. Never enable on a real public network. +disable-minority-fork-detection = false + # Minimum participation % required to produce blocks (0–99) required-participation = 33 @@ -203,4 +208,4 @@ All options listed by source file: | `plugins/chain/plugin.hpp` | `shared-file-size`, `min-free-shared-file-size`, `inc-shared-file-size`, `block-num-check-free-size`, `single-write-thread`, `enable-plugins-on-push-transaction`, `read-wait-micro`, `max-read-wait-retries`, `write-wait-micro`, `max-write-wait-retries`, `skip-virtual-ops`, `clear-votes-before-block`, `track-account-range`, `history-whitelist-ops`, `history-blacklist-ops`, `history-start-block` | | `plugins/p2p/p2p_plugin.hpp` | `p2p-endpoint`, `p2p-max-connections`, `p2p-seed-node`, `checkpoint` | | `plugins/webserver/webserver_plugin.hpp` | `webserver-http-endpoint`, `webserver-ws-endpoint`, `webserver-thread-pool-size` | -| `plugins/validator/validator.hpp` | `enable-stale-production`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` | +| `plugins/validator/validator.hpp` | `enable-stale-production`, `disable-minority-fork-detection`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` | diff --git a/docs/node/docker.md b/docs/node/docker.md index 2141e4c980..d776ca166b 100644 --- a/docs/node/docker.md +++ b/docs/node/docker.md @@ -110,6 +110,35 @@ docker run -d \ vizblockchain/vizd:testnet ``` +### Testnet bootstrap + +When a node starts without seed nodes, it tries to open a `snapshot.json` +file holding the initial users and their balances. If found, liquid tokens are +transferred from the `CHAIN_INITIATOR_NAME` account to user accounts as vesting +(shares). + +Initial users and their keys are defined in +`libraries/protocol/include/graphene/protocol/config.hpp`: + +- `CHAIN_INITIATOR_NAME` (`viz`) — holds the initial balance. +- `CHAIN_COMMITTEE_ACCOUNT` — the initial active validator. + +`viz` keys: + +- private: `5JabcrvaLnBTCkCVFX5r4rmeGGfuJuVp4NAKRNLTey6pxhRQmf4` +- public: `VIZ6MyX5QiXAXRZk7SYCiqpi6Mtm8UbHWDFSV8HPpt7FJyahCnc2T` + +`committee` keys: + +- private: `5Hw9YPABaFxa2LooiANLrhUK5TPryy8f7v9Y1rk923PuYqbYdfC` +- public: `VIZ6Yt7d6LsngBoXQr47aLv97bJVs7jyr7esZTM4UUSpLUf3nbRKS` + +To ease testing, several additional users are immediately available: `alice`, +`bob`, `charlie`, `chuck`, `dan`, `frank`. They all share the same keys: + +- private: `5J9DBCRX5D2ZUUuy9qV2ef9p5sfA3ydHsDs2G531bob7wbEigDJ` +- public: `VIZ5zTAE2hiGcqYaDTQeEBqTtPeoWtSNjpznwmbvqJXesrK1Qn3e8` + --- ## Building Images Locally diff --git a/docs/node/validator-node.md b/docs/node/validator-node.md index 6a00456b04..6aab9535e6 100644 --- a/docs/node/validator-node.md +++ b/docs/node/validator-node.md @@ -164,6 +164,9 @@ If fewer than 33% of validators are participating, production stops to prevent s ### Minority Fork Detection If the node's fork database shows 21+ consecutive blocks all from this node's own validators, it automatically rolls back to LIB and resyncs. This catches network isolation. +> [!WARNING] Single-operator forks +> On a testnet or mainnet fork where **one operator controls all validators**, "21 blocks all ours" is the normal healthy state, so this detector loops forever resetting to LIB. `enable-stale-production = true` does **not** help here: healthy participation (≥33%) auto-clears that override every block. Use `disable-minority-fork-detection = true` instead — it bypasses both the standard and DLT detection paths and is never auto-cleared. **Never enable it on a real public network** — it removes the isolation guard. + ### Production Watchdog If no block has been produced for 180 seconds (60s for emergency master) while `should_be_producing` is true, the watchdog automatically clears stuck flags (`minority_fork_recovering`, P2P catchup, chain syncing) and attempts to resume. diff --git a/docs/plugins/prediction-market-api.md b/docs/plugins/prediction-market-api.md new file mode 100644 index 0000000000..8ebb13d2ce --- /dev/null +++ b/docs/plugins/prediction-market-api.md @@ -0,0 +1,291 @@ +# `prediction_market_api` Plugin + +Read-only JSON-RPC access to HF14 prediction-market state (markets, bets, oracles, liquidity, disputes, the lazy pool, and the v5 chain properties). The plugin returns the raw consensus `pm_*` objects directly, plus a few computed DTOs for values that are derived rather than stored. + +**Enable:** add `prediction_market_api` to the node's plugin list (registered in `vizd` by default). Depends on `chain` + `json_rpc`. + +All list methods page with `from` (skip count) and `limit` (`≤ 1000`). + +--- + +## Methods + +### Markets + +| Method | Args | Returns | +|--------|------|---------| +| `get_market` | `market_id` | `pm_market_object` | +| `list_markets` | `status, from, limit, [show_risky]` | `pm_market_object[]` | +| `list_markets_by_oracle` | `oracle, from, limit` | `pm_market_object[]` | +| `list_markets_by_oracle_status` | `oracle, status, from, limit` | `pm_market_object[]` | +| `list_markets_awaiting_resolution` | `oracle, from, limit` | `pm_market_object[]` | +| `list_markets_by_creator` | `creator, from, limit` | `pm_market_object[]` | +| `get_market_outcomes` | `market_id` | `pm_outcome_object[]` | +| `get_market_weight_sums` | `market_id` | `pm_market_weight_sums` (computed) | +| `get_market_bets` | `market_id, from, limit` | `pm_bet_object[]` | +| `get_market_liquidity` | `market_id, from, limit` | `pm_liquidity_object[]` | +| `get_market_full` | `market_id, [account]` | `pm_market_full` (computed) | + +`status` for `list_markets`: `-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved. + +`list_markets_by_oracle_status` returns **one oracle's markets filtered to a single `status`** (`1` active, `3` resolved, …) — walked over the dedicated `by_oracle_status` composite index (keyed `oracle, status, id`) as a bounded prefix, instead of pulling the oracle's whole set with `list_markets_by_oracle` and filtering client-side. Use it for an oracle profile's "Active / Resolved" tabs, or to page just an oracle's resolved history. (For markets *awaiting* this oracle's result — active but past betting-close — use `list_markets_awaiting_resolution`, which cannot be expressed by `status` alone.) + +`list_markets_awaiting_resolution` returns the markets that need **this oracle's result now**: active (`status 1`) markets whose betting window has already closed (`betting_expiration ≤ head_block_time`) and are therefore not yet resolved. "Awaiting" is not a distinct status — a market stays `active` from open through betting-close until it is resolved — so it cannot be isolated by `status` alone. This method walks the `by_betting_expiration` index (keyed `status, betting_expiration, id`) over just the bounded prefix of active markets whose betting has passed, avoiding a full scan of the oracle's (mostly resolved) history that an Oracle Console would otherwise have to do client-side. + +`get_market_full` is a **one-call enriched view** for a market-detail screen: it returns the market + outcomes + weight sums + oracle (with reliability) + parsed metadata, and — when the optional `account` is given — that account's bets, leverage positions and LP **on this market**. Saves the thin client several round-trips. + +**Risk listing filter** (security-threat-model §4.3): by default `list_markets` hides under-insured markets — those whose oracle insurance covers less than **2.5×** the market's betting volume. Pass `show_risky = true` to reveal them. Markets are only hidden, never blocked/deleted (betting is always permitted on-chain — a consensus bet-block would be a censorship vector). The lazy-pool exposure penalties (active-market 5% recursive + fault stamps) are enforced in consensus on allocation. + +### Market metadata (off-chain parsed) + +Each market carries a free-form, consensus-opaque `metadata` JSON string. This plugin parses the keys it +indexes (category / subcategory / tags / banned jurisdictions / title / image / condition_id / +**description**) into a `pm_market_meta_object` for discovery, jurisdiction filtering and display — +**display/indexing only, never consensus**. `description` holds the short resolution rules (how the oracle +will resolve the market — surface-level, for clients); the market's on-chain `url` still points to the full +legal terms at the source. + +| Method | Args | Returns | +|--------|------|---------| +| `get_market_meta` | `market_id` | `pm_market_meta_object` (or error if none) | +| `list_markets_by_category` | `category, from, limit, [jurisdiction], [subcategory], [tag], [sort]` | `pm_market_meta_object[]` | +| `get_market_categories` | — | `pm_market_categories` (computed) | + +`list_markets_by_category` excludes markets whose `banned_jurisdictions` contains the optional +`jurisdiction` ISO code — so a regulated client passes its own jurisdiction to get only the markets it +may list. Optional `subcategory` (exact) and `tag` (CSV membership) narrow the set; `sort` ∈ `newest` +(market id desc, default) · `oldest` · `volume` (`bets_sum` desc) · `expiration` (`betting_expiration` +asc). `get_market_categories` returns the live taxonomy — per-category / per-subcategory counts plus the +top 20 hot tags (jurisdiction-* excluded) — aggregated over currently indexed markets, so a browse UI can +build its filter chips without hard-coding a taxonomy. The meta object: +``` +{ market: pm_object_id, + category, subcategory, tags, // strings; tags comma-joined + banned_jurisdictions, // comma-joined ISO codes; empty = allowed everywhere + title, image, condition_id, // display + source back-link + description, // short resolution rules (url = full legal terms) + expiry } // pruned after the dispute window closes + TTL +``` + +### Positions & oracles + +| Method | Args | Returns | +|--------|------|---------| +| `get_account_positions` | `account, from, limit` | `pm_position[]` (bet + `expected_payout`) | +| `get_account_leverage_positions` | `account, from, limit` | `pm_leverage_position_object[]` | +| `get_market_leverage_positions` | `market_id, from, limit` | `pm_leverage_position_object[]` | +| `get_creator_ban` | `account` | `pm_creator_ban_object` (or error if none) | +| `get_oracle` | `owner` | `pm_oracle` (object + `reliability_score` + workload/latency reads) | +| `list_oracles` | `from, limit` | `pm_oracle_object[]` | +| `list_markets_in_dispute_window` | `oracle, from, limit` | `pm_market_card[]` | +| `list_oracle_disputes` | `oracle, from, limit` | `pm_oracle_dispute[]` (computed) | + +`list_markets_in_dispute_window` drills into the `markets_in_dispute_window` gauge: this oracle's resolved (`status 3`, `payout_status 1`) markets that still carry **no dispute row** — i.e. the ones a challenger could still dispute within `pm_dispute_grace_sec` of the announcement. `list_oracle_disputes` drills into the two dispute gauges: this oracle's currently **open** disputes (`status 0`), each tagged with a `stage` — `awaiting_response` (the oracle has not answered yet) or `awaiting_decision` (answered, now with the resolver/committee) — derived from `oracle_response_time`, plus a `market_card` for rendering. Both walk only the oracle's own set (`by_oracle_status` / `by_auto_close`), so they stay O(this oracle), not a global scan. They pair with the stored gauges on `get_oracle` (below): the gauge is the count, these methods are the list. + +### Leverage previews (Boost) + +Read-only quotes that call the **same in-node margin math** the evaluators use, so a preview matches what the corresponding `pm_leverage_*` op would compute at the head block. They are non-consensus estimates (reserves move between the read and the broadcast — always send the on-chain slippage guards). + +| Method | Args | Returns | +|--------|------|---------| +| `get_leverage_quote` | `market_id, outcome_index, collateral` | `pm_leverage_quote` (computed) | +| `get_leverage_close_preview` | `position_id` | `pm_leverage_close_preview` (computed) | +| `get_leverage_convert_preview` | `position_id` | `pm_leverage_convert_preview` (computed) | + +`get_leverage_quote` mirrors `pm_leverage_open`: it returns the max solvent loan and resulting max leverage, the pool/position caps, up to 12 slider stops (each with tokens, threshold, current & worst-case cancel value), and — when leverage is not possible — `available = false` with a `failed_constraints[]` list. `get_leverage_close_preview` / `get_leverage_convert_preview` mirror `pm_leverage_close` / `pm_leverage_convert` at the current reserves (cancel value, pool obligation, what the bettor receives, whether it is closeable/convertible, and the conversion fee at the current median `pm_conversion_profit_cost_percent`). + +> Per-bettor settlement is emitted as the `pm_payout` virtual op (stake, side/outcome, realized payout — +> `0` on a loss); a leveraged position's settlement is the `pm_leverage_resolve` virtual op (with +> `outcome_index`, `won`, `leverage`). Both appear in `account_history`; the leverage-position objects +> themselves are queryable via the two methods above. + +### Disputes, lazy pool, governance + +| Method | Args | Returns | +|--------|------|---------| +| `get_dispute` | `market_id` | `pm_dispute_object` | +| `get_dispute_votes` | `market_id` | `pm_dispute_votes` (votes + live tally) | +| `get_lazy_pool` | — | `pm_lazy_pool_object` | +| `get_lazy_deposit` | `account` | `pm_lazy_deposit_object` | +| `get_lazy_allocations` | `from, limit` | `pm_lazy_allocation_object[]` | +| `get_market_lazy_allocation` | `market_id` | `pm_lazy_allocation_object` (or error if none) | +| `get_pm_chain_properties` | — | `chain_properties_pm` (median, v5) | + +`get_lazy_allocations` lists the lazy pool's per-market allocation records (for a pool dashboard); `get_market_lazy_allocation` fetches the one for a given market. Oracle penalty stamps need no separate method — they ship on `pm_oracle_object` (`penalty_stamps`, `last_penalty_stamp_time`) via `get_oracle`. + +### Charts — kline / weight history + +A time series for plotting how each outcome's weight evolves. The plugin appends one point **every time a market's per-outcome weights change** — a bet, a cancel, a liquidation, a batch settle, a leverage open, or a leverage settlement — as a timestamped snapshot of the parimutuel weight (staked amount) on every outcome. This is **non-consensus** plugin state (kept in chainbase, undo/redo-safe, never part of the state hash); history accrues from the moment the plugin is first enabled on the node. + +**Retention:** the kline history is pruned **together with the market's metadata**, on the same schedule — `result_expiration` + dispute grace + `pmm-ttl-days` (default 7). So a market's full chart is available throughout its life and for the retention window after settlement, then both indexes are cleaned up (draining over several blocks for very long histories) to keep node storage bounded. + +| Method | Args | Returns | +|--------|------|---------| +| `get_market_kline` | `market_id, [from], [limit]` | `pm_kline[]` (ascending by `seq`) | + +Pagination is **offset-from-newest** (kept deliberately simple for thin clients): `from` is how many of the **newest** points to skip, `limit ≤ 1000` is the page size. +- `(market_id, 0, 1000)` → the latest ≤ 1000 changes. +- `(market_id, 1000, 1000)` → the previous 1000 (one page further back) — repeat with `from += 1000` to lazy-load older history. + +Plot it as: x = `timestamp` (unix seconds), and one line per outcome `i` with y = `weights[i]` (or normalized `weights[i] / Σweights` for the implied probability). + +--- + +## Computed DTOs + +These wrap raw objects with values derived at read time (non-consensus). + +**`pm_position`** — a bet plus its parimutuel payout: +``` +{ bet: pm_bet_object, + expected_payout: share_type, // payout if this side wins (or realized once settled) + market_status: int8, + resolved_outcome: int16 } +``` +`expected_payout` byte-mirrors `settle_market`: for an active bet it is the conditional payout if the chosen side wins (`amount + winners_pool × weight / Σweight − time_penalty`); once settled it is the realized `resolved_amount`. + +**`pm_oracle`** — the raw `pm_oracle_object` plus read-time computed fields. All non-consensus (display only; never gate consensus). + +- `reliability_score` — bp `[0..10000]`. **v2.1** blends four reputation ratios, then docks decayed penalty stamps and bans, then confidence-shrinks a thin track record toward a neutral prior: + ``` + accuracy = markets_resolved / (markets_resolved + missed_count) // resolved vs missed-deadline + verdicts = disputes_won / (disputes_won + disputes_lost) // dispute outcomes + responsive = (disputes_received − dispute_responses_missed) / disputes_received + timely = (markets_resolved − resolved_late_count) / markets_resolved // on-time vs past-deadline resolves + score = accuracy·40% + verdicts·30% + responsive·15% + timely·15% + score −= penalty_stamps × 300bp (halved per 10 days since last_penalty_stamp_time) + score −= bans_received × 1500bp + if markets_resolved < 20: shrink toward a 6000 prior // unproven oracles neither sit at 100 nor crater + ``` + Each ratio defaults to a full `10000` until the oracle has the relevant history (optimistic when unproven). `timely` is the P5 lateness signal: a late-but-delivered resolve still counts as `markets_resolved` (so it earns full `accuracy`), and `timely` is what separates a chronically-late oracle from a punctual one. `avg_resolution_time` is deliberately **not** scored — it measures latency from betting close, not deadline overrun, and can't be normalized without the market length. Weights are non-consensus; tune freely. +- **Workload gauges** (stored on `pm_oracle_object`, O(1)-maintained across the op/cron transitions, seeded once on upgrade): `markets_in_dispute_window`, `disputes_awaiting_response`, `disputes_awaiting_decision` — the live counts an Oracle Console badges; drill into the lists with `list_markets_in_dispute_window` / `list_oracle_disputes`. +- **Computed-on-read** (time-dependent, so not stored): `markets_awaiting_resolution` + `oldest_unresolved_age` (this oracle's `status 1` markets past betting close, and the age of the oldest — one `by_oracle_status` walk), and `resolution_time_p50` / `resolution_time_p95` (percentiles read off the oracle's 8-bucket latency histogram `resolution_time_hist`, upper bucket edge where the cumulative count first crosses the percentile). + +**`pm_market_weight_sums`** — per side/outcome `bets_sum` and `weight_sum` (weight sums are computed by scanning bets, since they are not stored): +``` +{ market_type: uint8, bets_sum: share_type, + outcomes: [ { outcome_index, label, bets_sum, weight_sum } ] } +``` + +**`pm_dispute_votes`** — the committee tally **plus a stake-weighted projection of the finalize cron**, +so a caller can show the live quorum status and the verdict that would be applied under the current votes: +``` +{ votes: pm_dispute_vote_object[], + // legacy rough tally (weight = |vote_percent|, NOT stake) — kept for compatibility + uphold_weight, challenge_weight, total_weight, + challenger_leads: bool, // ≥ pm_dispute_approve_min_percent (rough) + proposed_outcome: int16, + // ── accurate stake-weighted projection (mirrors pm_dispute_finalize) ── + // every *_shares value is vesting-shares: effective_vesting_shares + lazy-pool stake → shares + participation_shares, // Σ weight of accounts that voted (= max_rshares) + electorate_shares, // total_vesting_shares + pool_NAV→shares (quorum base) + quorum_required_shares, // electorate × pm_dispute_approve_min_percent + quorum_percent_bp: int32, // participation / electorate (bp, 10000 = 100.00%) + quorum_reached: bool, // participation_shares ≥ quorum_required_shares + oracle_defense_shares, change_shares, // rshares defending the oracle vs. backing a change + outcome_change_shares: int64[], // per-outcome backing rshares (size = outcome_count) + expected_uphold: bool, // true ⇒ oracle resolution stands if finalized now + expected_outcome: int16, // outcome that would be set at finalize now + expected_consensus_strength_bp: int32 } // winning / participation (bp); 0 when uphold +``` +> The projection uses the **same** stake weighting and lazy-pool→shares bridge as the on-chain +> `pm_dispute_finalize`, so `expected_outcome` / `quorum_reached` match what the cron will apply at +> `voting_end_time` *given the votes cast so far* (votes are revisable until then — see +> [dispute operations](../protocol/operations/prediction-markets.md)). + +**`pm_kline`** — one charting point (per-outcome weight snapshot at a moment in time): +``` +{ seq: uint32, // 0-based, contiguous, monotonic per market (the change index) + timestamp: uint32, // unix seconds — x coordinate + reason: uint8, // 0 bet, 1 cancel, 2 liquidation, 3 batch settle, 4 leverage open, 5 leverage resolve + bets_sum: share_type, // total staked across all outcomes at this point + weights: share_type[] }// per-outcome staked weight (y values), index = outcome_index +``` + +**`pm_market_full`** — one-call enriched market view (`oracle`/`meta` are `null` when absent; the `my_*` +arrays are empty unless an `account` argument was supplied): +``` +{ market: pm_market_object, + outcomes: pm_outcome_object[], // empty for binary markets + weight_sums: pm_market_weight_sums, + oracle: pm_oracle | null, + meta: pm_market_meta_object | null, + my_positions: pm_position[], // account's bets on THIS market + my_leverage_positions: pm_leverage_position_object[], + my_liquidity: pm_liquidity_object[] } +``` + +**`pm_leverage_quote`** — leverage-open preview (from `pm::leverage::*`, the same math the evaluator runs): +``` +{ available: bool, outcome_index, collateral, + max_loan, max_leverage_x100, // 100 = 1.00× + pool_free_amount, fund_available, per_position_cap, market_position_cap, + pool_profit_percent, safety_margin_percent, max_slippage_percent, m_factor_percent, + expiration_buffer_sec, auto_close_time, // betting_expiration − buffer + stops: [ { leverage_x100, loan, total_bet, expected_tokens, pool_profit, + liquidation_threshold, current_cancel_value, worst_case_cancel_value } ], + failed_constraints: [ { constraint, reason } ] } // populated when !available +``` +**`pm_leverage_close_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, bettor_receives, collateral, loan, pool_profit_charge, closeable: bool, loss_vs_collateral, loss_percent_bp }`. +**`pm_leverage_convert_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, current_profit, conversion_profit_cost_percent, conversion_fee, total_user_payment, convertible: bool }`. + +**`pm_market_categories`** — browse taxonomy with live counts: +``` +{ categories: [ { category, count, subcategories: [ { subcategory, count } ] } ], // sorted by count desc + hot_tags: [ { tag, count } ] } // top 20 (jurisdiction-* excluded) +``` + +--- + +## Example + +Fetch a bettor's positions: +```bash +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_account_positions",["alice",0,100]]}' \ + http://127.0.0.1:8090 +``` + +Fetch the latest 1000 chart points for market `42`, then the previous 1000: +```bash +# newest page +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_market_kline",[42,0,1000]]}' http://127.0.0.1:8090 +# one page older +curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call", + "params":["prediction_market_api","get_market_kline",[42,1000,1000]]}' http://127.0.0.1:8090 +``` + +Thin-client charting (lazy-load older history on scroll-back), turning each point into +per-outcome series of `{ x: unixtime, y: weight }`: +```js +async function call(method, params) { + const r = await fetch('http://127.0.0.1:8090', { method: 'POST', + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'call', + params: ['prediction_market_api', method, params] }) }); + return (await r.json()).result; +} + +// Pull pages of 1000 from newest backwards until we have `want` points (or run out). +async function loadKline(marketId, want = 3000) { + const points = []; + for (let from = 0; points.length < want; from += 1000) { + const page = await call('get_market_kline', [marketId, from, 1000]); + if (!page.length) break; // reached the start of history + points.unshift(...page); // pages are ascending; prepend older pages + if (page.length < 1000) break; + } + return points; +} + +// One {x,y} series per outcome — feed straight into any charting lib. +function toSeries(points, outcomeCount) { + const series = Array.from({ length: outcomeCount }, () => []); + for (const p of points) + for (let i = 0; i < outcomeCount; i++) + series[i].push({ x: p.timestamp, y: Number(p.weights[i]) }); + return series; +} +``` + +See [Prediction Market Operations](../protocol/operations/prediction-markets.md) for the on-chain objects these methods expose, and [Chain Properties](../governance/chain-properties.md) for the v5 governance parameters. diff --git a/docs/plugins/validator.md b/docs/plugins/validator.md index c331de515a..28e9b54d7d 100644 --- a/docs/plugins/validator.md +++ b/docs/plugins/validator.md @@ -24,6 +24,7 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin | `private-key` | — | WIF private key(s) for signing; may be repeated | | `emergency-private-key` | — | WIF key for emergency consensus; auto-adds `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` to the validator set | | `enable-stale-production` | `false` | Bypass participation and sync checks (testnet / network recovery only) | +| `disable-minority-fork-detection` | `false` | Skip minority-fork detection entirely (single-operator testnet/fork only). Never auto-cleared by healthy participation — see [Minority Fork Detection](#minority-fork-detection) | | `required-participation` | `3300` | Minimum validator participation in **basis points** (3300 = 33%) | | `fork-collision-timeout-blocks` | `21` | Consecutive fork-collision deferrals before forcing production (one full validator round) | @@ -116,7 +117,8 @@ When a competing block exists at `head_block_num + 1`: Before each production attempt (after HF12 safety checks), the plugin walks the last 21 blocks in `fork_db`. If all 21 were produced by the node's own configured validators, the node is isolated on a minority fork. - **Default action:** Call `p2p().resync_from_lib()` — pop blocks to LIB, reset fork DB, re-initiate P2P sync, reconnect seed nodes. Returns `minority_fork`. -- **With `enable-stale-production=true`:** Log a warning, continue producing. +- **With `enable-stale-production=true`:** Log a warning, continue producing. **Note:** at ≥33% participation this override is auto-cleared every block, so on a single-operator fork it does *not* stop the detector — use `disable-minority-fork-detection` instead. +- **With `disable-minority-fork-detection=true`:** Both the standard and DLT detection paths are skipped entirely, and the flag is never auto-cleared. For single-operator testnet/forks where "21 blocks all ours" is the healthy steady state. **Never enable on a real public network** — it removes the isolation guard. - **Skipped when:** Emergency consensus is active (committee blocks would always match our configured set). A DLT-specific slave isolation check replaces it in emergency mode. --- @@ -217,7 +219,7 @@ Included in P2P FORWARD stagnation logs when the node is stuck with no peer ahea | `no_private_key` | Config missing `private-key` for the signing key that's registered on-chain | | `low_participation` | Network participation < 33%; check peer connectivity or set `enable-stale-production=true` | | `fork_collision` | Competing block at next height; wait for vote-weight resolution or 21-deferral timeout | -| `minority_fork` | Isolated; plugin auto-resyncs to LIB | +| `minority_fork` | Isolated; plugin auto-resyncs to LIB. On a single-operator fork this loops — set `disable-minority-fork-detection=true` | | Watchdog fires repeatedly | Sync or catchup flag stuck; watchdog auto-clears if head is advancing | | `SLOT-HIJACK` logs | Emergency master blanked our key; restore via `validator_update_operation` | diff --git a/docs/prediction-markets/concepts-analysis.md b/docs/prediction-markets/concepts-analysis.md new file mode 100644 index 0000000000..361daa7d33 --- /dev/null +++ b/docs/prediction-markets/concepts-analysis.md @@ -0,0 +1,244 @@ +--- +title: Concept analysis — Onix vs the 90 prediction-market concepts +description: How the live VIZ on-chain implementation (Onix protocol, Forecaster client) maps onto the 90 prediction-market theory concepts — what is solved, inherent, not needed, or roadmap. +--- + +# Concept analysis — Onix vs the 90 theory concepts + +> **Forecaster** is the thin client to the VIZ on-chain prediction market — the access layer that lets +> people anywhere participate by signing `pm_*` operations (see the [section overview](./)). +> **Onix** is the protocol it talks to. This page maps each of the **90 PM-Atlas prediction-market +> concepts** onto **how the live VIZ on-chain implementation addresses it**, and **whether it is needed** +> for this architecture. +> +> Grounding docs: [whitepaper](./whitepaper), [specification](./specification), +> [workflows & disputes](./workflows). + +## Legend + +| Mark | Meaning | +|------|---------| +| ✅ **Resolved** | Onix design directly solves or handles it | +| ⚪ **Inherent** | A property Onix exhibits/inherits by construction (no extra work) | +| ➖ **Not needed** | Architecturally unnecessary under Onix | +| 🟡 **Partial / Roadmap** | Partly addressed today; rest is on the VIZ roadmap | +| 🏛 **Client-layer** | Handled by the jurisdictional client, not the protocol | +| 🔴 **Open / Risk** | Still a live concern; not fully solved | + +The single biggest structural difference from every other platform: **LP principal is structurally guaranteed (winners paid only from losers' forfeited stakes), pricing is CPMM for binary and LMSR-softmax + parimutuel settlement for multi, and there is no order book.** Most "liquidity & trading" concepts that exist to manage market-maker inventory risk simply **do not apply** because Onix has no inventory-bearing maker. + +> **On-chain actualization (HF14 / live).** This mapping was first written against the whitepaper/spec. Several items then marked *roadmap* are now **implemented as consensus operations** and verified in `consensus_sim`: +> - **Batch auctions + commit-reveal betting** — `pm_commit_bet` / `pm_reveal_bet` / `pm_batch_settle`, per-market `allow_batch` / `allow_instant_bet`, median kill-switch `pm_commit_reveal_enabled`. **Binary only** (multi forces `allow_instant_bet` — no LMSR batch yet). +> - **Opt-in leverage subsystem** — `pm_leverage_open/close/convert`, lazy-pool-funded, kill-switch `pm_leverage_enabled` (default off). Directly answers **Position Collateralization (#30)**. +> - **The Lazy Pool itself** — singleton, auto-allocation, MasterChef accounting, leverage loans, graduated recall. +> - **`endogeneity_tier`** market field; on-chain **creator bans** (`pm_creator_ban_object`); richer settlement virtual ops (`pm_payout` per bettor, `pm_leverage_resolve`, `pm_market_accepted`, `pm_auto_payout`) + matching plugin API. +> - **New since the spec:** **lazy-pool stake counts as governance weight** in PM disputes *and* DAO committee-request voting (converted to vesting-shares, HF14-gated). +> +> **Deliberately NOT built:** commit-reveal *dispute* voting — committee disputes are **open public hearings by design** (votes stay public via `pm_dispute_vote`, and ballots are revisable until close). **Still roadmap:** automated exogenous data oracles. Rows and the mitigations table below are updated to this live state. + +--- + +## 1. Information Theory + +| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed | +|---|---------|---------|--------------------------------------------------| +| 1 | **Brier Score** | ⚪ Inherent | Not a protocol mechanism but the *metric* by which Onix markets are judged. On VIZ, every bet/resolution is a consensus-validated event, so per-market price histories and outcomes are fully on-chain → Brier scoring of the platform (and of oracles) is computable by anyone. Needed only as an analytics/reputation input, not core logic. | +| 2 | **Calibration** | ⚪ Inherent | Onix prices are genuine probabilities (CPMM `P(A)=reserve_b/(reserve_a+reserve_b)`, LMSR softmax sums to 1). Calibration is an emergent property to *measure*, improved indirectly by the time-penalty (discourages no-info last-second bets) and deep LP liquidity. Not something the protocol enforces. | +| 3 | **Credibility Markets** | 🟡 Partial | The bonded-oracle + 14-metric reputation + composite trust score is effectively a credibility market for *resolvers*. Staking credibility against an outcome is native. A general "stake reputation on claims" product is a possible client-layer build, not core. | +| 4 | **Distribution Markets** | 🟡 Roadmap | Onix Multi (3–10 discrete outcomes) approximates a distribution via bucketed outcomes. True continuous distribution markets (CDF/scalar) are **not** in scope today; would need a scalar-outcome operation. Listed-adjacent to the roadmap's "category-level AMMs." | +| 5 | **Endogeneity** | 🟡 Partial (mitigated) | The risk that the market changes the thing it predicts. Category-specific (econ-data clean, political/social risky). Mitigated by the **live `endogeneity_tier` market field** (oracle-tagged 1/2/3) and **opt-in commit-reveal/batch betting (now on-chain)** that stops the public price from "leaking" mid-window (the thermostat channel); exogenous resolution via automated data oracles is **still roadmap**. See [Mitigations §](#mitigations-for-the-reflexivity-family). | +| 6 | **Forecasting Accuracy** | ⚪ Inherent | The whole value proposition. Onix improves it indirectly: zero-risk LP → deeper books → less slippage → more informed participation → better prices. Accuracy is the output to measure, not a feature to build. | +| 7 | **Info Finance** | ⚪ Inherent | Onix *is* an info-finance instrument: consensus-level operations turn information into priced, settleable positions. VIZ migration makes the information layer censorship-resistant and composable. | +| 8 | **Information Aggregation** | ✅ Resolved | Core function. CPMM/LMSR pricing aggregates dispersed bets into a single probability; deep risk-free LP liquidity is precisely the lever Onix pulls to make aggregation work (the flywheel in §7.3 of the whitepaper). | +| 9 | **Information Asymmetry** | 🟡 Partial | Onix's parimutuel/CPMM design means informed traders extract from *other losing bettors*, not from the LP — so asymmetry doesn't bankrupt liquidity (unlike CLOB/LMSR makers). **Opt-in commit-reveal + batch betting is now live (binary):** committed bets settle at a uniform batch price, removing the mempool-direction leak; per-market `allow_batch` + median kill-switch keep it optional. | +| 10 | **Legibility** | ✅ Resolved | Every financial action is a consensus-validated VIZ operation with a `market_log` audit trail (before/after reserves). Fully legible/auditable by any node — strictly more legible than a centralized backend or opaque CLOB. New settlement virtual ops (**`pm_payout`** per bettor, **`pm_leverage_resolve`**, **`pm_market_accepted`**) + dedicated plugin API methods make per-bettor outcomes and leverage resolutions directly queryable. | +| 11 | **Longshot Bias** | 🟡 Partial | CPMM/LMSR pricing can still exhibit favorite-longshot bias from bettor behavior; Onix doesn't correct it directly. The time penalty and deep liquidity dampen distortion, but bias is a behavioral output, not eliminated. | +| 12 | **Noise Decomposition** | ⚪ Inherent | Analytical lens, not a protocol feature. On-chain price/volume series on VIZ make signal-vs-noise decomposition feasible for analysts. Not needed in core. | +| 13 | **Nowcasting** | ⚪ Inherent | Onix prices update continuously per bet (~3s VIZ blocks), giving real-time nowcast estimates. Inherent to any live AMM market; no extra mechanism. | +| 14 | **Price Discovery** | ✅ Resolved | CPMM and LMSR-softmax are continuous price-discovery engines; price coherence (`Σ price = 1`) holds *by construction* with no arbitrage/split-merge layer needed. | +| 15 | **Probability Infrastructure** | ✅ Resolved | This is essentially Onix's thesis on VIZ: prediction markets as **first-class consensus operations** (`pm_*`), not smart contracts — a base-layer probability primitive. Directly the migration goal. | +| 16 | **Superforecasting** | ⚪ Inherent | Individual-skill concept; Onix rewards accurate bettors via the losers→winners payout. Position transfers + reputation could support superforecaster identity, but it's a participant trait, not protocol logic. | +| 17 | **Wisdom of Crowds** | ✅ Resolved | The mechanism Onix monetizes. Risk-free LP lowers the barrier so more of the crowd participates, sharpening the aggregate. Core to the design rationale. | +| 18 | **Yes Bias** | 🟡 Partial | Behavioral tilt toward "Yes." Onix's symmetric CPMM and profit-only time penalty don't structurally favor Yes, but they don't correct human bias either. Mitigated by liquidity depth; a measurement concern. | + +--- + +## 2. Mechanism Design + +| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed | +|---|---------|---------|--------------------------------------------------| +| 19 | **Binary Contracts** | ✅ Resolved | Onix Binary = CPMM (`x·y=k`) on two outcomes. AM-GM proof guarantees `reserve_a+reserve_b ≥ L`, so LP principal is covered. This is the primary market type. | +| 20 | **Combinatorial Prediction Markets** | ➖ Not needed (today) | LMSR decomposes naturally over combinatorial spaces, but Onix Multi caps at 3–10 *independent* outcomes and deliberately omits CTF split/merge. Combinatorial/conditional bundles are explicitly out of scope; not required for the LP-guarantee model. | +| 21 | **Incentive Compatibility** | ✅ Resolved | LMSR inherits IC from the log scoring rule (truth-telling dominant). Onix adds incentive alignment via bonded oracles (insurance > manipulation profit), losers-fund-winners settlement, and time-weighted LP rewards. | +| 22 | **Keynesian Beauty Contest** | ✅ Resolved (market) / ⚪ (dispute layer — accepted by design) | KBC is a pathology of *relative/peer scoring*. The Onix **market** layer pays bettors against external ground truth (parimutuel), so it is structurally anti-KBC — you profit by *deviating* from the crowd price when it's wrong. The only KBC exposure is the **stake-weighted committee dispute vote** (a peer mechanism). **Decision: commit-reveal dispute voting will NOT be implemented** — a committee dispute is an **open public hearing**, and the DAO's credibility depends on resolving disputes as transparently as possible; hiding ballots would erode that trust. The residual KBC risk is accepted and structurally small: voters are **not paid** for matching the majority (no bandwagon bounty) and **ballots are revisable** until close (so honest updates on new evidence are expected, not suppressed). Pooled DAO members are also **enfranchised** (lazy-pool stake → vesting-shares, HF14). See [Mitigations §](#mitigations-for-the-reflexivity-family). | +| 23 | **LMSR** | ✅ Resolved (the key innovation) | The concept file notes LMSR *failed for binaries* (permanent loss on the 0/1 boundary). Onix's answer: **use CPMM for binary, and use LMSR only for multi where the maker is NOT the counterparty** — parimutuel settlement pays winners from losers, so the LMSR subsidy is never at risk (`LP max loss = 0` vs `b·ln(N)`). This is the central design move. | +| 24 | **LOX (Log-Odds Excess Lateness)** | ➖ Not needed | A specialized scoring/lateness metric. Onix instead uses a **quadratic time penalty on profit** to handle late-bet incentives — a simpler, settlement-time mechanism. LOX scoring is not part of the model. | +| 25 | **Market Manipulation** | 🟡 Partial | Bonded oracle (bond must exceed manipulation profit), DPoS-validated operations, and committee dispute arbitration raise manipulation cost. Price manipulation via large bets is bounded by depth and — on **batch/commit-reveal markets (now live)** — by uniform-price settlement that neutralises speed-based sniping (the "sniper's tax"); active surveillance is still roadmap. | +| 26 | **Market Scoring Rules** | ✅ Resolved | Onix Multi is a market scoring rule (LMSR) implementation, repurposed with parimutuel settlement. Directly used. | +| 27 | **Multi-Outcome Markets** | ✅ Resolved | Onix Multi handles N=3–10 via LMSR softmax pricing + parimutuel payout, with `b = S/ln(N)`. First-class market type. | +| 28 | **Parimutuel Markets** | ✅ Resolved (foundational) | Settlement in *both* market types is parimutuel: losers' forfeited stakes form `winners_pool`, distributed by token share. This is what makes the LP guarantee structural rather than insured. | +| 29 | **Peer Prediction** | ➖ Not needed | Truth-telling-without-ground-truth schemes. Onix relies on bonded oracles + committee dispute, not peer-prediction scoring. Could inform subjective-market resolution but not used. | +| 30 | **Position Collateralization** | ✅ Resolved (+ opt-in leverage, live) | By default every bet is fully prepaid (full amount enters reserves; no fees at bet time) — total collateralization by construction. Onix now **also** ships the concept's "next level": an **opt-in leverage subsystem** (`pm_leverage_open/close/convert`, kill-switch `pm_leverage_enabled`, default off). Margin is a **loan from the Lazy Pool** (no token emission — zero-sum preserved), so the position stays fully collateralized *from the system's view*. The binary "jump risk" that breaks CLOB liquidation engines (per this concept) is handled by **liquidating against pre-bet reserves**: opposing-bet / settlement force-close recovers `min(cancel_value, obligation) ≥ loan`, so the pool gets loan + interest back; the **only** bounded bad-debt path is a same-side `pm_cancel_bet` (Case B). The liquidation cascade is deliberately **not** gated by the kill-switch, so toggling leverage off never strips protection from open positions. | +| 31 | **Proper Scoring Rules** | ✅ Resolved | LMSR is the cost-function dual of the log proper scoring rule; Onix Multi inherits its truthful-elicitation property. | +| 32 | **Reflexivity** | 🟡 Partial (mitigated) | Parent of endogeneity. Mitigated by the same toolkit — **commit-reveal/batch betting + `endogeneity_tier` are now live**, exogenous resolution still roadmap — **plus on-chain creator bans** (`pm_creator_ban_object`) for harmful reflexivity (assassination/"hit" markets, propaganda markets that create a "constituency for the outcome"); the prohibited-category *list* itself stays client-layer. Deep risk-free LP depth also raises the cost of newsworthy price manipulation. See [Mitigations §](#mitigations-for-the-reflexivity-family). | + +--- + +## 3. Liquidity & Trading + +> **Headline:** Onix has **no order book and no inventory-bearing market maker**. A large class of these concepts exists specifically to manage CLOB/maker inventory risk and therefore **do not apply** to Onix. + +| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed | +|---|---------|---------|--------------------------------------------------| +| 33 | **Adverse Selection** | ✅ Resolved (reframed) | The classic problem (informed flow bankrupts the maker) **cannot bankrupt the Onix LP**: winners are paid from losers, never from LP principal (AM-GM / parimutuel guarantee). Informed traders extract from other *bettors*, not the LP. Eliminates the core LP failure mode. | +| 34 | **Arbitrage** | ⚪ Inherent | Intra-market arbitrage is unnecessary: price coherence (`Σ price = 1`) holds by construction in both CPMM and LMSR-softmax. No split/merge arbitrage layer needed. | +| 35 | **Batched Auctions** | ✅ Resolved (opt-in, live) | Implemented as a **per-market uniform-price batch** (`mode=1` bets + commit-reveal → `pm_batch_settle` at each `pm_batch_epoch_blocks` epoch); only the **net residual** moves the AMM, so all same-side fills clear at one price and speed-based sniping (the "sniper's tax") is neutralised. Per-market `allow_batch` + median kill-switch `pm_commit_reveal_enabled`; **binary only** today (multi forces `allow_instant_bet`). The LP `Σreserve ≥ L` invariant is untouched — each batch is one valid CPMM transition. | +| 36 | **Bid-Ask Spread** | ➖ Not needed | No order book → no quoted spread. "Cost of trading" appears as CPMM/LMSR slippage, governed by liquidity depth, not maker spreads. Concept doesn't map. | +| 37 | **Bonding Trades** | ⚪ Inherent | Bets *are* bonded trades: capital is committed into reserves and only released at resolution (or via cancellation/transfer). Native behavior. | +| 38 | **Continuous Double Auction** | ➖ Not needed | CDA is the CLOB model Onix explicitly rejects in favor of AMM pricing. Not used. | +| 39 | **Covariance Markets** | ➖ Not needed | Trading correlation between events requires combinatorial/conditional structure Onix deliberately omits. Out of scope. | +| 40 | **Cross-Platform Arbitrage** | 🟡 Partial | Onix prices can diverge from Polymarket/Kalshi; arbitrage across platforms is possible but external to the protocol. VIZ's open API + headless client make price data accessible; no native cross-platform bridge. | +| 41 | **Execution Quality** | ✅ Resolved (reframed) | No partial fills/queue position. Execution quality = deterministic slippage + optional `min_tokens`/`min_return` slippage guards, validated at consensus. Predictable by construction. | +| 42 | **Gap Risk** | ✅ Resolved (for LP) | Gap risk (sudden jump to 0/1 wiping the maker) is the failure mode Onix's structural LP guarantee eliminates — the LP never holds the losing side's terminal risk. Bettors still bear their own outcome risk (as intended). | +| 43 | **Hedging** | 🟡 Partial | Bettors can hedge by taking offsetting positions, transferring positions (`pm_transfer_position`), bet cancellation (if allowed) via reverse CPMM, and now **opt-in leverage** (`pm_leverage_open/convert`) for capital-efficient offsetting. No native multi-leg derivatives; basic + leveraged hedging is possible. | +| 44 | **Implied Correlation** | ➖ Not needed | Requires multi-event/combinatorial markets Onix omits. Out of scope. | +| 45 | **Insider Trading** | 🟡 Partial / 🏛 Client | Protocol can't detect insider info; mitigated by time penalty (late-info bets earn less profit) and bonded-oracle resolution. KYC/surveillance to police insiders is a **client-layer** responsibility (regulated clients). | +| 46 | **Kelly Criterion** | ⚪ Inherent | A bettor staking strategy, not a protocol feature. Onix exposes clean probabilities and full collateralization so Kelly sizing is computable by participants; the new **opt-in leverage** lets a bettor act on a fractional-Kelly edge with margin (pool-funded, liquidation-bounded). No core involvement beyond exposing the primitives. | +| 47 | **Liquidity Fragmentation** | 🟡 Roadmap | Per-market pools fragment liquidity today. The whitepaper's top-priority roadmap item — **shared/category-level AMM pools** — is the architectural fix. Lazy Pool already mutualizes *deposits* across markets. | +| 48 | **Liquidity Provision** | ✅ Resolved (core differentiator) | Risk-free LP is the headline: principal structurally guaranteed, time-weighted fee rewards, Lazy Pool auto-allocation + MasterChef accounting. Solves the "LPs lose money" problem that motivates the whole protocol. | +| 49 | **Market Making** | ✅ Resolved (reframed) | No active maker needed — the AMM + LP pool *is* the maker, and it bears no inventory risk. "Market making" collapses into passive, risk-free liquidity provision. | +| 50 | **Minimum Viable Liquidity** | ✅ Resolved | Enforced floor: min initial liquidity 100 VIZ; Lazy Pool auto-seeds every new market with `free_balance × allocation_%`. MVL is structurally bootstrapped rather than left to chance. | +| 51 | **Order Book** | ➖ Not needed | Onix is AMM-based; no order book by design. | +| 52 | **Orderflow Arbitrage** | ➖ Not needed | No order book / no PFOF-style flow routing → not applicable. | +| 53 | **Relative Value Trading** | ➖ Not needed | Cross-instrument RV requires correlated/combinatorial markets Onix omits. Out of scope. | +| 54 | **Retail Flow** | ✅ Resolved (reframed) | In CLOB models retail flow subsidizes maker losses to toxic flow. In Onix there is no maker to protect — retail and informed bettors all pay into the same parimutuel pool; LP is indifferent. The "retail-vs-toxic" tension dissolves at the LP layer. | +| 55 | **Semantic Tick Size** | ➖ Not needed | Tick granularity is a CLOB concept. Onix prices are continuous AMM functions; precision is the fixed mVIZ unit (1/1000). No tick design needed. | +| 56 | **Temporal Arbitrage** | 🟡 Partial | Betting earlier vs later carries different risk; Onix's **time penalty on profit** is precisely the mechanism that prices in lateness, dampening "wait-for-certainty" temporal arbitrage. Not eliminated, but explicitly disincentivized. | +| 57 | **Time Arbitrage** | 🟡 Partial | Same family as #56 — exploiting information timing. Quadratic time penalty + ~3s block cadence reduce, but don't remove, the edge. Addressed by design intent. | +| 58 | **Toxic Flow** | ✅ Resolved (for LP) | The defining CLOB/LMSR problem (sniper clears the book at 10¢ on a 99¢ outcome, maker eats 80¢) **does not hit the Onix LP** — payouts come from losers' stakes, and the LP subsidy is returned unconditionally. Toxic flow simply means informed bettors win the parimutuel pool, as intended. Major structural win. | +| 59 | **Wash Trading** | 🟡 Partial / 🏛 Client | No fees at bet time removes one wash incentive, but volume-faking is still possible; transfers are pure reassignment (no fee farming there). Detection/surveillance is a client + roadmap surveillance concern. | + +--- + +## 4. Oracle & Resolution + +| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed | +|---|---------|---------|--------------------------------------------------| +| 60 | **Corruption Value Multiple (CVM)** | ✅ Resolved (by design principle) | The protocol's explicit security invariant: oracle **insurance bond must exceed potential manipulation profit**. Risk factor (insurance/bets ratio) feeds the composite trust score. CVM is directly the bonding rationale. | +| 61 | **Dispute Resolution** | ✅ Resolved | Full system: 12h grace, `dispute_fee`, mandatory oracle response, per-market resolver (`dispute_mode==0` committee stake-weighted vote / `==1` named resolver), insurance slashing, 3-outcome no-contest disputes, 14-day auto-close anti-freeze, on-chain creator/oracle bans. Among the most fully specified parts. **HF14 addition:** lazy-pool depositors keep their dispute vote weight (pool NAV → vesting-shares, added to `effective_vesting_shares`). | +| 62 | **Oracle Design** | ✅ Resolved | Bonded oracle model: registration fee, ≥5000 VIZ insurance, explicit acceptance, evidence-backed resolution, 14-metric reputation, freshness decay, ban mechanics. Core subsystem. | +| 63 | **Resolution Criteria** | 🟡 Partial / 🏛 Client | Market question/criteria live in `url`/description (custom_json, display-only). Protocol enforces *process* (who resolves, disputes) but not criterion *quality* — ambiguous criteria are a creator/client responsibility, policed retroactively via disputes + **on-chain creator bans** (`pm_creator_ban_object`, now live). | +| 64 | **Self-Resolving Markets** | ➖ Not needed (today) | Onix resolution is oracle-driven, not algorithmic self-resolution. Automated data oracles (Chainlink-style feeds) are a high-priority roadmap item for objective markets, which would approximate self-resolution. | +| 65 | **UMA Protocol** | ➖ Not needed (replaced) | UMA's optimistic oracle (used by Polymarket) is functionally replaced by Onix's bonded oracle + VIZ committee dispute model. Same problem, native VIZ solution — no external oracle dependency. | + +--- + +## 5. Governance & Decisions + +| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed | +|---|---------|---------|--------------------------------------------------| +| 66 | **Attention Markets** | ➖ Not needed | Trading attention/virality is a distinct product; Onix is event-resolution focused. Could be a client-layer market category, not core. | +| 67 | **Conditional Tokens** | ➖ Not needed (explicit) | Whitepaper §7.2 argues CTF split/merge is **architecturally unnecessary** — price coherence is mathematical, not enforced by tokens. The *one* useful CTF feature (transferable positions) is reimplemented natively as `pm_transfer_position` with encrypted memos. Deliberately omitted. | +| 68 | **Decision Markets** | 🟡 Possible | Onix Multi could express decision markets, but conditional "if-policy-then-metric" structure isn't native (no conditional tokens). Buildable at client layer; not a core primitive. | +| 69 | **Futarchy** | 🟡 Possible (client) | Concept file: futarchy = decision markets on conditional futures. Onix lacks native conditional markets, so full futarchy isn't supported in core. VIZ's stake-weighted committee already governs *parameters*; governance-by-market would be a client/roadmap construction. | +| 70 | **Hyperstition Markets** | ➖ Not needed (deliberate) | Reflexivity-as-a-feature (coordinate, don't forecast). This is a *design choice, not a bug to fix*: Onix's requirement that an outcome be **externally verifiable by a bonded oracle** structurally excludes hyperstition markets by default. Could exist as a separate client-layer "coordination market" product with milestone resolution, but is not a core target. See [Mitigations §](#mitigations-for-the-reflexivity-family). | +| 71 | **Impact Markets** | ➖ Not needed | Retrospective-funding/impact-certificate markets are a separate domain. Possible client-layer category; not core. | +| 72 | **No-Loss Prediction Markets** | 🟡 Adjacent | Onix isn't no-loss for *bettors* (losers forfeit stakes — that funds winners). But it **is** "no-loss" for *LPs* (principal guaranteed). The yield-funded no-loss variant (stake yield, principal returned) is a different model; LP-side no-loss is already delivered. | +| 73 | **Opportunity Markets** | ⚪ Inherent (adjacent) | The Lazy Pool's **opportunity-cost protection** (graduated recall, active-market penalty, fault stamps) addresses capital-opportunity-cost directly — though "opportunity markets" as a product category is out of scope. | + +--- + +## 6. Business & Platforms + +| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed | +|---|---------|---------|--------------------------------------------------| +| 74 | **AI agents** | 🟡 Roadmap | Headless client + open VIZ operations make programmatic agents (bettors, LPs, automated oracles) straightforward. AI-driven liquidity/resolution is a natural extension, not yet specified. | +| 75 | **Cross-subsidization** | ⚪ Inherent | The Lazy Pool cross-subsidizes liquidity across many markets from one deposit; MasterChef `reward_per_share` shares fee yield. Cross-subsidization is built into the pool economics. | +| 76 | **Demand markets** | ➖ Not needed | Markets that gauge/aggregate demand are a product category; not a core Onix primitive. Client-layer. | +| 77 | **Distribution moat** | 🟡 Strategy | Onix's moat is risk-free LP yield + VIZ-native infrastructure (the flywheel). Distribution (Telegram WebApp today → headless web client) is a go-to-market concern, partly addressed by platform-independence post-migration. | +| 78 | **Election markets** | 🏛 Client | Supported as ordinary binary/multi markets; their *legality* is a jurisdictional-client matter (whitelisted oracles, category filters). Protocol-neutral. | +| 79 | **Event contracts** | ⚪ Inherent | Every Onix market *is* an event contract. The regulatory classification of these contracts is a client/legal question, not protocol logic. | +| 80 | **Federal preemption** | 🏛 Client (N/A to protocol) | Concept file: turns on whether US event contracts are "swaps." VIZ DLT is **infrastructure, not an operator** (whitepaper §6.2) — like Bitcoin is a ledger. Legal obligations attach to clients, not consensus. Not a protocol concern. | +| 81 | **Long-tail markets** | ✅ Resolved | The exact niche LMSR's bounded-loss enables — and Onix makes it *risk-free* to seed via Lazy Pool auto-allocation + min-liquidity floor. Long-tail viability is a core selling point. | +| 82 | **Market structure** | ✅ Resolved (defined) | Onix defines a clear structure: AMM pricing, parimutuel settlement, bonded oracles, DPoS-governed parameters, consensus-level ops. A coherent, novel market structure vs CLOB platforms. | +| 83 | **Market surveillance** | 🟡 Roadmap / 🏛 Client | Full on-chain audit trail (`market_log`, every op consensus-validated) makes surveillance *possible* by anyone. Active surveillance/enforcement is a client + roadmap concern. | +| 84 | **Network effects** | 🟡 Strategy | The flywheel (risk-free LP → depth → bettors → fees → more LP) is the intended network effect. Shared liquidity pools (roadmap) strengthen it. Go-to-market, not protocol mechanics. | +| 85 | **Parlays** | ➖ Not needed | Multi-leg combined bets need conditional/combinatorial structure Onix omits. Out of scope (could be a client construction over independent markets). | +| 86 | **Platform competition** | 🟡 Strategy | Competes on the unique "passive yield without impermanent loss" angle vs Polymarket/Kalshi (whitepaper §7.1 comparison table). Strategic positioning, not protocol logic. | +| 87 | **Polymarket** | ⚪ Reference | The primary benchmark. Onix differs on every axis: CPMM/LMSR vs CLOB, zero LP risk vs inventory risk, native ops vs Polygon contracts, bonded oracle vs UMA, no CTF. Used as comparison, not adopted. | +| 88 | **Regulatory arbitrage** | 🏛 Client | Jurisdictional-client model means each region builds its compliant (or permissionless) client on neutral VIZ rails. Regulatory positioning lives entirely at the client layer. | +| 89 | **Regulatory classification** | 🏛 Client | Whether markets are swaps/gaming/securities is decided per-jurisdiction at the client layer; the protocol is classification-neutral (same `pm_*` ops for permissionless and regulated clients). Not a protocol concern. | + +--- + +## Summary — what the Onix-on-VIZ design actually changes + +**Solved structurally (the core wins):** +- LP-side **adverse selection, toxic flow, gap risk, impermanent loss, market-maker inventory risk** → all eliminated because winners are paid only from losers' forfeited stakes and LP principal is returned unconditionally (CPMM AM-GM proof; LMSR parimutuel settlement). +- **LMSR's binary-market failure** → sidestepped by using CPMM for binary and confining LMSR to multi-outcome markets where the maker is not the counterparty. +- **Liquidity provision, minimum viable liquidity, long-tail viability** → risk-free LP + Lazy Pool auto-allocation. +- **Oracle design, dispute resolution, CVM** → bonded oracle + 14-metric reputation + stake-weighted committee disputes. +- **Price discovery, arbitrage, conditional tokens** → price coherence is mathematical (`Σ price = 1`), so no order book, no split/merge, no internal arbitrage layer needed. + +**Not needed / deliberately omitted:** order book, CDA, bid-ask spread, semantic tick size, orderflow arbitrage, CTF split/merge, combinatorial/covariance/correlation/relative-value/parlay markets, UMA, peer prediction, LOX. + +**Pushed to the jurisdictional client layer:** federal preemption, regulatory classification/arbitrage, election-market legality, KYC/insider-trading enforcement, surveillance. + +**Newly implemented since the spec-era mapping (now live on-chain, HF14):** opt-in batch auctions + commit-reveal betting (binary), the opt-in leverage subsystem (position collateralization), the Lazy Pool, `endogeneity_tier`, on-chain creator bans, per-bettor/leverage settlement vops + plugin API, and lazy-pool governance weight in PM disputes + DAO committee-request voting. + +**On the VIZ roadmap (partial today):** shared/category liquidity pools (fixes fragmentation), automated data oracles (→ self-resolving objective markets), distribution markets, AI agents. *(Note: commit-reveal **dispute** voting is **not** on this list — it is deliberately rejected; dispute hearings stay public. Commit-reveal **betting** is already live.)* + +**Still open / behavioral (mitigated, not eliminated):** longshot/yes bias, market manipulation via depth, cross-platform arbitrage. The reflexivity family (endogeneity, reflexivity, KBC, hyperstition) has a concrete mitigation plan — see below. + +--- + +## Mitigations for the reflexivity family + +Endogeneity, reflexivity, the Keynesian beauty contest (KBC), and hyperstition are **one root problem at different layers**: the market/price influences the outcome it measures. One small set of primitives addresses all four. + +### Root-cause map + +| Layer | Concept | Channel | +|-------|---------|---------| +| Forecaster-level | **Keynesian Beauty Contest** | herding to visible consensus in *relative/peer scoring* | +| Market-level | **Endogeneity** | market *existence/visibility* changes behavior (category-specific) | +| Market-level | **Reflexivity** | general price↔reality feedback; manipulation-as-propaganda | +| By-design | **Hyperstition** | reflexivity used *intentionally* to coordinate an outcome | + +### Mitigation primitives + +| Primitive | Status | Fixes | Notes | +|-----------|--------|-------|-------| +| **Commit-reveal dispute voting** | **rejected (will NOT be built)** | KBC | A committee dispute is an **open public hearing**: `pm_dispute_vote` is a public ballot and **stays that way by design** — DAO credibility depends on transparent adjudication. Ballots are **revisable** until close (re-vote overwrites) so voters update honestly on new evidence; KBC residual is accepted (voters aren't paid for matching the majority). | +| **Commit-reveal betting (batched)** | **live (opt-in, binary)** | endogeneity, reflexivity, info-asymmetry | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` hide in-flight order direction/size so the public price doesn't "leak" during the betting window (kills the thermostat channel). Settled as a uniform-price **batch** (see below). | +| **`endogeneity_tier` market field** | **live field** | endogeneity | Oracle tags tier 1 (econ data — clean), 2 (sports/scheduled), 3 (political/social — risky); UI surfaces the reflexive-risk level; clients can restrict tier-3. | +| **Exogenous resolution (automated data oracles)** | roadmap (high) | endogeneity, reflexivity | Resolution bound to an external feed (BLS/Fed/sports API) the market can't influence → clean thermometer. | +| **Prohibited-category list + creator ban** | **creator ban live on-chain**; list client-layer | harmful reflexivity, hyperstition | Block markets where YES creates a "constituency for the outcome" (assassination/"hit"/terror markets, propaganda markets). Enforced via on-chain creator ban (`pm_creator_ban_object`) + client category filter. | +| **Deep risk-free LP liquidity** | core today | manipulation-driven reflexivity | The flywheel makes the book deep, so moving price for a "newsworthy" manipulated headline is expensive. | + +### KBC: why the market layer is already safe + +KBC is a pathology of **relative scoring** (pay for closeness to peers → herd to peers). Onix's market layer pays bettors against **external ground truth via parimutuel settlement** — you are rewarded for *deviating* from a wrong crowd price, not for matching it. So the bettor layer is structurally anti-KBC. The only relative/peer mechanism in the protocol is the **stake-weighted committee dispute vote**, and the residual KBC risk there is **accepted by design** — the dispute is kept an open public hearing (no commit-reveal) because DAO credibility depends on transparent adjudication; what bounds the risk instead is that voters aren't paid for matching the majority and ballots stay revisable as evidence comes in. + +### Commit-reveal vs. the CPMM `a·b=k` invariant + +CPMM is **path-dependent** (tokens depend on reserves at execution time), so commit-reveal *cannot* be done bet-by-bet against the live curve — reveal ordering would re-introduce MEV and leak price. The fix (and why §8.3 pairs commit-reveal with the batch-auction model): **stop updating the curve per-bet; update it once per epoch via a uniform-price batch settlement.** + +1. **Commit:** submit `hash(side, amount, salt, min_tokens)` and escrow `amount`. +2. **Reveal:** reveal `(side, amount, salt)`; reveals are collected but **not applied** until epoch close (seeing others' reveals is useless — yours is already committed). +3. **Settle once:** opposing flow (`A_in` vs `B_in`) nets between bettors at a single clearing price `p*`; only the **net residual** moves the AMM, so `k` is recomputed **once**. All A-fills get `p*`, all B-fills get `1−p*` → no intra-batch ordering advantage. +4. **`min_tokens` floor:** since price is invisible at commit time, a per-bet token floor is mandatory; if `tokens < min_tokens` at settlement, the bet is rejected and refunded from escrow. +5. **Anti-griefing:** non-reveal forfeits a penalty from escrow to the LP-fee/DAO pool, killing the "commit optionality, reveal only winners" attack. + +**The LP guarantee is untouched:** each batch settlement is still a valid CPMM transition, so AM-GM `reserve_a + reserve_b ≥ L` still holds. Only the *granularity* of curve updates changes (per-bet → per-epoch). Onix Multi is analogous via the aggregated LMSR cost function. + +**Phasing:** (1) uniform-price batch auctions first — already kill ordering MEV/front-running cheaply; (2) commit-reveal hiding on top — adds in-flight confidentiality, enabled selectively for tier-3 (endogeneity-sensitive) markets. **Both (1) and (2) are now implemented on-chain for binary markets** (multi still forces instant betting — no LMSR batch yet). + +### Net verdict change + +| Concept | Before | After | +|---------|--------|-------| +| Keynesian Beauty Contest | 🔴 Open | ✅ market / ⚪ dispute — votes public **by design** (no commit-reveal; revisable ballots, unpaid voters, pooled voters enfranchised) | +| Endogeneity | 🔴 Open | 🟡 mitigated — `endogeneity_tier` + commit-reveal/batch **live**; exogenous oracles roadmap | +| Reflexivity | 🔴 Open | 🟡 mitigated — creator ban **live on-chain**; category list client-layer | +| Hyperstition | 🔴 Open | ➖ excluded by design (optional client product) | diff --git a/docs/prediction-markets/early-exit-deferred-claim.md b/docs/prediction-markets/early-exit-deferred-claim.md new file mode 100644 index 0000000000..d7777822b5 --- /dev/null +++ b/docs/prediction-markets/early-exit-deferred-claim.md @@ -0,0 +1,120 @@ +# Early-exit deferred claim (F1 / #300) + +Status: design locked (owner 2026-08-08), implementation in progress on branch `pm`. + +## Problem + +The market is a hybrid: a CPMM (binary) / LMSR (multi) **curve** for entry and early exit, +and **pari-mutuel** settlement for positions held to resolution. Any round-trip through the +curve (buy then sell before settlement) realizes a trading P&L against the curve depth — the +LPs — exactly like Uniswap impermanent loss. But the design promises LPs **principal +protection** (fee-only, no IL). Those two are in tension. + +Two code paths exit against the curve on a binary market: + +- **Leverage** (`liquidate_position` / `pm_leverage_close`): always; force-closed at + settlement; amplified by the pool loan. +- **Regular bet cancel** (`cancel_bet`, F2 curve-priced refund): during the betting window. + +Both route `residual = stake − curve_refund` to `forfeit_pool` (signed). When an early exit is +*profitable* (`curve_refund > stake`), `forfeit_pool` goes **negative**. At settlement +`winners_pool = losers_sum − fees + forfeit_pool`; if leverage/early-exit profit outran the +losing stakes, `winners_pool < 0`, is floored to 0, and the shortfall (`uncovered`, F1) is +charged to LP principal — or minted when LP principal is exhausted. Reachable: proven with a +gate-respecting CPMM simulation (one-sided pump, `uncovered = 7316`); Babin's replay corpus hit +it in 1163/1988 pairs. + +Root cause: **curve-priced exit pays a bonding-curve value that is not bounded by the losing +pool**, while settlement pays pari-mutuel. The gap lands on the LP. + +## Model (locked) + +Early exits no longer extract curve value from LPs. Instead an exit records an +**outcome-contingent deferred claim**, funded at settlement from a **bounded slice of the +losing pool**. + +### Recorded on exit +`{ position_id, kind (bet|leverage), chosen_outcome, claim_amount, exit_time }`. + +### Regular bet cancel +- **Principal returned immediately, unconditionally**: `refund = min(curve_refund, stake)` + (own money, not borrowed). A cancel can cut losses or break even but never realizes + curve-profit at cancel time. +- **Profit tail** `max(curve_refund − stake, 0)` → deferred claim on the chosen outcome. + +### Leverage close / liquidate +- **Collateral is NOT separately returned** — it is first-loss margin for the pool. The pool + recovers its obligation (`loan·(1+R) + funding`) from `cv` first; if `cv < obligation`, the + collateral covers the gap. +- **Residual** `max(cv − obligation, 0)` → deferred claim on the chosen outcome. +- Leverage is therefore a **leveraged directional bet**, not a volatility harvest: you profit + only if your outcome wins and the bucket has room; a wrong outcome loses the collateral. +- NB two distinct predicates now: **solvency** (`cv ≥ obligation`, governs loan recovery) vs + **outcome-win** (governs the right to a claim). A position can be solvent yet on the losing + outcome → pool made whole, claim = 0. + +### Settlement +1. `bucket = pm_early_exit_reward_cap_percent × losers_sum / 10000` (default 33%). +2. Collect deferred claims on the **winning outcome only** (losing-outcome claims → 0). +3. Pay them **FIFO by `exit_time`** (first out, first paid) until the bucket is drained; no + per-position cap (owner 2026-08-08: FIFO ordering + total bucket is the bound). A claim that + the remaining bucket cannot fully fund is paid partially; the rest is unpaid (haircut). +4. **Any unused bucket returns to the winners' pool** — held winning bets share it pari-mutuel. + +### Guarantees +``` +paid_claims ≤ bucket = cap · losers_sum +winners_pool = losers_sum − fees − paid_claims + honest_forfeits + ≥ (1 − cap) · losers_sum − fees ≥ 0 (cap < 100%) +``` +- `uncovered` is **impossible by construction**; no mint; **LP principal never touched**; the + lazy pool bears no leverage IL. +- A **losing outcome never profits** (owner requirement). +- Held winners receive `≥ (1 − cap)` of the losing pool plus any unused bucket. +- Early exit is a **bounded, contingent discount** (≤ cap, FIFO) vs holding to resolution + (full pari-mutuel share) → no arbitrage against holding; a deliberate liquidity discount. + +## Chain parameter + +`pm_early_exit_reward_cap_percent` (uint16, bp, default **3300** = 33% of `losers_sum`). +Median-voted validator param; `validate()` bounds `≤ 10000`. Added to +`chain_properties_pm` + FC_REFLECT + `calc_median` (DONE, single-TU verified). + +## Implementation touchpoints (node) + +- [x] chain param `pm_early_exit_reward_cap_percent` (struct/validate/reflect/median). +- [ ] object `pm_deferred_claim_object` (+ index by market, by exit_time) — space 30, append at + end of `object_type` enum (snapshot-safe, like `pm_lazy_withdraw_request`). +- [ ] `cancel_bet`: return `min(curve_refund, stake)`, record profit-tail claim; stop routing a + negative residual to `forfeit_pool`. +- [ ] `liquidate_position` / `pm_leverage_close`: pool takes obligation, record `cv − obligation` + claim, tag chosen outcome; drop immediate `bettor_received`; stop negative `forfeit_pool`. +- [ ] settlement (`settle_market`): after force-close, compute `bucket`, pay winning-outcome + claims FIFO by exit_time, remainder → winners' pool; remove the `uncovered`/F1 + `settle_liquidity` charge path (LP no longer absorbs it). +- [ ] snapshot: include `pm_deferred_claim_object` in allowlist (+ import handler). +- [x] virtual op `pm_early_exit_claim_paid` (account, market, kind, outcome, `claimed`, `paid`) — + appended at the end of the `operation` variant (op-ids stay stable), FC_REFLECT'd, emitted in + the settlement distribution loop next to the `adjust_balance`, and routed to the early-exiter's + account history (`account_history` impacted-accounts visitor). `claimed` vs `paid` exposes any + bucket-exhaustion haircut. `adjust_balance` alone leaves no history trace — this closes that gap. +- [x] read API: `get_deferred_claims(market, [from=0], [limit=100])` — FIFO exit order via + `by_claim_market`; empty on a settled market (claims consumed). Plugin-only (clients call via + rawApi/JSON-RPC), matching `get_lazy_withdraw_requests`; no wallet wiring. + +## Client / lib / docs follow-ups +- viz-js-lib / viz-php-lib / viz-python-lib: new chain param in v5 chain_properties_pm + (serialization lock-step, byte-verify), any new read method / vop. +- Forecaster: notices + operation descriptions (leverage = directional, early exit = bounded + discount), show pending deferred claim on positions. +- WebVIZWallet: same operation-description updates if surfaced. +- Scientific article: `early-exit choice` with the math (regular vs leverage-from-lazy-pool, + validator-set reward cap). + +## Rejected alternatives (why) +- Smear `uncovered` across all LP / mint (status quo) — breaks LP principal promise. +- Localize only to lazy pool — pool can be exhausted; still an approximation; cancels leak. +- Cap the win at exit — you don't know `losers_sum` at exit time; deferral removes that. +- Full-AMM settlement — abandons the pari-mutuel thesis of VIZ. +The deferred outcome-contingent claim is the only option giving a **hard** LP guarantee while +staying pari-mutuel. diff --git a/docs/prediction-markets/index.md b/docs/prediction-markets/index.md new file mode 100644 index 0000000000..76b5ebe344 --- /dev/null +++ b/docs/prediction-markets/index.md @@ -0,0 +1,53 @@ +--- +title: Prediction Markets (Onix) — overview & map +description: The VIZ on-chain prediction-market stack — Onix protocol and the Forecaster client — with a full documentation tree from whitepaper to spec, objects, operations, workflows, and concept analysis. +--- + +# Prediction Markets on VIZ + +VIZ Ledger runs prediction markets as **first-class consensus operations** (`pm_*`), live since HF14. +Two names you will see throughout: + +- **Onix** — the **protocol**: the on-chain market engine (CPMM binary + LMSR multi, parimutuel + zero-sum settlement, bonded oracles, lazy pool, opt-in leverage, batch / commit-reveal betting). +- **Forecaster** — the **thin client** to that protocol on VIZ Ledger. It is a headless, platform- + independent front-end that lets **people anywhere in the world participate in the on-chain + prediction market** — create markets, bet, provide liquidity, oracle, and dispute — by signing + `pm_*` operations directly against public VIZ nodes. The protocol is neutral; Forecaster (and any + jurisdictional client built like it) is the access layer. + +## Documentation map + +```mermaid +flowchart TD + ROOT["Prediction Markets (Onix)"] + ROOT --> OV["Overview — the one-page pitch (this stack at a glance)"] + ROOT --> WP["Whitepaper — the thesis: why LP-risk-free, the two market types, the flywheel"] + ROOT --> SP["Specification — formal mechanics + §17 On-Chain Object Model"] + ROOT --> OPS["Operations — the signed pm_* consensus operations"] + ROOT --> VOPS["Virtual Operations — deterministic vops emitted at settlement / by deadline"] + ROOT --> API["Plugin API — prediction_market_api read methods"] + ROOT --> WF["Workflows & diagrams — one canonical market traced through every role"] + ROOT --> CA["Concept analysis — 90 PM-theory concepts vs the live VIZ implementation"] +``` + +## Start here + +| Page | What it is | +|------|-----------| +| [Overview](./onix) | One-page positioning: AMM-priced parimutuel with structurally risk-free liquidity. | +| [Whitepaper](./whitepaper) | The industry thesis — LP guarantee, Onix Binary (CPMM) + Onix Multi (LMSR), oracles, lazy pool, leverage, governance. | +| [Specification](./specification) | Formal spec: parameters, state machine, pricing, settlement, disputes, lazy pool, leverage, and the **[On-Chain Object Model](./specification#17-on-chain-object-model)** (every `pm_*_object` and its lookup index). | +| [Operations](../protocol/operations/prediction-markets) | The 21 signed consensus operations (`pm_create_market`, `pm_place_bet`, …). | +| [Virtual Operations](../protocol/virtual-operations) | Deterministic virtual ops (`pm_payout`, `pm_market_accepted`, `pm_leverage_resolve`, `pm_batch_settle`, …). | +| [Plugin API](../plugins/prediction-market-api) | `prediction_market_api` — read-only access to markets, bets, oracles, disputes, the lazy pool, and the median-voted parameters. | +| [Workflows & interaction diagrams](./workflows) | One canonical binary market traced through every participant, with the zero-sum master ledger for normal and disputed resolution. | +| [Concept analysis (Onix vs 90 concepts)](./concepts-analysis) | How the on-chain implementation maps onto the prediction-market theory atlas — what's solved, inherent, not needed, or roadmap. | + +## Governance + +All economic parameters are delegate **median-voted** and live in the `chain_properties_pm` struct — +see [Chain Properties → Prediction-market parameters](../governance/chain-properties#pm-parameters). +There is no hard fork to tune fees, penalties, lazy-pool, leverage, or batch/commit-reveal timing; +three live kill-switches (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) +let the validator median disable a whole subsystem without a fork. diff --git a/docs/prediction-markets/onix.md b/docs/prediction-markets/onix.md new file mode 100644 index 0000000000..93460b8af4 --- /dev/null +++ b/docs/prediction-markets/onix.md @@ -0,0 +1,93 @@ +--- +title: Onix — AMM-Priced Parimutuel Prediction Markets +description: Onix puts continuous AMM price discovery on top of parimutuel settlement, so prices move like an AMM while liquidity carries the risk profile of a tote — the market maker can never be bankrupted. +--- + +# Onix — AMM-Priced Parimutuel Prediction Markets + +> Continuous **AMM price discovery** on top of **parimutuel settlement** — the price moves like an +> AMM, while liquidity carries the risk profile of a tote: **the market maker can never be +> bankrupted.** + +::: info Onix & Forecaster +**Onix** is the on-chain protocol. **Forecaster** is the thin client to it on VIZ Ledger — the headless, +platform-independent access layer that lets people anywhere in the world participate in the on-chain +prediction market by signing `pm_*` operations directly against public VIZ nodes. See the +[section overview & map](./) for the full documentation tree. +::: + +## The one idea + +Onix **decouples price from payout**: + +- **Price (discovery)** — a CPMM curve (binary) or LMSR-softmax (multi) updates a live probability on + every bet and assigns each bet a **weight** (its claim ticket). +- **Payout (settlement)** — winners are paid **only** from the losers' forfeited stakes, split by + weight: pure **parimutuel**, strictly zero-sum (the protocol never mints a token). + +Everything distinctive about Onix follows from this split. + +## Why it matters — three things + +::: tip 1 · Liquidity that cannot be drained +Because winners are paid from losers and never from LP principal, the liquidity provider **cannot be +bankrupted** — no impermanent loss, no inventory risk, no death-by-sniper. Guaranteed *by construction* +(AM–GM for CPMM, conservation for LMSR), not by insurance. +::: + +::: tip 2 · Passive yield without IL — the Lazy Pool +One deposit auto-spreads as silent liquidity across many markets and funds opt-in leverage, with +MasterChef-style reward accounting. Earn prediction-market liquidity yield **without** picking markets +or bearing impermanent loss. +::: + +::: tip 3 · Native to the chain, zero-sum +Markets are first-class consensus operations (`pm_*`), not smart contracts: censorship-resistant, +composable, ~3-second blocks, no oracle bridge. The protocol never emits tokens — it only redistributes. +::: + +## How a bet works + +1. **You bet** `X` on an outcome. `X` enters the curve; the curve returns your **weight** — more weight + if you bet earlier, before the price moves. +2. **The board updates.** The live coefficient for a side is + `1 + opposing_pool × (1 − commission) / own_pool`, with the commission (oracle + creator + LP) already + baked in. +3. **At resolution**, the losers' stakes (minus commission) are split among the winners by weight. Your + payout = your stake back **+** your share of the losing pool. LP principal is returned untouched. + +## How it compares + +| | CLOB / AMM (Polymarket, Kalshi) | Plain parimutuel (tote) | **Onix** | +|---|---|---|---| +| Live price | yes | no (pool ratio only) | **yes (CPMM / LMSR)** | +| Odds locked at bet time | yes | no | no (honest parimutuel) | +| LP / maker can be bankrupted | **yes** (IL, snipers, gap risk) | n/a | **no (structural)** | +| Yield-bearing liquidity layer | fragile | none | **Lazy Pool, no IL** | +| Lives in | contracts / backend | backend | **consensus (`pm_*`)** | +| Token emission | sometimes | no | **no (zero-sum)** | + +## The honest tradeoff + +::: warning Odds are parimutuel — they drift until close +Onix does **not** lock your coefficient at bet time. The board moves as money flows, and the final +coefficient is known only at close — exactly like a tote. This is not a flaw to patch: the *only* way to +lock odds is to have a counterparty bear the risk (a bookmaker, or an AMM LP that can lose). Onix's drift +is the direct price of its LP guarantee — risk lives **between bettors**, so no one's liquidity can burn. +::: + +## What's novel + +- **AMM weighting + parimutuel settlement** in one integrated engine — continuous price discovery + *without* maker inventory risk. +- **Structural, provable LP safety** instead of insured or subsidized liquidity. +- **A mutualized, yield-bearing liquidity layer** (the Lazy Pool) that also funds opt-in leverage — + liquidations run against pre-bet reserves so the pool is always made whole. +- **Opt-in anti-MEV** (batch / commit-reveal betting) and **transparent governance** (bonded oracles, + public-hearing disputes with revisable votes) — all layered on the safe base without ever touching the + LP guarantee. + +## Learn more + +- Protocol operations — [Prediction Markets](../protocol/operations/prediction-markets) +- Plugin API — [Prediction Market API](../plugins/prediction-market-api) diff --git a/.qoder/plans/pm-plan/onix-protocol-specification.md b/docs/prediction-markets/specification.md similarity index 62% rename from .qoder/plans/pm-plan/onix-protocol-specification.md rename to docs/prediction-markets/specification.md index f1d1d720f4..1faad1abf2 100644 --- a/.qoder/plans/pm-plan/onix-protocol-specification.md +++ b/docs/prediction-markets/specification.md @@ -65,16 +65,23 @@ Time penalty values use precision = 1/1,000,000 (micro-units). ### Median-voted parameters (`chain_properties_pm`) All economic parameters are delegate median-voted (no hard fork to tune) and live in the on-chain -`chain_properties_pm` struct. **All percentages are basis points (bp, 10000 = 100.00%); durations are -seconds or blocks.** Exact defaults and ranges are in [Chain Properties](../../../docs/governance/chain-properties.md#pm-parameters); -the authoritative source is the struct itself. +`chain_properties_pm` struct. Each delegate publishes its preferred values through the standard +**`versioned_chain_properties_update_operation`** (op ID 46) — `chain_properties_pm` is the current +(v5, HF14) version of that versioned struct — and the network applies the **per-field median** of the +active delegates. The two risk-coverage knobs (`pm_listing_min_coverage_percent`, +`pm_betting_min_coverage_percent`) are part of this same v5 struct and are tuned exactly the same way. +**All percentages are basis points (bp, 10000 = 100.00%); durations are seconds or blocks** — except the +two coverage knobs, which are percent-of-volume (100 = 1.0×). Exact defaults and ranges are in +[Chain Properties](../governance/chain-properties#pm-parameters); the authoritative source is the struct itself. | Group | Parameters | |---|---| | Registration & floors | `pm_oracle_registration_fee`, `pm_min_oracle_insurance`, `pm_market_creation_fee`, `pm_min_liquidity`, `pm_max_outcomes`, `pm_max_market_duration` | | Fees & penalties (bp) | `pm_max_oracle_fee_percent`, `pm_oracle_penalty_percent`, `pm_no_contest_penalty_percent`, `pm_default_time_penalty_percent`, `pm_max_time_penalty` | +| Acceptance window | `pm_oracle_accept_window_sec` (default 3600 = 1h; pending markets not accepted/rejected within this are voided by the cron — seed refunded, creation fee kept) | +| Risk / coverage (% of volume) | `pm_listing_min_coverage_percent` (250 = 2.5×; markets covered below this are hidden from the default catalog, shown via `show_risky`), `pm_betting_min_coverage_percent` (150 = 1.5×; advisory client risk-confirm threshold, `≤` the listing one, not enforced on-chain) | | Disputes | `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_oracle_dispute_response_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent` (bp), `pm_dispute_reward_multiplier` (bp) | -| Lazy pool | `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent` | +| Lazy pool | `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (default 200 = 2%; pool skips markets whose `liquidity_fee_percent` is below this reward floor) | | Leverage | `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent` | | Batch / commit-reveal | `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_commit_no_reveal_penalty_percent` (bp), `pm_min_batch_bet` | | Processing | `pm_processing_cap_per_block` | @@ -85,7 +92,7 @@ fund — not a PM-specific account. ### Per-market parameters (`pm_create_market` operation) Set by the creator at creation; the oracle fee fields are a **ceiling** the oracle quotes against at -acceptance (offer→quote). Full field reference: the Prediction Market operations doc. +acceptance (offer→quote). Full field reference: [Prediction Market operations](../protocol/operations/prediction-markets). | Field | Description | |---|---| @@ -110,7 +117,7 @@ acceptance (offer→quote). Full field reference: the Prediction Market operatio | Status | Name | Description | |--------|------|-------------| -| -1 | Deleted | Oracle rejected; liquidity returned to creator | +| -1 | Deleted | Oracle rejected, **or** the acceptance window (`pm_oracle_accept_window_sec`) expired; seed liquidity returned to creator (creation fee kept) | | 0 | Waiting | Awaiting oracle review | | 1 | Active | Accepting bets until `betting_expiration` | | 2 | Closed | Betting ended, awaiting oracle resolution | @@ -127,16 +134,25 @@ acceptance (offer→quote). Full field reference: the Prediction Market operatio ### Transitions -``` -Waiting (0) ──[oracle accepts]──► Active (1) ──[betting_expiration]──► Closed (2) - │ │ │ - │ oracle rejects │ early resolution │ oracle resolves - ▼ │ (if allowed) ▼ -Deleted (-1) └──────────────────────────► Resolved (3) - │ - grace period (12h) - ▼ - Paid out +```mermaid +stateDiagram-v2 + direction LR + state "Waiting (0)" as Waiting + state "Active (1)" as Active + state "Closed (2)" as Closed + state "Resolved (3)" as Resolved + state "Deleted (-1)" as Deleted + state "Paid out" as Paid + [*] --> Waiting + Waiting --> Active: oracle accepts + Waiting --> Deleted: oracle rejects + Waiting --> Deleted: accept window expires (pm_market_expired) + Active --> Closed: betting_expiration + Active --> Resolved: early resolution (if allowed) + Closed --> Resolved: oracle resolves + Resolved --> Paid: grace period (12h), no dispute + Deleted --> [*] + Paid --> [*] ``` **Preconditions:** @@ -145,8 +161,9 @@ Deleted (-1) └──────────── |-----------|---------------| | 0 → 1 | Oracle has insurance ≥ `min_oracle_insurance`; oracle accepts | | 0 → 1 (self-oracle) | Creator = oracle; insurance check; auto-approves at creation | -| 0 → -1 | Oracle rejects; liquidity returned to creator | -| 1 → 3 | Oracle submits resolution with outcome (0, 1, or -1 for no-contest); `allow_early_resolution=1` or `time ≥ betting_expiration` | +| 0 → -1 | Oracle rejects; seed liquidity returned to creator | +| 0 → -1 (expiry) | `now ≥ created_time + pm_oracle_accept_window_sec` with no oracle action; cron voids the market, refunds the seed (creation fee kept), emits `pm_market_expired` | +| 1 → 3 | Oracle submits resolution with outcome (0, 1, or -1 for no-contest); `allow_early_resolution=1` or `time ≥ betting_expiration`. Resolution sets `result_expiration = now` **unconditionally** (early *or* late, mirrors no-contest), so the dispute-grace / settle / LP-lock schedule is always anchored to the announcement — disputers get the full `pm_dispute_grace_sec` window whether the report was early or late (a late report can't grief them by burning the window) | | 2 → 3 | Oracle submits resolution; `time ≤ result_expiration` | | 3 → paid | Grace period passed with no dispute; cron processes payouts | @@ -158,16 +175,20 @@ Deleted (-1) └──────────── 4. Initialize reserves: `reserve_a = floor(liquidity/2)`, `reserve_b = liquidity − reserve_a` 5. Compute `k = reserve_a × reserve_b` 6. If self-oracle: auto-approve to status=1 with insurance check -7. If external oracle: enter status=0 +7. If external oracle: enter status=0 and set `accept_deadline = created_time + pm_oracle_accept_window_sec` ### Oracle Acceptance Flow -When oracle accepts (status 0 → 1): +A pending market must be resolved by its oracle within the acceptance window +(`pm_oracle_accept_window_sec`, default 1h). Three outcomes: -1. Transfer `oracle_fixed_fee` from creator balance to oracle balance (skipped if self-oracle) -2. Increment oracle `markets_accepted` counter -3. Update oracle `last_active_time` -4. Lazy Pool auto-allocation triggered (if pool has free balance) +- **Accept** (status 0 → 1): (1) transfer `oracle_fixed_fee` from creator to oracle (skipped if + self-oracle); (2) increment oracle `markets_accepted`; (3) update `last_active_time`; (4) trigger + Lazy Pool auto-allocation (if the pool has free balance **and** the market's `liquidity_fee_percent + ≥ pm_lazy_min_liquidity_fee_percent`). +- **Reject** (status 0 → -1): seed liquidity refunded to creator; no vop. +- **Expiry** (status 0 → -1): if neither happens by `accept_deadline`, the per-block cron voids the + market, refunds the seed liquidity (**not** the creation fee), and emits `pm_market_expired`. ### Audit Trail @@ -396,6 +417,21 @@ net_payout = bet_amount + profit − penalty_deduction **Invariant:** `net_payout ≥ bet_amount` — the penalty applies only to the profit share, so winners always receive at least their principal. (Identical for Onix Binary and Onix Multi.) +### Risk-Time Source + +`time_penalty` is computed once, when the bet is created, from the moment the market exposure was +actually taken — not from later mechanics: + +| Path | `risk_time` used | +|------|------------------| +| Instant / direct batch bet (`pm_place_bet`) | placement time | +| Commit–reveal (`pm_reveal_bet`) | the **commit** time (blind), so revealing late within the window is not penalised | +| Converted leverage position (`pm_leverage_convert`) | the leverage **open** time (opens are gated ≥24 h before expiry) | +| Transferred position (`pm_transfer_position`) | **inherited** from the source bet | + +A market with `time_penalty_value = 0` (the default) has no window — the penalty is always 0 and the +mechanism is dormant until a creator opts in. Open-ended markets (no `betting_expiration`) never apply it. + --- ## 9. Liquidity Provision @@ -403,11 +439,14 @@ net_payout = bet_amount + profit − penalty_deduction ### Adding Liquidity ``` -add_a = amount × reserve_a / (reserve_a + reserve_b) -add_b = amount − add_a -new_reserve_a = reserve_a + add_a -new_reserve_b = reserve_b + add_b -new_k = new_reserve_a × new_reserve_b +// Price-neutral: scale both reserves by (L + amount) / L, where L = liquidity_sum +// BEFORE the deposit. The reserve ratio (the odds) is unchanged — only depth grows, +// linearly with capital (k scales by ((L+amount)/L)²). A first deposit into an empty +// curve (L = 0) seeds a balanced 50/50 split, as at market genesis. +factor = (liquidity_sum + amount) / liquidity_sum +new_reserve_a = floor(reserve_a × factor) +new_reserve_b = floor(reserve_b × factor) +new_k = new_reserve_a × new_reserve_b ``` Records `sec_to_expiration = betting_expiration − current_time` at deposit time. @@ -427,18 +466,17 @@ Each deposit is an independent position. Multiple deposits by the same user are ### Early Withdrawal -**Preconditions:** market status=1, `time < betting_expiration`, `resulting liquidity_sum ≥ 100,000 mVIZ`. +**Preconditions:** market live (not resolved) and betting still open (`time < betting_expiration`, or an open-ended market with no deadline); `resulting liquidity_sum ≥ pm_min_liquidity`. Once betting closes — or, for an open-ended market, once the oracle resolves — the position is locked until **settlement** (not merely resolution): the principal backs the pending F1 shortfall charge until `finalized_time` is stamped by the auto-payout / void / expiry sweep (see below). ``` -// Fractional withdrawal -fraction = withdraw_amount / lp_amount -withdraw_weight_a = floor(weight_a × fraction) -withdraw_weight_b = floor(weight_b × fraction) - -// Reverse reserves -new_reserve_a = reserve_a − withdraw_weight_a -new_reserve_b = reserve_b − withdraw_weight_b -new_k = new_reserve_a × new_reserve_b +// Price-neutral: shrink both reserves by (L − withdraw_amount) / L, where L = +// liquidity_sum BEFORE the withdrawal. Mirror of the proportional add — the reserve +// ratio (the odds) is unchanged, depth falls with capital, and an add→withdraw +// round-trip of the same amount leaves the curve untouched. +factor = (liquidity_sum − withdraw_amount) / liquidity_sum +new_reserve_a = floor(reserve_a × factor) +new_reserve_b = floor(reserve_b × factor) +new_k = new_reserve_a × new_reserve_b // Time-ratio discount time_served = current_time − lp_deposit_time @@ -547,6 +585,8 @@ Optional `min_return` parameter. If `amount_returned < min_return`, transaction Mandatory within `pm_oracle_dispute_response_sec`. If missed, `pm_dispute_fee` is auto-slashed from insurance and recorded on the oracle object. +The oracle posts its rebuttal with **`pm_dispute_oracle_respond`** (op ID 98). Because a dispute is an open public hearing, the text is stored **on the dispute object** (`oracle_response` + `oracle_response_time`, readable via `get_dispute`) so every committee voter or account resolver can weigh it before deciding. Only the market's oracle may respond, only while the dispute is open and `now ≤ oracle_response_deadline`; re-posting overwrites the previous response. + ### Dispute Lifecycle ``` @@ -556,39 +596,48 @@ Resolution (T=0) → Grace period (T to T+12h) → Dispute filed (T≤12h) → Auto-close fallback (T+14 days): full refund + oracle penalty ``` -### Dispute Upheld (Oracle Wrong) +### Dispute Upheld (Oracle Wrong — overturned) + +The disputer's reward is a carve-out of the slash; the **rest of the slash funds the winning bettors** +(via `forfeit_pool`), not a resolver and not the DAO. **Neither the committee voters nor the account-mode +resolver are paid** — voting/resolving is an unpaid duty. ``` -reward_pool = min(dispute_fee × multiplier, oracle_insurance) +reward_target = floor(dispute_fee × pm_dispute_reward_multiplier / 10000) // bp; 30000 = ×3 +bonus = max(0, reward_target − dispute_fee), capped at the slash -1. Disputer: dispute_fee returned (from escrow) -2. From oracle insurance (reward_pool): - disputer_reward = floor(reward_pool / multiplier) → disputer - voter_reward = reward_pool − disputer_reward → resolver -3. Remaining insurance: extra penalty (resolver discretion) → DAO fund +1. Disputer ← dispute_fee (escrow returned) + bonus // bonus drawn from the slashed insurance +2. forfeit_pool += (slash − bonus) // → winners, through winners_pool at settlement ``` -### Dispute Rejected (Oracle Right) +Slash size: committee mode scales the oracle's `dispute_penalty_percent` by `consensus_strength`; account +mode uses the resolver's `penalty_amount` (both capped at the remaining insurance). + +### Dispute Rejected (Oracle Right — upheld) ``` -Disputer loses dispute_fee: - 50% → resolver (dispute_rejected_voter_percent / 10000) - 50% → oracle (dispute_rejected_oracle_percent / 10000) +Disputer forfeits the whole dispute_fee → oracle (100%, compensation). // no resolver/DAO split ``` ### Recalculation Process (Oracle Wrong) -1. Validate penalty (capped at remaining insurance after reward_pool) -2. Disputer reward paid -3. Resolver reward paid -4. Oracle insurance slashed +1. Validate penalty (capped at remaining insurance) +2. Disputer reward (fee + bonus) paid; slash remainder → `forfeit_pool` (winners) +3. Oracle insurance slashed 5. Bans applied (if requested) 6. Delete all existing non-paid payouts 7. Flip winning outcome (A↔B) 8. Regenerate payouts from scratch with corrected outcome 9. Audit trail recorded -### Committee Powers +### Resolver Powers — bans are a compliance/regulator feature (account mode only) + +The sanctions below are fields of the **account-mode** `pm_dispute_resolve` (op ID 80), issued by the +market's named `dispute_resolver`. When that resolver is a **regulator or a licensed arbitrator**, this is +how it enforces off-chain rules: in a single verdict it can slash insurance **and bar both the oracle and +the market creator** from the platform, temporarily or permanently. **Committee/DAO mode +(`dispute_mode = 0`) has no ban power by design** — a public hearing only slashes insurance (scaled by +consensus strength) and adjusts reputation via `pm_dispute_finalize`; it never bans. | Parameter | Type | Description | |-----------|------|-------------| @@ -598,6 +647,11 @@ Disputer loses dispute_fee: | `ban_creator` | 0/1 | Ban creator | | `ban_creator_until` | unix ts / 0 | 0=permanent, >0=expires | +A ban records the issuing `resolver` in the target's `banned_by`. **Lifting a ban:** the same resolver +may lift it **early** with **`pm_unban`** (op ID 99, `unban_oracle` / `unban_creator`); otherwise it simply +lapses at `banned_until`, at which point the per-block cron clears it and emits the **`pm_ban_expired`** +virtual op (ID 100) so history/indexers observe the lift. + ### Auto-Close (14-day fallback) | Action | Description | @@ -633,7 +687,10 @@ If oracle wrong: pending refund payouts deleted, replaced with correct winner pa ## 13. Oracle Penalty for Missed Resolution -If oracle fails to resolve by `result_expiration`: +If the oracle still has not reported by `result_expiration + pm_dispute_grace_sec` (a resolution +grace: the missed-resolution cron voids only past this cutoff, so a fixed-deadline market stays +resolvable *at* its deadline instead of being voided one block before any resolve transaction's +clock can reach it): ``` penalty_amount = floor(oracle_insurance × oracle_penalty_percent / 100) @@ -657,95 +714,131 @@ Market finalized: status=3, payout_status=2. ## 14. Oracle Reputation Scoring -### Raw Metrics (14 counters per oracle) +### Raw Metrics (stored on `pm_oracle_object`) + +Reputation counters and telemetry (exact field names as reflected on the object): -| Metric | Type | Source | +| Field | Type | Source | |--------|------|--------| | `markets_accepted` | counter | oracle-accept-market | | `markets_resolved` | counter | resolve-market | -| `markets_no_contest` | counter | oracle-no-contest | -| `markets_missed` | counter | cron (missed deadline) | +| `no_contest_count` | counter | oracle-no-contest | +| `missed_count` | counter | cron (missed deadline) | | `disputes_received` | counter | create-dispute | | `disputes_lost` | counter | resolve-dispute (status=1) | | `disputes_won` | counter | resolve-dispute (status=2) | | `disputes_auto_closed` | counter | cron (14-day auto-close) | -| `dispute_responses_missed` | counter | cron (12h response deadline) | -| `total_volume_resolved` | mVIZ | resolve-market (sum of bets_sum) | +| `dispute_responses_missed` | counter | cron (12 h response deadline) | +| `total_volume_resolved` | mVIZ | resolve-market (Σ `bets_sum`) | | `total_insurance_slashed` | mVIZ | all penalty events | -| `avg_resolution_time` | seconds | resolve-market | +| `avg_resolution_time` | seconds | resolve-market (running mean of betting-close→resolve latency) | +| `resolved_late_count` | counter | resolve-market (`now > result_expiration`) — feeds `timely` | +| `resolution_time_hist` | 8×counter | resolve-market (latency histogram, guaranteed zero-init `fc::array`) | +| `penalty_stamps` | counter | slash events (decays in the score) | +| `last_penalty_stamp_time` | timestamp | most recent stamp (10-day decay anchor) | | `bans_received` | counter | resolve-dispute | +| `active_markets` | gauge | O(1) count of `status 1` markets | +| `markets_in_dispute_window` | gauge | resolved+disputable markets (O(1)) | +| `disputes_awaiting_response` | gauge | open disputes not yet answered (O(1)) | +| `disputes_awaiting_decision` | gauge | open disputes answered, pending decision (O(1)) | | `active_since` | timestamp | register-oracle | | `last_active_time` | timestamp | accept/resolve/no-contest | +The four gauges are seeded once on the upgrade block, then maintained incrementally at every transition; +they are display-only and never gate consensus. See §14 → *Workload Gauges & Latency Telemetry*. + ### Derived Rates -Denominator: `total_outcomes = markets_resolved + markets_no_contest + markets_missed` +Denominator: `total_outcomes = markets_resolved + no_contest_count + missed_count` | Rate | Formula | |------|---------| | `resolution_rate` | `markets_resolved / total_outcomes` | | `dispute_loss_rate` | `disputes_lost / disputes_received` | -| `no_contest_rate` | `markets_no_contest / total_outcomes` | -| `deadline_miss_rate` | `markets_missed / total_outcomes` | +| `no_contest_rate` | `no_contest_count / total_outcomes` | +| `deadline_miss_rate` | `missed_count / total_outcomes` | | `dispute_response_rate` | `1 − (dispute_responses_missed / disputes_received)` | - -### Reliability Score (0–100) - -``` -reliability_score = clamp(0, 100, - BASE_SCORE - − W_DISPUTE_LOSS × dispute_loss_rate × 100 - − W_NO_CONTEST × excess_no_contest × 100 - − W_DEADLINE_MISS × deadline_miss_rate × 100 - − W_NO_RESPONSE × (1 − dispute_response_rate) × 100 - + W_VOLUME_BONUS × volume_tier - + W_EXPERIENCE × experience_tier × freshness_multiplier - − W_BAN_PENALTY × bans_received -) -``` - -Where `excess_no_contest = max(0, no_contest_rate − 0.10)`. - -### Weight Defaults - -| Weight | Value | -|--------|-------| -| BASE_SCORE | 50 | -| W_DISPUTE_LOSS | 0.40 | -| W_NO_CONTEST | 0.10 | -| W_DEADLINE_MISS | 0.20 | -| W_NO_RESPONSE | 0.15 | -| W_VOLUME_BONUS | 0–25 (tiered: ≥10K→+5, ≥100K→+10, ≥500K→+15, ≥1M→+20, ≥5M→+25) | -| W_EXPERIENCE | 0–25 (tiered: ≥7d→+5, ≥30d→+10, ≥90d→+15, ≥180d→+20, ≥365d→+25) | -| W_BAN_PENALTY | 15 per ban | - -### Freshness Decay - -| Days since last active | Multiplier | -|----------------------|-----------| -| ≤ 30 | 1.00 | -| 31–90 | 0.75 | -| 91–180 | 0.50 | -| > 180 | 0.25 | - -### Composite Trust Score - -``` -trust_score = reliability_score × risk_factor -``` - -| Risk score (insurance/bets) | risk_factor | -|---------------------------|-------------| -| ≥ 3.0× | 1.00 | -| ≥ 2.0× | 0.95 | -| ≥ 1.0× | 0.85 | -| < 1.0× | 0.70 | +| `on_time_rate` | `(markets_resolved − resolved_late_count) / markets_resolved` | + +### Reliability Score (v2.1 — basis points, 0–10000) + +The shipped score is a **non-consensus API heuristic** (display only — it never gates consensus, so +its weights are freely tunable). It is computed on read in `reliability_score()` (the +`prediction_market_api` plugin), **not stored**, and returned on the `pm_oracle` DTO from `get_oracle`. +It blends four reputation ratios, docks time-decayed penalty stamps and bans, then confidence-shrinks a +thin track record toward a neutral prior: + +``` +accuracy = markets_resolved / (markets_resolved + missed_count) // resolved vs missed-deadline +verdicts = disputes_won / (disputes_won + disputes_lost) // dispute outcomes upheld +responsive = (disputes_received − dispute_responses_missed) / disputes_received +timely = (markets_resolved − resolved_late_count) / markets_resolved // on-time vs past-deadline resolves + +score = accuracy·40% + verdicts·30% + responsive·15% + timely·15% // each ratio in bp [0..10000] +score −= penalty_stamps × 300 bp // halved per 10 days since last_penalty_stamp_time (decay, ≤16 halvings) +score −= bans_received × 1500 bp +if markets_resolved < 20: // unproven oracle: neither a hard 100 nor a crater on one dispute + score = (score × n + 6000 × (20 − n)) / 20 // n = markets_resolved; shrink toward 6000 prior +score = clamp(0, 10000, score) +``` + +Each ratio defaults to a full `10000` until the oracle has the relevant history (optimistic when +unproven). **`timely`** is the P5 lateness signal (variant A): a late-but-delivered resolve still +increments `markets_resolved` — so it earns full `accuracy` credit — and `timely` is what separates a +chronically-late oracle from a punctual one (max −1500 bp for an always-late oracle at 15% weight). +`avg_resolution_time` is deliberately **not** scored: it measures latency from betting close, not +deadline overrun, and cannot be normalized without the market length. Weight defaults: + +| Component | Weight | +|-----------|--------| +| `accuracy` | 40% | +| `verdicts` | 30% | +| `responsive` | 15% | +| `timely` | 15% | +| penalty stamp | −300 bp each (10-day half-life) | +| ban | −1500 bp each | +| confidence prior | 6000 bp until `markets_resolved` ≥ 20 | + +Clients render the score as `reliability_score / 100` (percent). v1 (used through early testnet) blended +only the success and dispute-win ratios minus a ban dock; v2 added responsiveness, decayed penalty +stamps and confidence shrink; v2.1 added the `timely` factor and rebalanced the weights. + +### Workload Gauges & Latency Telemetry + +Per-oracle operational metrics exposed via `get_oracle` (and drill-down list methods) so an Oracle +Console shows what an oracle owes *now* without paging its whole market set. Also non-consensus. + +**Stored gauges** on `pm_oracle_object` — maintained O(1) at every state transition (op- and cron-driven) +and seeded once on the upgrade block: + +| Gauge | Meaning | Drill-down | +|-------|---------|-----------| +| `markets_in_dispute_window` | resolved markets (`status 3`, `payout 1`) still disputable, no dispute row yet | `list_markets_in_dispute_window(oracle, from, limit)` | +| `disputes_awaiting_response` | open disputes the oracle has not answered | `list_oracle_disputes` → `stage = awaiting_response` | +| `disputes_awaiting_decision` | open disputes answered, now with the resolver/committee | `list_oracle_disputes` → `stage = awaiting_decision` | + +**Computed on read** (time-dependent, cheaper to derive than to store): + +| Field | Meaning | +|-------|---------| +| `markets_awaiting_resolution` | this oracle's `status 1` markets past betting close (a `by_oracle_status` walk) | +| `oldest_unresolved_age` | seconds since betting close of the oldest such market | +| `resolution_time_p50` / `resolution_time_p95` | latency percentiles read off the 8-bucket `resolution_time_hist` | + +**Latency accounting** at resolve (`markets_resolved` just incremented): +`resolved_late_count += (now > result_expiration)`; `avg_resolution_time` running-mean of +`rt = now − betting_expiration`; `resolution_time_hist[bucket(rt)] += 1` over 8 buckets +(≤1h, ≤6h, ≤24h, ≤3d, ≤7d, ≤14d, ≤30d, >30d). Open-ended markets contribute `rt = 0`. + +**Dispute-lifecycle audit (P1):** opening a dispute emits the `pm_dispute_opened` virtual op, and +`pm_dispute_finalize` / `pm_dispute_auto_close` carry the `oracle` field — so an oracle's dispute +history (and the disputer's) is reconstructable from `account_history` alone. ### New Oracle Detection -`total_outcomes < 5` → `is_new = true`. Distinct badge in UI. - -Score computed on read via `compute_oracle_reliability_score()`, not stored. +`markets_resolved < 20` is treated as an unproven oracle: the confidence shrink above pulls the score +toward the 6000 prior (UI may badge it as new). All scores and the workload/latency fields are computed +on read; only the raw counters and stored gauges live on `pm_oracle_object`. --- @@ -786,6 +879,7 @@ Encryption: ECIES shared-secret `ECDH(sender_memo_private, recipient_memo_public | `pm_lazy_recall_step_percent` | graduated-recall step on idle markets (bp) | | `pm_lazy_lock_sec` | deposit lock period (seconds) | | `pm_lazy_emergency_penalty_percent` | penalty on locked profit for emergency withdrawal (bp) | +| `pm_lazy_min_liquidity_fee_percent` | minimum market `liquidity_fee_percent` (bp) for the pool to allocate; below it the market gets no pool liquidity (reward floor) | | `pm_min_liquidity` | minimum allocation per market (also the market seed floor) | ### Deposit @@ -805,6 +899,10 @@ alloc_amount = free_balance × allocation_percent / 100 × (1 − fault_penalty_pct / 100) ^ oracle_active_fault_stamps ``` +Reward-floor gate (checked first): if the market's `liquidity_fee_percent < +pm_lazy_min_liquidity_fee_percent`, the pool allocates **nothing** — it only subsidizes markets whose +LP fee pays it enough. The creator's own seed remains the sole liquidity. + Checks: `alloc_amount ≥ min_market_allocation`, `allocated + alloc_amount ≤ total × max_total_allocation / 100`. Pool LP inserted with `user=0`. Participates identically in time-weighted fee distribution. @@ -852,22 +950,17 @@ All deposits (locked + unlocked): ### Opportunity-Cost Protection -**A. Graduated Recall:** Market duration divided into 10 steps. At each step, if volume in period < `allocation × recall_volume_threshold_pct / 100`, recall `recall_pct_per_step`% of current allocation to pool. - -**B. Active Market Penalty:** `factor = (1 − active_market_penalty_pct / 100) ^ active_market_count`. Recursive 5% reduction per oracle active market. - -**C. Fault Stamps:** On bad market outcomes (no-contest, missed deadline, zero volume, dispute loss, no response, auto-close), oracle receives a fault stamp that auto-expires after a clean-operation window. `fault_factor = (1 − fault_penalty_pct / 100) ^ active_stamps`. - -### Design Decision: Real Depth Only (No Virtual/Phantom Liquidity) +> **Governance vs. fixed.** Only the recall **step size** is median-voted — `pm_lazy_recall_step_percent` +> (how much is pulled per idle step). The rest is **hardcoded** and needs a hardfork to change: the +> **10-step** division (`window/10`, `check_step ≥ 10`), the idle test (a step is idle when *no new bets* +> arrived since the last check — `bets_sum ≤ bets_sum_at_check`), and the **5% per-active-market penalty** +> (`alloc × 95/100`). The fault-stamp penalty and its expiry window are likewise hardcoded. -A *virtual* (phantom) liquidity offset — a curve reserve added for pricing but backed by no real capital and deleted at settlement, optionally median-voted — is value-conservative in the closed bet→cancel→settle loop (the vAMM technique) and is tempting as a cold-start stabilizer for thin markets. Onix **deliberately does not implement it.** Lazy-Pool auto-allocation (§16) already delivers the same launch-smoothing with **real** capital that earns fees, has an accountable owner, and follows demand per market. Phantom depth is rejected because, applied carelessly, it harms market structure and trust: +**A. Graduated Recall:** Market duration divided into **10 fixed steps**. At each step, if **no new bets** arrived since the last check, recall `pm_lazy_recall_step_percent` (bp, governance) of the current allocation back to the pool. -1. **Forgeable depth** lets a thin or manipulated market look deep and liquid, eroding the price signal that real, costly depth would carry. -2. **Distorted information aggregation** — virtual depth flattens curve weights, weakening the reward for early correct information and making the price unresponsive to news (a stale forecast). The right depth is per-market and volume-dependent; a single governance constant cannot track it. -3. **Conditional solvency** — it stays solvent only while never redeemed or used as collateral. The moment it backs a cancel, early withdrawal, or leverage loan it must be excluded everywhere or it leaks real money (e.g. leverage sized/recovered against fake depth → real bad debt to pool depositors). Every "size-against-liquidity / pay-the-LP" path becomes an excludability footgun. -4. **No owner, no yield, no accountability** — virtual depth bears no risk and earns no fee for anyone real, deleting the retail safe-yield product on the markets it touches. +**B. Active Market Penalty:** `factor = (1 − 5% ) ^ active_market_count` — a hardcoded recursive 5% reduction per concurrent active market from the same oracle. -Onix therefore keeps **only real numbers**: every unit of depth is real capital — redeemable, fee-earning, accountable — provided through the Lazy Pool. The conscious tradeoff is to forgo a cheap virtual stabilizer in favour of price-signal integrity and the solvency of every real-money path. +**C. Fault Stamps:** On bad market outcomes (no-contest, missed deadline, zero volume, dispute loss, no response, auto-close), the oracle gets a fault stamp that auto-expires after a fixed clean-operation window; each active stamp further reduces its allocation. (Penalty size + expiry are hardcoded.) --- @@ -885,6 +978,18 @@ Live since HF14; opt-in, governed by median kill-switch `pm_leverage_enabled` (d full-recovery (loan + interest → pool); the **only** bounded bad-debt path is a same-side `pm_cancel_bet` (Case B). The cascade is **not** gated by `pm_leverage_enabled` (the flag blocks only new opens), so disabling leverage never strips protection from open positions. +- **Exit residual → `forfeit_pool`** — on close/liquidate the holder is paid the floored + `cancel_value`; the sub-mVIZ remainder `curve_residual = total_bet − cancel_value` is **dust of the + pool's own curve assets**, so it is routed to the market's `forfeit_pool` (accrues to the remaining + bettors at settlement), not the DAO fund. This keeps `pool + bettor + residual = total_bet` exact on + every exit (fix `6b002c14`; the general final-payout dust rule above still sends to the DAO fund). +- **Close timing (does NOT wait for the oracle)** — a leveraged position is a bet on the market + *price* (crowd sentiment), settled at its `cancel_value`; it is independent of the resolved + outcome. So `process_pm_markets` **force-closes it the moment new betting is impossible**: at + `betting_expiration` for fixed-deadline markets (i.e. *before* the oracle resolves), or at + resolve/void (`status >= 3`) for open-ended markets (`betting_expiration == 0`). The holder never + bleeds funding, and can never be funding-liquidated, during the post-betting resolve + dispute + window. `settle_market` / `return_liquidity` still force-close as an idempotent backstop. - **Virtual ops** — `pm_leverage_resolve` (force-close at settlement, with outcome + leverage), `pm_leverage_liquidate` (mid-market, `reason` 0 opposing / 1 cancel). - **Governance weight** — Lazy-Pool depositors keep their PM-dispute and DAO-committee vote weight @@ -907,24 +1012,24 @@ opt-in per market (`allow_batch` / `allow_instant_bet`), median kill-switch `pm_ ## 17. On-Chain Object Model All state lives in **chainbase objects** registered as core indexes at HF14 — there is no SQL database. Field-level definitions live in the operation/object headers and -are queryable read-only via the [`prediction_market_api` plugin](../../../docs/plugins/prediction-market-api.md). The +are queryable read-only via the [`prediction_market_api` plugin](../plugins/prediction-market-api). The reputation counters the prototype kept on a `users` table are now fields on `pm_oracle_object`. | Object (index) | Holds | Looked up by | |---|---|---| -| `pm_oracle_object` | oracle registration, insurance, the 14 reputation counters, fault stamps, bans | owner | -| `pm_market_object` | market config, CPMM reserves (`reserve_a/b`, `k`), `*_fee_percent` (bp), `status` / `payout_status`, timers, `dispute_mode`, `a_bets_sum` / `b_bets_sum` | id / creator / oracle / result_expiration | +| `pm_oracle_object` | oracle registration, insurance, the 14 reputation counters, fault stamps, ban (`banned_until` + `banned_by`) | owner | +| `pm_market_object` | market config, CPMM reserves (`reserve_a/b`, `k`), `*_fee_percent` (bp), `status` / `payout_status`, timers, `dispute_mode`, `a_bets_sum` / `b_bets_sum`, oracle resolution statement (`decision_url` / `decision_reason`) | id / creator / oracle / result_expiration | | `pm_outcome_object` | per-outcome LMSR `q`, `bets_sum`, `bets_count` (multi markets) | market + outcome | | `pm_bet_object` | a bet — account, `side` / `outcome_index`, `amount`, curve `weight`, `time_penalty`, `status`, `mode` | market / account | | `pm_liquidity_object` | an LP position — principal, deposit time, time-weight; `provider` empty ⇒ lazy-pool LP | market | | `pm_commit_object` | a commit-reveal commitment hash + escrow (batch / commit-reveal) | market / account | -| `pm_dispute_object` | a dispute — disputer, `proposed_outcome`, fee escrow, timers, `status`, `dispute_mode` | market | +| `pm_dispute_object` | a dispute — disputer, `proposed_outcome`, fee escrow, timers, `status`, `dispute_mode`, oracle rebuttal (`oracle_response` / `oracle_response_time`) | market | | `pm_dispute_vote_object` | one committee ballot — voter, `vote_outcome`, `vote_percent` (revisable until close) | market + voter | | `pm_lazy_pool_object` | the singleton pool — `free_balance` / `allocated_balance` / `earned_balance`, `reward_per_share`, `leverage_fund_used`, `total_shares` | singleton (id 0) | | `pm_lazy_deposit_object` | a depositor — shares, reward snapshot, unlock time | account | | `pm_lazy_allocation_object` | the pool's silent LP allocation to one market + graduated-recall state (`bets_sum_at_check`, `check_step`, `recalled_amount`) | market | | `pm_leverage_position_object` | an open leveraged position — collateral, loan, obligation, curve weight, `status` | account / market + status | -| `pm_creator_ban_object` | a banned creator — `banned_until`, `ban_count` | ban account | +| `pm_creator_ban_object` | a banned creator — `banned_until`, `ban_count`, `banned_by` | ban account | Reputation metrics are computed on read (`compute_oracle_reliability_score()` — §14), not stored. All percentage fields are basis points (`*_percent`, bp). Lazy-pool per-market allocations and @@ -933,7 +1038,8 @@ live on `pm_oracle_object`. **Plugin-only (non-consensus):** `pm_market_meta_object` — off-chain-parsed market metadata (category / tags / banned jurisdictions) for discovery and jurisdiction filtering; it is built by the -`prediction_market_api` plugin from each market's opaque `metadata` string and never participates in -consensus. +[`prediction_market_api`](../plugins/prediction-market-api) plugin from each market's opaque `metadata` +string and never participates in consensus. -See the Prediction Market operations reference for the full field definitions of these objects. +See [Prediction Market operations](../protocol/operations/prediction-markets) for the full field +definitions of these objects. diff --git a/docs/prediction-markets/whitepaper.md b/docs/prediction-markets/whitepaper.md new file mode 100644 index 0000000000..bf54060575 --- /dev/null +++ b/docs/prediction-markets/whitepaper.md @@ -0,0 +1,681 @@ +# Onix Protocol: LP-Guaranteed Prediction Markets on VIZ DLT + +**An Industry Whitepaper** + +*Anatoly Piskunov (On1x)* +*Version 2.0 — June 2026 (on-chain / HF14)* + +*Published (peer-facing) version — cite this for academic use:* +*Piskunov, Anatoly. "Combining Parimutuel Settlement with Automated Market Makers for Liquidity-Guaranteed Prediction Markets: The Onix Protocol" (June 22, 2026). SSRN: [ssrn.com/abstract=6978138](https://ssrn.com/abstract=6978138) · DOI: [10.2139/ssrn.6978138](https://dx.doi.org/10.2139/ssrn.6978138). Posted 16 Jul 2026, 29 pp. © All rights reserved; no reuse without permission.* + +--- + +> **On-chain status (HF14).** This paper was first written against the centralized prototype. The +> protocol now runs as **first-class consensus operations (`pm_*`) on VIZ DLT**, verified in +> `consensus_sim`. Live since HF14: both market types (CPMM binary + LMSR multi), the parimutuel +> zero-sum settlement, the **Lazy Pool**, an opt-in **leverage** subsystem, opt-in **batch / +> commit-reveal betting** (binary), bonded oracles, and a two-mode dispute system (committee / +> account). All percentage parameters are **basis points (bp): 10000 = 100.00%** (the prototype used +> permille). Sections below are annotated where the live design differs from the original prototype text. + +## Abstract + +Prediction markets aggregate dispersed information into prices, producing probability estimates that consistently outperform polls, expert panels, and statistical models. Yet adoption remains constrained by a single structural problem: **liquidity providers lose money.** + +Uniswap v3 LPs suffer impermanent loss. LMSR market makers risk their entire subsidy. CLOB market makers face adverse selection. Every existing model asks capital providers to accept downside risk in exchange for uncertain yield — and the data shows most of them lose. + +The **Onix Protocol** eliminates LP risk entirely. It is a prediction market architecture where LP principal is **structurally guaranteed** — not by insurance, not by hedging, but by the payout mechanics themselves. Winners are paid exclusively from losers' forfeited stakes. LP capital provides market depth but is never used to settle bets. + +This paper describes the Onix Protocol's two market types — **Onix Binary** (Constant Product Market Maker) and **Onix Multi** (LMSR pricing with parimutuel settlement) — along with the market architecture, oracle and dispute resolution system, lazy liquidity pool, opt-in leverage, governance model, and its implementation on VIZ DLT as consensus-level operations. + +--- + +## 1. The Problem: LP Risk in Prediction Markets + +Every prediction market needs liquidity. Without it, prices are meaningless — a bet that moves the market by 20% reveals the bettor's capital, not the crowd's wisdom. The fundamental question is: **who provides that liquidity, and what do they risk?** + +### 1.1 The Current Landscape + +| Platform | LP Model | LP Risk | Yield Source | +|----------|----------|---------|-------------| +| **Uniswap v3** | Concentrated AMM | Impermanent loss (often >5% annualized; >50% of v3 LPs underperform buy-and-hold) | Trading fees | +| **Aave / Compound** | Lending pool | Smart contract risk, liquidation cascades | Borrower interest | +| **Curve** | Stableswap AMM | Low IL for pegged assets, smart contract risk | Fees + CRV emissions | +| **Standard LMSR** | Market maker subsidy | Loss up to `b × ln(N)` — the entire subsidy | Bid-ask spread | +| **Polymarket (CLOB)** | Active market making | Inventory risk, adverse selection | Bid-ask spread | +| **Kalshi** | No LP concept | N/A (exchange model) | N/A | + +The pattern is clear: providing liquidity to prediction markets requires either active management skill (CLOB), tolerance for capital loss (LMSR), or acceptance of impermanent loss (AMM). None of these are suitable for retail participants. + +### 1.2 Why This Matters + +Prediction markets work best when they are deep and liquid. Deep markets produce accurate prices, attract informed traders, and generate the information value that makes prediction markets useful as a public good. But depth requires capital, and capital requires compensation for risk. + +The result is a chicken-and-egg problem: +- Thin markets → high slippage → poor UX → few bettors → low fees → no LP incentive → thin markets + +Breaking this cycle requires removing the risk from the LP side of the equation. If providing liquidity is risk-free, the barrier to entry drops to zero, and the flywheel can start spinning. + +--- + +## 2. The Onix Protocol + +### 2.1 Design Principles + +The Onix Protocol is built on three architectural invariants: + +1. **LP principal is structurally safe.** This is not a risk-mitigation strategy — it is a property of the payout architecture. LP capital and bet settlement draw from physically separate pools. + +2. **Losers fund winners.** All payouts (winner profits, oracle fees, creator fees, LP fees) are sourced exclusively from losers' forfeited stakes. Fees are computed at resolution as `floor(losers_sum × fee_bp / 10000)` (bp: 10000 = 100.00%), never deducted at bet time. + +3. **Dual market types, single guarantee.** Binary markets (Onix Binary) and multi-outcome markets (Onix Multi) use different pricing formulas but share the same settlement model and the same LP guarantee. + +### 2.2 Onix Binary (CPMM + Parimutuel Settlement) + +Onix Binary uses the Constant Product Market Maker formula — the same `x * y = k` invariant used by Uniswap — as the **pricing engine** for binary outcomes, with **parimutuel settlement** (losers fund winners pro-rata by weight), the same settlement model as Onix Multi. + +**Mechanics:** + +A market maintains two reserves, `reserve_a` and `reserve_b`, with constant product `k`: + +``` +k = reserve_a × reserve_b +``` + +When a user bets `amount` on outcome A (in the implementation, side 0 → `reserve_a`), the stake enters +that side's reserve and tokens are drawn from the **opposing** reserve: + +``` +new_reserve_a = reserve_a + amount +new_reserve_b = floor(k / new_reserve_a) +tokens_received = reserve_b − new_reserve_b +``` + +The `tokens_received` (called `weight`) is the user's **relative claim** on the winners' pool if outcome A wins (settlement is parimutuel — see below, identical to Onix Multi). Implied probability rises for the side that is bet (more money on A → `reserve_a` grows → `P(A)` grows): + +``` +P(A) = reserve_a / (reserve_a + reserve_b) +P(B) = reserve_b / (reserve_a + reserve_b) +``` + +**Settlement and proof of LP safety (parimutuel):** + +At resolution, winners receive their stake back plus a proportional share of the losers' pool, by weight (identical to Onix Multi): + +``` +winners_pool = losers_sum − fees +payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit +``` + +LP principal `L` is returned unconditionally, and the guarantee is exact: + +``` +Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN +``` + +Total payout is capped at `losers_sum` regardless of weights, so LP capital is never used to settle bets. The CPMM is the **pricing engine** (probability + weight); it does not gate payout. (The AM-GM relation `reserve_a + reserve_b ≥ 2√k = L` still holds for the pricing curve but is no longer relied upon for solvency.) + +**Worked example:** + +``` +Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000 +Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%) + +Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%) +Bob bets 80 VIZ on B → receives weight 81.82 + +Resolution: A wins + Losers (Bob): 80 VIZ forfeited → losers_sum = 80 + oracle_fee = floor(80 × 50/10000) = 0.4 VIZ + creator_fee = floor(80 × 50/10000) = 0.4 VIZ + liq_fee = floor(80 × 100/10000) = 0.8 VIZ + winners_pool = 80 − 1.6 = 78.4 VIZ + + Alice (only winner, weight 33.33 of 33.33): + payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit) + LP return: 200 VIZ principal + share of 0.8 VIZ fee pool +``` + +### 2.3 Onix Multi (LMSR + Parimutuel Settlement) + +Onix Multi is the protocol's innovation for markets with 3–10 outcomes. It combines Hanson's Logarithmic Market Scoring Rule (LMSR, 2003) for real-time pricing with parimutuel settlement for LP safety. + +**Pricing (LMSR softmax):** + +For a market with outcomes {1, 2, ..., N}, each with quantity parameter `q_i`: + +``` +price(i) = exp(q_i / b) / Σ_j exp(q_j / b) +``` + +This is the softmax function — prices always sum to exactly 1.0 by construction. No arbitrage mechanism or split/merge operation is needed. + +The cost to buy Δ tokens on outcome i: + +``` +C(q) = b × ln(Σ_j exp(q_j / b)) + +cost = C(q + Δ·e_i) − C(q) +``` + +The parameter `b` controls price sensitivity (higher b = less price impact per bet). It is funded by the LP subsidy: `b = S / ln(N)` where S is the total subsidy. + +**The innovation — parimutuel settlement:** + +In **standard LMSR**, the market maker is the counterparty to all bets. If the crowd correctly predicts the outcome, the market maker loses up to `b × ln(N)` — potentially the entire subsidy. This is why LMSR has seen limited adoption outside corporate prediction markets (Microsoft, Inkling) where the operator absorbs the loss. + +**Onix Multi changes the payout source.** At resolution: + +``` +1. Oracle declares the winning outcome +2. Losers forfeit 100% → losers_sum +3. Fees deducted from losers_sum (bp; 10000 = 100.00%): + oracle_fee = floor(losers_sum × oracle_fee_bp / 10000) + creator_fee = floor(losers_sum × creator_fee_bp / 10000) + liq_fee = floor(losers_sum × liquidity_fee_bp / 10000) + winners_pool = losers_sum − fees +4. Winners receive: + payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty +5. LP subsidy returned unconditionally +``` + +Winners are paid by losers, not by the LP. The subsidy is architecturally separate from the settlement flow. + +**Proof of LP principal guarantee:** + +1. The LP deposits `S` VIZ as subsidy, which funds market depth. +2. During betting, users pay VIZ → receive outcome tokens. The VIZ accumulates as the betting pool. +3. At resolution, losers' forfeited stakes fund winner payouts and fees. The subsidy `S` was never in the payout pool. +4. The subsidy is returned to the LP unconditionally, regardless of outcome. + +**Comparison:** + +| Dimension | Standard LMSR | Onix Multi | +|-----------|---------------|------------| +| LP role | Counterparty to all bets | Depth deposit (not counterparty) | +| LP max loss | `b × ln(N)` (entire subsidy) | **Zero** | +| Winner payout | 1 token = 1 unit of currency | Token = proportional claim on losers' pool | +| CTF split/merge needed? | Yes (enforce price sum = 1) | No (softmax guarantees it) | + +**Worked example (3-outcome election):** + +``` +Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ +Initial: price(A) = price(B) = price(C) = 33.3% + +Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%) +Bob bets 100 VIZ on B → ~88 tokens +Carol bets 30 VIZ on C → ~29 tokens + +Resolution: A wins + Losers: Bob (100) + Carol (30) = 130 VIZ + Fees (200 bp = 2% total): 2.6 VIZ + winners_pool = 127.4 VIZ + + Alice: 50 + (47/47 × 127.4) = 177.4 VIZ + LP: 1000 VIZ returned in full + share of liquidity fees +``` + +### 2.4 Edge Cases + +| Scenario | Outcome | +|----------|---------| +| All bets on the winner | `losers_sum = 0` → every bettor gets back exactly their bet amount. LP subsidy returned. Zero-sum. | +| No bets on the winner | Entire losers' pool is undistributed → LP bonus. LP profits maximally. | +| Zero-volume market | LP subsidy returned in full. No fees, no payouts. | +| Single bettor wins | That bettor receives `bet_amount + winners_pool`. LP subsidy returned. | + +--- + +## 3. Market Architecture + +### 3.1 Market Lifecycle + +```mermaid +stateDiagram-v2 + direction LR + state "Waiting (0)" as Waiting + state "Active (1)" as Active + state "Closed (2)" as Closed + state "Resolved (3)" as Resolved + state "Deleted (-1)" as Deleted + state "Paid out" as Paid + [*] --> Waiting + Waiting --> Active: oracle accepts + Waiting --> Deleted: oracle rejects + Active --> Closed: betting expires + Active --> Resolved: early resolution (if allowed) + Closed --> Resolved: oracle resolves + Resolved --> Paid: grace period (12h) + Deleted --> [*] + Paid --> [*] +``` + +Markets are created by a market creator, reviewed and accepted by an oracle (who stakes insurance), open for betting, resolved with an outcome, and paid out after a dispute grace period. + +### 3.2 Fee Model (Losers-Only Fee Extraction) + +A distinctive feature of the Onix Protocol is that **no fees are deducted at bet time**. The full bet amount enters the market reserves. Fees are computed only at resolution, exclusively from the losing side's forfeited stakes: + +```mermaid +flowchart TD + LS["losers_sum (100% of losing bets)"] + LS --> OF["oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)"] + LS --> CF["creator_fee = floor(losers_sum × creator_fee_bp / 10000)"] + LS --> LF["liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)"] + LS --> WP["winners_pool = losers_sum − all fees"] +``` + +This provides a structural guarantee: fees and winner payouts draw from completely separate funding sources. Fee extraction can never compete with winner obligations. + +**Oracle fee terms are frozen at acceptance (offer→quote).** The creator publishes a *maximum* the oracle may charge (`oracle_fee_percent` ceiling in bp + `oracle_fixed_fee` ceiling); when the oracle accepts it quotes its actual terms (≤ the creator's ceiling and ≤ the median governance cap `pm_max_oracle_fee_percent`), which are frozen onto the market and a `pm_market_accepted` virtual op is emitted. A self-oracle freezes its terms at creation. The **oracle fixed fee** (per-market) is paid from the losers' pool remainder (never minted). A **market creation fee** goes to the DAO fund as anti-spam protection. + +### 3.3 Time-Weighted LP Distribution + +LP fee shares are distributed proportionally to `amount × max(1, seconds_to_expiration)`: + +``` +weight_i = amount_i × max(1, sec_to_expiration_i) +fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j) +``` + +Early LPs earn dramatically more per unit of capital than late LPs. In a 48-hour market, an LP who deposits at hour 1 earns ~2,400x more per VIZ than one who deposits at hour 47. + +Each deposit is tracked as an independent position — multiple deposits by the same user are weighted and paid separately. LP principal is always returned in full, regardless of market outcome. + +### 3.4 Time Penalty for Late Bets + +To discourage last-minute betting (which carries less uncertainty risk), a configurable time penalty applies to bets placed near expiration: + +``` +if time_to_expiration < penalty_window: + ratio = 1 − (time_to_expiration / penalty_window) + penalty_ratio = ratio² // quadratic (default) + time_penalty = floor(penalty_ratio × max_penalty) +``` + +The penalty applies **only to profit**, never principal. A winning bettor always receives at least their original stake. The quadratic curve is gentle early in the penalty window and steep late, rewarding "somewhat late" over "extremely late." + +### 3.5 Position Transfers + +Positions are transferable between accounts via a native protocol operation: + +``` +pm_transfer_position { bet_id, to_user, amount, memo } +``` + +No slippage, no market impact — pure record reassignment. The `memo` field supports both plaintext and encrypted modes (ECIES via VIZ account memo keys), enabling P2P deals, OTC trading, and private annotations. + +This is the only composability feature from the Conditional Tokens Framework (Polymarket/Gnosis) that provides real user value. CTF split/merge is architecturally unnecessary — Onix pricing formulas guarantee price coherence by construction. + +--- + +## 4. Oracle and Dispute Resolution + +### 4.1 Bonded Oracle Model + +Oracles in the Onix Protocol are not trusted by default — they are **bonded**. Each oracle must: + +- Register with a one-time fee (default 10 VIZ) +- Deposit insurance (minimum 5,000 VIZ) +- Accept markets explicitly (staking their insurance on each acceptance) +- Resolve markets with an outcome and supporting evidence (decision URL) + +The insurance bond creates accountability: oracles who misresolve, miss deadlines, or lose disputes have their insurance slashed. The bond must exceed the oracle's potential manipulation profit for the economic security model to hold. + +Oracle revenue comes from two sources: +1. **Fixed fee** (per market) — compensates for staking insurance and providing resolution +2. **Percentage fee** (from losers' pool at resolution) — scales with market volume + +### 4.2 Dispute Arbitration + +Any bettor can challenge a resolution within a grace period by paying a dispute fee. During disputes, all payouts are frozen. + +Resolution runs in one of **two per-market modes**, chosen at creation: + +- **Committee mode (`dispute_mode = 0`, default)** — the *whole SHARES electorate* decides by + **stake-weighted vote** (`pm_dispute_vote`), tallied deterministically by the `pm_dispute_finalize` + cron at `voting_end_time`. It is an **open public hearing**: the live tally is queryable and votes are + **not** hidden behind commit-reveal (a deliberate, permanent choice — the DAO resolves disputes as + transparently as possible). Because new evidence surfaces during the hearing, **a ballot is revisable** + until close (a repeat vote overwrites the prior one). A voter's weight is its `effective_vesting_shares` + **plus its Lazy-Pool stake converted to vesting-shares**, so DAO members who park VIZ in the pool keep + their governance weight. +- **Account mode (`dispute_mode = 1`)** — a single named `dispute_resolver` (recommended multisig) + issues the verdict (`pm_dispute_resolve`). + +The verdict logic in either mode: + +**If the oracle was wrong (overturned):** +- The correct outcome is applied and payouts recalculated. +- The disputer gets their fee back **plus a reward carve-out** drawn from the slashed insurance, sized as `dispute_fee × pm_dispute_reward_multiplier` (bp; e.g. 30000 = ×3), capped by the slash. +- **The remainder of the slash is added to the winners' pool** (via `forfeit_pool`) — it goes to the winning bettors, **not** to a resolver or the DAO. Neither the committee voters nor the account-mode resolver receive any reward (committee voting is an unpaid governance duty). +- The oracle's insurance is slashed (scaled by consensus strength in committee mode, or by the resolver's `penalty_amount` in account mode), with optional ban. + +**If the oracle was right (upheld):** +- The disputer **forfeits the whole dispute fee to the oracle** (compensation for the bad-faith challenge). +- Original payouts proceed unchanged. + +**Denial-of-resolution prevention:** If the resolver fails to act within 14 days, disputes auto-close: all bets and LP are refunded, the oracle is penalized, and the disputer's fee is returned. This guarantees funds are never frozen indefinitely. + +### 4.3 Oracle Reputation Scoring + +The protocol tracks 14 on-chain metrics per oracle and computes a reliability score (0–100): + +``` +reliability_score = clamp(0, 100, + 50 (base) + − 0.40 × dispute_loss_rate × 100 + − 0.10 × excess_no_contest × 100 + − 0.20 × deadline_miss_rate × 100 + − 0.15 × (1 − dispute_response_rate) × 100 + + volume_bonus (0–25) + + experience_bonus × freshness_multiplier (0–25) + − 15 × bans_received +) +``` + +Key design choices: +- **Rates, not counts** — 1 dispute lost out of 100 (1%) scores better than 1 out of 2 (50%) +- **Neutral start at 50** — new oracles must earn reputation, not start at 100 +- **Freshness decay** — inactive oracles lose their experience bonus over time +- **Volume tiers** — high-volume oracles receive bonus points for proven track record + +The reliability score combines with a risk factor (insurance-to-bets ratio) to produce a **composite trust score** — the primary metric shown to users. + +### 4.4 No-Contest and 3-Outcome Resolution + +An oracle who cannot verify an outcome can voluntarily declare **no-contest**, triggering refunds at a reduced penalty (50% of dispute fee from insurance — much cheaper than losing a dispute). This creates an incentive gradient: + +| Scenario | Oracle Cost | Ban Risk | +|----------|------------|----------| +| Voluntary no-contest | 500 VIZ | None | +| Dispute loss | 1,000+ VIZ + extra penalty | Permanent or temporary | +| Missed deadline | 250 VIZ (auto-penalty) | None (but reputation damage) | + +If users believe the oracle abused no-contest, they can dispute it. The resolver then chooses from **three** possible correct outcomes: A wins, B wins, or confirm no-contest. This prevents oracles from using no-contest to avoid paying out winning bettors. + +--- + +## 5. Lazy Liquidity Pool + +### 5.1 The Capital Deployment Problem + +Individual LP provision requires active market selection. Most users won't manually evaluate and deposit into specific markets. The result: most markets launch with only the creator's initial liquidity, producing thin order books and high slippage. + +### 5.2 Automated Pool-to-Market Allocation + +The Lazy Liquidity Pool solves this by accepting deposits and **automatically allocating** a percentage of the pool's free balance to every new market when it activates: + +``` +alloc_amount = free_balance × allocation_percent / 100 +``` + +Allocations are computed from the current free balance (not the original total), creating geometric decay — the pool can never be fully depleted: + +``` +After 50 markets (2% allocation each): ~357 VIZ free from original 1,000 +After 100 markets: ~133 VIZ still free +``` + +A maximum total allocation cap (default 70%) provides additional safety. + +### 5.3 Reward Distribution (one shared accumulator) + +The problem: when a market resolves with pool profit, that profit must be split among **all** current +depositors in proportion to their shares — but iterating every depositor on every market would be O(N) +and unbounded. The pool avoids that with **one global running total**, `reward_per_share` ("rps"): + +``` +// When a market resolves with pool LP profit, the per-share value of the pool rises once: +pool.reward_per_share += profit × PRECISION / total_shares + +// A depositor's earnings = their shares × how much rps has risen since they last touched the pool: +live_reward = pending + shares × (pool.reward_per_share − user.snapshot) / PRECISION +``` + +In plain terms: every depositor "owns" a slice of each rise in `reward_per_share`, and their reward is +just `shares × (current rps − the rps recorded when they last deposited/withdrew)`. A depositor's own +record is touched **only when they act** (deposit or withdraw); until then their entitlement accrues +silently in the global number. So distributing profit to thousands of depositors is **O(1)** — a single +addition — and no funds are paid until claimed. This is the well-known accumulator pattern from +[SushiSwap's MasterChef contract](https://github.com/sushiswap/masterchef/blob/master/contracts/MasterChef.sol) +(and Compound's cToken index) — `PRECISION` (1e9) keeps the integer division exact. + +### 5.4 Opportunity-Cost Protection + +The pool auto-allocates to every market, creating an attack vector: a malicious oracle could create long-duration zero-volume markets to lock pool capital. Three mechanisms address this: + +**Graduated Early Recall:** The market's duration is divided into 10 steps. At each step, if betting volume is below a threshold (1% of allocation), 10% of the current allocation is recalled to the pool. A completely idle 30-day market loses ~60% of its allocation. + +**Active Market Penalty:** Each additional active market from the same oracle reduces that oracle's allocation by 5% (recursive). An oracle with 10 active markets receives ~60% of the base allocation per market, incentivizing quality over quantity. + +**Fault Penalty Stamps:** Bad outcomes (missed deadlines, disputes lost, zero-volume resolutions) generate penalty stamps that further reduce future allocations. Stamps auto-expire after 10 days of clean operation. + +### 5.5 Opt-In Leverage (Lazy-Pool-Funded) + +The Lazy Pool serves **two roles from one `free_balance`**: silent market-LP allocations *and* funding for +an **opt-in leverage** subsystem. A bettor can open a leveraged position (`pm_leverage_open`) where margin +is a **loan from the pool** — no token emission, the position stays fully collateralized from the system's +view. The binary "jump risk" that breaks CLOB liquidation engines is handled by **liquidating against +pre-bet reserves**: an opposing-bet or settlement force-close recovers `min(cancel_value, obligation) ≥ +loan`, so the pool gets its loan plus interest back; the only bounded bad-debt path is a same-side +`pm_cancel_bet`. A median kill-switch (`pm_leverage_enabled`, default off) blocks *new* opens, but the +protective liquidation cascade is deliberately **not** gated by it — toggling leverage off never strips +protection from open positions. The pool earns leverage interest in addition to LP yield, accounted via the same shared `reward_per_share` accumulator as in §5.3. Settlement of pool loans emits `pm_leverage_resolve` / `pm_leverage_liquidate`. + +A leveraged position is a bet on the market **price** (crowd sentiment), not on the oracle's verdict — +so it **does not wait for resolution**. It is force-closed at its `cancel_value` the instant new betting +becomes impossible: at `betting_expiration` for a fixed-deadline market (before the oracle even reports), +or at resolve for an open-ended one. The holder therefore never carries funding, nor faces liquidation, +across the resolution + dispute window. + +--- + +## 6. Governance + +### 6.1 Delegate-Voted Chain Parameters + +VIZ uses Delegated Proof of Stake (DPoS) consensus where elected delegates (validators) govern chain parameters through a median-vote mechanism: + +1. Each delegate publishes preferred values for all parameters +2. The network computes the **median** of all active delegates' votes +3. Parameters change automatically when the median shifts — no hard fork, no deployment + +All prediction-market parameters (fees, penalties, insurance requirements, dispute windows, lazy-pool +settings, leverage knobs, batch/commit-reveal timing) are delegate-voted. All percentage parameters are +in **basis points (bp), 10000 = 100.00%**: + +| Examples | Governance | +|----------|-----------| +| `pm_dispute_fee`, `pm_max_oracle_fee_percent` (bp) | Delegate median vote | +| `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec` | Delegate median vote | +| `pm_dispute_approve_min_percent`, `pm_dispute_reward_multiplier` (bp) | Delegate median vote | +| `pm_lazy_*` allocation/recall, `pm_leverage_*` (enabled, fund %, max position) | Delegate median vote | +| `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks` | Delegate median vote | + +Hard forks are only needed for structural changes (new operation types, formula changes), not for economic tuning. Kill-switches (`pm_leverage_enabled`, `pm_commit_reveal_enabled`) let governance disable a whole subsystem by median vote without a fork. + +### 6.2 Jurisdictional Client Model + +VIZ DLT is infrastructure, not an operator — analogous to how Bitcoin is a ledger, not a money transmitter. The protocol is neutral and permissionless. Legal obligations attach to **client applications**, not to the consensus algorithm. + +Any jurisdiction can build a compliant client on VIZ DLT: + +| Client Component | Implementation | +|-----------------|---------------| +| Pre-approved oracles | Client whitelist of licensed, KYC-verified oracles | +| Pre-approved resolvers | Government-approved dispute resolution bodies | +| KYC/AML | Client-level identity verification | +| Fee routing as tax revenue | `dao_fund_account_id` → state treasury account | +| Market restrictions | Client filters by allowed categories | +| Betting limits | Client-enforced per-user caps | + +The same protocol operations (`pm_place_bet`, `pm_resolve`, `pm_dispute`) work identically for permissionless and regulated clients. The difference is entirely at the client layer. + +--- + +## 7. Competitive Landscape + +### 7.1 Platform Comparison + +| Dimension | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR | +|-----------|-------------------|------------|--------|---------------| +| **Pricing** | CPMM (binary) / LMSR softmax (multi) | CLOB | CLOB | LMSR | +| **LP Risk** | **Zero** (structural guarantee) | Inventory risk | N/A | Up to `b × ln(N)` | +| **LP Knowledge** | Low (deposit and earn) | High (manage orders) | N/A | Medium | +| **Fee Model** | % of losers' pool at resolution | Bid-ask spread | Exchange fees (1-7%) | Spread | +| **Oracle** | Per-market bonded + committee dispute | UMA Optimistic Oracle | Kalshi (CFTC-regulated) | Operator | +| **Late Bet Penalty** | Quadratic, configurable | None | None | None | +| **Position Transfer** | Native protocol operation + encrypted memo | CTF (ERC-1155) | None | None | +| **Governance** | Delegate-voted parameters | Team multisig | CFTC process | Operator | +| **Infrastructure** | VIZ DLT (consensus-level) | Polygon (smart contracts) | Proprietary servers | Various | + +### 7.2 Why CTF Split/Merge Is Unnecessary + +Polymarket uses the Gnosis Conditional Tokens Framework (CTF) where positions are ERC-1155 tokens that can be split and merged to enforce price coherence (prices sum to $1). + +Under the Onix Protocol, this mechanism is architecturally unnecessary: + +- **Onix Binary (CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` — by definition +- **Onix Multi (LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` — by definition of softmax + +No arbitrage mechanism needed. Price coherence is a mathematical property of the formulas, not an external enforcement layer. + +### 7.3 The Flywheel + +``` +Risk-free LP → lower barrier for retail LPs + → more liquidity deposited + → deeper markets, less slippage + → better UX for bettors + → more volume + → more fees for LPs + → attracts even more LPs +``` + +"Passive yield without impermanent loss" is the value proposition that Uniswap, Balancer, and Curve cannot offer. For the crypto-native audience, this is a compelling narrative: earn yield by providing liquidity to prediction markets, with zero risk to principal. + +--- + +## 8. VIZ DLT: From Prototype to Protocol + +### 8.1 Current State + +The protocol began as a Telegram WebApp with a centralized backend (all market logic server-side) — a working prototype with known limits: no sybil resistance beyond Telegram accounts, no censorship resistance, no composability. **That migration is now done:** the full market logic runs **on VIZ DLT as consensus-validated `pm_*` operations** (HF14), exercised end-to-end in `consensus_sim`. The remainder of this section describes that on-chain architecture, now realized. + +### 8.2 Migration Architecture + +VIZ DLT is a Distributed Ledger Technology with ~3-second block times, DPoS consensus, named accounts (Graphene-style), and no general-purpose smart contracts. Prediction market operations will be implemented as **first-class consensus-validated operations** — not smart contracts, not `custom_json` payloads. + +| Layer | Examples | Consensus-Validated? | +|-------|---------|---------------------| +| **Protocol operations** | `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, `pm_commit_bet`/`pm_reveal_bet`, `pm_resolve_market`, `pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`, `pm_lazy_deposit`/`pm_lazy_withdraw`, `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | Yes — every node validates | +| **Virtual operations** | `pm_payout` (per bettor), `pm_auto_payout`, `pm_market_accepted`, `pm_dispute_finalize`, `pm_dispute_auto_close`, `pm_oracle_missed_penalty`, `pm_lazy_recall`, `pm_batch_settle`, `pm_commit_forfeit`, `pm_leverage_resolve`/`pm_leverage_liquidate` | Yes — deterministic, generated at block time | +| **metadata / custom_json** | Dispute comments, market descriptions, UI metadata | No — display/indexing only | + +Every financial action (placing bets, adding liquidity, resolving markets, seizing insurance) is validated by every validator. Invalid operations are rejected before block inclusion. No Solidity, no gas estimation, no bytecode deployment. + +The frontend is a **fully headless web client** — no backend server, no database, no sessions. Private keys stored in the browser (encrypted), transactions signed locally and broadcast to public VIZ nodes. No Telegram dependency; the core app is platform-independent. + +### 8.3 Delivered Since the Prototype, and Remaining Roadmap + +**Delivered on-chain (HF14):** + +| Feature | Status | +|---------|--------| +| Commit-reveal + batch betting (binary, opt-in, median kill-switch) | ✅ Live | +| Opt-in leverage (Lazy-Pool-funded, pre-bet-reserve liquidation) | ✅ Live | +| Lazy Pool (auto-allocation, graduated recall, MasterChef accounting) | ✅ Live | +| Per-bettor / leverage settlement virtual ops + plugin API | ✅ Live | +| Lazy-pool stake as governance weight (PM disputes + DAO requests) | ✅ Live | + +**Remaining roadmap:** + +| Priority | Feature | Impact | +|----------|---------|--------| +| High | Shared liquidity pools (category-level AMMs) | Solves liquidity fragmentation at the architecture level | +| High | Automated data oracles (exogenous feeds) | Eliminates manipulation for objective markets | +| Medium | Tiered dispute windows (small vs large markets) | Better UX calibration | +| Medium | LMSR batch settlement (extend batch/commit-reveal to multi) | Multi markets currently force instant betting | +| — | Commit-reveal **dispute** voting | **Deliberately rejected** — disputes stay public hearings (see §4.2) | + +--- + +## 9. What Onix Does NOT Claim + +Honest disclosure of tradeoffs and limitations: + +- **LP profit is not guaranteed.** If a market has zero losing bets, there are no fees to distribute. LP gets principal back but earns nothing. + +- **Platform risk exists.** Bugs, exploits, and governance attacks are separate from the market maker model. The LP guarantee is structural (payout architecture), not insured (no external guarantee fund). + +- **Onix Multi tokens are not fixed-value instruments.** In standard LMSR, 1 winning token = 1 unit of currency. In Onix Multi, tokens are proportional claims on the losers' pool. If all bettors pick the winner, everyone breaks even. + +- **LP yield depends on volume, not depth.** A market with 100,000 VIZ subsidy and one with 1,000 VIZ subsidy earn the same absolute fee if both have identical betting volume and fee rates. The subsidy provides depth, not yield. + +- **DPoS governance has known tradeoffs.** Fewer validators than PoW/PoS, delegate concentration risks, token-weighted voting. These are inherent to the DPoS model (shared by EOS, Hive, Tron), not VIZ-specific. + +- **VIZ token liquidity is currently low.** Economic guarantees (insurance bonds, dispute fees) scale with token price. The protocol assumes that utility drives demand over time — the same bet every protocol-native token project makes. + +--- + +## 10. Conclusion + +The Onix Protocol addresses the fundamental barrier to prediction market adoption: LP risk. By structurally separating LP capital from bet settlement — in both binary (CPMM) and multi-outcome (LMSR + parimutuel) markets — Onix makes liquidity provision risk-free and accessible to retail participants. + +The key innovations: + +1. **LP principal guarantee** as an architectural invariant, not insurance +2. **Losers-fund-winners** settlement eliminating fee competition with winner payouts +3. **LMSR pricing with parimutuel settlement** (Onix Multi) — combining proven price discovery with LP safety +4. **Time-weighted LP distribution** rewarding early capital commitment +5. **Quadratic time penalty** on profit (never principal) for late bets +6. **Lazy Liquidity Pool** with automated allocation, graduated recall, and MasterChef accounting — also funding the opt-in **leverage** subsystem (pool-funded margin, pre-bet-reserve liquidation, never bad debt outside a bounded cancel-bet path) +7. **Bonded oracle model** with reputation scoring, offer→quote fee freezing, and a two-mode dispute system — committee disputes are **public hearings** with revisable, lazy-pool-weighted votes +8. **Opt-in anti-MEV** — batch / commit-reveal betting (binary) with a median kill-switch +9. **Consensus-level implementation** on VIZ DLT — no smart contracts, no gas, no external keepers; strictly **zero-sum** (the protocol never mints a token) + +The bet is straightforward: if zero-risk LP attracts capital, capital creates depth, depth improves prices, and prices attract bettors, then the Onix Protocol solves the prediction market liquidity problem. The protocol mechanics are mathematically verifiable. The economic hypothesis will be tested by the market. + +--- + +## 11. Author and Disclosure + +### Author + +**Anatoly Piskunov** (On1x) — Russian IT innovator, Web3/DLT developer, and creator of the VIZ blockchain. His work spans distributed ledger technology, decentralized social protocols, and economic models for digital communities. + +Key contributions include: VIZ Blockchain (Fair DPoS, social capital primitives), the Onix Protocol (LP-guaranteed prediction markets), Voice Protocol (censorship-resistant messaging), and extensive publications on blockchain economics and Web3 architecture. + +Full list of publications and projects: [https://on1x.com](https://on1x.com) + +### Disclosure + +The author of Forecaster and the Onix Protocol is also the creator of VIZ DLT. The migration roadmap proposes moving the platform to a blockchain the author designed and built. + +This is disclosed upfront. It is also the norm: Polymarket depends on Polygon Labs' infrastructure, Kalshi runs on its own servers, Augur designed the REP token it runs on. Every platform argues for its own infrastructure. The question is not whether the author has an interest — they always do — but whether the technical claims are falsifiable. Every formula, proof, and mechanism in this paper is mathematically verifiable and the codebase is open-source. + +--- + +## 12. References + +1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. — The Logarithmic Market Scoring Rule (LMSR). + +2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* — Constant Product Market Maker (`x * y = k`). + +3. Adams, H., et al. (2021). *Uniswap v3 Core.* — Concentrated liquidity and impermanent loss analysis. + +4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ — ERC-1155 prediction market positions. + +5. UMA Protocol. *Optimistic Oracle Documentation.* — Dispute escalation mechanism used by Polymarket. + +6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* — cToken accumulator pattern (basis for reward_per_share). + +7. SushiSwap. *MasterChef Contract.* — Lazy accounting pattern for reward distribution. + +8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* — VIZ DLT architecture, DPoS consensus, named accounts. + +9. Piskunov, A. (2019). *What is Fair DPoS.* — Governance innovation in delegated proof of stake. + +10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* — Framework for blockchain systems as digital polities. diff --git a/docs/prediction-markets/workflows.md b/docs/prediction-markets/workflows.md new file mode 100644 index 0000000000..22c9d7076e --- /dev/null +++ b/docs/prediction-markets/workflows.md @@ -0,0 +1,754 @@ +--- +title: Prediction Markets — workflows & interaction diagrams +description: One canonical Onix binary market traced through every participant, with the zero-sum master ledger for normal and disputed resolution, and the verified operation / virtual-operation status. +--- + +# Workflows & interaction diagrams + +One **canonical scenario** traced through every participant. Each role sends specific **signed +operations**, is touched by specific **virtual operations**, and ends with a **tokens sent / received** +table for two outcomes: + +- **Normal resolve** — oracle resolves, grace passes, `pm_auto_payout` settles. No dispute. +- **Disputed resolve** — oracle resolves **A**, a dispute **overturns to B**, then settlement runs. + +All amounts are abstract **VIZ** units. All percents are **bp** (10000 = 100.00%). Settlement is strictly +**zero-sum** — no tokens are ever minted; `current_supply` is untouched: + +``` +Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal + == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, in a dispute) +``` + +## Canonical market **M** (binary CPMM, A vs B) + +| Item | Value | +|------|-------| +| Engine | binary CPMM (`x·y=k`), `weight = tokens_out` | +| Seed liquidity (marketmaker) | **2000** → reserves A=1000 / B=1000 | +| `oracle_fee_percent` (oracle quote) | **1000** (10%) | +| `creator_fee_percent` | **500** (5%) | +| `liquidity_fee_percent` | **500** (5%) | +| `oracle_fixed_fee` (oracle quote) | **10** | +| `dispute_penalty_percent` | **+10000** (slash up to 100% of insurance ×consensus) | + +Illustrative chain props: `pm_market_creation_fee` 5, `pm_oracle_registration_fee` 10, +`pm_min_oracle_insurance` 5000, `pm_dispute_fee` 1000, `pm_dispute_reward_multiplier` 30000 (**3×**), +`pm_no_contest_penalty_percent` 5000 (50% of dispute fee), `pm_oracle_penalty_percent` 500 (5% of +insurance on a missed deadline), `pm_lazy_emergency_penalty_percent` 5000 (50% of profit), +`pm_leverage_pool_profit_percent` **R = 10%**, `pm_lazy_alloc_percent` 2000 (20%). + +### Roster + +| Actor | Role | Stake / action | +|-------|------|----------------| +| **maker** | creator + first LP | seeds 2000 liquidity | +| **orac** | external oracle | insurance 5000; quotes fee 10% + fixed 10 | +| **LP1** | in-market liquidity provider | adds 1000 liquidity | +| **A** | bettor — winner | 100 on **A**, early; weight 100 | +| **C** | bettor — late winner | 100 on **A** at T+85%; weight 100; time-penalty **50%** | +| **B** | bettor — loser | 200 on **B**; weight 200 | +| **D** | leverage **×10** winner | collateral 10 + loan 90 (market **L**) | +| **E** | leverage **×5** liquidated | collateral 20 + loan 80 (market **L**) | +| **LZ1** | lazy-pool depositor | deposits 1000 | +| **disp** | disputer | escrows dispute fee 1000 | + +> Curve weights (100 / 100 / 200) are written explicitly to keep the parimutuel arithmetic readable; +> a real CPMM hands out slightly less weight as reserves shift. + +## Interaction diagrams + +**Market lifecycle.** + +```mermaid +flowchart LR + W["Waiting (0)"] -->|oracle accepts| A["Active (1)"] + W -->|oracle rejects| X["Deleted (-1)"] + A -->|betting_expiration| C["Closed (2)"] + A -->|early resolution| R["Resolved (3)"] + C -->|oracle resolves| R + R -->|grace, no dispute| P["Paid out"] + R -->|dispute filed| D["Disputed"] + D -->|finalize / resolver| P +``` + +**Settlement — NORMAL resolve (A wins).** Losers fund winners; LP principal is untouched (zero-sum). + +```mermaid +flowchart TD + B["B loses 200 (losers_pool)"] --> POOL{"split 200"} + POOL -->|oracle_fee 20 + fixed 10| OR["oracle +30"] + POOL -->|creator_fee 10| CR["creator +10"] + POOL -->|liq_fee 10 + penalty 37| LPS["LPs +47"] + POOL -->|winners_pool 150 → profit 75| A["A → payout 175"] + POOL -->|profit 75 − time-penalty 37| C["C → payout 138"] + MK["maker + LP1 principal 3000"] -.returned in full.-> MK +``` + +**Dispute — oracle said A, overturned to B.** The punishment is the insurance slash (separate money). + +```mermaid +sequenceDiagram + participant O as Oracle + participant D as Disputer + participant V as Committee / Resolver + O->>O: resolve A + D->>V: pm_dispute_create (escrow dispute_fee) + O-->>V: mandatory response (deadline) + V->>V: pm_dispute_vote / pm_dispute_resolve → overturn to B + V-->>O: insurance slashed (5000) + V-->>D: fee back + reward (2000 from slash) + V->>V: settlement re-runs → B wins +``` + +## Master ledger — NORMAL resolve (A wins) + +`losers_sum = 200` (B). Fees off the losers' pool: +`oracle_fee = 200×10% = 20`, `creator_fee = 200×5% = 10`, `liq_fee = 200×5% = 10`, `oracle_fixed = 10`. +`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`. `Σ winning weight = 200` (A 100 + C 100). + +- **A**: profit `150×100/200 = 75`, penalty 0 → **payout 175**. +- **C**: profit 75, time-penalty `75×50% = 37` (→ LP) → **payout 138**. +- **LP bonus** = `liq_fee 10 + penalties 37 = 47`, split by time in market: **maker ~31 / LP1 ~16**. +- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`. **creator_take** = `creator_fee 10`. + +| Actor | sends | receives | net (this market) | +|-------|-------|----------|-------------------| +| maker | 2000 liquidity + 5 creation-fee | 2000 principal + 10 creator-fee + 31 LP-bonus | **+36** | +| orac | (10 reg-fee, 5000 insurance locked) | 30 oracle-take | **+30** | +| LP1 | 1000 liquidity | 1000 principal + 16 LP-bonus | **+16** | +| A | 100 | 175 | **+75** | +| C | 100 | 138 | **+38** | +| B | 200 | 0 | **−200** | + +**Zero-sum:** in `= bets 400 + LP principal 3000 = 3400`; out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`. ✔ +The 5 creation-fee + 10 reg-fee leave to the **DAO fund** (not part of the market pool). + +## Master ledger — DISPUTED resolve (oracle said A → overturned to B) + +`disp` escrows `dispute_fee 1000`. The verdict overturns to **B**; the oracle is slashed. +With `dispute_penalty_percent = 10000` and consensus strength **100%**: `slash = 5000×100%×100% = 5000`. +Reward carve-out: `reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000` (≤ slash). Disputer gets +`fee 1000 + bonus 2000 = 3000`. Remainder `slash − bonus = 3000 → forfeit_pool`. + +Now **B wins**. `losers_sum = 200` (A 100 + C 100). Fees 20/10/10 + fixed 10. +`winners_pool = 200 − 50 + forfeit 3000 = 3150`. `Σ winning weight = 200` (B). +- **B**: profit `3150×200/200 = 3150` → **payout 3350**. +- **oracle_take** still `30` (the *market* fee is paid from the frozen config even when overturned — the + punishment is the **insurance slash**, separate money). **creator_take** 10. **LP bonus** = liq 10. + +| Actor | sends | receives | net (this market) | +|-------|-------|----------|-------------------| +| maker | 2000 + 5 | 2000 principal + 10 creator-fee + ~6 LP-bonus | **+11** | +| orac | insurance −**5000** slashed | 30 oracle-take | **−4970** | +| LP1 | 1000 | 1000 principal + ~4 LP-bonus | **+4** | +| A | 100 | 0 | **−100** | +| C | 100 | 0 | **−100** | +| B | 200 | 3350 | **+3150** | +| disp | 1000 dispute-fee | 3000 (fee back + 2000 reward) | **+2000** | + +**Zero-sum:** in `= bets 400 + LP principal 3000 + dispute_fee 1000 + insurance slash 5000 = 9400`; +out `= B 3350 + oracle 30 + creator 10 + lp_bonus 10 + LP principal 3000 + disputer 3000 = 9400`. ✔ +The slash 5000 splits into disputer bonus 2000 + forfeit 3000 (→ B via the winners' pool). + +## Implementation status (verified against the code) + +**Regular operations — all 21 present** in the `operation` variant (`operations.hpp`), validated + +evaluated in `pm_evaluator.cpp`: +`pm_oracle_register`, `pm_oracle_update`, `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, +`pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_add_liquidity`, `pm_withdraw_liquidity`, +`pm_resolve_market`, `pm_no_contest`, `pm_dispute_create`, `pm_dispute_vote`, `pm_dispute_resolve`, +`pm_transfer_position`, `pm_lazy_deposit`, `pm_lazy_withdraw`, `pm_leverage_open`, `pm_leverage_close`, +`pm_leverage_convert`. ✔ + +**Virtual operations** — emitted by `database::process_pm_markets()` / the evaluators: + +| Virtual op | Fires? | Trigger (code) | +|------------|--------|----------------| +| `pm_market_accepted` | ✔ | on `pm_oracle_accept_market` (accept) **and** self-oracle `pm_create_market` | +| `pm_payout` | ✔ | **per active bet** at settle — carries `account`, `market_id`, `bet_id`, `side`/`outcome_index`, `amount` (stake), `payout` (**0 on a loss**) | +| `pm_auto_payout` | ✔ | **once per market** at settle — a summary marker (`bets_sum`) alongside the per-bet `pm_payout`s | +| `pm_commit_forfeit` | ✔ | unrevealed commit past `reveal_deadline` | +| `pm_dispute_finalize` | ✔ | committee `voting_end_time` | +| `pm_dispute_auto_close` | ✔ | `auto_close_time` (anti-freeze) | +| `pm_oracle_missed_penalty` | ✔ | oracle missed `result_expiration` | +| `pm_lazy_recall` | ✔ | idle-allocation graduated recall step | +| `pm_batch_settle` | ✔ | epoch boundary | +| `pm_leverage_liquidate` | ✔ | mid-market liquidation: reason **0** opposing-bet, **1** cancel-bet (`cascade_liquidate`) | +| `pm_leverage_resolve` | ✔ | **settlement** of a leveraged position: carries `market_id`, `outcome_index`, `won` (solvent ⇒ positive), `pool_received`/`bettor_received`, and `leverage` (= `total_bet/collateral`) | + +> A leveraged position is force-closed at its `cancel_value` **as soon as new betting is impossible** — +> at `betting_expiration` (fixed-deadline markets, *before* the oracle resolves) or at resolve/void +> (open-ended markets). It is a price/sentiment bet, so it does **not** wait for the outcome: the pool +> takes `min(cv, obligation)`, the bettor gets the rest. **`pm_leverage_resolve`** marks that close +> (positive if `cv ≥ obligation`, else collateral lost); **`pm_leverage_liquidate`** is only for the +> *mid-market* opposing-bet / cancel-bet cascades. `settle_market` still force-closes as an idempotent backstop. + +See the [Plugin API](../plugins/prediction-market-api) for the read methods that surface each of these +(`get_account_leverage_positions`, `get_market_leverage_positions`, `get_creator_ban`, `get_dispute_votes`, …); +per-bettor results (`pm_payout`) and leverage settlements (`pm_leverage_resolve`) also appear in +`account_history`. + +## Roles in the canonical scenario + +Every participant traced through market **M** (and the leverage sub-market **L**): its interaction +diagram, the **signed** operations it sends, the **virtual** operations that touch it, a +**tokens sent / received** ledger for both outcomes, and a code-verification pointer. Each per-actor +ledger is a slice of the two [master ledgers](#master-ledger--normal-resolve-a-wins) above. + +### Market maker (creator + first LP) + +The maker creates market M, seeds the **2000** liquidity (becoming the first `pm_liquidity_object`), and +proposes the oracle's **offer ceiling**. It does **not** resolve (that's the oracle). + +```mermaid +flowchart LR + maker -->|pm_create_market| M[(pm_market_object
status=0)] + maker -->|seed 2000| LP0[(pm_liquidity_object
provider=maker)] + M -. fee 5 .-> DAO[(committee_fund)] + orac -->|pm_oracle_accept_market| M2[(M status=1)] + M2 -. VIRTUAL .-> VA[[pm_market_accepted]] + M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus] + RET --> maker +``` + +- **Sends:** `pm_create_market` (sets the `oracle_fee_percent`/`oracle_fixed_fee` **offer ceiling** plus + its own `creator_fee_percent` 5% and `liquidity_fee_percent` 5%; pays `pm_market_creation_fee` 5 → DAO, + locks `liquidity` 2000); optional `pm_add_liquidity` / `pm_withdraw_liquidity` (principal-safe, locked + from `betting_expiration` to resolution). +- **Touched by:** `pm_market_accepted` (oracle accepts, or self-oracle at creation); `pm_auto_payout` + (returns LP principal + time-weighted LP-bonus share). + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | 2000 liquidity + 5 creation-fee (→DAO) | 2000 principal + **creator_fee 10** + **LP-bonus ~31** | **+36** | +| disputed (→B) | 2000 + 5 | 2000 principal + creator_fee 10 + LP-bonus ~6 | **+11** | + +The creator fee is **still paid** from the frozen market config on an overturn — the dispute punishes the +**oracle** (insurance slash), not the maker. LP principal is returned unconditionally. + +- **Self-oracle variant:** `oracle == creator` → active at creation, `pm_market_accepted` fires with + `self_oracle=true`, and the maker also earns the `oracle_take`. +- **Verify:** `pm_create_market_evaluator`; LP via `settle_liquidity`; `committee_fund += pm_market_creation_fee`. + **Observe:** `get_market`, `list_markets_by_creator`, `get_market_liquidity` (`earned_fee`), `get_market_meta`. + +### Oracle (register → accept/quote → resolve) + +External oracle **orac** bonds insurance, **quotes** its fee at accept (≤ the maker's offer and ≤ +`pm_max_oracle_fee_percent`), and resolves. Its market fee is paid from the losers' pool; its bond is at +risk only on a missed deadline or a lost dispute. + +```mermaid +flowchart LR + orac -->|pm_oracle_register
insurance 5000| O[(pm_oracle_object)] + orac -. reg-fee 10 .-> DAO[(committee_fund)] + orac -->|pm_oracle_accept_market
quote fee 10% + fixed 10| M[(M status=1)] + M -. VIRTUAL .-> VA[[pm_market_accepted]] + orac -->|pm_resolve_market A| M3[(M status=3)] + M3 ==>|grace passes| VP[[pm_auto_payout]] + VP -->|oracle_take 30| orac +``` + +- **Sends:** `pm_oracle_register` (locks insurance 5000, pays reg-fee 10 → DAO, sets advisory list-price); + `pm_oracle_accept_market` (**quotes** fee 10% + fixed 10, each ≤ the creator's offer and ≤ the median + cap; freezes them onto M); `pm_resolve_market` (sets `winning_outcome`, opens the grace window); + optional `pm_oracle_update` / `pm_no_contest`. The standing list-price can also pre-authorise markets to + go live at creation via **auto-accept** — see the oracle ops doc. +- **Touched by:** `pm_market_accepted`; `pm_auto_payout` (credits `oracle_take`); + `pm_oracle_missed_penalty` (never resolves → slashes `pm_oracle_penalty_percent` of insurance → DAO, + refunds all bets). + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | insurance 5000 (locked) + reg-fee 10 (→DAO) | **oracle_take 30** = fee 20 + fixed 10 | **+30** | +| disputed (→B) | insurance −**5000 slashed** | oracle_take 30 | **−4970** | + +Even when overturned the oracle keeps the small **market fee** (frozen config); the punishment is the +**insurance slash**, split into the disputer's reward and the winners' `forfeit_pool`. Quoting **below** +the offer is allowed (price = reputation); quoting **above** is rejected. + +- **Verify:** `pm_oracle_register_evaluator`, `pm_oracle_accept_market_evaluator` (≤ offer, ≤ cap, freeze), + `process_pm_markets` missed-deadline scan. **Observe:** `get_oracle` (insurance, counters, reliability + score), `list_oracles`, `get_market` (frozen terms). + +### Oracle — upheld in a dispute (dispute winner) + +The oracle resolved **A**; a disputer challenged it but the verdict **upholds** A. The oracle keeps its +market fee **and** collects the forfeited `dispute_fee`; insurance untouched, `disputes_won++`. + +```mermaid +flowchart LR + orac -->|pm_resolve_market A| M[(market resolved A)] + disp -->|pm_dispute_create| D[(dispute)] + D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]] + FIN -->|dispute_fee 1000| orac + FIN --> AUTO[[pm_auto_payout settles A]] + AUTO -->|oracle_take 30| orac +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| disputed, upheld | insurance 5000 (**not** slashed) | oracle_take 30 + **dispute_fee 1000** | **+1030** | +| normal (no dispute) | insurance 5000 (locked) | oracle_take 30 | **+30** | + +The challenge backfires and **pays the oracle**. A *good-faith* market (`dispute_penalty_percent < 0`) +can even hand the oracle a fee bonus when the outcome is changed — recognising an honest mistake. +**Verify:** uphold branch of `pm_dispute_finalize` / `pm_dispute_resolve`. **Observe:** `get_oracle` +(`disputes_won`, higher score), `get_dispute`. + +### Oracle — overturned + slashed (dispute loser) + +The oracle resolved **A**; the dispute **overturns to B** and insurance is **slashed**. It still collects +the tiny frozen market fee (fee and punishment are separate money) but loses a large slice of bond and +reputation. + +```mermaid +flowchart LR + orac -->|pm_resolve_market A| M[(resolved A)] + disp -->|pm_dispute_create proposed=B| D[(dispute)] + D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]] + FIN -->|slash 5000| INS[oracle.insurance ↓] + INS --> SPLIT[bonus 2000 → disputer
3000 → forfeit_pool → B] + FIN --> AUTO[[pm_auto_payout settles B]] + AUTO -->|oracle_take 30| orac +``` + +`slash = insurance 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`, +**redistributed** not burned: `bonus 2000 →` disputer, `3000 → forfeit_pool →` the new winners (B). Net +**−4970** vs **+30** undisputed. Slash scales with **consensus strength** (`winning_rshares / +max_rshares`); `dispute_penalty_percent < 0` (good-faith) → **no** slash. A slashed oracle is often +**banned** too (next role). **Verify:** overturn branch of `pm_dispute_finalize` / `pm_dispute_resolve`; +fee still from `mkt.oracle_fee_percent`. **Observe:** `get_oracle` (`total_insurance_slashed`, +`banned_until`), `get_dispute`. + +### Banned oracle (and banned creator) + +A ban is a **status**, not a transfer: `pm_oracle_object.banned_until` (and, for creators, a +`pm_creator_ban_object`) blocks the actor from **new** markets until the timestamp passes. It usually +rides along with an overturn slash, but moves no tokens by itself. + +```mermaid +flowchart LR + resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object
banned_until = T)] + orac -->|pm_create_market / accept| CHK{now < banned_until?} + CHK -->|yes| REJ[REJECTED: 'Oracle is banned'] + CHK -->|no, expired| OK[allowed again] + resolver -->|ban_creator| CB[(pm_creator_ban_object)] + maker -->|pm_create_market| CHK2{banned?} + CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned'] +``` + +- **Who sets it:** account mode → `pm_dispute_resolve` (`ban_oracle`/`ban_creator` + `…_until`); committee + mode → `pm_dispute_finalize` scales a ban by consensus on overturn. `banned_until = + time_point_sec::maximum()` ⇒ **permanent**. +- **Tokens:** the ban itself is **0** (pure status); the accompanying slash is the overturn case above. + Insurance stays locked, refundable after the ban lifts and no active markets remain. +- Bans survive snapshots and are keyed by account — re-registration cannot wipe one. **Verify:** + `pm_create_market_evaluator` (`"Oracle is banned"` / `"Creator is banned"`). **Observe:** `get_oracle` + (`banned_until`, `bans_received`), **`get_creator_ban(account)`**. + +### Bettor A — early winner + +**A** stakes **100 on side A early** (no time penalty) and wins when M resolves to A. Payout = stake + +weight-proportional share of the winners' pool. + +```mermaid +flowchart LR + A -->|pm_place_bet side=A 100| BET[(pm_bet_object
weight 100)] + BET --> M[(market M
reserves shift)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|payout 175| A +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | 100 | **175** | **+75** | +| disputed (→B) | 100 | **0** | **−100** | + +`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`; no penalty → payout `100 + 75`. An overturn +makes A the **losing** side. Winnings come **only** from losers' stakes (+ forfeit pool), never emission. +**Sends:** `pm_place_bet` (`side=0`, instant); optional `pm_transfer_position` / `pm_cancel_bet`. +**Verify:** `pm_place_bet_evaluator`, `settle_market`. **Observe:** `get_account_positions` +(`expected_payout`), `get_market_weight_sums`; realized `pm_payout` in `account_history`. + +### Bettor B — loser + +**B** stakes **200 on side B**. When M resolves to **A**, B's stake funds the winners and B gets nothing. +In the disputed path B becomes the winner. + +```mermaid +flowchart LR + B -->|pm_place_bet side=B 200| BET[(pm_bet_object
status active)] + BET --> M[(market M)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|status=resolved, payout 0| BET +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | 200 | **0** | **−200** | +| disputed (→B) | 200 | **3350** | **+3150** | + +B's 200 **is** the `losers_sum` (pays the 40 fees + 150 winners' pool + LP bonus) — the parimutuel +"losers fund winners" rule. On overturn B wins and the oracle's forfeit 3000 is injected into B's pool +(`payout = 200 + 3150`). A losing bet is still recorded by a `pm_payout` with **payout=0**. **Verify:** +`settle_market` loser branch. **Observe:** `get_account_positions`, `get_market_bets`, `get_dispute`. + +### Bettor C — late winner (time penalty) + +**C** stakes **100 on side A** but **late** (T+85% of the betting window), so a **time penalty** docks +its *profit only* (not principal). Same weight as A, but earns less; the docked amount flows to the LPs. + +```mermaid +flowchart LR + C -->|pm_place_bet side=A 100
at T+85%| BET[(pm_bet_object
weight 100
time_penalty 50%)] + BET --> M[(market M)] + M ==>|grace passes| VP[[pm_auto_payout]] + VP -->|payout 138| C + VP -. penalty 37 .-> LPb[LP bonus] +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | 100 | **138** | **+38** | +| disputed (→B) | 100 | **0** | **−100** | + +`profit = 75`; `penalty = 75 × 50% = 37` (→ LPs); `payout = 100 + 75 − 37 = 138` — **−37** vs A's +75 for +the same weight. The node stamps `time_penalty` at placement from the market's penalty curve +(`time_penalty_type/value`, `penalty_curve_type`). It discourages last-second sniping and subsidises +liquidity, not the protocol. **Verify:** `pm_place_bet_evaluator` (curve eval), `compute_settlement`. +**Observe:** `get_account_positions` (`time_penalty`), `get_market_bets`. + +### Bettor D — leverage ×10 winner + +**D** opens a **×10** position: **10 collateral + 90 loan** from the [lazy pool](#the-lazy-liquidity-pool-system-object) += **100** on side A, in isolated leverage market **L** (`pm_leverage_enabled=true`, `R = 10%`). When A +wins, D keeps the upside on the full 100 after repaying loan + interest. +`pool_profit = loan 90 × R 10% = 9`; `obligation = 90 × 1.10 = 99`. + +```mermaid +flowchart LR + D -->|pm_leverage_open
collateral 10 + loan 90| POS[(pm_leverage_position
total_bet 100, obligation 99)] + POOL[(lazy pool)] -.loan 90.-> POS + POS --> L[(market L, side A)] + L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]] + VR -->|min(cv,obligation) 99| POOL + VR -->|cv 200 − 99 = 101| D +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | collateral **10** | cancel_value 200 − obligation 99 = **101** | **+91** | +| disputed (→B) | collateral 10 | 0 | **−10** | + +A profitable position closes at its `cancel_value`; the pool reclaims `obligation 99` (loan 90 + **9 +interest**), D keeps the rest on just 10 of its own → **+91** (pool **+9**). Leverage settles by +liquidation, **never** via `pm_auto_payout`. Zero-sum (L): in `10 + 90 + 100 = 200`; out `101 + 99 = +200`. **Sends:** `pm_leverage_open`; optional `pm_leverage_close` (only if `cv ≥ obligation`) / +`pm_leverage_convert`. **Touched by:** `pm_leverage_resolve` (settlement force-close, +`reason=expiration`). **Verify:** `force_close_positions` → `liquidate_position(reason=2)`. **Observe:** +**`get_account_leverage_positions`** / **`get_market_leverage_positions`**, `get_lazy_pool`. + +### Bettor E — leverage ×5 liquidated + +**E** opens a **×5** position: **20 collateral + 80 loan** = **100** on side B (market **L**, `R = 10%`). +Before resolution an **opposing bet** moves the curve against B; the **cascade liquidation** force-closes +it. **E loses its collateral, but the pool is always made whole.** `obligation = 80 × 1.10 = 88`. + +```mermaid +flowchart LR + E -->|pm_leverage_open
collateral 20 + loan 80| POS[(pm_leverage_position
obligation 88)] + POOL[(lazy pool)] -.loan 80.-> POS + X -->|pm_place_bet side=A| L[(market L)] + L ==>|cascade at PRE-bet reserves
cv 88 ≤ threshold| VL[[pm_leverage_liquidate
reason=opposing_bet]] + VL -->|pool_received 88 = loan 80 + profit 8| POOL + VL -->|bettor_received 0| E +``` + +E is liquidated **before** resolution, so the final A/B result (disputed or not) doesn't change it: + +| actor | sends | receives | net | +|-------|-------|----------|-----| +| **E** | collateral **20** | **0** | **−20** | +| **pool** | loan 80 | **88** (loan 80 + R% 8) | **+8** | + +Opposing-bet liquidations run at **pre-bet** reserves where `cancel_value ≥ loan`, so `pool_received = +min(cv, obligation)` returns at least the loan — the pool **never** loses. + +> **The only path that can go negative** is a same-side **`pm_cancel_bet` (Case B)**: a cancel reverses a +> *prior, large* same-side bet (beyond the per-bet slippage cap) and, for fairness to the cancel-bettor, +> executes **first** at their submitted price — so the cascade can land `cancel_value < loan`: +> `shortfall = obligation − cancel_value`, `lazy_pool.free_balance −= shortfall`. This **bad debt** is +> **bounded** (`≤ cancel_value_before × SL%`) and **rare** (the pool's R% on every other position offsets +> it). Covered by `leverage_cancel_bet_cascade_bad_debt`. + +Pool protection is structural (`max_per_position`, `max_position_ratio`, `safety_margin`, the slippage +cap, the `expiration_buffer`). `pm_leverage_enabled=false` blocks **new** opens only — the liquidation +cascade is **not** gated by the flag, so governance can never strip the pool's protection mid-flight +(`leverage_disabled_keeps_liquidation_protection`). **Verify:** `pm_place_bet` → +`cascade_liquidate(reason=0)`; `pm_cancel_bet` → `cascade_liquidate(reason=1)`; `liquidate_position`. +**Observe:** **`get_account_leverage_positions`** (`status=1`, `pool_received`, `bettor_received`), +`get_lazy_pool`. + +### Liquidity provider in the market (LP1) + +**LP1** adds **1000** liquidity to active M (after the maker's seed). Principal is **always** returned; +on top it earns a **time-weighted** slice of the LP bonus (liquidity fee + time penalties + dust). +Distinct from a [lazy-pool provider](#liquidity-provider-in-the-lazy-pool-lz1), who deposits once and is +auto-allocated across many markets. + +```mermaid +flowchart LR + LP1 -->|pm_add_liquidity 1000| L1[(pm_liquidity_object
provider=LP1)] + L1 --> M[(market M reserves)] + M ==>|settle| SL[[settle_liquidity]] + SL -->|principal 1000 + bonus ~16| LP1 + LP1 -->|pm_withdraw_liquidity
after resolution| OUT[principal-safe exit] +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| normal (A) | 1000 | **1000 principal + ~16 bonus** | **+16** | +| disputed (→B) | 1000 | 1000 principal + ~4 bonus | **+4** | + +LP bonus pool = `liq_fee 10 + time-penalties 37 = 47`, split by `principal × seconds-in-market` (earlier +maker ~31, later LP1 ~16). The **principal guarantee** is architectural — the seed is returned before any +winner is paid; an LP can only forgo bonus, never lose principal. Withdrawing is locked from +`betting_expiration` until resolution. **Verify:** `pm_add_liquidity_evaluator` (records `deposit_time`), +`settle_liquidity` → `distribute_lp`. **Observe:** `get_market_liquidity` (`earned_fee`), +`get_market_weight_sums`. + +### The Lazy Liquidity Pool (system object) + +A **singleton** `pm_lazy_pool_object` — not an account. Depositors fund it once; the pool +**auto-allocates** a slice to each accepted market as a silent LP (`pm_liquidity_object` with empty +`provider`), **funds leverage loans**, and **recalls** idle allocations. It earns LP yield + leverage +interest, accounted MasterChef-style (one global `reward_per_share`, O(1) — see the whitepaper). +Fields: `total_shares`, `free_balance`, `allocated_balance`, `earned_balance`, `reward_per_share`, +`leverage_fund_used`. + +```mermaid +flowchart TD + LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool
free 1000 / shares 1000)] + POOL ==>|on market accept
alloc 20% = 200| ALLOC[(pm_lazy_allocation
+ pm_liquidity provider=∅)] + ALLOC -->|market settles| YLD[route_pool_lp_return
principal 200 + yield 20] + YLD --> POOL + POOL -->|leverage loan 90| Dpos[D position] + Dpos -->|close/resolve: 90 + interest 9| POOL + POOL -. idle market .-> VR[[pm_lazy_recall]] + VR -->|step back to free| POOL +``` + +| pool money flow | effect | +|-----------------|--------| +| `pm_lazy_deposit` | `free_balance += amount`, mint shares | +| auto allocation (on accept) | `free → allocated` (silent LP) | +| market settles | `route_pool_lp_return`: principal + yield → `free`; yield → `earned` & `reward_per_share` | +| leverage open (D/E) | `free −= loan`, `leverage_fund_used += loan` | +| leverage close / resolve / opposing-bet liquidation | `min(cv, obligation) → free`; `cv ≥ loan` ⇒ **never a loss** | +| cancel-bet liquidation (Case B only) | recovers `cv` which **may be < loan** → bounded **bad debt** | +| `pm_lazy_recall` (idle market) | one 10% step of an idle allocation → `free` | +| `pm_lazy_withdraw` | burn shares → principal + pending; emergency penalty stays in pool | + +Over the canonical scenario the pool nets **+37 earned** (market M yield +20, leverage D interest +9, +leverage E opposing-bet recovery +8). As a market LP its principal is returned unconditionally; only the +*bonus* yield varies with a dispute. + +> The pool serves **both** roles from a single `free_balance`: market-LP allocations +> (`maybe_allocate_lazy`) and leverage loans (`leverage_fund_used` caps the latter). All leverage knobs +> are checked **at `pm_leverage_open` time** against the current median, so later property swings only +> affect *new* opens, never loans already out. + +VIZ in the pool is **liquid**, not vested → **no** validator-scheduling or committee-request weight. +**Exception (HF14):** for **PM committee disputes** a depositor's pool stake **is** counted — converted +to vesting-shares via `get_vesting_share_price()` and added to their `pm_dispute_vote` weight (see +[committee resolver](#resolver--committee-stake-weighted-dispute_mode--0)). **Verify:** +`apply_hardfork(CHAIN_HARDFORK_14)` (singleton), `maybe_allocate_lazy`, `route_pool_lp_return`. +**Observe:** `get_lazy_pool`. + +### Liquidity provider in the lazy pool (LZ1) + +**LZ1** deposits **1000** into the pool **once** and lets it spread across markets + leverage loans. It +earns a share of the pool's aggregate yield (`reward_per_share`), not any single market's outcome. Two +exits: **planned** (after the lock) and **emergency** (before the lock, with a penalty on *profit*). + +```mermaid +flowchart LR + LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object
shares 1000, unlock=+7d)] + DEP --> POOL[(lazy pool)] + POOL -. yield accrues .-> RPS[reward_per_share ↑] + LZ1 -->|pm_lazy_withdraw| OUT{planned or
emergency?} + OUT -->|planned, t≥unlock| P[principal 1000 + pending 29] + OUT -->|emergency, t|pm_dispute_create proposed=B
escrow fee 1000| D[(pm_dispute_object
status open)] + D --> VOTE{committee vote
or account resolve} + VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]] + FIN -->|fee 1000 + bonus 2000| disp + FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp
3000 → forfeit_pool → winners] +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| disputed, overturned (the "win") | dispute_fee **1000** | fee 1000 back + **bonus 2000** | **+2000** | + +`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`, **capped +at the actual slash**; the remainder (3000) → `forfeit_pool` → the new winners. disp risked 1000, walks +away **+2000**. (Committee mode: disp does **not** vote on its own — the SHARES electorate does.) +**Verify:** `pm_dispute_create_evaluator`, overturn branch of `pm_dispute_finalize`/`pm_dispute_resolve`. +**Observe:** `get_dispute`, `get_dispute_votes`. + +### Disputer — fee forfeited (oracle upheld) + +**disp** disputes the oracle's **A** but the verdict **upholds the oracle**. The escrowed fee is +**forfeited to the oracle** as compensation, and the market settles as originally resolved (A wins). + +```mermaid +flowchart LR + disp -->|pm_dispute_create proposed=B
escrow fee 1000| D[(pm_dispute_object)] + D --> VOTE{committee vote
or account resolve} + VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]] + FIN -->|dispute_fee 1000| orac[oracle compensation] + FIN -->|market settles as A| AUTO[[pm_auto_payout]] +``` + +| outcome | sends | receives | net | +|---------|-------|----------|-----| +| disputed, upheld (the "loss") | dispute_fee **1000** | **0** | **−1000** | + +The fee is the disputer's skin-in-the-game: a wrong/frivolous dispute pays the oracle. This asymmetry +(lose the fee if wrong, win a multiple if right) keeps the channel honest. A dispute that is never +**decided** (oracle silent / no quorum) is force-closed and the fee **returned** (net 0) — see +[dispute auto-close](#dispute-forced-to-end-anti-freeze-auto-close), which is different from losing on +the merits. **Verify:** uphold branch of `pm_dispute_finalize`/`pm_dispute_resolve`. **Observe:** +`get_dispute`, `get_oracle` (gains the fee, `disputes_won++`). + +### Resolver — committee (stake-weighted, dispute_mode = 0) + +The *whole SHARES electorate* decides by **stake-weighted vote**; no single resolver account. The verdict +is tallied deterministically by `pm_dispute_finalize` at `voting_end_time`. + +**Voting weight** = live **`effective_vesting_shares`** (`vesting − delegated + received`) **plus +lazy-pool stake converted to vesting-shares**, since many DAO members park VIZ in the pool (where it is +liquid): + +``` +pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares +pool_weight = pool_claim_viz × get_vesting_share_price() +voter_weight = effective_vesting_shares + pool_weight +``` + +The participation quorum denominator is likewise `total_vesting_shares + (pool_NAV → vesting-shares)`. The +7-day deposit lock prevents deposit-vote-withdraw gaming. + +```mermaid +flowchart LR + V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)] + V2[voter · eff_vshares] -->|pm_dispute_vote| D + D ==>|voting_end_time| FIN[[pm_dispute_finalize]] + FIN -->|argmax rshares,
threshold check| VERDICT{uphold / overturn} + VERDICT -->|consensus_strength
scales slash & bans| OUT[settle] +``` + +- **Sends (voters):** `pm_dispute_vote` — **auth `regular`**. `vote_outcome = -1` upholds, else proposes + the correct outcome; `vote_percent ∈ [-10000, 10000]`. A voter may **revise** their ballot any number + of times while voting is open — a repeat vote **overwrites** the prior (latest wins, no "Already voted"). +- **No commit-reveal — deliberate, will NOT change.** A committee dispute is an **open public hearing**: + the running tally is visible (`get_dispute_votes`) and votes are not hidden. The DAO's value + proposition is resolving disputes as truthfully and transparently as possible; new evidence surfaces + during voting and voters are *expected* to update; and voters are **not paid** for matching the + majority, so the usual anti-herding (beauty-contest) rationale for commit-reveal does not apply. + +| actor | sends | receives | +|-------|-------|----------| +| each voter | 0 | **0** — voting is a governance duty, not a paid action | + +Voters never receive tokens; influence is pure stake weight. Economic flows land on the disputer, oracle, +and bettors per the [disputed master ledger](#master-ledger--disputed-resolve-oracle-said-a--overturned-to-b). +Niche markets may fail the threshold → fall through to +[dispute auto-close](#dispute-forced-to-end-anti-freeze-auto-close). **Verify:** `pm_dispute_vote_evaluator` +(modify-or-create on `by_market_voter`); `pm_dispute_finalize` (`lazy_vote_weight`, `get_vesting_share_price`, +quorum, argmax, `consensus_strength`). **Observe:** `get_dispute_votes` (live tally + finalize projection: +`quorum_percent_bp`, `expected_uphold`, `expected_outcome`, `expected_consensus_strength_bp`). Tests: +`committee_dispute_lazy_pool_voting_weight`, `committee_dispute_flips_outcome`. + +### Resolver — single account (centralized, dispute_mode = 1) + +The market names one `dispute_resolver` account (e.g. a regulator multisig) that decides alone — **no +stake weight, no DAO vote**. Set at creation, it must differ from both `oracle` and `creator` (anti +self-judging). Same op set as committee mode; only *who decides* differs. + +```mermaid +flowchart LR + disp -->|pm_dispute_create| D[(dispute, mode=1)] + resolver -->|pm_dispute_resolve
correct_outcome=B
penalty_amount, ban flags| FIN[[verdict]] + FIN -->|slash penalty_amount| orac[oracle.insurance ↓] + FIN -->|fee + reward| disp + FIN --> AUTO[[pm_auto_payout settles B]] +``` + +- **Sends:** `pm_dispute_resolve` — **auth `active` of the named `dispute_resolver`** only: + `correct_outcome`, `penalty_amount` (insurance to slash — a fixed amount, **not** stake-scaled), + `ban_oracle`/`ban_creator` (+ `…_until`). + +| actor | sends | receives | +|-------|-------|----------| +| resolver | 0 | **0** — a neutral arbiter | + +The post-verdict canon is identical to committee mode; only the slash size differs (resolver-set +`penalty_amount`, no `consensus_strength` scaling, since there is a single decider). KYC/whitelisting of +the resolver is a **client-layer** concern. **Verify:** `pm_dispute_resolve_evaluator` (only the named +resolver, `dispute_mode==1`). **Observe:** `get_dispute`, `get_oracle`, **`get_creator_ban(account)`**. + +### Dispute forced to end (anti-freeze auto-close) + +A dispute that is **never decided** — oracle silent and (committee) no quorum — cannot freeze the market +forever. At `auto_close_time` the `pm_dispute_auto_close` processor force-ends it: **everyone is +refunded**, the disputer's fee is **returned**, and the unresponsive oracle is penalised. No winner is +picked. + +```mermaid +flowchart LR + disp -->|pm_dispute_create
escrow fee 1000| D[(dispute, status open)] + D -. oracle silent / no quorum .-> WAIT[auto_close_time reached] + WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]] + AC -->|refund all bets| bettors + AC -->|fee 1000 back| disp + AC -->|insurance slash → DAO| orac +``` + +| actor | sends | receives | net | +|-------|-------|----------|-----| +| A / B / C | bet | full refund | **0** | +| maker / LP1 | liquidity | principal back | **0** (no bonus) | +| disp | dispute_fee 1000 | **1000 back** | **0** | +| orac | insurance −slash → DAO | — | **− slash** | + +This is **not** "the disputer lost": a returned fee (net 0) differs from a forfeited fee +([disputer-loser](#disputer--fee-forfeited-oracle-upheld), net −1000). Nobody profits; the market is +voided to break the freeze, the cost falls on the oracle that didn't respond. The same void-and-refund +shape covers `pm_oracle_missed_penalty` and `pm_no_contest`. Tune `pm_dispute_auto_close_sec` (14 d) vs +`pm_dispute_vote_period_sec` (3 d) so honest disputes resolve first. **Verify:** `process_pm_markets` +auto-close scan (`refund_all_bets` + `return_liquidity` + fee credit; `disputes_auto_closed++`). +**Observe:** `get_dispute` (status → auto-closed), `get_market`, `get_oracle`. diff --git a/docs/protocol/operations/overview.md b/docs/protocol/operations/overview.md index 6b695bc64d..41fa9eeffd 100644 --- a/docs/protocol/operations/overview.md +++ b/docs/protocol/operations/overview.md @@ -52,6 +52,32 @@ These are user-initiated operations that can be broadcast to the network. | 58 | `use_invite_balance_operation` | active | [Invites](./invites.md) | | 60 | `fixed_award_operation` | regular | [Awards](./awards.md) | | 61 | `target_account_sale_operation` | master | [Account Market](./account-market.md) | +| 64 | `set_reward_sharing_operation` | active | [Validators](./validators.md) | +| 66 | `pm_oracle_register_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 67 | `pm_oracle_update_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 68 | `pm_create_market_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 69 | `pm_oracle_accept_market_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 70 | `pm_place_bet_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 71 | `pm_commit_bet_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 72 | `pm_reveal_bet_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 73 | `pm_cancel_bet_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 74 | `pm_add_liquidity_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 75 | `pm_withdraw_liquidity_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 76 | `pm_resolve_market_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 77 | `pm_no_contest_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 78 | `pm_dispute_create_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 79 | `pm_dispute_vote_operation` | regular | [Prediction Markets](./prediction-markets.md) | +| 80 | `pm_dispute_resolve_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 81 | `pm_transfer_position_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 82 | `pm_lazy_deposit_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 83 | `pm_lazy_withdraw_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 91 | `pm_leverage_open_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 92 | `pm_leverage_close_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 93 | `pm_leverage_convert_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 98 | `pm_dispute_oracle_respond_operation` | active | [Prediction Markets](./prediction-markets.md) | +| 99 | `pm_unban_operation` | active | [Prediction Markets](./prediction-markets.md) | + +> IDs are the fixed index in the chain's single `operation` variant (append-only). Gaps in this table are **virtual** operations (below) interleaved by ID — e.g. 62–63, 65, 84–90, 94–97, 100. --- @@ -83,6 +109,19 @@ Virtual operations are generated by the blockchain itself during block processin | 59 | `expire_escrow_ratification_operation` | Escrow deadline missed | [Virtual Ops](../virtual-operations.md) | | 62 | `bid_operation` | Auction bid placed | [Virtual Ops](../virtual-operations.md) | | 63 | `outbid_operation` | Auction outbid | [Virtual Ops](../virtual-operations.md) | +| 65 | `stakeholder_reward_operation` | Reward-sharing payout to a stakeholder | [Validators](./validators.md) | +| 84 | `pm_batch_settle_operation` | Batch epoch boundary settled | [Prediction Markets](./prediction-markets.md) | +| 85 | `pm_commit_forfeit_operation` | Commit-reveal escrow forfeited (unrevealed) | [Prediction Markets](./prediction-markets.md) | +| 86 | `pm_auto_payout_operation` | Market settled (per-market payout marker) | [Prediction Markets](./prediction-markets.md) | +| 87 | `pm_dispute_finalize_operation` | Committee vote tallied | [Prediction Markets](./prediction-markets.md) | +| 88 | `pm_dispute_auto_close_operation` | Dispute anti-freeze auto-close | [Prediction Markets](./prediction-markets.md) | +| 89 | `pm_oracle_missed_penalty_operation` | Oracle missed resolution deadline | [Prediction Markets](./prediction-markets.md) | +| 90 | `pm_lazy_recall_operation` | Lazy-pool graduated recall step | [Prediction Markets](./prediction-markets.md) | +| 94 | `pm_leverage_liquidate_operation` | Leveraged position liquidated | [Prediction Markets](./prediction-markets.md) | +| 95 | `pm_leverage_resolve_operation` | Leveraged position settled at resolution | [Prediction Markets](./prediction-markets.md) | +| 96 | `pm_market_accepted_operation` | Market went live (oracle accepted / self / auto) | [Prediction Markets](./prediction-markets.md) | +| 97 | `pm_payout_operation` | Per-bettor parimutuel payout | [Prediction Markets](./prediction-markets.md) | +| 100 | `pm_ban_expired_operation` | Temporary oracle/creator ban lapsed | [Prediction Markets](./prediction-markets.md) | --- diff --git a/docs/protocol/operations/prediction-markets.md b/docs/protocol/operations/prediction-markets.md index bf977a9d18..33e93e533f 100644 --- a/docs/protocol/operations/prediction-markets.md +++ b/docs/protocol/operations/prediction-markets.md @@ -106,6 +106,8 @@ Creates a market; the creator seeds the first liquidity and becomes the first LP Oracle accepts (`status → active`) or rejects (liquidity refunded to creator; `status → deleted`) a pending market. On accept the oracle **quotes its actual terms** via `oracle_fee_percent` + `oracle_fixed_fee` — each must be `≤` the creator's offer on the market and `oracle_fee_percent ≤ pm_max_oracle_fee_percent`. The quote is **frozen onto the market** and a `pm_market_accepted` virtual op is emitted (so history parsers see the launch + terms). Settlement later reads only these frozen fields — never the live median. +The oracle must act within `pm_oracle_accept_window_sec` (default 1h) of creation. If it does neither by the market's `accept_deadline`, the per-block cron voids the market (`status → deleted`), refunds the creator's seed liquidity (**not** the non-refundable creation fee), and emits `pm_market_expired` (see Virtual operations). + | Field | Type | Description | |-------|------|-------------| | `market_id` | `int64` | Pending market | @@ -155,7 +157,14 @@ Withdraws liquidity (principal-safe). Locked from `betting_expiration` until res ### `pm_resolve_market_operation` (ID 76) **Auth:** `active` of `oracle` -Oracle resolves to `winning_outcome`. Opens the dispute grace window (`result_expiration + pm_dispute_grace_sec`); after it elapses `pm_auto_payout` settles. +Oracle resolves to `winning_outcome`. Opens the dispute grace window (`result_expiration + pm_dispute_grace_sec`); after it elapses `pm_auto_payout` settles. The oracle's resolution statement is **stored on the market** (like an oracle's `rules_url`) so a client can read it directly via `get_market` without scanning history. + +| Field | Type | Description | +|-------|------|-------------| +| `market_id` | `int64` | Target market | +| `winning_outcome` | `int16_t` | Winning outcome index | +| `decision_url` | `string` | Evidence link, `≤ MAX_PM_DECISION_URL_LEN`; stored on the market | +| `decision_reason` | `string` | Free-text justification, `≤ MAX_PM_DISPUTE_REASON_LEN`; stored on the market (`decision_reason`) | ### `pm_no_contest_operation` (ID 77) **Auth:** `active` of `oracle` @@ -177,7 +186,39 @@ A committee dispute is an **open public hearing** — there is **no commit-revea ### `pm_dispute_resolve_operation` (ID 80) **Auth:** `active` of `resolver` -Account-mode verdict by the market's configured `dispute_resolver`. May slash `penalty_amount` of insurance and ban the oracle/creator until the given times. +Account-mode verdict by the market's configured `dispute_resolver`. May slash `penalty_amount` of insurance and ban the oracle/creator until the given times (`ban_*_until = time_point_sec::maximum()` = permanent). + +> **Bans are a compliance/regulator feature, exclusive to account mode.** When a market routes its disputes to an account-mode `dispute_resolver` (e.g. a regulator or a licensed arbitrator), that resolver can sanction **both the oracle and the market creator** — temporarily or permanently — in the same verdict, on top of the insurance slash: it lets a regulator serving as resolver bar a bad-faith oracle or a repeat-offender creator from the platform. **Committee/DAO mode (`dispute_mode == 0`) has no ban power by design** — it is a transparent public hearing that only slashes insurance and adjusts reputation (`pm_dispute_finalize`), never bans. A ban set here records the issuing `resolver` in the target's `banned_by`, so only that resolver may lift it early via `pm_unban`; otherwise it lapses at `banned_until` (the cron emits `pm_ban_expired`). + +| Field | Type | Description | +|-------|------|-------------| +| `market_id` | `int64` | Disputed market | +| `correct_outcome` | `int16_t` | Final correct outcome (`-1` = void/no-contest) | +| `penalty_amount` | `asset` (VIZ) | Oracle insurance to slash | +| `ban_oracle` / `ban_oracle_until` | `bool` / `time_point_sec` | Ban the oracle until the given time | +| `ban_creator` / `ban_creator_until` | `bool` / `time_point_sec` | Ban the creator from creating markets until the given time | + +### `pm_dispute_oracle_respond_operation` (ID 98) +**Auth:** `active` of `oracle` + +The market's oracle posts a **public rebuttal** onto an open dispute. Because a dispute is a public hearing, the text is stored on the dispute object (`oracle_response` / `oracle_response_time`, readable via `get_dispute`) so every voter/resolver can weigh it. Allowed only while the dispute is open and `now ≤ oracle_response_deadline`; re-posting overwrites the previous response. + +| Field | Type | Description | +|-------|------|-------------| +| `market_id` | `int64` | Disputed market | +| `response` | `string` | Rebuttal text, non-empty, `≤ MAX_PM_DISPUTE_REASON_LEN` | + +### `pm_unban_operation` (ID 99) +**Auth:** `active` of `resolver` + +Lifts a ban imposed by an account-mode `pm_dispute_resolve` **early**. Only the account recorded in the target's `banned_by` (the resolver that set the ban) may lift it; at least one of `unban_oracle` / `unban_creator` must be set, and the corresponding ban must currently be active. Sets `banned_until` to a past time and clears `banned_by`. (Bans not lifted here simply expire at `banned_until` — the cron then emits `pm_ban_expired`.) + +| Field | Type | Description | +|-------|------|-------------| +| `resolver` | `account_name_type` | The account that imposed the ban (must equal the target's `banned_by`) | +| `target` | `account_name_type` | The banned oracle / creator | +| `unban_oracle` | `bool` | Clear the oracle ban (`pm_oracle_object.banned_until`) | +| `unban_creator` | `bool` | Clear the creator ban (`pm_creator_ban_object.banned_until`) | ### `pm_transfer_position_operation` (ID 81) **Auth:** `active` of `from` @@ -213,9 +254,12 @@ Emitted by the PM consensus logic — either by a signed operation's evaluator ( | 95 | `pm_leverage_resolve_operation` | Settlement — leveraged position force-closed: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` | | 96 | `pm_market_accepted_operation` | Evaluator — market went live: oracle accepted, self-oracle, or auto-accept; frozen oracle terms + `self_oracle` | | 97 | `pm_payout_operation` | Settlement — per active bet: `amount` (stake), `side`/`outcome_index`, `payout` (**0 on a loss**) | +| 100 | `pm_ban_expired_operation` | A temporary oracle/creator ban lapsed at `banned_until`: the cron cleared it (fields `account`, `oracle`, `creator`). Early manual lifts use the signed `pm_unban` instead | +| 101 | `pm_market_expired_operation` | A pending market's `accept_deadline` passed: the oracle never accepted/rejected within `pm_oracle_accept_window_sec` — market voided, seed refunded (creation fee kept). Fields `oracle`, `creator`, `market_id`, `refunded_liquidity` | > IDs 91–93 are the *regular* ops `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` (see the -> spec). Per-bettor results are `pm_payout`; the per-market `pm_auto_payout` remains a settlement marker. +> spec); IDs 98–99 are the *regular* ops `pm_dispute_oracle_respond`/`pm_unban` (above). Per-bettor +> results are `pm_payout`; the per-market `pm_auto_payout` remains a settlement marker. --- diff --git a/docs/protocol/operations/validators.md b/docs/protocol/operations/validators.md index 5089314540..599325782a 100644 --- a/docs/protocol/operations/validators.md +++ b/docs/protocol/operations/validators.md @@ -163,4 +163,39 @@ Delegates all validator voting to a proxy account. All existing direct votes are --- +## `set_reward_sharing_operation` (ID 64) + +**Auth:** `active` of `owner` + +**HF13 validator reward sharing.** A validator opts to forward a fraction of its block reward to its **stakeholders** — the accounts that voted for it — proportionally to time-weighted vote weight. `sharing_rate` is that fraction in basis points; the shared pool accumulates and is distributed at each epoch end via the `stakeholder_reward` virtual op. + +| Field | Type | Description | +|-------|------|-------------| +| `owner` | `account_name_type` | Validator setting its sharing rate | +| `sharing_rate` | `uint16_t` | Fraction of block reward forwarded to stakeholders, in basis points (0 = none, 10000 = 100%) | + +```json +[64, { + "owner": "alice", + "sharing_rate": 2500 +}] +``` + +- `sharing_rate` is capped at 10000 (100%). +- Distribution is by **time-weighted** vote weight, so recently-added votes earn a smaller share until they mature. + +--- + +## `stakeholder_reward_operation` (ID 65) — virtual + +Emitted at each distribution epoch when a validator with a non-zero `sharing_rate` pays out a stakeholder's share of the shared block reward. Virtual (never signed); appears in `account_history`. + +| Field | Type | Description | +|-------|------|-------------| +| `validator` | `account_name_type` | The validator that shared the reward | +| `stakeholder` | `account_name_type` | The voter receiving a share | +| `shares` | `asset` (SHARES) | Amount credited to the stakeholder | + +--- + See also: [Data Types](../data-types.md), [Operations Overview](./overview.md), [Chain Properties](../../governance/chain-properties.md). diff --git a/docs/protocol/virtual-operations.md b/docs/protocol/virtual-operations.md index 6e38c7b3d7..b0ac23d5b7 100644 --- a/docs/protocol/virtual-operations.md +++ b/docs/protocol/virtual-operations.md @@ -351,4 +351,32 @@ All locked funds return to `from`. --- +## Prediction Markets (HF14) + +Emitted by the PM consensus logic — **not** a wall-clock cron. Two sources: +- A **signed operation's evaluator**, at the instant it applies — `pm_market_accepted` (on accept / self-oracle / auto-accept) and `pm_leverage_liquidate` (on an opposing- or cancel-bet that pushes a leveraged position under threshold). +- The **deadline processor `process_pm_markets()`**, run each block: it settles markets that have reached an **expiration / deadline / dispute-grace / epoch boundary** (bounded at `pm_processing_cap_per_block`, oldest-deadline-first). + +See [Prediction Market Operations](./operations/prediction-markets.md). (IDs 91–93 are the *regular* ops `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`, and IDs 98–99 the *regular* ops `pm_dispute_oracle_respond`/`pm_unban` — see that page.) + +| ID | Operation | Trigger | +|----|-----------|---------| +| 84 | `pm_batch_settle_operation` | Epoch boundary reached: queued bets executed on the epoch-open snapshot | +| 85 | `pm_commit_forfeit_operation` | `reveal_deadline` passed unrevealed: penalty → `forfeit_pool`, rest refunded | +| 86 | `pm_auto_payout_operation` | Dispute grace elapsed (per market): parimutuel settlement + LP principal returned | +| 87 | `pm_dispute_finalize_operation` | Committee `voting_end_time` reached: tally decides; oracle penalty; re-resolved/upheld | +| 88 | `pm_dispute_auto_close_operation` | `auto_close_time` reached, oracle never responded: anti-freeze refund, insurance slashed → DAO | +| 89 | `pm_oracle_missed_penalty_operation` | `result_expiration` passed unresolved: insurance slashed → DAO, all bets refunded | +| 90 | `pm_lazy_recall_operation` | An idle lazy-pool allocation reached a recall step: one graduated step returned to the pool | +| 94 | `pm_leverage_liquidate_operation` | Evaluator — mid-market leverage liquidation (opposing-bet `0` / cancel-bet `1` cascade) | +| 95 | `pm_leverage_resolve_operation` | Settlement — leveraged position force-closed at `cancel_value`: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` | +| 96 | `pm_market_accepted_operation` | Evaluator — market went live: oracle accepted, self-oracle, or auto-accept; frozen oracle terms + `self_oracle` | +| 97 | `pm_payout_operation` | Settlement — per active bet: `amount` (stake), `side`/`outcome_index`, `payout` (**0 on a loss**); alongside the per-market `pm_auto_payout` | +| 100 | `pm_ban_expired_operation` | A temporary oracle/creator ban lapsed at `banned_until`: the cron cleared it (`account`, `oracle`, `creator`). Early manual lifts use the signed `pm_unban` instead | +| 101 | `pm_market_expired_operation` | `accept_deadline` passed on a pending market: the named oracle never accepted/rejected within `pm_oracle_accept_window_sec` — market voided (`status -1`), seed liquidity refunded (`refunded_liquidity`), creation fee **not** refunded (`oracle`, `creator`, `market_id`, `refunded_liquidity`) | + +All PM money movement is strictly zero-sum (no emission); settlement conserves `Σ out == Σ bets + LP principal + forfeit_pool`. + +--- + See also: [Operations Overview](./operations/overview.md), [Awards](./operations/awards.md), [Committee](./operations/committee.md). diff --git a/documentation/api_notes.md b/documentation/api_notes.md deleted file mode 100644 index 607633d79c..0000000000 --- a/documentation/api_notes.md +++ /dev/null @@ -1,120 +0,0 @@ - -What is an API --------------- - -An API is a set of related methods, accessible over Websocket / HTTP and provided by a single C++ class. API's exist on a per-connection basis. - -Compiling with hello_api plugin -------------------------------- - -We will use the `hello_api` example plugin. To follow along with the examples, you may enable the `hello_api` example plugin by running the following command and recompiling: - - ln -s ../example_plugins/hello_api external_plugins/hello_api - -Publicly available API's ------------------------- - -Some API's are public API's which are available to clients without login. These API's may be specified as follows in the configuration file: - - public-api = database_api login_api hello_api_api - -Note, `hello_api` is the name of the plugin and `hello_api_api` is the name of the API it provides. This slightly confusing naming may be revised in a later version of the plugin. - -You may also specify `--public-api` on the command line. - -Server configuration note: If you customize `public-api` configuration, you should set the first two API's to `database_api` and `login_api`. - -For experts: The reason for recommending `database_api` and `login_api` to be the first two API's is that many clients expect these to be first two API's. -Additionally, the string API identifier feature's FC implementation requires a working `get_api_by_name` method to be available on API ID 1, so effectively -any client that uses string API identifiers (as recommended) requires `login_api` to be on API ID 1 even if no client uses the login feature. - -Numeric API identifiers ------------------------ - -API's are assigned numeric ID's in the order they are specified as public API's. So for example you can access `hello_api_api` over HTTP like this: - - curl --data '{"jsonrpc": "2.0", "params": [2, "get_message", []], "id":1, "method":"call"}' http://127.0.0.1:8090/rpc - -The number `2` is the numeric API identifier for `hello_api_api`. These identifiers are assigned sequentially starting with 0 when the API is registered to the connection. -So effectively this means numeric API identifiers are determined by the order of `hello_api_api` in the `public-api` declaration above. - -The `get_api_by_name` method on API 1 (the `login_api`) can be used to query the numeric ID by name: - - curl --data '{"jsonrpc": "2.0", "params": [1, "get_api_by_name", ["hello_api_api"]], "id":1, "method":"call"}' http://127.0.0.1:8790/rpc - -String API identifiers ----------------------- - -Client code that references API's by their numeric identifiers have to coordinated with server-side configuration. -This is inconvenient, so the API server supports identifying the API by name as well, like this: - - curl --data '{"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":1, "method":"call"}' http://127.0.0.1:8090/rpc - -It is considered best practice for API clients to use this syntax to reference API's by their string identifiers. The reason is that the client becomes robust against -server-side configuration changes which result in renumbering of API's. - -API access control methods --------------------------- - -There are three methods to secure the API: - -- Limit access to the API socket to a trusted machine by binding the RPC server to localhost -- Limit access to the API socket to a trusted LAN by firewall configuration -- Limit access to particular API's with username/password authentication - -The developers recommend using the first of these methods to secure the API by binding to localhost, as follows: - - rpc-endpoint = 127.0.0.1:8090 - -Securing specific API's ------------------------ - -The problem with securing API's at the network level is that there are deployment scenarios where a node may want to have some API's public, but other API's private. -The `vizd` process includes username/password based authentication to individual API's. - -Since the username/password is sent directly over the wire, you should use a TLS connection when authenticating with username and password. TLS connection can be achieved by one of two methods: - -- Configure an `rpc-tls-endpoint`. -- Configure an `rpc-endpoint` bound to localhost (or a secure LAN), and use an external reverse proxy (for example, `nginx`). This advanced configuration will not be covered here. - -You will need to create a key and certificate for the server in `.pem` format. Many tutorials for creating SSL certificates exist, and many providers allow creating SSL -certificates signed by widely recognized CA's. If you have a working `openssl` installation you can use these commands to generate a self-signed certificate: - - openssl req -x509 -newkey rsa:4096 -keyout data/ss-key.pem -out data/ss-cert.pem -days 365 - cat data/ss-{cert,key}.pem > data/ss-cert-key.pem - -Then in your `config.ini` you must give the server access to the key and certificate: - - rpc-tls-endpoint = 0.0.0.0:8091 - server-pem = data/ss-cert-key.pem - server-pem-password = password - -When connecting to the API, you can use the `cli_wallet` to honor a specific certificate, individual CA, or CA bundle file with the `-a` option: - - programs/cli_wallet/cli_wallet -s wss://mydomain.tld:8091 -a data/ss-cert.pem - -Note that the hostname `mydomain.tld` needs to match the CN field in the certificate. - -Now let's protect the `hello_api_api` API to only be usable by user `bytemaster` with password `supersecret`. Modify the `config.ini` file to make -sure we have the correct plugin which provides the sensitive API, make sure the sensitive API is not accessible in `public-api`, and add an -`api-user` definition to define the access policy: - - enable-plugin = witness account_history hello_api - public-api = database_api login_api - api-user = {"username" : "bytemaster", "password_hash_b64" : "T2k/wMBB9BKyv7X+ngghL+MaoubEuFb6GWvF3qQ9NU0=", "password_salt_b64" : "HqK9mAQCkWU=", "allowed_apis" : ["hello_api_api"]} - -The values of `password_hash_b64` and `password_salt_b64` are generated by running `programs/util/saltpass.py` and entering your desired password. - -Username/password access is only available through websockets, since login is inherently *stateful*. We can use the `wscat` program to demonstrate the login functionality: - - wscat -c wss://mydomain.tld:8091 - {"jsonrpc": "2.0", "params": ["database_api", "get_dynamic_global_properties", []], "id":1, "method":"call"} - < (ok) - {"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":2, "method":"call"} - < (error) - {"jsonrpc": "2.0", "params": ["login_api", "login", ["bytemaster", "supersecret"]], "id":3, "method":"call"} - < (ok) - {"jsonrpc": "2.0", "params": ["hello_api_api", "get_message", []], "id":4, "method":"call"} - < (ok) - -The `cli_wallet` also has the capability to login to provide access to restricted API's. diff --git a/documentation/building.md b/documentation/building.md deleted file mode 100644 index 6facd0f1f9..0000000000 --- a/documentation/building.md +++ /dev/null @@ -1,865 +0,0 @@ -# Building VIZ - -## Compile-Time Options (cmake) - -### CMAKE_BUILD_TYPE=[Release/Debug] - -Specifies whether to build with or without optimization and without or with -the symbol table for debugging. Unless you are specifically debugging or -running tests, it is recommended to build as release. - -### LOW_MEMORY_NODE=[FALSE/TRUE] - -Builds vizd to be a consensus-only low memory node. Data and fields not -needed for consensus are not stored in the object database. This option is -recommended for validators and seed-nodes. - -## Building under Docker - -We ship Dockerfiles for building production and testnet images. - -### Prerequisites - -- Docker installed and running -- Docker Hub account (for pushing images) -- Git with submodules initialized - -### Available Dockerfiles - -| Dockerfile | Purpose | Image Tag | CMake Options | -|------------|---------|-----------|---------------| -| `share/vizd/docker/Dockerfile-production` | Mainnet node | `vizblockchain/vizd:latest` | `LOW_MEMORY_NODE=FALSE` | -| `share/vizd/docker/Dockerfile-testnet` | Testnet node | `vizblockchain/vizd:testnet` | `BUILD_TESTNET=TRUE`, `LOW_MEMORY_NODE=FALSE` | -| `share/vizd/docker/Dockerfile-lowmem` | Low memory consensus node | `vizblockchain/vizd:lowmem` | `LOW_MEMORY_NODE=TRUE` | - -### Building Locally - -Clone the repository with submodules: - -```bash -git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node -cd viz-cpp-node -``` - -If you already cloned without `--recursive`, initialize submodules: - -```bash -git submodule update --init --recursive -``` - -Build the production image: - -```bash -docker build -t vizblockchain/vizd:latest -f share/vizd/docker/Dockerfile-production . -``` - -Build the testnet image: - -```bash -docker build -t vizblockchain/vizd:testnet -f share/vizd/docker/Dockerfile-testnet . -``` - -Build the low-memory image (for witnesses and seed-nodes): - -```bash -docker build -t vizblockchain/vizd:lowmem -f share/vizd/docker/Dockerfile-lowmem . -``` - -### Pushing to Docker Hub - -1. **Login to Docker Hub:** - -```bash -docker login -``` - -Enter your Docker Hub username and password when prompted. - -2. **Tag the image (if using a different local tag):** - -```bash -docker tag vizblockchain/vizd:latest YOUR_USERNAME/vizd:latest -``` - -3. **Push the image:** - -```bash -# Push to official VIZ repository (requires access) -docker push vizblockchain/vizd:latest - -# Or push to your personal repository -docker push YOUR_USERNAME/vizd:latest -``` - -### Building Specific Versions - -To build a specific version or branch: - -```bash -git checkout v1.2.3 # or any tag/branch -git submodule update --init --recursive -docker build -t vizblockchain/vizd:1.2.3 -f share/vizd/docker/Dockerfile-production . -``` - -### Running the Container - -```bash -# Production node -docker run -d \ - --name vizd \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /path/to/blockchain:/var/lib/vizd \ - vizblockchain/vizd:latest - -# Testnet node -docker run -d \ - --name vizd-testnet \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /path/to/testnet-data:/var/lib/vizd \ - vizblockchain/vizd:testnet - -# Low-memory node (witness/seed node) -docker run -d \ - --name vizd-lowmem \ - -p 8090:8090 -p 8091:8091 -p 2001:2001 \ - -v /path/to/blockchain:/var/lib/vizd \ - vizblockchain/vizd:lowmem - -``` - -### Troubleshooting - -**Mirror sync errors during apt-get:** -Ubuntu mirrors occasionally fail during sync. The Dockerfile includes retry logic, but if it persists, re-run the build: - -```bash -docker build --no-cache -t vizblockchain/vizd:latest -f share/vizd/docker/Dockerfile-production . -``` - -**Submodule issues:** -Ensure submodules are properly initialized: - -```bash -git submodule deinit -f . -git submodule update --init --recursive -``` - -**GCC alignment errors:** -If you see `size of array element is not a multiple of its alignment`, ensure you're using the latest code which fixes GCC 12+ compatibility. - -**Dockerfile Base Image:** -All Dockerfiles use `phusion/baseimage:noble-1.0.3` (Ubuntu 24.04 Noble) as the base image. This provides GCC 12+ and modern dependencies required for building VIZ. - -**Build Stages:** -All Dockerfiles use multi-stage builds: -- **Builder stage**: Compiles the VIZ node with all dependencies -- **Production stage**: Minimal runtime image with only the compiled binaries - -**Exposed Ports:** -All Docker images expose the following ports: -- `8090` - HTTP RPC service -- `8091` - WebSocket RPC service -- `2001` - P2P network service - -**Volumes:** -All Docker images define two volumes: -- `/var/lib/vizd` - Blockchain data directory -- `/etc/vizd` - Configuration directory - - -## Building on Ubuntu 24.04 (without Docker) - -VIZ requires **Boost 1.71 or later** and a C++14-capable compiler (GCC 8+). -Ubuntu 24.04 (Noble) provides Boost 1.74 from the package manager, which meets -this requirement. - -### Quick Build (using build scripts) - -The build is split into two scripts so that dependency installation (which -requires root) is kept separate from the build (which must run as a regular -user): - -**Step 1 — install system dependencies (once, as root):** - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node - cd viz-cpp-node - chmod +x install-deps-linux.sh build-linux.sh - sudo ./install-deps-linux.sh - -**Step 2 — configure and build (as regular user):** - - ./build-linux.sh - -> **Note:** `build-linux.sh` refuses to run as root. Always run it as your -> regular user account after installing dependencies. - -Common options: - - ./build-linux.sh -l # Low memory node (witness/seed) - ./build-linux.sh -n # Testnet build - ./build-linux.sh -t Debug # Debug build - ./build-linux.sh -j 4 # Limit to 4 parallel jobs - ./build-linux.sh --clean # Remove build dir before configuring - ./build-linux.sh --install # Build and install to /usr/local - ./build-linux.sh --boost-root /opt/boost_1_74_0 # Custom Boost path - -Run `./build-linux.sh -h` for full usage information. - -### Manual Build - - # Required packages - sudo apt-get install -y \ - autoconf \ - automake \ - cmake \ - g++ \ - git \ - libssl-dev \ - libtool \ - make \ - pkg-config - - # Boost packages (also required) - sudo apt-get install -y \ - libboost-chrono-dev \ - libboost-context-dev \ - libboost-coroutine-dev \ - libboost-date-time-dev \ - libboost-filesystem-dev \ - libboost-iostreams-dev \ - libboost-locale-dev \ - libboost-program-options-dev \ - libboost-serialization-dev \ - libboost-system-dev \ - libboost-test-dev \ - libboost-thread-dev - - # Compression libraries (required for Boost.Iostreams) - sudo apt-get install -y \ - libbz2-dev \ - liblzma-dev \ - libzstd-dev \ - zlib1g-dev - - # Optional packages (not required, but will make a nicer experience) - sudo apt-get install -y \ - libncurses5-dev \ - libreadline-dev \ - perl - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node - cd viz-cpp-node - mkdir build - cd build - cmake -DCMAKE_BUILD_TYPE=Release -DBoost_NO_BOOST_CMAKE=ON .. - make -j$(nproc) vizd - make -j$(nproc) cli_wallet - # optional - sudo make install # defaults to /usr/local - -### Building on Older Ubuntu Versions - -Ubuntu versions older than 24.04 ship Boost versions below 1.71 in their -package managers, which does not satisfy the project's requirement. If you -need to build on an older Ubuntu, you must compile Boost 1.71+ from source: - - BOOST_ROOT=$HOME/opt/boost_1_74_0 - wget https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.bz2 - tar xjf boost_1_74_0.tar.bz2 - cd boost_1_74_0 - ./bootstrap.sh --prefix=$BOOST_ROOT - ./b2 -j$(nproc) install - -Then build VIZ pointing to the custom Boost installation: - - cmake -DCMAKE_BUILD_TYPE=Release -DBoost_NO_BOOST_CMAKE=ON -DBOOST_ROOT=$BOOST_ROOT .. - make -j$(nproc) vizd - make -j$(nproc) cli_wallet - -## Building on macOS - -VIZ requires **Boost 1.71 or later** and a C++14-capable compiler. -macOS uses Clang (via Xcode) with `libc++` as the standard library. - -### Prerequisites - -- macOS 12 (Monterey) or later -- Xcode Command Line Tools -- Homebrew package manager - -### Install Xcode Command Line Tools - -Install Xcode and its command line tools. In macOS 10.14 (Mojave) -and newer, you will be prompted to install developer tools when running a -developer command in the terminal. Alternatively: - - xcode-select --install - -Accept the Xcode license if you have not already: - - sudo xcodebuild -license accept - -### Install Homebrew - -Install Homebrew by following the instructions here: https://brew.sh/ - -### Install VIZ Dependencies - - brew install \ - autoconf \ - automake \ - boost \ - cmake \ - git \ - libtool \ - openssl \ - python3 \ - readline - -Homebrew provides a recent version of Boost (1.74+) which satisfies the -Boost 1.71 minimum requirement. - -*Optional.* To use TCMalloc in LevelDB: - - brew install google-perftools - -### Quick Build (using build script) - -The `build-mac.sh` script handles Xcode/Homebrew checks, dependency -installation, OpenSSL path detection, submodule initialization, CMake -configuration, and building automatically: - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node.git - cd viz-cpp-node - chmod +x build-mac.sh - ./build-mac.sh - -Common options: - - ./build-mac.sh -l # Low memory node (witness/seed) - ./build-mac.sh -n # Testnet build - ./build-mac.sh -t Debug # Debug build - ./build-mac.sh -j 4 # Limit to 4 parallel jobs - ./build-mac.sh --install # Build and install to /usr/local - ./build-mac.sh --boost-root /opt/boost_1_74_0 # Custom Boost path - ./build-mac.sh --skip-deps # Skip brew install - -Run `./build-mac.sh -h` for full usage information. - -### Manual Build - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node.git - cd viz-cpp-node - export OPENSSL_ROOT_DIR=$(brew --prefix openssl) - mkdir build && cd build - cmake -DCMAKE_BUILD_TYPE=Release .. - make -j$(sysctl -n hw.logicalcpu) - -Also, some useful build targets for `make` are: - - vizd - cli_wallet - -e.g.: - - make -j$(sysctl -n hw.logicalcpu) vizd - -This will only build `vizd`. - -### macOS Notes - -- **OpenSSL**: macOS ships an outdated version of OpenSSL. The Homebrew - installation is required, and `OPENSSL_ROOT_DIR` must be exported before - running CMake. -- **ZLIB**: On macOS, zlib is linked automatically because the Homebrew - OpenSSL static libraries have a dependency on it. -- **readline**: Optional but recommended for `cli_wallet` interactive use. - If not found, the build proceeds without it. -- **libc++**: The build system uses `libc++` (the macOS standard library) - with C++14. Do not attempt to use `libstdc++`. - -## Building on Windows - -VIZ supports building on Windows with **MSVC** (Visual Studio) or **MinGW**. -The build system requires **Boost 1.71 or later** with static linking. - -### Prerequisites - -- Windows 10 or later -- CMake 3.16 or later -- Git for Windows -- Boost 1.71+ (built from source or prebuilt binaries) -- OpenSSL for Windows - -### Windows Dependencies Download Links - -All external libraries required for building VIZ on Windows are listed below. -Download and install each one before proceeding. - -| Dependency | Minimum Version | Recommended Version | Download URL | -|------------|----------------|---------------------|---------------| -| Visual Studio (MSVC) | 2019 | 2019 or 2022 | https://visualstudio.microsoft.com/downloads/ | -| CMake | 3.16 | Latest 3.x | https://cmake.org/download/ | -| Git for Windows | Any | Latest | https://git-scm.com/download/win | -| Boost | 1.71 | 1.84.0 | https://www.boost.org/users/download/ | -| OpenSSL | 1.1.1 | 3.0.x | https://slproweb.com/products/Win32OpenSSL.html | -| Perl (OpenSSL source build) | 5.20 | Strawberry Perl 5.40 | https://strawberryperl.com/ | -| NASM (OpenSSL source build) | 2.13 | Latest 2.x | https://www.nasm.us/ | -| MinGW-w64 (MinGW builds only) | Any recent | Via MSYS2 | https://www.msys2.org/ | - -#### Visual Studio - -Download from https://visualstudio.microsoft.com/downloads/ (the free Community -edition is sufficient). During installation, select the **Desktop development -with C++** workload. - -Tested generators: - -- `Visual Studio 16 2019` (VS 2019, MSVC 19.29) -- `Visual Studio 17 2022` (VS 2022) - -#### CMake - -Download from https://cmake.org/download/. Make sure `cmake` is in your -`PATH` (the installer offers to add it). Alternatively, install the -**CMake tools for Windows** component from the Visual Studio installer. - -#### Git - -Download from https://git-scm.com/download/win. A standard installation -is sufficient. - -#### Boost - -Download the source archive from https://www.boost.org/users/download/ -(Boost 1.84.0 recommended). Build from source with MSVC: - - :: Using VS 2019 (Developer Command Prompt) - cd boost_1_84_0 - bootstrap.bat - b2 -j%NUMBER_OF_PROCESSORS% variant=release link=static threading=multi runtime-link=shared install --prefix=D:\Boost - -For MinGW builds, replace the toolset: - - b2 -j%NUMBER_OF_PROCESSORS% toolset=gcc variant=release link=static threading=multi install --prefix=D:\Boost - -After building, set the environment variable: - - setx BOOST_ROOT "D:\Boost" - -#### OpenSSL - -**Option A — Prebuilt binaries (recommended):** - -Download the full Win64 installer from -https://slproweb.com/products/Win32OpenSSL.html (not the "Light" version). -Set the environment variable after installation: - - setx OPENSSL_ROOT_DIR "C:\OpenSSL-Win64" - -**Option B — Build from source:** - -If you need a specific version or static libraries, build from source. -This requires **Perl** (Strawberry Perl from https://strawberryperl.com/) -and **NASM** (from https://www.nasm.us/): - - :: Using VS 2019 (Developer Command Prompt) - cd openssl-3.0.16 - perl Configure VC-WIN64A no-shared --prefix=D:\OpenSSL --openssldir=D:\OpenSSL\ssl - nmake - nmake install - -After building, set the environment variable: - - setx OPENSSL_ROOT_DIR "D:\OpenSSL" - -#### Perl and NASM (only needed for building OpenSSL from source) - -- **Perl**: Install Strawberry Perl from https://strawberryperl.com/. ActiveState - Perl (https://activestate.com/products/activeperl/) also works. -- **NASM**: Download from https://www.nasm.us/ and add it to your `PATH`. - -#### MinGW-w64 (only for MinGW builds) - -The easiest way to get MinGW-w64 is via **MSYS2** -(https://www.msys2.org/). After installing MSYS2, run: - - pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake make - -Add the MSYS2 `mingw64/bin` directory to your system `PATH`. - -### Using Visual Studio (MSVC) - -#### 1. Install Visual Studio - -Install Visual Studio 2019 or later with the **Desktop development with C++** -workload. This provides the MSVC compiler and Windows SDK. - -#### 2. Install CMake - -Download and install CMake from https://cmake.org/download/ or install via -the Visual Studio CMake workload. - -#### 3. Install Boost - -Download Boost source from https://www.boost.org/users/download/ and build -it from source (Boost 1.74 recommended): - - :: Download and extract boost_1_74_0.tar.bz2 - cd boost_1_74_0 - bootstrap.bat - b2 -j%NUMBER_OF_PROCESSORS% variant=release link=static threading=multi runtime-link=shared install - -Set the `BOOST_ROOT` environment variable to point to your Boost installation: - - setx BOOST_ROOT "C:\Boost" :: adjust to your install path - -#### 4. Install OpenSSL - -Download prebuilt OpenSSL for Windows from https://slproweb.com/products/Win32OpenSSL.html -(the full install, not the "Light" version). Set `OPENSSL_ROOT_DIR`: - - setx OPENSSL_ROOT_DIR "C:\OpenSSL-Win64" - -#### 5. Clone and Build - -Open a **Developer Command Prompt for VS** and run: - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node.git - cd viz-cpp-node - -**Using the build script** (recommended): - - build-msvc.bat - -This script reads `BOOST_ROOT` and `OPENSSL_ROOT_DIR` from environment -variables and runs CMake configure + build automatically. See the script -header for available options like `VIZ_BUILD_TYPE`, `VIZ_LOW_MEMORY`, -`VIZ_BUILD_TESTNET`, and `VIZ_VS_VERSION`. - -**Manual build**: - - mkdir build && cd build - cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=%BOOST_ROOT% -DOPENSSL_ROOT_DIR=%OPENSSL_ROOT_DIR% .. - cmake --build . --config Release - -This will build `vizd.exe` and `cli_wallet.exe` in `build/programs/`. - -### Using MinGW - -MinGW builds are also supported. - -**Using the build script** (recommended): - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node.git - cd viz-cpp-node - build-mingw.bat - -This script reads `BOOST_ROOT` and `OPENSSL_ROOT_DIR` from environment -variables and runs CMake configure + build automatically. See the script -header for available options like `VIZ_BUILD_TYPE`, `VIZ_LOW_MEMORY`, -`VIZ_BUILD_TESTNET`, and `VIZ_FULL_STATIC`. - -**Using the Python helper** for cross-compilation from Linux: - - python programs/build_helpers/configure_build.py --win --release - -**Manual build**: - - git clone --recursive https://github.com/VIZ-Blockchain/viz-cpp-node.git - cd viz-cpp-node - mkdir build && cd build - cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=%BOOST_ROOT% -DOPENSSL_ROOT_DIR=%OPENSSL_ROOT_DIR% .. - mingw32-make -j%NUMBER_OF_PROCESSORS% - -### Windows Notes - -- **Boost linkage**: The build system forces static Boost linkage on Windows - (`BOOST_ALL_DYN_LINK=OFF`). Make sure you built Boost with `link=static`. -- **MSVC warnings**: The build suppresses specific MSVC warnings (C4503, - C4267, C4244) and disables Safe Exception Handler emission (`/SAFESEH:NO`). -- **MinGW Debug builds**: Use `-O2` optimization to avoid "File too big" - assembler errors. Release builds use `-O3`. -- **MinGW requires**: `-Wa,-mbig-obj` flag for large object file support. -- **FULL_STATIC_BUILD**: Set this CMake option to produce fully static - executables (links static libstdc++ and libgcc with MinGW). -## Building on Windows with MinGW UCRT64 (fully static, runs on Win7+) - -This method produces a fully static `vizd.exe` with no external DLL -dependencies beyond what Windows itself provides (kernel32, ntdll, ws2_32, -bcrypt). The binary runs on Windows 7 SP1 x64 and later without installing -any runtime redistributables. - -**Why UCRT64 and not MINGW64?** UCRT (Universal C Runtime) is the modern -replacement for MSVCRT. It is available on Win7/8/8.1 via Windows Update -(KB2999226) and built-in from Win10 onward. The UCRT64 toolchain can link -UCRT statically, producing a binary that is self-contained and -behaviorally close to a Linux build. - -**Why `fcontext` matters for Boost.Context/fibers?** VIZ uses -Boost.Coroutine2 which relies on Boost.Context for fiber/coroutine -switching. On Win64 MinGW there are three possible backends: - -| Backend | Problem | -|---|---| -| `winfibers` | Requires explicit thread conversion via Win32 API; crashes if a thread was not converted | -| `ucontext` | Does not save XMM6–XMM15 registers (non-volatile in the Win64 ABI) — guaranteed crash with SSE2/AVX code | -| `fcontext` | Pure ASM, saves TIB pointers and all non-volatile XMM registers, 16-byte stack alignment — stable | - -Boost must be compiled with `context-impl=fcontext` explicitly. - -### Prerequisites - -- Windows 7 SP1 x64 or later (build machine: Windows 10/11 recommended) -- MSYS2 installed to `C:\msys64` — https://www.msys2.org/ -- CMake 3.16 or later (the MSYS2 package is used below) -- ~5 GB free disk space for dependencies and build artifacts - -All commands below are run inside the **MSYS2 UCRT64** terminal -(not MINGW64, not the plain MSYS terminal). - -### ⚠️ NTP Note -To avoid issues related to time synchronization and NTP offsets, **do not use `w32tm`** (the default Windows time utility). -Instead, it is recommended to install and run **Meinberg NTP for Windows**, available here: -[https://www.meinbergglobal.com/english/sw/ntp.htm#ntp_stable](https://www.meinbergglobal.com/english/sw/ntp.htm#ntp_stable) - -Meinberg provides a full NTP service implementation compatible with Unix systems, ensuring more accurate and stable local clock synchronization — which is critical for running the blockchain node correctly. - -### Step 1 — Install toolchain packages - -```bash -pacman -Syu -# Restart the terminal if pacman asks, then run again: -pacman -Syu - -pacman -S \ - mingw-w64-ucrt-x86_64-gcc \ - mingw-w64-ucrt-x86_64-make \ - mingw-w64-ucrt-x86_64-cmake \ - mingw-w64-ucrt-x86_64-perl \ - mingw-w64-ucrt-x86_64-nasm \ - autoconf \ - automake \ - libtool \ - make \ - git \ - wget -``` - -Create a `make` alias so autoconf test scripts can find it: - -```bash -ln -s /ucrt64/bin/mingw32-make.exe /usr/bin/make -``` - -### Step 2 — Build Boost 1.84 (static, fcontext) - -```bash -cd ~ -wget https://archives.boost.io/release/1.84.0/source/boost_1_84_0.tar.bz2 -tar xjf boost_1_84_0.tar.bz2 -cd boost_1_84_0 - -./bootstrap.sh --with-toolset=gcc - -./b2 -j4 \ - toolset=gcc \ - address-model=64 \ - variant=release \ - link=static \ - threading=multi \ - runtime-link=static \ - context-impl=fcontext \ - define=_WIN32_WINNT=0x0601 \ - define=WINVER=0x0601 \ - cxxflags="-march=x86-64 -mtune=generic" \ - cflags="-march=x86-64 -mtune=generic" \ - --with-atomic \ - --with-chrono \ - --with-context \ - --with-coroutine \ - --with-date_time \ - --with-filesystem \ - --with-iostreams \ - --with-locale \ - --with-program_options \ - --with-regex \ - --with-serialization \ - --with-system \ - --with-test \ - --with-thread \ - install --prefix=/c/Boost - -``` - -### Step 3 — Build OpenSSL 3.0 (static, mingw64 target) - -```bash -cd ~ -wget https://github.com/openssl/openssl/releases/download/openssl-3.0.16/openssl-3.0.16.tar.gz -tar xzf openssl-3.0.16.tar.gz -cd openssl-3.0.16 - -# Use MSYS2's Perl — NOT Strawberry/ActivePerl from Windows PATH -/usr/bin/perl Configure mingw64 no-shared \ - --prefix=/c/OpenSSL \ - --openssldir=/c/OpenSSL/ssl \ - -D_WIN32_WINNT=0x0601 - -make -j4 -make install_sw -make install_ssldirs -``` - -> If you have Strawberry Perl or ActivePerl installed on Windows, make sure -> `/usr/bin/perl` is used, not the Windows one. You can verify with: -> `perl -e 'print $^O'` — it must print `msys`, not `MSWin32`. - -Note: In some cases, OpenSSL searches for openssl.cnf directly in the root installation directory rather than in the ssl subdirectory. To prevent build or runtime issues, it is safer to copy the configuration file immediately after installation: - -```bash -cp ./apps/openssl.cnf /c/OpenSSL/ -``` - -### Step 4 — Clone the repository - -```bash -cd ~ -git clone --recursive https://github.com/m0ssa99/viz-cpp-node.git -b windows_suport -cd viz-cpp-node -``` - -If you already cloned without `--recursive`: - -```bash -git submodule update --init --recursive -``` - -Pre-generate the secp256k1 configure script (required on Windows because -the autoconf step runs outside of CMake): - -```bash -cd thirdparty/fc/vendor/secp256k1-zkp -autoreconf -fi -cd ~/viz-cpp-node -``` - -### Step 5 — Configure and build - -```bash -mkdir build && cd build - -cmake -G "MinGW Makefiles" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - -DCMAKE_CXX_STANDARD=14 \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DBOOST_ROOT="/c/Boost" \ - -DBOOST_INCLUDEDIR="/c/Boost/include/boost-1_84" \ - -DBOOST_LIBRARYDIR="/c/Boost/lib" \ - -DBoost_NO_BOOST_CMAKE=ON \ - -DBoost_NO_SYSTEM_PATHS=ON \ - -DBoost_USE_STATIC_LIBS=ON \ - -DBoost_USE_STATIC_RUNTIME=ON \ - -DBoost_USE_MULTITHREADED=ON \ - -DBoost_ARCHITECTURE="-x64" \ - -DBoost_COMPILER="-mgw16" \ - -DOPENSSL_ROOT_DIR="/c/OpenSSL" \ - -DOPENSSL_USE_STATIC_LIBS=ON \ - -DFULL_STATIC_BUILD=ON \ - -DCMAKE_C_FLAGS="-DWINVER=0x0601 -DSECP256K1_STATIC" \ - -DCMAKE_CXX_FLAGS="-DWINVER=0x0601 -DSECP256K1_STATIC -Wa,-mbig-obj" \ - -DCMAKE_EXE_LINKER_FLAGS="-static -Wl,--start-group -lbcrypt -Wl,--end-group" \ - .. - -mingw32-make -j4 vizd -mingw32-make -j4 cli_wallet -``` - -The binaries will be in `build/programs/vizd/vizd.exe` and -`build/programs/cli_wallet/cli_wallet.exe`. - -### Step 6 — Verify no unexpected DLL dependencies - -```bash -objdump -p programs/vizd/vizd.exe | grep "DLL Name" -``` - -Expected output — only Windows system DLLs, all present on Win7+: - -``` -DLL Name: KERNEL32.dll -DLL Name: ntdll.dll -DLL Name: WS2_32.dll -DLL Name: IPHLPAPI.dll -DLL Name: bcrypt.dll -``` - -If you see any `libstdc++`, `libgcc`, `libwinpthread`, or `msvcrt` entries, -`FULL_STATIC_BUILD` was not applied correctly — recheck the cmake flags. - -### Notes - -- **`Boost_COMPILER=-mgw16`**: the GCC 16 toolchain in MSYS2 UCRT64 tags - its libraries with `-mgw16-`. If you use a different GCC version the - suffix will differ (e.g. `-mgw14-` for GCC 14). Check with - `ls /c/Boost/lib/ | head -5` and adjust accordingly. -- **`SECP256K1_STATIC`**: without this define, the secp256k1 headers emit - `__declspec(dllimport)` on all symbols, causing link errors with - `__imp_` prefixed undefined references even though you have a static - library. -- **`bcrypt.dll`**: present on all Windows versions from Vista onward. - Boost.Filesystem uses BCrypt for `unique_path()`. It is a system DLL — - no redistribution needed. -- **`-Wa,-mbig-obj`**: MinGW on Win64 generates large `.o` files due to - C++ template instantiations. Without this flag you get "File too big" - assembler errors. -- **`autoreconf -fi`** on secp256k1: the submodule ships only - `configure.ac`, not the generated `configure` script. On Linux/macOS, - the CMake ExternalProject autogen step handles this automatically. On - Windows it must be run once manually before the first build. - -### Quick Build Automation with `build_mingw.sh` - -For convenience, you can automate the build process using the `build_mingw.sh` helper script located in the repository root. This script handles the entire toolchain setup, dependency compilation (Boost, OpenSSL), and the node build itself. - -**Prerequisites:** - -1. Download and install **MSYS2/MinGW** from https://www.msys2.org/. -2. Launch the **MinGW UCRT64** terminal and update the package system: - ```bash - pacman -Syu - ``` - (Confirm any terminal restart requests and run the command again if necessary.) - -**Instructions:** - -1. **Copy the helper script:** - Copy `build_mingw.sh` from the VIZ repository root into your MSYS2 home directory (usually `C:\msys64\home\`). - - ```bash - # From your MSYS2 home directory: - cp /path/to/viz-cpp-node/build_mingw.sh . - ``` - -2. **Run the automation:** - Open the **MinGW UCRT64** terminal and execute: - - ```bash - cd ~ - chmod +x build_mingw.sh - ./build_mingw.sh - ``` - -This script reproduces the entire build process with a single command, producing fully static binaries without manual step execution. - -## Building on Other Platforms - -- The developers normally compile with GCC and Clang. These compilers should - be well-supported on Unix-like systems. -- Community members occasionally attempt to compile the code with Intel and - Microsoft compilers. These compilers may work, but the developers do not - use them. Pull requests fixing warnings / errors from these compilers are - accepted. -- The project requires **Boost 1.71+**, **CMake 3.16+**, and a **C++14** - compiler. Ensure your platform meets these minimum requirements. \ No newline at end of file diff --git a/documentation/debug_node_plugin.md b/documentation/debug_node_plugin.md deleted file mode 100644 index a5fb66b35d..0000000000 --- a/documentation/debug_node_plugin.md +++ /dev/null @@ -1,133 +0,0 @@ - -Plugin readme -------------- - -The latest release (TODO: Version number) exposes the plugin architecture. -Each plugin has full access to the database and can change node behavior. -Each plugin may also provide API's. - -The debug_node plugin ---------------------- - -The goal of the `debug_node` plugin is to start with the live chain, then easily -simulate future hypothetical actions. The plugin simulates changes to chain state. -For example, you can edit an account's balances and signing keys to enable -performing (simulated) actions with that account. - - -Why this isn't unsafe ---------------------- - -Anyone (even you!) can edit their account to contain 1 million VIZ. It's really -impossible for the developers to physically stop you from editing the memory/disk of your own -computer to make your local node think any account balance is any amount, so you can "give" -yourself 1 million VIZ. Just like it's really impossible for your bank to physically stop -you from writing any numbers you'd like when balancing your checkbook, so you can "give" -yourself 1 million dollars. - -But you have no way to control what other nodes do (or what your bank's clerks and computer systems do). -They do their own bookkeeping and keep track of what your real balance is (without all -the fake VIZ or fake dollars you "gave" yourself). So you can believe whatever you want -about your balance and rewrite the rules of your own bookkeeping system to show you whatever balance you -want to be shown, but as soon as you try to actually spend VIZ (or dollars) that you don't actually have, -you'll be stopped because every other node on the network is a system you don't control that's keeping the -books properly (without all your edits to give yourself extra funds), and they do their own verification -of every transaction and will suppress any that doesn't have sufficient balance and a proper cryptographic -signature. - -Why this is useful for debugging --------------------------------- - -Say you've written some code for a hardfork -- some new feature or a bugfix that activates on every node -at a certain time or a certain block number -- e.g. January 1 at midnight (hypothetical date). -One basic question to ask is, if we apply that code to the current chain, does block production continue -as expected on January 1? If the process segfaults, or block production hangs, or nobody can do transactions -after January 1, those are certainly showstopping problems that need to be fixed before your code is ready to -be included in a release. So to test that it would be nice to speed up time and simulate creating blocks to -January 1, but you don't have any witness private keys. With `debug_node_plugin` you can simulate changing -of witness keys to a key you control and produce as many blocks as you'd like. - -Example usage -------------- - -Let's say we want to get the history of the chain. The first order of business is to save the blocks from a running node: - - cp -Rp datadir/blockchain/database/block_num_to_block /mydir/myblocks - -Then create a new node with an empty datadir and no seed nodes. (No seed nodes means it won't participate in the p2p network -and won't receive any blocks except the ones we'll explicitly tell it to load later.) We enable the debug node and allow -access to its API to anyone: - - # no seed-node in config file or command line - p2p-endpoint = 127.0.0.1:2001 # bind to localhost to prevent remote p2p nodes from connecting to us - rpc-endpoint = 127.0.0.1:8090 # bind to localhost to secure RPC API access - enable-plugin = witness account_history debug_node - public-api = database_api login_api debug_node_api - -The `public-api` section lists API's accessible to the RPC endpoint. Since (as explained above) the `debug_node_api` -allows the database to be edited in ways that cause the node to report simulated chain state to clients and fail -to sync to the real network, RPC should not be accessible to the entire internet when the `debug_node_api` is in -the list of public API's. - -The API's configured with `public-api` are assigned numbers starting at zero. So the `debug_node_api` is callable by -API number 2 (TODO: Explain about resolving names to API's and get it working). - -The API provides the following methods -(see `libraries/plugins/debug_node/include/graphene/plugins/debug_node/debug_node_api.hpp` -for these definitions): - - void debug_push_blocks( std::string src_filename, uint32_t count ); - void debug_generate_blocks( std::string debug_key, uint32_t count ); - void debug_update_object( fc::variant_object update ); - void debug_stream_json_objects( std::string filename ); - void debug_stream_json_objects_flush(); - -Okay, let's run `vizd`. It should start immediately with no blocks. We can ask it to read blocks from the directory we saved earlier: - - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_push_blocks",["/mydir/myblocks", 1000]], "id": 1}' http://127.0.0.1:8090/rpc - -As expected we now have 1000 blocks: - - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0,"get_dynamic_global_properties",[]], "id": 2}' http://127.0.0.1:8090/rpc - -It queries the directory for blocks after the current head block, so we can subsequently load 500 more: - - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_push_blocks",["/mydir/myblocks", 500]], "id": 3}' http://127.0.0.1:8090/rpc - -Let's generate some blocks: - - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_generate_blocks",["5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3",5]], "id": 4}' http://127.0.0.1:8090/rpc - -As you can see, the `debug_node` performs a local edit of each witness's public key: - - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0,"get_witness_by_account",["dantheman4"]], "id": 5}' http://127.0.0.1:8990/rpc - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0,"get_witness_by_account",["thisisnice4"]], "id": 6}' http://127.0.0.1:8990/rpc - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0,"get_dynamic_global_properties",[]], "id": 7}' http://127.0.0.1:8090/rpc - -The important information in the above is: - - ... "signing_key":"STM6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" ... - ... "head_block_number":1505 ... - -which demonstrates the validator keys have been reset and the head block number has been advanced with new blocks. The blocks are signed by the above private key, and the database is edited to set the block signing key of the scheduled validators accordingly so the node accepts the simulated signatures as valid. - -If we want to take control of an account we can do so by editing its key with `debug_update_object` command like this: - - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [0,"lookup_account_names",[["steemit"]]], "id": 8}' http://127.0.0.1:8090/rpc # find out ID of account we want is 2.2.28 - curl --data '{"jsonrpc": "2.0", "method": "call", "params": [2,"debug_update_object",[{"_action":"update","id":"2.2.28","active":{"weight_threshold":1,"key_auths":[["STM6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",1]]}}]], "id": 9}]' http://127.0.0.1:8090/rpc - -Now that we've reset its key, we can take control of it in the wallet: - programs/cli_wallet/cli_wallet -w debug_wallet.json -s ws://127.0.0.1:8090 - set_password abc - unlock abc - import_key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 - list_my_accounts - transfer steemit dantheman "1.234 VIZ" "make -j100 money" true - list_my_accounts - get_account_history steemit -1 1000 - -(For some unknown reason, the current version of the wallet hangs after the transfer command -- why?) - -Again, we're not actually taking control of anything, we're doing a "what-if" experiment -- what if the keys to this account had suddenly changed at block 1505 and then this transfer operation was broadcast? And we find out that the chain and the wallet handle the situation as exepected (processing the transfer, putting it in the account history, and updating the balance). - -This plugin allows all sorts of creative "what-if" experiments with the chain. diff --git a/documentation/plugin.md b/documentation/plugin.md deleted file mode 100644 index 8b76bf0841..0000000000 --- a/documentation/plugin.md +++ /dev/null @@ -1,31 +0,0 @@ - -How plugins work ----------------- - -Plugins live under `plugins//` at the repo root. Each plugin is built as a static library (or shared, when `BUILD_SHARED_LIBRARIES=TRUE`) and registered with the appbase plugin framework. - -`plugins/CMakeLists.txt` walks every subdirectory and includes the ones that contain a `CMakeLists.txt`. Plugins are then wired into the `vizd` binary by explicit registration in `programs/vizd/main.cpp` — there is no auto-discovery at runtime. - -To add a new plugin: - -1. Create a directory under `plugins//`. -2. Add a `CMakeLists.txt` modeled on an existing plugin (e.g. `plugins/raw_block/CMakeLists.txt`). -3. Register the plugin in `programs/vizd/main.cpp` (see the `register_plugins()` function). - -Registering plugins -------------------- - -- Plugins are enabled with the `enable-plugin` config file option. -- When specifying plugins, you should specify `witness` and `account_history` in addition to the new plugins. -- Some plugins may keep records in the database (currently only `account_history` does). If you change whether such a plugin is disabled/enabled, you should also replay the chain. Detecting this situation and automatically replaying when needed will be implemented in a future release. -- If you want to make API's available publicly, you must use the `public-api` option. -- When specifying public API's, you should specify `database_api` and `login_api` in addition to the new plugins. -- The `api-user` option allows for password protected access to an API. - -Authoring a plugin ------------------- - -When implementing a new plugin: - -- Register signal handlers in `plugin_startup()` (if needed) -- Add methods to `myplugin_api` class and reflect them in `FC_API` declaration diff --git a/documentation/snapshot-plugin.md b/documentation/snapshot-plugin.md deleted file mode 100644 index 6a17462953..0000000000 --- a/documentation/snapshot-plugin.md +++ /dev/null @@ -1,472 +0,0 @@ -# Snapshot Plugin - -The snapshot plugin provides DLT (Distributed Ledger Technology) state snapshots for VIZ blockchain nodes. It supports creating, loading, and syncing snapshots between nodes via a dedicated TCP protocol. - -## Basic Usage - -### Creating a Snapshot - -Create a snapshot from a fully-synced node: - -```bash -vizd --create-snapshot /path/to/snapshot-block-500000.vizjson -``` - -The node will load the blockchain, create a zlib-compressed snapshot file, and exit. - -### Loading from a Snapshot - -Start a node from a snapshot instead of replaying the entire blockchain: - -```bash -vizd --snapshot /path/to/snapshot-block-500000.vizjson -``` - -### Periodic Snapshots - -Automatically create snapshots at regular intervals: - -```ini -# In config.ini -plugin = snapshot -snapshot-every-n-blocks = 100000 -snapshot-dir = /var/lib/vizd/snapshots -``` - -**Witness-Aware Deferral:** When the node is also a block-producing witness, periodic snapshot creation is automatically deferred if the witness is scheduled to produce within the next 5 slots (~15 seconds). This covers the typical snapshot creation time (~10 seconds) plus a safety margin, preventing snapshot serialization from causing the witness to miss its production slot. The deferred snapshot waits until the witness's specific slot has been filled (its block produced and applied, or the slot missed and the chain moved past it), then starts immediately — avoiding any overlap with witness production. - -**Non-Blocking Snapshot Creation:** Snapshot creation runs asynchronously on a dedicated background thread. Only the database read phase (serialization of state) holds a read lock (~1 second); compression and file I/O run without any lock. This means block processing is only briefly paused during the read phase, and API/P2P reads are never blocked — eliminating the read-lock timeouts and `unlinkable_block_exception` errors that occurred when snapshot creation ran synchronously inside the write-lock scope. - -### Snapshot at Specific Block - -```ini -snapshot-at-block = 500000 -snapshot-dir = /var/lib/vizd/snapshots -``` - -## P2P Snapshot Sync - -Nodes can serve and download snapshots from each other via dedicated TCP connections (separate from the Graphene P2P layer). - -### Serving Snapshots - -Enable snapshot serving on a node that creates periodic snapshots: - -```ini -plugin = snapshot - -# Enable serving (anyone can download) -allow-snapshot-serving = true -snapshot-serve-endpoint = 0.0.0.0:8092 -snapshot-dir = /var/lib/vizd/snapshots -snapshot-every-n-blocks = 100000 - -# Optional: restrict to trusted peers only -# allow-snapshot-serving-only-trusted = true -# trusted-snapshot-peer = 1.2.3.4:8092 -# trusted-snapshot-peer = 5.6.7.8:8092 -``` - -### Downloading Snapshots (New Node Bootstrap) - -Start a fresh node that automatically downloads a snapshot from trusted peers: - -```bash -vizd --sync-snapshot-from-trusted-peer true -``` - -With the following in `config.ini`: - -```ini -plugin = snapshot -trusted-snapshot-peer = seed1.example.com:8092 -trusted-snapshot-peer = seed2.example.com:8092 -snapshot-dir = /var/lib/vizd/snapshots -sync-snapshot-from-trusted-peer = true -``` - -**Note:** `sync-snapshot-from-trusted-peer` can be set in both `config.ini` and as a CLI option. - -The node will: -1. Query all trusted peers for their latest snapshot (30-second timeout per peer) -2. Select the peer with the highest block number -3. Wait briefly for the server to release the query session, then connect for download (up to 3 retries with 2-second delays if the server's anti-spam check hasn't cleared the previous session yet) -4. Download the snapshot in 1 MB chunks with progress logging (5-minute timeout per chunk) -5. Verify the checksum -6. Load the snapshot and start syncing from that block - -If no trusted peers respond, the node will **retry automatically** every `stalled-sync-timeout-minutes` minutes (default: 5) until a snapshot becomes available. The node will not fall back to genesis sync. - -**Note on Timeouts:** Peer queries use a 30-second timeout. Chunk downloads use a 5-minute timeout to support slow connections (minimum ~3.4 KB/s). If a peer doesn't respond within the timeout (e.g., accepts TCP connection but never sends data), the node will skip that peer and try the next one. - -### Trust Model - -- **Public serving** (`allow-snapshot-serving = true`, `allow-snapshot-serving-only-trusted = false`): Any IP can connect and download snapshots. Good for public seed nodes. -- **Trusted-only serving** (`allow-snapshot-serving-only-trusted = true`): Only IPs from `trusted-snapshot-peer` list can download. Good for private networks. -- **Client side**: Only connects to endpoints in `trusted-snapshot-peer` (inherent trust). - -### Anti-Spam Protection - -The snapshot TCP server has built-in anti-spam measures: - -- **Max 5 concurrent connections**: The server accepts up to 5 simultaneous connections, each handled in a separate fiber (via `fc::async`). Additional connections are rejected. -- **1 active session per IP**: If an IP already has an active download in progress, additional connections from the same IP are rejected. Sessions are eagerly released via RAII when the connection handler exits, minimizing the window where a legitimate reconnection (e.g., query → download) could be falsely rejected. -- **Rate limiting (3 connections/hour per IP)**: Each IP is limited to 3 connections per hour. Exceeding this triggers a rejection with a warning log. This prevents abuse where a client repeatedly connects to waste server bandwidth. -- **Enforced connection timeout (60s)**: Each connection has a hard deadline. The timeout is checked before each I/O operation (initial request read, each chunk transfer). Slow or stalled clients are disconnected when the deadline expires, freeing the session slot. -- **Trusted peers bypass nothing**: Anti-spam rules apply equally to all connections (trusted and untrusted). Trust enforcement (`allow-snapshot-serving-only-trusted`) is checked first; anti-spam is checked after. - -### Protocol - -The snapshot sync uses a binary request-response protocol over TCP: - -``` -Wire format: [4 bytes: payload_size][4 bytes: msg_type][payload_bytes] -``` - -Message types: -| Type | Name | Description | -|------|------|-------------| -| 1 | `SNAPSHOT_INFO_REQUEST` | Empty payload. "What's your latest snapshot?" | -| 2 | `SNAPSHOT_INFO_REPLY` | `{block_num, block_id, checksum, compressed_size}` | -| 3 | `SNAPSHOT_DATA_REQUEST` | `{block_num, offset, chunk_size}` | -| 4 | `SNAPSHOT_DATA_REPLY` | `{offset, data_bytes, is_last}` | -| 5 | `SNAPSHOT_NOT_AVAILABLE` | No snapshot available | - -## DLT Rolling Block Log - -In DLT mode (snapshot-based nodes), the main `block_log` is empty because it requires contiguous blocks from genesis. To allow DLT nodes to serve recent blocks to P2P peers, a **separate** `dlt_block_log` file with an offset-aware index is used. - -The `dlt_block_log` stores blocks in the same binary format as the regular block_log, but its index file has an 8-byte header storing the first block number. This allows starting from any block number (e.g., 10,000,000) without needing entries for blocks 1 through 9,999,999. - -```ini -# Keep last 100,000 blocks in dlt_block_log (default) -dlt-block-log-max-blocks = 100000 -``` - -**Behavior:** -- When `> 0` and in DLT mode: irreversible blocks are written to `dlt_block_log`, and old blocks are truncated when the window exceeds 2x the limit (amortized cost). -- When `= 0`: no DLT block_log writes (original behavior). -- Ignored for normal (non-DLT) nodes that always write the full `block_log`. -- The P2P layer automatically falls back to `dlt_block_log` when serving blocks not found in the main `block_log`. - -Files stored in the blockchain data directory: -- `dlt_block_log` -- block data (same format as `block_log`) -- `dlt_block_log.index` -- offset-aware index - -## P2P Sync After Snapshot Import (DLT Mode) - -When a node starts from a snapshot, it loads state at the snapshot's block height but has no block history. P2P sync must then fetch all subsequent blocks from the network. Several fixes ensure this works reliably: - -### LIB Promotion - -After snapshot import, LIB (Last Irreversible Block) is promoted to equal the head block number. This ensures that P2P's blockchain synopsis starts from the snapshot's head, causing peers to only offer blocks after the snapshot point (which can link correctly in the fork database). - -### Fork Database Seeding - -- **Fresh snapshot import**: The fork database (`fork_db`) is seeded with the head block so incoming blocks can link to it. -- **DLT mode restart**: On restart, `fork_db` is in-memory and empty. The node tries to seed it from the `dlt_block_log` if the head block is covered. If not (e.g., first restart after snapshot import), the early rejection logic (below) handles the empty `fork_db` case. - -### Early Block Rejection in `_push_block` - -When a node is far behind the network, it receives two types of blocks simultaneously: -- **Sync blocks** (sequential, starting from head+1) — must be accepted -- **Broadcast blocks** (real-time, potentially thousands ahead) — must be rejected silently - -Early-rejection checks in `_push_block` prevent sync disruption: - -1. **Duplicate blocks**: If a block is at or before head and its ID matches our chain, it's already applied — skip silently (prevents unnecessary `fork_db` push attempts that would throw `unlinkable_block_exception`). - -2. **Blocks at/before head on a different fork with unknown parent**: If a block is at or before our head but on a different fork (different ID), AND its parent isn't in the fork_db (the fork diverged before the fork_db's window), throw `unlinkable_block_exception`. The P2P layer catches this and soft-bans the peer (stale fork) or restarts sync (block ahead of head). Without this check, `fork_db.push_block` throws the same exception anyway, but the P2P layer can't distinguish the cause. - -3. **Far-ahead blocks with unknown parent**: If a block is above our head, its `previous` is neither our `head_block_id()` nor in `fork_db`, and it's not the genesis block, return `false` silently instead of pushing to `fork_db`. Without this guard, `fork_db.push_block` throws `unlinkable_block_exception` which triggers P2P sync restart, clearing the sync queue and preventing forward progress. This is critical after snapshot import: the node is at block N, the network is at N+1000, and every broadcast block would otherwise disrupt the sequential sync. - -4. **Immediate successor always allowed**: Blocks whose `previous == head_block_id()` always pass — this is the critical sync case where the next sequential block must be accepted, even when `fork_db` is empty after a restart. - -5. **Fork DB head-seeding**: Before pushing to `fork_db`, if `new_block.previous == head_block_id()` and the head block is NOT in `fork_db`, the head block is fetched from the block log and seeded into `fork_db` via `start_block()`. This ensures the incoming block can link to the chain even after snapshot import, stale sync recovery, or `fork_db` trimming where the head was absent. Without this, `fork_db::_push_block()` would throw `unlinkable_block_exception` ("block does not link to known chain"), silently rejecting valid next-blocks. This also fixes witness nodes generating their own blocks (where `generate_block()` sets `pending_block.previous = head_block_id()`). - -6. **Direct-extension bypass**: After pushing to `fork_db`, if `new_block.previous == head_block_id()`, the fork switch logic is bypassed entirely and the block falls through to `apply_block()`. This handles the case where `fork_db._head` points to a stale higher block accumulated from previous failed sync cycles (stale sync recovery does not reset `fork_db`), preventing valid next-blocks from being silently rejected by the fork switch comparison. - -### Deferred Resize and Sync Recovery - -When shared memory is exhausted during block processing, the node schedules a deferred resize and throws `deferred_resize_exception`. The P2P layer handles this as a transient local condition: - -- **Sync path**: Does NOT soft-ban the peer (the peer did nothing wrong). Instead, restarts sync with all active peers so the missed block is re-fetched after the resize completes. -- **Broadcast path**: Does NOT soft-ban or disconnect. The block will be re-received naturally after the resize. - -Without this fix, `deferred_resize_exception` in the sync path would fall through to the generic error handler, causing a 1-hour soft-ban of the peer AND losing the missed block — the next sync block (N+2) would fail to link because N+1 was never applied, permanently stalling sync. - -### P2P Soft-Ban Trigger Reference - -All soft-ban triggers set `fork_rejected_until = now + duration` and `inhibit_fetching_sync_blocks = true`. The default duration is 1 hour (3600s), but **trusted peers** (IPs matching `trusted-snapshot-peer` config) get a reduced 5-minute (300s) ban, allowing faster recovery from transient errors. Broadcast blocks from soft-banned peers are silently discarded. The `inhibit_fetching_sync_blocks` flag is automatically reset when the ban expires. - -| # | Code Path | Exception / Condition | Block Position | Action | Reason | -|---|-----------|----------------------|----------------|--------|--------| -| 1 | Sync: `send_sync_block_to_node_delegate` | `block_older_than_undo_history` | any | Soft-ban 1h (5 min trusted) | Peer on a fork too old for undo history | -| 2 | Sync: `send_sync_block_to_node_delegate` | `unlinkable_block_exception` + block ≤ head | ≤ head | Soft-ban 1h (5 min trusted) | Dead fork, block at/below head | -| 3 | Sync: `send_sync_block_to_node_delegate` | `unlinkable_block_exception` + block > head | > head | Restart sync | Behind; need to fetch missing parents | -| 4 | Sync: `send_sync_block_to_node_delegate` | `deferred_resize_exception` | any | Restart sync, no soft-ban | Local condition; peer not at fault | -| 5 | Sync: `send_sync_block_to_node_delegate` | Generic `fc::exception` | any | Soft-ban 1h (5 min trusted) | Unspecified rejection; prevent reconnect loops | -| 6 | Broadcast: `process_block_during_normal_operation` | `unlinkable_block_exception` + block ≤ head | ≤ head | Soft-ban 1h (5 min trusted) | Stale fork | -| 7 | Broadcast: `process_block_during_normal_operation` | `unlinkable_block_exception` + block > head | > head | Restart sync | Behind; resync to catch up | -| 8 | Broadcast: `process_block_during_normal_operation` | `block_older_than_undo_history` | any | Soft-ban 1h (5 min trusted) | Dead fork, stale blocks | -| 9 | Broadcast: `process_block_during_normal_operation` | `deferred_resize_exception` | any | No action | Local transient; block re-received after resize | -| 10 | Broadcast: `process_block_during_normal_operation` | `fc::exception` + block ≤ head | ≤ head | Soft-ban 1h (5 min trusted) | Fork rejection; prevent cascading disconnects | -| 11 | Broadcast: `process_block_during_normal_operation` | `fc::exception` + block > head | > head | Disconnect | Genuinely invalid block | -| 12 | Chain: `_push_block` | Block ≤ head, different fork, parent not in fork_db | ≤ head | Throw `unlinkable_block_exception` | Fork diverged before fork_db window | -| 13 | Chain: `_push_block` | Block > head, `previous != head_block_id`, parent not in fork_db | > head | Return `false` silently | Prevent sync restart storms from broadcast blocks | -| 14 | Chain: `push_block` | `bad_alloc` → `deferred_resize_exception` | any | Throw `deferred_resize_exception` | Shared memory exhausted; P2P must not penalize peer | - -### Soft-Ban Notification Protocol - -When a node soft-bans a peer (e.g., spam strike threshold exceeded), it sends a `dlt_soft_ban_message` (type 5114) **before** closing the connection. This allows the banned peer to: - -1. **Stop sending data immediately** — instead of continuing to spam until the connection times out -2. **Enter BANNED state** with the correct duration — the ban message includes `ban_duration_sec` and a `reason` string -3. **Log a yellow/orange notice** — the receiving node logs: `Peer X soft-banned us for Ns (reason: Y)` - -The receiving node closes the connection and waits for the ban duration before attempting to reconnect. This prevents wasted bandwidth from both sides when a peer is already rejected. - -| Field | Type | Description | -|-------|------|-------------| -| `ban_duration_sec` | uint32_t | Ban duration in seconds (typically 3600 for 1 hour) | -| `reason` | string | Human-readable ban reason (e.g., "spam strike threshold exceeded") | - -### `is_known_block()` in DLT Mode - -In DLT mode, the `block_summary` table (TAPOS buffer, 65536 entries) survives snapshot import, but block data may not be available on disk. Simply returning `true` from `block_summary` would mislead P2P peers into requesting blocks the node can't serve. - -The DLT mode fix checks `block_summary` as a hint, then verifies the block is on the **preferred chain** via `find_block_id_for_num()`. This allows P2P's `has_item()` to correctly identify blocks we already have (enabling proper sync negotiation) while preventing false positives for blocks we can't serve. - -### Fork Database Bug Fixes - -Several bugs in `fork_database` were fixed that previously prevented out-of-order block caching from working: -- **Dead code fix**: `_unlinked_index.insert()` was after `throw` (unreachable) — moved before the throw -- **Missing `_push_next()` call**: After inserting a new block, previously-unlinkable blocks that can now link were never resolved — added the call -- **Duplicate block check**: Added early return if a block already exists in the index -- **`reset()` cleanup**: Added `_unlinked_index.clear()` to properly clear the unlinked cache on reset - -### Async Snapshot Creation Fix - -Snapshot creation previously ran **synchronously** inside `on_applied_block`, which is called within the database write-lock scope of `push_block()`. This caused the write lock to be held for the entire 3+ second snapshot duration, blocking all other threads from reading the database. - -**Symptoms:** -- "Read lock timeout" / "No more retries for read lock" in API and P2P threads -- `unlinkable_block_exception` for P2P blocks that arrived during the stall (triggering sync restarts with peers) -- Witness block production continuing (on the same thread) while P2P blocks were blocked - -**Fix:** -- Snapshot creation now runs **asynchronously** on a dedicated `fc::thread` — the `on_applied_block` callback returns immediately, releasing the write lock -- `create_snapshot` is split into two phases: - - **Phase 1 (read lock, ~1 second)**: Serializes all database state into local variables. Block processing waits during this brief phase, but API/P2P reads proceed concurrently - - **Phase 2 (no lock, ~2 seconds)**: Compression, checksum computation, and file I/O. Block processing and API reads run normally -- `snapshot_in_progress` atomic flag prevents overlapping snapshots -- Plugin shutdown waits for any in-progress snapshot to complete before quitting - -## Trusted Seeds Diagnostic Test - -The `test-trusted-seeds` option probes all configured `trusted-snapshot-peer` endpoints at startup and reports connectivity metrics, then exits. It is intended as a diagnostic tool to verify that snapshot seeds are reachable and performant before deploying a node. - -### What Is Measured - -For each trusted peer: -- **Connection time** — TCP handshake duration in milliseconds -- **Latency** — Round-trip time for an info-request / info-reply in milliseconds -- **Snapshot info** — Block number, compressed size, checksum (if snapshot is available) -- **Download speed** — Throughput measured from a single 1 MB chunk download in KB/s -- **Status** — `REACHABLE`, `NO_SNAPSHOT`, `TIMEOUT`, or `ERROR` - -### Configuration - -```ini -plugin = snapshot - -trusted-snapshot-peer = 80.87.202.57:8092 -trusted-snapshot-peer = seed2.example.com:8092 - -# Enable the diagnostic test (node exits after testing) -test-trusted-seeds = true -``` - -Or as a CLI flag: - -```bash -vizd --plugin snapshot --trusted-snapshot-peer 80.87.202.57:8092 --test-trusted-seeds true -``` - -### Example Output - -``` -[test-trusted-seeds] Testing 2 trusted peer(s)... - -[test-trusted-seeds] Peer 1/2: 80.87.202.57:8092 - Connection: 45 ms - Latency (info request): 120 ms - Snapshot: block 18500000, size 156 MB, checksum abc123456789... - Download speed (1 MB probe): 2450 KB/s - -[test-trusted-seeds] Peer 2/2: 192.168.1.10:8092 - Connection: timeout (30s) - -=== Trusted Seeds Test Summary === (2 peer(s)) - 80.87.202.57:8092 REACHABLE connect=45ms latency=120ms speed=2450KB/s block=18500000 size=156MB - 192.168.1.10:8092 TIMEOUT - -Test complete. Exiting. -``` - -### Notes - -- The node exits after the test is complete. This option is **not** for normal operation. -- If no `trusted-snapshot-peer` entries are configured, the node exits immediately with a warning. -- The speed probe downloads one 1 MB chunk. Actual full-download speed may differ slightly. -- The test runs before the snapshot TCP server starts, so it does not affect other connected clients. - -## Stale Snapshot Detection (DLT Mode) - -In DLT mode, the `dlt_block_log` is a rolling window — old blocks are pruned as new ones arrive. If the node's latest snapshot is older than the DLT block log's start block, downloading nodes would face an **unsyncable gap**: the snapshot restores state at block N, but the DLT block log starts at block M > N, leaving blocks N+1..M-1 unavailable. - -### How It Works - -At startup, the snapshot plugin checks: -1. Is the node in DLT mode? -2. Is snapshot serving or periodic creation enabled? -3. Is the latest snapshot's block number **less than** `dlt_block_log.start_block_num()`? - -If all conditions are true, the plugin logs a **STALE SNAPSHOT DETECTED** warning and sets an internal flag. On the first fully-synced block (not during P2P catch-up), the plugin creates an **urgent fresh snapshot** immediately — either asynchronously or deferred if the witness is about to produce. - -### Example Scenario - -``` -Latest snapshot: snapshot-block-900.vizjson (block 900) -DLT block log: blocks 1000..2000 -Gap: blocks 901..999 are missing -``` - -A downloading node would restore state at block 900 but the serving node can only provide blocks from 1000 onward — P2P sync fails. The stale detection creates a fresh snapshot at the current head (e.g., block 2000), eliminating the gap. - -### Log Output - -``` -STALE SNAPSHOT DETECTED: latest snapshot at block 900 is older than DLT block log start at block 1000. -Downloading nodes would have a sync gap (blocks 900..1000 missing). -A fresh snapshot will be created on the first synced block. -``` - -When the fresh snapshot is created: -``` -Creating urgent fresh snapshot (stale snapshot detected at startup): /data/snapshots/snapshot-block-2000.vizjson -``` - -### Notes - -- The check only runs when `allow-snapshot-serving = true` or `snapshot-every-n-blocks > 0`. -- The urgent snapshot follows the same witness-aware deferral and async creation as periodic snapshots. -- After the fresh snapshot is created, normal periodic snapshot scheduling resumes. -- If no snapshot exists at all (`snap_block = 0`), it is also considered stale (0 < any DLT start block). - -## Stalled Sync Detection (DLT Mode) - -For DLT mode nodes that may fall behind the network, automatic stalled sync detection can re-download a newer snapshot when P2P sync is no longer possible (peers have pruned old blocks). - -### How It Works - -1. Node tracks the time of last received block -2. Background thread checks every 30 seconds -3. If no blocks received for the configured timeout, node queries trusted peers -4. If a newer snapshot is available, node clears state and reloads from it -5. If no newer snapshot, timer resets and P2P sync continues - -### Configuration - -```ini -plugin = snapshot - -# Required: trusted peers for snapshot download -trusted-snapshot-peer = seed1.example.com:8092 -trusted-snapshot-peer = seed2.example.com:8092 - -# Enable stalled sync detection -enable-stalled-sync-detection = true - -# Timeout before triggering re-download (default: 5 minutes) -stalled-sync-timeout-minutes = 5 -``` - -### Use Cases - -- **Node was offline for days/weeks**: Instead of failing to sync because peers no longer have old blocks, the node automatically downloads a fresh snapshot. -- **Network partition**: If the node cannot reach the chain head via P2P, it will attempt to bootstrap from a snapshot. -- **DLT mode recovery**: Essential for nodes running in DLT mode without full block history. - -## P2P Stale Sync Detection - -The P2P plugin can automatically detect and recover from network stalls — when no blocks are received from any peer for an extended period. This is a lightweight recovery mechanism that does **not** require downloading a snapshot. - -### How It Works - -When enabled, the P2P plugin tracks the last time a block was received via the network. A background task checks every 30 seconds whether the elapsed time exceeds the configured timeout. If a stall is detected, the node performs three recovery actions in sequence: - -1. **Reset sync from LIB** — The P2P layer's sync start point is reset to the last irreversible block (LIB). This ensures the node resumes from a safe, fork-proof position instead of potentially chasing a dead fork. -2. **Resync with connected peers** — The node explicitly restarts synchronization with all currently connected peers by sending fresh `fetch_blockchain_item_ids_message` requests. -3. **Reconnect seed peers** — All seed nodes from `p2p-seed-node` config are re-added to the connection queue and reconnection is attempted for any that were disconnected. - -This is complementary to the snapshot plugin's stalled sync detection (which downloads a new snapshot). The P2P stale recovery is faster and less disruptive — it only adjusts sync state and reconnects peers, without requiring any state reload. - -### Config Options - -```ini -# Enable P2P stale sync detection (default: false) -p2p-stale-sync-detection = true - -# Timeout in seconds before recovery triggers (default: 120 = 2 minutes) -p2p-stale-sync-timeout-seconds = 120 -``` - -### Comparison with Snapshot Stalled Sync Detection - -| Feature | P2P Stale Sync | Snapshot Stalled Sync | -|---------|---------------|----------------------| -| Plugin | P2P | Snapshot | -| Trigger | No blocks received for timeout | No blocks received for timeout | -| Recovery action | Reset sync + reconnect peers | Download newer snapshot + reload state | -| Timeout default | 120 seconds | 5 minutes | -| Use case | Temporary network partition, peer disconnections | Node far behind, peers lack old blocks | -| DLT mode | Works for all nodes | Designed for DLT mode | - -Both can be enabled independently. For DLT nodes, the snapshot detection provides deeper recovery (fresh state), while P2P detection handles transient connectivity issues without state reload. - -## Config Reference - -### Config file options (`config.ini`) - -| Option | Default | Description | -|--------|---------|-------------| -| `snapshot-at-block` | 0 | Create snapshot at specific block number | -| `snapshot-every-n-blocks` | 0 | Create periodic snapshots (0 = disabled) | -| `snapshot-dir` | "" | Directory for auto-generated snapshots | -| `allow-snapshot-serving` | false | Enable TCP snapshot serving | -| `allow-snapshot-serving-only-trusted` | false | Restrict serving to trusted IPs | -| `snapshot-serve-endpoint` | 0.0.0.0:8092 | TCP listen endpoint for serving | -| `trusted-snapshot-peer` | (none) | Trusted peer IP:port (repeatable) | -| `sync-snapshot-from-trusted-peer` | false | Download snapshot on empty state (config.ini or CLI) | -| `enable-stalled-sync-detection` | false | Auto-detect stalled sync and re-download snapshot | -| `stalled-sync-timeout-minutes` | 5 | Timeout for stalled sync detection and startup retry interval | -| `test-trusted-seeds` | false | Probe all trusted peers at startup (connect time, latency, speed) and exit | -| `dlt-block-log-max-blocks` | 100000 | Rolling DLT block_log window size (0 = disabled) | -| `dlt-stats-interval-sec` | 300 | Interval in seconds between P2P peer stats log output (min 30) | -| `dlt-peer-max-disconnect-hours` | 8 | Remove peer from known list after this many hours of non-response | -| `dlt-mempool-max-tx` | 10000 | Maximum number of transactions in P2P mempool | -| `dlt-mempool-max-bytes` | 104857600 | Maximum total bytes of transactions in P2P mempool (default 100MB) | -| `dlt-mempool-max-tx-size` | 65536 | Maximum single transaction size in bytes (default 64KB) | -| `dlt-mempool-max-expiration-hours` | 24 | Reject transactions with expiration too far in the future (hours) | -| `dlt-peer-exchange-max-per-reply` | 10 | Max peers to include in a peer exchange reply | -| `dlt-peer-exchange-max-per-subnet` | 2 | Max peers per /24 subnet in peer exchange replies | -| `dlt-peer-exchange-min-uptime-sec` | 600 | Min connection uptime (seconds) before sharing a peer in exchange replies | - -### CLI options - -| Option | Description | -|--------|-------------| -| `--snapshot ` | Load state from snapshot file | -| `--create-snapshot ` | Create snapshot and exit | -| `--sync-snapshot-from-trusted-peer true` | Download snapshot from trusted peers on empty state (also available in config.ini) | diff --git a/documentation/testing.md b/documentation/testing.md deleted file mode 100644 index e9f257a442..0000000000 --- a/documentation/testing.md +++ /dev/null @@ -1,42 +0,0 @@ -# Testing - -The unit test target is `make chain_test` -This creates an executable `./tests/chain_test` that will run all unit tests. - -Tests are broken in several categories: -``` -basic_tests // Tests of "basic" functionality -block_tests // Tests of the block chain -live_tests // Tests on live chain data (currently only past hardfork testing) -operation_tests // Unit Tests of operations -operation_time_tests // Tests of operations that include a time based component (ex. vesting withdrawals) -serialization_tests // Tests related of serialization -``` - -## Configuration (draft) - -Some config options: -* `log_level` — allows setting the log level. Available values: `all`, `success`, `test_suite`, `message`, `warning`, . `error`, `cpp_exception`, `system_error`, `fatal_error`, `nothing`. Sample usage: `--log-level=test_suite` -* `report_level` — allows setting the level of detailization for the testing results report. Available values: `no`, `confirm`, `short`, `detailed`. Sample usage: `--report_level=detailed` -* `run_test` — specifies which test units to run. You can specify individual test (separated with comma) or test cases. Sample usage: `--run_test=operation_tests/delegation` - -For more info about runtime config check [Boost.Tests documentation](https://www.boost.org/doc/libs/1_58_0/libs/test/doc/html/utf/user-guide/runtime-config/reference.html). - - -# Code Coverage Testing - -If you have not done so, install lcov `brew install lcov` - -``` -cmake -D ENABLE_COVERAGE_TESTING=true -D CMAKE_BUILD_TYPE=Debug . -make -lcov --capture --initial --directory . --output-file base.info --no-external -tests/chain_test -lcov --capture --directory . --output-file test.info --no-external -lcov --add-tracefile base.info --add-tracefile test.info --output-file total.info -lcov -o interesting.info -r total.info tests/\* -mkdir -p lcov -genhtml interesting.info --output-directory lcov --prefix `pwd` -``` - -Now open `lcov/index.html` in a browser diff --git a/documentation/testnet.md b/documentation/testnet.md deleted file mode 100644 index 0b74987fc7..0000000000 --- a/documentation/testnet.md +++ /dev/null @@ -1,53 +0,0 @@ -# How to run a test network - -When viz node starts without seed nodes, it tries to open a *snapshot.json* file which holds data on initial users and their balances. -If found, liquid tokens are transferred from `CHAIN_INITIATOR_NAME` account to users accounts into vesting (shares). - -Initial users and their keys are defined inside *libraries/protocol/include/graphene/protocol/config.hpp* - -* `CHAIN_INITIATOR_NAME` ("viz") is a user which holds initial balance. -* `CHAIN_COMMITTEE_ACCOUNT` is an initial active witness. - -*viz* keys: - -* private: 5JabcrvaLnBTCkCVFX5r4rmeGGfuJuVp4NAKRNLTey6pxhRQmf4 -* public: VIZ6MyX5QiXAXRZk7SYCiqpi6Mtm8UbHWDFSV8HPpt7FJyahCnc2T - -*committee* keys: - -* private: 5Hw9YPABaFxa2LooiANLrhUK5TPryy8f7v9Y1rk923PuYqbYdfC -* public: VIZ6Yt7d6LsngBoXQr47aLv97bJVs7jyr7esZTM4UUSpLUf3nbRKS - -## Launch - -To run a testnet node, please use [Docker](https://docs.docker.com/engine/install/). - -To build image locally, use Dockerfile-testnet: - -``` -docker build -t viz:testnet -f share/vizd/docker/Dockerfile-testnet . -docker run -d --name vizd viz:testnet -docker logs --tail 100 -f vizd -``` - -To use pre-built image, run node with - -``` -docker run -d --name vizd vizblockchain/vizd:testnet -``` - -## Additional users - -To ease testing, several additional users are immediately available: - -* alice -* bob -* charlie -* chuck -* dan -* frank - -All users have the same keys: - -* private: 5J9DBCRX5D2ZUUuy9qV2ef9p5sfA3ydHsDs2G531bob7wbEigDJ -* public: VIZ5zTAE2hiGcqYaDTQeEBqTtPeoWtSNjpznwmbvqJXesrK1Qn3e8 diff --git a/documentation/webserver-plugin.md b/documentation/webserver-plugin.md deleted file mode 100644 index db88e716ad..0000000000 --- a/documentation/webserver-plugin.md +++ /dev/null @@ -1,200 +0,0 @@ -# Webserver Plugin - -The webserver plugin provides HTTP and WebSocket endpoints for JSON-RPC API access to the VIZ blockchain node. It includes a response cache to improve performance for frequently-called read-only API methods. - -## Overview - -The webserver plugin starts an embedded HTTP/WebSocket server that accepts JSON-RPC 2.0 requests and dispatches them to registered API methods. It handles both: - -- **HTTP requests** - Standard POST requests with JSON-RPC body -- **WebSocket connections** - Persistent connections for real-time API access - -## Basic Usage - -Enable the webserver plugin in `config.ini`: - -```ini -plugin = webserver - -# HTTP endpoint (required for HTTP API access) -webserver-http-endpoint = 127.0.0.1:8090 - -# WebSocket endpoint (required for WebSocket API access) -webserver-ws-endpoint = 127.0.0.1:8091 - -# Or use a single endpoint for both (deprecated) -# rpc-endpoint = 127.0.0.1:8090 -``` - -## Response Cache - -The webserver plugin includes a response cache that stores JSON-RPC responses to reduce database load for frequently-called methods like `database_api.get_dynamic_global_properties`. - -### How It Works - -- Cache key is a SHA256 hash of the **method + params** from the parsed JSON-RPC request (excluding the `id` field) -- This prevents cache bypass via `id` rotation — a common spam attack pattern where each request uses a different `id` value (e.g., 397, 398, 399...) to avoid cache hits -- Responses are cached per-block — the cache is cleared when a new block is applied -- Cache hits are served immediately without acquiring database read locks -- The response `id` is patched to match the original request `id` per JSON-RPC 2.0 spec -- Thread-safe implementation with mutex protection -- Invalid or malformed JSON bypasses the cache and is forwarded to the JSON-RPC plugin for proper error handling - -### Mutability Filtering - -Only **read-only** API methods are cached. Mutating methods are excluded to prevent stale responses for write operations: - -- `network_broadcast_api.*` — never cached (broadcasts transactions) -- `debug_node.*` — never cached (modifies node state) -- The filter checks both the top-level `method` field and the first element of `params` for `call`-style requests (e.g., `{"method":"call","params":["network_broadcast_api","broadcast_transaction",...]}`) - -### Id-Independent Cache Keys - -The cache key is generated by properly parsing the JSON-RPC request (using `fc::json::from_string`) and hashing only the serialized `method` and `params` fields. This means: - -| Request | Cache Key Source | -|---------|-----------------| -| `{"id":1,"method":"call","params":["database_api","get_dynamic_global_properties",[]]}` | `method` + `params` only | -| `{"id":999,"method":"call","params":["database_api","get_dynamic_global_properties",[]]}` | **Same key** as above | -| `{"id":"abc","method":"call","params":["database_api","get_dynamic_global_properties",[]]}` | **Same key** as above | - -When serving a cached response, the `id` field in the response is replaced with the original request's `id` to comply with the JSON-RPC 2.0 specification. - -### Cache Configuration - -```ini -# Enable/disable the response cache (default: true) -webserver-cache-enabled = true - -# Maximum number of cached responses (default: 10000) -webserver-cache-size = 10000 -``` - -### When to Disable Cache - -Consider disabling the cache (`webserver-cache-enabled = false`) if: - -- Your node serves mostly unique requests -- Memory usage is a concern -- You're running a private node with low API traffic - -### Cache Benefits - -The cache is most beneficial for: - -- Public API nodes serving many concurrent users -- Methods like `get_dynamic_global_properties`, `get_config`, `get_chain_properties` -- Scenarios where users poll the same data frequently (e.g., every 0.5 seconds) -- Mitigating spam attacks that flood the node with identical read-only requests using incrementing IDs - -## Thread Pool - -The webserver uses a thread pool to handle concurrent requests: - -```ini -# Number of threads for handling queries (default: 256) -webserver-thread-pool-size = 256 -``` - -Each incoming request is dispatched to the thread pool, allowing concurrent processing of multiple API calls. - -## Security Considerations - -### Binding to localhost - -For security, bind to localhost (127.0.0.1) unless you need external access: - -```ini -# Local access only (recommended) -webserver-http-endpoint = 127.0.0.1:8090 - -# External access (use with caution) -webserver-http-endpoint = 0.0.0.0:8090 -``` - -### API Access Control - -To control which APIs are publicly accessible, use the `public-api` option: - -```ini -# Make specific APIs public -public-api = database_api -public-api = network_broadcast_api -``` - -### Authentication - -For authenticated API access, use the `api-user` option: - -```ini -api-user = username:password:database_api -``` - -## Config Reference - -### Config file options (`config.ini`) - -| Option | Default | Description | -|--------|---------|-------------| -| `webserver-http-endpoint` | (none) | HTTP listen endpoint (IP:port) | -| `webserver-ws-endpoint` | (none) | WebSocket listen endpoint (IP:port) | -| `rpc-endpoint` | (none) | Combined HTTP/WS endpoint (deprecated) | -| `webserver-thread-pool-size` | 256 | Number of handler threads | -| `webserver-cache-enabled` | true | Enable response caching | -| `webserver-cache-size` | 10000 | Maximum cached responses | - -### Deprecated Options - -The `rpc-endpoint` option is deprecated in favor of separate `webserver-http-endpoint` and `webserver-ws-endpoint` options. - -## Architecture - -The webserver plugin architecture: - -1. **Main thread** - Handles incoming connections -2. **Thread pool** - Processes JSON-RPC requests concurrently -3. **JSON parser** - Parses request body once using `fc::json::from_string` for cache decisions -4. **Cache layer** - Intercepts cacheable requests before API dispatch, generates id-independent cache keys, patches response IDs -5. **Signal connection** - Clears cache on each new block via `applied_block` signal - -``` -Client Request - | - v -[HTTP/WebSocket Server] - | - v -[JSON Parse] ---> Invalid JSON? ---> Forward to JSON-RPC (skip cache) - | | - | Valid | - v | -[Mutability Check] | - | | - +-- Mutating? ----> Skip cache -------->| - | | - | Read-only | - v | -[Cache Lookup] | - | | - +-- Hit? ---> Patch response ID ---> Return to client - | | - | Miss | - v | -[Thread Pool] | - | | - v | -[JSON-RPC Plugin] | - | | - v | -[API Method] | - | | - +-- Cache response (read-only only) --->| - | - v -Return to client -``` - -## See Also - -- [Plugin Overview](plugin.md) - General plugin documentation -- [API Notes](api_notes.md) - API usage notes diff --git a/install-deps-linux.sh b/install-deps-linux.sh index a7908fbbbb..e877fd46c1 100644 --- a/install-deps-linux.sh +++ b/install-deps-linux.sh @@ -101,7 +101,7 @@ elif command -v dnf &>/dev/null; then install_deps_fedora else warn "Unsupported package manager. Please install dependencies manually." - warn "See documentation/building.md for the required package list." + warn "See docs/node/building.md for the required package list." exit 1 fi diff --git a/libraries/api/CMakeLists.txt b/libraries/api/CMakeLists.txt index 5c62ef3c91..1b7b5a56d4 100644 --- a/libraries/api/CMakeLists.txt +++ b/libraries/api/CMakeLists.txt @@ -25,17 +25,10 @@ list(APPEND CURRENT_TARGET_SOURCES paid_subscription_api_object.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/libraries/api/account_api_object.cpp b/libraries/api/account_api_object.cpp index 408437053f..f38fa06b75 100644 --- a/libraries/api/account_api_object.cpp +++ b/libraries/api/account_api_object.cpp @@ -24,7 +24,8 @@ account_api_object::account_api_object(const account_object& a, const graphene:: subaccount_seller(a.subaccount_seller), subaccount_offer_price(a.subaccount_offer_price), subaccount_on_sale(a.subaccount_on_sale), reserved_balance(a.reserved_balance), target_buyer(a.target_buyer), account_on_auction(a.account_on_auction), current_bid(a.current_bid), current_bidder(a.current_bidder), current_bidder_key(a.current_bidder_key), - last_bid(a.last_bid){ + last_bid(a.last_bid), + pm_liquidity_committed(a.pm_liquidity_committed), pm_bets_staked(a.pm_bets_staked), pm_leverage_collateral(a.pm_leverage_collateral){ size_t n = a.proxied_vsf_votes.size(); proxied_vsf_votes.reserve(n); for (size_t i = 0; i < n; i++) { diff --git a/libraries/api/include/graphene/api/account_api_object.hpp b/libraries/api/include/graphene/api/account_api_object.hpp index 9c6fd4fdbe..9115ad137b 100644 --- a/libraries/api/include/graphene/api/account_api_object.hpp +++ b/libraries/api/include/graphene/api/account_api_object.hpp @@ -96,6 +96,10 @@ struct account_api_object { account_name_type current_bidder; public_key_type current_bidder_key; asset last_bid; + + asset pm_liquidity_committed;///< PM: own liquidity frozen in live markets (display-only) + asset pm_bets_staked; ///< PM: own stake frozen in open/queued bets + asset pm_leverage_collateral;///< PM: own collateral frozen in active leverage positions }; } } // graphene::api @@ -118,6 +122,7 @@ FC_REFLECT( (subaccount_seller)(subaccount_offer_price)(subaccount_on_sale) (reserved_balance)(target_buyer) (account_on_auction)(current_bid)(current_bidder)(current_bidder_key)(last_bid) + (pm_liquidity_committed)(pm_bets_staked)(pm_leverage_collateral) ) #endif //CHAIN_ACCOUNT_API_OBJ_HPP diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index b4c8d3aa18..7eb4ca9625 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -24,29 +24,28 @@ else() ) endif() -# Target comun care depinde de fișierul generat +# Common target that depends on the generated file add_custom_target(build_hardfork_hpp DEPENDS "${hardfork_hpp_out}") -# Marchează fișierul ca generat +# Mark the file as generated set_source_files_properties("${hardfork_hpp_out}" PROPERTIES GENERATED TRUE) -# Include dir-ul binar +# Include the binary include dir include_directories("${CMAKE_CURRENT_BINARY_DIR}/include") -# Variabila folosită mai jos în add_library +# Variable used below in add_library set(hardfork_hpp_file "${hardfork_hpp_out}") ## SORT .cpp by most likely to change / break compile -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_chain SHARED +add_library(graphene_chain ${VIZ_LIBRARY_TYPE} # As database takes the longest to compile, start it first database.cpp + database_hardfork.cpp fork_database.cpp chain_evaluator.cpp - invite_evaluator.cpp chain_objects.cpp shared_authority.cpp @@ -60,61 +59,15 @@ if(BUILD_SHARED_LIBRARIES) committee_evaluator.cpp invite_evaluator.cpp paid_subscription_evaluator.cpp - - include/graphene/chain/account_object.hpp - include/graphene/chain/block_log.hpp - include/graphene/chain/dlt_block_log.hpp - include/graphene/chain/block_summary_object.hpp - include/graphene/chain/content_object.hpp - include/graphene/chain/proposal_object.hpp - include/graphene/chain/compound.hpp - include/graphene/chain/custom_operation_interpreter.hpp - include/graphene/chain/database.hpp - include/graphene/chain/database_exceptions.hpp - include/graphene/chain/db_with.hpp - include/graphene/chain/evaluator.hpp - include/graphene/chain/evaluator_registry.hpp - include/graphene/chain/fork_database.hpp - include/graphene/chain/generic_custom_operation_interpreter.hpp - include/graphene/chain/global_property_object.hpp - include/graphene/chain/immutable_chain_parameters.hpp - include/graphene/chain/index.hpp - include/graphene/chain/node_property_object.hpp - include/graphene/chain/operation_notification.hpp - include/graphene/chain/shared_authority.hpp - include/graphene/chain/shared_db_merkle.hpp - include/graphene/chain/chain_evaluator.hpp - include/graphene/chain/chain_object_types.hpp - include/graphene/chain/chain_objects.hpp - include/graphene/chain/transaction_object.hpp - include/graphene/chain/validator_objects.hpp - include/graphene/chain/committee_objects.hpp - include/graphene/chain/paid_subscription_objects.hpp - - ${hardfork_hpp_file} - ) -else() - add_library(graphene_chain STATIC - - # As database takes the longest to compile, start it first - database.cpp - fork_database.cpp - - chain_evaluator.cpp - - chain_objects.cpp - shared_authority.cpp - # transaction_object.cpp - block_log.cpp - dlt_block_log.cpp - proposal_object.cpp - proposal_evaluator.cpp - database_proposal_object.cpp - chain_properties_evaluators.cpp - committee_evaluator.cpp - invite_evaluator.cpp - paid_subscription_evaluator.cpp - + pm/lmsr_q96.cpp + pm/parimutuel.cpp + pm/leverage.cpp + pm_evaluator.cpp + + include/graphene/chain/pm/lmsr_q96.hpp + include/graphene/chain/pm/parimutuel.hpp + include/graphene/chain/pm/leverage.hpp + include/graphene/chain/pm_evaluator.hpp include/graphene/chain/account_object.hpp include/graphene/chain/block_log.hpp include/graphene/chain/dlt_block_log.hpp @@ -145,14 +98,18 @@ else() include/graphene/chain/committee_objects.hpp include/graphene/chain/invite_objects.hpp include/graphene/chain/paid_subscription_objects.hpp + include/graphene/chain/pm_objects.hpp ${hardfork_hpp_file} ) -endif() add_dependencies(graphene_chain graphene_protocol graphene_utilities build_hardfork_hpp) target_link_libraries(graphene_chain graphene_protocol graphene_utilities fc chainbase appbase ${PATCH_MERGE_LIB}) -target_include_directories(graphene_chain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../../") +target_include_directories(graphene_chain PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../../") + +if(ENABLE_PCH) + target_precompile_headers(graphene_chain PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/pch.hpp") +endif() if(WITH_COVERAGE) target_compile_options(graphene_chain PRIVATE --coverage) @@ -161,6 +118,10 @@ endif() if(MSVC) set_source_files_properties(database.cpp PROPERTIES COMPILE_FLAGS "/bigobj") + # HF14 LMSR is pure integer math, but forbid contraction/fast-math defensively (consensus determinism). + set_source_files_properties(pm/lmsr_q96.cpp PROPERTIES COMPILE_FLAGS "/fp:precise") +else() + set_source_files_properties(pm/lmsr_q96.cpp PROPERTIES COMPILE_FLAGS "-fno-fast-math -ffp-contract=off") endif(MSVC) install(TARGETS diff --git a/libraries/chain/chain_properties_evaluators.cpp b/libraries/chain/chain_properties_evaluators.cpp index a71eeb848c..0e3e130ff5 100644 --- a/libraries/chain/chain_properties_evaluators.cpp +++ b/libraries/chain/chain_properties_evaluators.cpp @@ -84,6 +84,11 @@ namespace graphene { namespace chain { _wprops = p; } + result_type operator()(const chain_properties_pm& p) const { + FC_ASSERT( _db.has_hardfork(CHAIN_HARDFORK_14), "chain_properties_pm" ); + _wprops = p; + } + template result_type operator()(Props&& p) const { _wprops = p; diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index f09fc14ba0..75716b251b 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -2763,8 +2765,9 @@ namespace graphene { namespace chain { pending_block.transactions.push_back(tx); } catch (const fc::exception &e) { - // Do nothing, transaction will not be re-applied - //wlog( "Transaction was not processed while generating block due to ${e}", ("e", e) ); + // Transaction will not be re-applied. Log why so silently-dropped + // txs (valid at pending push, but failing block-gen apply) are debuggable. + wlog( "Transaction was not processed while generating block due to ${e}", ("e", e.to_detail_string()) ); //wlog( "The transaction was ${t}", ("t", tx) ); } } @@ -3146,6 +3149,32 @@ namespace graphene { namespace chain { void database::committee_processing() { const auto &props = get_dynamic_global_properties(); const validator_schedule_object &consensus = get_validator_schedule_object(); + + // HF14 governance bridge: VIZ parked in the PM lazy pool keeps its DAO-committee voting + // weight — a voter's pool claim (NAV × their shares / total shares) is converted to + // vesting-shares with the SAME price as create_vesting and added to their vote weight. + // GATED on the PM hardfork: pre-HF14 the pool does not exist and this contributes 0. + const bool pm_active = has_hardfork(CHAIN_HARDFORK_14); + const auto vsh_price = props.get_vesting_share_price(); + const pm_lazy_pool_object* lpool = pm_active + ? find(pm_lazy_pool_id_type(0)) : nullptr; + const int64_t lp_nav = (lpool && lpool->total_shares.value > 0) + ? lpool->free_balance.value + lpool->allocated_balance.value + lpool->leverage_fund_used.value : 0; + const int64_t lp_total_shares = (lpool ? lpool->total_shares.value : 0); + const int64_t lp_nav_shares = (lp_nav > 0) + ? (asset(share_type(lp_nav), TOKEN_SYMBOL) * vsh_price).amount.value : 0; + auto lazy_vote_weight = [&](const account_name_type& acct) -> int64_t { + if (lp_nav <= 0 || lp_total_shares <= 0) return 0; + const auto& ldidx = get_index().indices().get(); + auto d = ldidx.find(acct); + if (d == ldidx.end() || d->shares.value <= 0) return 0; + int64_t viz = (int64_t)(fc::uint128_t((uint64_t)lp_nav) + * fc::uint128_t((uint64_t)d->shares.value) + / fc::uint128_t((uint64_t)lp_total_shares)).lo; + if (viz <= 0) return 0; + return (asset(share_type(viz), TOKEN_SYMBOL) * vsh_price).amount.value; + }; + const auto &idx0 = get_index().indices().get(); auto itr0 = idx0.lower_bound(0); while (itr0 != idx0.end() && @@ -3164,10 +3193,12 @@ namespace graphene { namespace chain { const auto &cur_vote = *vote_itr; ++vote_itr; const auto &voter_account = get_account(cur_vote.voter); - max_rshares+=voter_account.effective_vesting_shares().amount.value; - actual_rshares+=voter_account.effective_vesting_shares().amount.value*cur_vote.vote_percent/CHAIN_100_PERCENT; + int64_t vw = voter_account.effective_vesting_shares().amount.value + + lazy_vote_weight(cur_vote.voter); // + lazy-pool stake (HF14) + max_rshares+=vw; + actual_rshares+=vw*cur_vote.vote_percent/CHAIN_100_PERCENT; } - approve_min_shares=props.total_vesting_shares.amount * consensus.median_props.committee_request_approve_min_percent / CHAIN_100_PERCENT; + approve_min_shares=(props.total_vesting_shares.amount + share_type(lp_nav_shares)) * consensus.median_props.committee_request_approve_min_percent / CHAIN_100_PERCENT; if(has_hardfork(CHAIN_HARDFORK_2)){ if(approve_min_shares > max_rshares){ modify(cur_request, [&](committee_request_object &c) { @@ -3654,7 +3685,7 @@ namespace graphene { namespace chain { return; } - chain_properties_hf13 median_props; + chain_properties_pm median_props; auto median = active.size() / 2; auto calc_median = [&](auto&& param) { @@ -3703,6 +3734,55 @@ namespace graphene { namespace chain { if(has_hardfork(CHAIN_HARDFORK_13)){ calc_median(&chain_properties_hf13::distribution_epoch_length); } + if(has_hardfork(CHAIN_HARDFORK_14)){ + calc_median(&chain_properties_pm::pm_oracle_registration_fee); + calc_median(&chain_properties_pm::pm_min_oracle_insurance); + calc_median(&chain_properties_pm::pm_market_creation_fee); + calc_median(&chain_properties_pm::pm_min_liquidity); + calc_median(&chain_properties_pm::pm_max_outcomes); + calc_median(&chain_properties_pm::pm_max_market_duration); + calc_median(&chain_properties_pm::pm_max_oracle_fee_percent); + calc_median(&chain_properties_pm::pm_oracle_accept_window_sec); + calc_median(&chain_properties_pm::pm_listing_min_coverage_percent); + calc_median(&chain_properties_pm::pm_betting_min_coverage_percent); + calc_median(&chain_properties_pm::pm_default_time_penalty_percent); + calc_median(&chain_properties_pm::pm_max_time_penalty); + calc_median(&chain_properties_pm::pm_dispute_fee); + calc_median(&chain_properties_pm::pm_dispute_grace_sec); + calc_median(&chain_properties_pm::pm_oracle_dispute_response_sec); + calc_median(&chain_properties_pm::pm_dispute_auto_close_sec); + calc_median(&chain_properties_pm::pm_dispute_vote_period_sec); + calc_median(&chain_properties_pm::pm_dispute_approve_min_percent); + calc_median(&chain_properties_pm::pm_oracle_penalty_percent); + calc_median(&chain_properties_pm::pm_no_contest_penalty_percent); + calc_median(&chain_properties_pm::pm_dispute_reward_multiplier); + calc_median(&chain_properties_pm::pm_batch_epoch_blocks); + calc_median(&chain_properties_pm::pm_reveal_window_blocks); + calc_median(&chain_properties_pm::pm_commit_no_reveal_penalty_percent); + calc_median(&chain_properties_pm::pm_min_batch_bet); + calc_median(&chain_properties_pm::pm_commit_reveal_enabled); + calc_median(&chain_properties_pm::pm_processing_cap_per_block); + calc_median(&chain_properties_pm::pm_lazy_pool_enabled); + calc_median(&chain_properties_pm::pm_lazy_alloc_percent); + calc_median(&chain_properties_pm::pm_lazy_max_total_alloc_percent); + calc_median(&chain_properties_pm::pm_lazy_lock_sec); + calc_median(&chain_properties_pm::pm_lazy_recall_step_percent); + calc_median(&chain_properties_pm::pm_lazy_emergency_penalty_percent); + calc_median(&chain_properties_pm::pm_lazy_min_liquidity_fee_percent); + calc_median(&chain_properties_pm::pm_leverage_enabled); + calc_median(&chain_properties_pm::pm_leverage_fund_percent); + calc_median(&chain_properties_pm::pm_leverage_max_per_position_bp); + calc_median(&chain_properties_pm::pm_leverage_pool_profit_percent); + calc_median(&chain_properties_pm::pm_leverage_safety_margin_percent); + calc_median(&chain_properties_pm::pm_leverage_max_slippage_percent); + calc_median(&chain_properties_pm::pm_leverage_min_market_liquidity); + calc_median(&chain_properties_pm::pm_leverage_max_position_ratio_percent); + calc_median(&chain_properties_pm::pm_leverage_expiration_buffer_sec); + calc_median(&chain_properties_pm::pm_leverage_m_factor_percent); + calc_median(&chain_properties_pm::pm_leverage_funding_rate_ppm_per_day); + calc_median(&chain_properties_pm::pm_conversion_profit_cost_percent); + calc_median(&chain_properties_pm::pm_early_exit_reward_cap_percent); + } modify(wso, [&](validator_schedule_object &_wso) { _wso.median_props = median_props; @@ -5008,6 +5088,30 @@ namespace graphene { namespace chain { _my->_evaluator_registry.register_evaluator(); _my->_evaluator_registry.register_evaluator(); _my->_evaluator_registry.register_evaluator(); + + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); } void database::set_custom_operation_interpreter(const std::string &id, std::shared_ptr registry) { @@ -5057,6 +5161,23 @@ namespace graphene { namespace chain { add_core_index(*this); add_core_index(*this); + // HF14 Prediction Markets (Onix) — consensus objects, registered as core indexes. + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + add_core_index(*this); + _plugin_index_signal(); } @@ -5671,6 +5792,7 @@ namespace graphene { namespace chain { committee_processing(); paid_subscribe_processing(); + process_pm_markets(); process_hardforks(); check_block_post_validation_chain(); @@ -7009,983 +7131,8 @@ namespace graphene { namespace chain { } - void database::init_hardforks() { - const dynamic_global_property_object &dpo = get_dynamic_global_properties(); - _hardfork_times[0] = dpo.genesis_time; - _hardfork_versions[0] = hardfork_version(CHAIN_STARTUP_VERSION); - - _hardfork_times[CHAIN_HARDFORK_1] = fc::time_point_sec(CHAIN_HARDFORK_1_TIME); - _hardfork_versions[CHAIN_HARDFORK_1] = CHAIN_HARDFORK_1_VERSION; - - _hardfork_times[CHAIN_HARDFORK_2] = fc::time_point_sec(CHAIN_HARDFORK_2_TIME); - _hardfork_versions[CHAIN_HARDFORK_2] = CHAIN_HARDFORK_2_VERSION; - - _hardfork_times[CHAIN_HARDFORK_3] = fc::time_point_sec(CHAIN_HARDFORK_3_TIME); - _hardfork_versions[CHAIN_HARDFORK_3] = CHAIN_HARDFORK_3_VERSION; - - _hardfork_times[CHAIN_HARDFORK_4] = fc::time_point_sec(CHAIN_HARDFORK_4_TIME); - _hardfork_versions[CHAIN_HARDFORK_4] = CHAIN_HARDFORK_4_VERSION; - - _hardfork_times[CHAIN_HARDFORK_5] = fc::time_point_sec(CHAIN_HARDFORK_5_TIME); - _hardfork_versions[CHAIN_HARDFORK_5] = CHAIN_HARDFORK_5_VERSION; - - _hardfork_times[CHAIN_HARDFORK_6] = fc::time_point_sec(CHAIN_HARDFORK_6_TIME); - _hardfork_versions[CHAIN_HARDFORK_6] = CHAIN_HARDFORK_6_VERSION; - - _hardfork_times[CHAIN_HARDFORK_7] = fc::time_point_sec(CHAIN_HARDFORK_7_TIME); - _hardfork_versions[CHAIN_HARDFORK_7] = CHAIN_HARDFORK_7_VERSION; - - _hardfork_times[CHAIN_HARDFORK_8] = fc::time_point_sec(CHAIN_HARDFORK_8_TIME); - _hardfork_versions[CHAIN_HARDFORK_8] = CHAIN_HARDFORK_8_VERSION; - - _hardfork_times[CHAIN_HARDFORK_9] = fc::time_point_sec(CHAIN_HARDFORK_9_TIME); - _hardfork_versions[CHAIN_HARDFORK_9] = CHAIN_HARDFORK_9_VERSION; - - _hardfork_times[CHAIN_HARDFORK_10] = fc::time_point_sec(CHAIN_HARDFORK_10_TIME); - _hardfork_versions[CHAIN_HARDFORK_10] = CHAIN_HARDFORK_10_VERSION; - - _hardfork_times[CHAIN_HARDFORK_11] = fc::time_point_sec(CHAIN_HARDFORK_11_TIME); - _hardfork_versions[CHAIN_HARDFORK_11] = CHAIN_HARDFORK_11_VERSION; - - _hardfork_times[CHAIN_HARDFORK_12] = fc::time_point_sec(CHAIN_HARDFORK_12_TIME); - _hardfork_versions[CHAIN_HARDFORK_12] = CHAIN_HARDFORK_12_VERSION; - - _hardfork_times[CHAIN_HARDFORK_13] = fc::time_point_sec(CHAIN_HARDFORK_13_TIME); - _hardfork_versions[CHAIN_HARDFORK_13] = CHAIN_HARDFORK_13_VERSION; - - const auto &hardforks = get_hardfork_property_object(); - FC_ASSERT( - hardforks.last_hardfork <= CHAIN_NUM_HARDFORKS, - "Chain knows of more hardforks than configuration", - ("hardforks.last_hardfork", hardforks.last_hardfork) - ("CHAIN_NUM_HARDFORKS", CHAIN_NUM_HARDFORKS)); - FC_ASSERT( - _hardfork_versions[hardforks.last_hardfork] <= CHAIN_VERSION, - "Blockchain version is older than last applied hardfork"); - FC_ASSERT(CHAIN_HARDFORK_VERSION == _hardfork_versions[CHAIN_NUM_HARDFORKS]); - } - - void database::reset_virtual_schedule_time() { - const validator_schedule_object &wso = get_validator_schedule_object(); - modify(wso, [&](validator_schedule_object &o) { - o.current_virtual_time = fc::uint128_t(); // reset it 0 - }); - - const auto &idx = get_index().indices(); - for (const auto &validator : idx) { - modify(validator, [&](validator_object &wobj) { - wobj.virtual_position = fc::uint128_t(); - wobj.virtual_last_update = wso.current_virtual_time; - wobj.virtual_scheduled_time = VIRTUAL_SCHEDULE_LAP_LENGTH2 / - (wobj.counted_votes.value + 1); - }); - } - } - - void database::process_hardforks() { - try { - // If there are upcoming hardforks and the next one is later, do nothing - const auto &hardforks = get_hardfork_property_object(); - - while (_hardfork_versions[hardforks.last_hardfork] < - hardforks.next_hardfork - && - hardforks.next_hardfork_time <= head_block_time()) { - if (hardforks.last_hardfork < CHAIN_NUM_HARDFORKS) { - apply_hardfork(hardforks.last_hardfork + 1); - } else { - throw unknown_hardfork_exception(); - } - } - } - FC_CAPTURE_AND_RETHROW() - } - - bool database::has_hardfork(uint32_t hardfork) const { - return get_hardfork_property_object().processed_hardforks.size() > - hardfork; - } - - void database::set_hardfork(uint32_t hardfork, bool apply_now) { - auto const &hardforks = get_hardfork_property_object(); - - for (uint32_t i = hardforks.last_hardfork + 1; - i <= hardfork && i <= CHAIN_NUM_HARDFORKS; i++) { - modify(hardforks, [&](hardfork_property_object &hpo) { - hpo.next_hardfork = _hardfork_versions[i]; - hpo.next_hardfork_time = head_block_time(); - }); - - if (apply_now) { - apply_hardfork(i); - } - } - } - - void database::apply_hardfork(uint32_t hardfork) { - if (_log_hardforks) { - elog("HARDFORK ${hf} at block ${b}", ("hf", hardfork)("b", head_block_num())); - } - - switch (hardfork) { - case CHAIN_HARDFORK_1: - break; - case CHAIN_HARDFORK_2: - break; - case CHAIN_HARDFORK_3: - break; - case CHAIN_HARDFORK_4: - { - const auto &props = get_dynamic_global_properties(); - u256 summary_awarded_rshares_u256 = 0; - const auto block_time = head_block_time(); - //need to calc summary content net_rshares for competition with summary_awarded_rshares from accounts - //also set cashout time for current block_time for init process_content_cashout after summary_awarded_rshares payouts - const auto &cidx = get_index().indices().get(); - - auto current = cidx.begin(); - while (current != cidx.end() && current->cashout_time > block_time) { - summary_awarded_rshares_u256 += to256(current->net_rshares.value); - modify(*current, [&](content_object &c) { - c.cashout_time = block_time; - }); - current = cidx.begin(); - } - - //recalc summary_awarded_rshares and split reward fund between all accounts contains awarded_rshares - const auto &sidx = get_index().indices().get(); - for (auto itr = sidx.begin(); itr != sidx.end(); ++itr) { - if(itr->awarded_rshares > 0){ - summary_awarded_rshares_u256 += to256(itr->awarded_rshares); - } - } - - u256 reward_fund = to256(props.total_reward_fund.amount.value); - u256 payout_u256 = 0; - u256 awarded_rshares_u256 = 0; - uint64_t payout = 0; - uint64_t summary_payout = 0; - - const auto &eidx = get_index().indices().get(); - for (auto itr = eidx.begin(); itr != eidx.end(); ++itr) { - if(itr->awarded_rshares > 0){ - awarded_rshares_u256 = to256(itr->awarded_rshares); - payout_u256 = (awarded_rshares_u256 * reward_fund) / summary_awarded_rshares_u256; - FC_ASSERT(payout_u256 <= u256(uint64_t(std::numeric_limits::max()))); - - payout = static_cast< uint64_t >( payout_u256 ); - summary_payout += payout; - share_type payout_tokens = payout; - asset account_payout=asset(payout_tokens,TOKEN_SYMBOL); - elog("HF4 awarded payment for ${a}: ${n}", ("a", itr->name)("n", payout_tokens)); - adjust_balance(get_account(itr->name), account_payout); - modify(*itr, [&](account_object &a) { - a.awarded_rshares = 0; - }); - } - } - modify(props, [&](dynamic_global_property_object &p) { - p.total_reward_fund.amount -= summary_payout; - }); - - //finally exec process_content_cashout for content objects (using remaining reward fund) - process_content_cashout(); - - //remove all content vote index objects - const auto &r1idx = get_index().indices(); - auto r1itr = r1idx.begin(); - while(r1itr != r1idx.end()) - { - const auto& current = *r1itr; - ++r1itr; - remove(current); - } - //remove all content index objects - const auto &r2idx = get_index().indices(); - auto r2itr = r2idx.begin(); - while(r2itr != r2idx.end()) - { - const auto& current = *r2itr; - ++r2itr; - remove(current); - } - //remove all content type index objects - const auto &r3idx = get_index().indices(); - auto r3itr = r3idx.begin(); - while(r3itr != r3idx.end()) - { - const auto& current = *r3itr; - ++r3itr; - remove(current); - } - - modify(props, [&](dynamic_global_property_object &p) { - p.total_reward_shares=0; - }); - - //recalc validator votes for fair DPOS - const auto &widx = get_index().indices(); - for(auto witr = widx.begin(); witr != widx.end(); ++witr) { - const auto &voter = get(witr->account); - share_type old_weight=voter.validator_vote_weight(); - share_type new_weight=voter.validator_vote_fair_weight_prehf5(); - adjust_validator_vote(get(witr->validator), -old_weight); - adjust_validator_vote(get(witr->validator), new_weight); - } - - break; - } - case CHAIN_HARDFORK_5: - { - //clear votes for each validator - const auto &widx = get_index().indices().get(); - for (auto itr = widx.begin(); - itr != widx.end(); - ++itr) { - modify(*itr, [&](validator_object &w) { - elog("HF5 validator ${a} was votes: ${n}", ("a", w.owner)("n", w.votes)); - w.votes = 0; - w.counted_votes = 0; - }); - } - //recalc validator votes for fair DPOS - const auto &widx2 = get_index().indices(); - for(auto witr = widx2.begin(); witr != widx2.end(); ++witr) { - const auto &voter = get(witr->account); - const auto &validator = get(witr->validator); - - share_type fair_weight=voter.validator_vote_fair_weight(); - modify(voter, [&](account_object &a) { - a.validators_vote_weight = fair_weight; - }); - elog("HF5 validator ${a} calc votes: ${n}", ("a", validator.owner)("n", fair_weight)); - - adjust_validator_vote(get(witr->validator), fair_weight); - } - break; - } - case CHAIN_HARDFORK_6: - { - const auto &props = get_dynamic_global_properties(); - //recalc shares to change ratio with VIZ token 1:1 - auto old_price=props.get_vesting_share_price(); - price new_price(asset(1000, TOKEN_SYMBOL), asset(1000000, SHARES_SYMBOL)); - asset new_total_vesting_shares(0, SHARES_SYMBOL); - - const auto &eidx = get_index().indices().get(); - for (auto itr = eidx.begin(); itr != eidx.end(); ++itr) { - elog("HF6 account shares recalc ${a}:", ("a", itr->name)); - elog(" - old vesting_shares: ${a}", ("a", itr->vesting_shares.amount)); - if(itr->proxy != CHAIN_PROXY_TO_SELF_ACCOUNT){ - elog("- proxy found: ${a}", ("a", itr->proxy)); - std::array delta; - delta[0] = -(itr->vesting_shares.amount); - for (int i = 0; i < CHAIN_MAX_PROXY_RECURSION_DEPTH; ++i) { - delta[i + 1] = -(itr->proxied_vsf_votes[i]); - } - adjust_proxied_validator_votes(get_account(itr->name), delta); - } - - auto old_shares=itr->vesting_shares; - old_shares=old_shares*1000; - auto tokens=old_shares*old_price; - auto new_shares=tokens*new_price; - new_shares=new_shares/1000; - new_total_vesting_shares+=new_shares; - - auto old_withdraw_rate=itr->vesting_withdraw_rate; - old_withdraw_rate=old_withdraw_rate*1000; - auto withdraw_tokens=old_withdraw_rate*old_price; - auto new_withdraw_rate=withdraw_tokens*new_price; - new_withdraw_rate=new_withdraw_rate/1000; - - modify(*itr, [&](account_object &a) { - a.vesting_shares = new_shares; - a.vesting_withdraw_rate = new_withdraw_rate; - a.delegated_vesting_shares = asset(0, SHARES_SYMBOL); - a.received_vesting_shares = asset(0, SHARES_SYMBOL); - }); - - elog(" - new vesting_shares: ${a}", ("a", itr->vesting_shares.amount)); - if(itr->proxy != CHAIN_PROXY_TO_SELF_ACCOUNT){ - elog("- proxy votes updated: ${a}", ("a", itr->proxy)); - std::array delta; - delta[0] = itr->vesting_shares.amount; - for (int i = 0; i < CHAIN_MAX_PROXY_RECURSION_DEPTH; ++i) { - delta[i + 1] = itr->proxied_vsf_votes[i]; - } - adjust_proxied_validator_votes(get_account(itr->name), delta); - } - } - - elog("New total_vesting_shares: ${a}", ("a", new_total_vesting_shares.amount)); - modify(props, [&](dynamic_global_property_object &p) { - p.total_vesting_shares = new_total_vesting_shares; - }); - - //recalc delegations - const auto& delegations = get_index().indices().get(); - for (auto itr = delegations.begin(); - itr != delegations.end(); - ++itr) { - auto old_delegation_shares=itr->vesting_shares; - //fix excess delegated_vesting_shares (need hf7 fix object) - create([&](fix_vesting_delegation_object& o) { - o.delegator = itr->delegator; - o.delegatee = itr->delegatee; - o.vesting_shares = old_delegation_shares; - elog("!!! HF6 delay fix decrease ${a} delegated_vesting_shares: ${n}", ("a", itr->delegator)("n", old_delegation_shares)); - elog("!!! HF6 delay fix decrease ${a} received_vesting_shares: ${n}", ("a", itr->delegatee)("n", old_delegation_shares)); - }); - /* - modify(get_account(itr->delegator), [&](account_object& a) { - a.delegated_vesting_shares -= old_delegation_shares; - }); - modify(get_account(itr->delegatee), [&](account_object& a) { - a.received_vesting_shares -= old_delegation_shares; - }); - */ - old_delegation_shares=old_delegation_shares*1000; - auto old_delegation_tokens=old_delegation_shares*old_price; - auto new_delegation_shares=old_delegation_tokens*new_price; - new_delegation_shares=new_delegation_shares/1000; - modify(*itr, [&](vesting_delegation_object &a) { - a.vesting_shares = new_delegation_shares; - }); - modify(get_account(itr->delegator), [&](account_object& a) { - a.delegated_vesting_shares += new_delegation_shares; - }); - modify(get_account(itr->delegatee), [&](account_object& a) { - a.received_vesting_shares += new_delegation_shares; - }); - } - - //recalc expiring delegations - const auto& delegations_by_exp = get_index().indices().get(); - for (auto itr = delegations_by_exp.begin(); - itr != delegations_by_exp.end(); - ++itr) { - auto old_delegation_shares=itr->vesting_shares; - //fix excess delegated_vesting_shares (need hf7 fix object) - create([&](fix_vesting_delegation_object& o) { - o.delegator = itr->delegator; - o.vesting_shares = old_delegation_shares; - elog("!!! HF6 delay fix decrease ${a} delegated_vesting_shares: ${n}", ("a", itr->delegator)("n", old_delegation_shares)); - }); - /* - modify(get_account(itr->delegator), [&](account_object& a) { - a.delegated_vesting_shares -= old_delegation_shares; - }); - */ - old_delegation_shares=old_delegation_shares*1000; - auto old_delegation_tokens=old_delegation_shares*old_price; - auto new_delegation_shares=old_delegation_tokens*new_price; - new_delegation_shares=new_delegation_shares/1000; - modify(*itr, [&](vesting_delegation_expiration_object &a) { - a.vesting_shares = new_delegation_shares; - }); - modify(get_account(itr->delegator), [&](account_object& a) { - a.delegated_vesting_shares += new_delegation_shares; - }); - } - - //clear votes for each validator - const auto &widx = get_index().indices().get(); - for (auto itr = widx.begin(); - itr != widx.end(); - ++itr) { - modify(*itr, [&](validator_object &w) { - elog("HF6 validator ${a} has votes: ${n}", ("a", w.owner)("n", w.votes)); - w.votes = 0; - w.counted_votes = 0; - }); - } - //recalc validator votes after shares ratio fix - const auto &widx2 = get_index().indices(); - for(auto witr = widx2.begin(); witr != widx2.end(); ++witr) { - const auto &voter = get(witr->account); - const auto &validator = get(witr->validator); - - share_type fair_weight=voter.validator_vote_fair_weight(); - modify(voter, [&](account_object &a) { - a.validators_vote_weight = fair_weight; - }); - elog("HF6 validator ${a} recalc votes from ${a}: ${n}", ("a", validator.owner)("n", fair_weight)); - - adjust_validator_vote(get(witr->validator), fair_weight); - } - break; - } - case CHAIN_HARDFORK_7: - { - const auto &committee_account = get_account(CHAIN_COMMITTEE_ACCOUNT); - - const auto &fix_idx = get_index().indices(); - auto fix_itr = fix_idx.begin(); - while(fix_itr != fix_idx.end()) { - const auto &fix_current = *fix_itr; - ++fix_itr; - if(fix_current.delegator!=""){ - modify(get_account(fix_current.delegator), [&](account_object& a) { - a.delegated_vesting_shares -= fix_current.vesting_shares; - elog("!!! HF7 delayed fix decrease ${a} delegated_vesting_shares: ${n}", ("a", fix_current.delegator)("n", fix_current.vesting_shares)); - }); - } - if(fix_current.delegatee!=""){ - modify(get_account(fix_current.delegatee), [&](account_object& a) { - a.received_vesting_shares -= fix_current.vesting_shares; - elog("!!! HF7 delayed fix decrease ${a} received_vesting_shares: ${n}", ("a", fix_current.delegatee)("n", fix_current.vesting_shares)); - }); - } - remove(fix_current); - } - - const auto &eidx = get_index().indices().get(); - auto itr = eidx.begin(); - while(itr != eidx.end()){ - const auto ¤t = *itr; - ++itr; - if(!current.valid){ - elog("HF7 found invalid account ${a}", ("a", current.name)); - if(current.proxy != CHAIN_PROXY_TO_SELF_ACCOUNT){ - elog("- remove proxy: ${a}", ("a", current.proxy)); - std::array delta; - delta[0] = -(current.vesting_shares.amount); - for (int i = 0; i < CHAIN_MAX_PROXY_RECURSION_DEPTH; ++i) { - delta[i + 1] = -(current.proxied_vsf_votes[i]); - } - adjust_proxied_validator_votes(get_account(current.name), delta); - } - //move shares and balance to committee - elog("- add to committee funds: ${a} SHARES, ${b} TOKEN", ("a", current.vesting_shares)("b", current.balance)); - modify(committee_account, [&](account_object &a) { - a.vesting_shares += current.vesting_shares; - a.balance += current.balance; - }); - - //remove from master_authority_history_index - const auto &d1idx = get_index().indices().get(); - auto delete_itr1 = d1idx.lower_bound(current.name); - while(delete_itr1 != d1idx.end() && - delete_itr1->account == current.name) { - const auto &delete_current = *delete_itr1; - ++delete_itr1; - remove(delete_current); - } - - //remove from account_metadata_index - const auto &d2idx = get_index().indices().get(); - auto delete_itr2 = d2idx.lower_bound(current.name); - while(delete_itr2 != d2idx.end() && - delete_itr2->account == current.name) { - const auto &delete_current = *delete_itr2; - ++delete_itr2; - remove(delete_current); - } - - //remove from account_authority_index - const auto &d3idx = get_index().indices().get(); - auto delete_itr3 = d3idx.lower_bound(current.name); - while(delete_itr3 != d3idx.end() && - delete_itr3->account == current.name) { - const auto &delete_current = *delete_itr3; - ++delete_itr3; - remove(delete_current); - } - - //remove from vesting_delegation_expiration_index by_account_expiration - const auto& d4idx = get_index().indices().get(); - auto delete_itr4 = d4idx.lower_bound(std::make_tuple(current.name, fc::time_point_sec())); - while(delete_itr4 != d4idx.end() && - delete_itr4->delegator == current.name) { - const auto &delete_current = *delete_itr4; - ++delete_itr4; - modify(get_account(delete_current.delegator), [&](account_object& a) { - a.delegated_vesting_shares -= delete_current.vesting_shares; - }); - remove(delete_current); - } - - //remove from vesting_delegation_index by_delegation - const auto &d5idx = get_index().indices().get(); - auto delete_itr5 = d5idx.lower_bound(std::make_tuple(current.name,account_name_type())); - while(delete_itr5 != d5idx.end() && - delete_itr5->delegator == current.name) { - const auto &delete_current = *delete_itr5; - ++delete_itr5; - modify(get_account(delete_current.delegator), [&](account_object& a) { - a.delegated_vesting_shares -= delete_current.vesting_shares; - }); - modify(get_account(delete_current.delegatee), [&](account_object& a) { - a.received_vesting_shares -= delete_current.vesting_shares; - }); - remove(delete_current); - } - - //remove from vesting_delegation_index by_received - const auto &d6idx = get_index().indices().get(); - auto delete_itr6 = d6idx.lower_bound(std::make_tuple(current.name,account_name_type())); - while(delete_itr6 != d6idx.end() && - delete_itr6->delegatee == current.name) { - const auto &delete_current = *delete_itr6; - ++delete_itr6; - modify(get_account(delete_current.delegator), [&](account_object& a) { - a.delegated_vesting_shares -= delete_current.vesting_shares; - }); - modify(get_account(delete_current.delegatee), [&](account_object& a) { - a.received_vesting_shares -= delete_current.vesting_shares; - }); - remove(delete_current); - } - - //remove from account_recovery_request_index - const auto &d7idx = get_index().indices().get(); - auto delete_itr7 = d7idx.lower_bound(std::make_tuple(current.name,account_recovery_request_id_type())); - while(delete_itr7 != d7idx.end() && - delete_itr7->account_to_recover == current.name) { - const auto &delete_current = *delete_itr7; - ++delete_itr7; - remove(delete_current); - } - - //remove from change_recovery_account_request_index - const auto &d8idx = get_index().indices().get(); - auto delete_itr8 = d8idx.lower_bound(std::make_tuple(current.name,change_recovery_account_request_id_type())); - while(delete_itr8 != d8idx.end() && - delete_itr8->account_to_recover == current.name) { - const auto &delete_current = *delete_itr8; - ++delete_itr8; - remove(delete_current); - } - - //decrease validators_vote_weight from all votes by invalid account - const auto &vidx = get_index().indices().get(); - auto vitr = vidx.lower_bound(boost::make_tuple(current.id, validator_id_type())); - while (vitr != vidx.end() && vitr->account == current.id) { - adjust_validator_vote(get(vitr->validator),-current.validators_vote_weight); - ++vitr; - } - - //remove from validator_vote_index by_account_validator (remove all votes from invalid account) - const auto &d10idx = get_index().indices().get(); - auto delete_itr10 = d10idx.lower_bound(boost::make_tuple(current.id, validator_id_type())); - while(delete_itr10 != d10idx.end() && - delete_itr10->account == current.id) { - const auto &delete_current = *delete_itr10; - adjust_validator_vote(get(delete_itr10->validator),-current.validators_vote_weight); - modify(current, [&](account_object &a) { - a.validators_voted_for--; - }); - ++delete_itr10; - remove(delete_current); - } - - //recalc validators_vote_weight (must be 0 after remove all validator votes from invalid account) - share_type current_fair_vote_weight = current.validator_vote_fair_weight(); - modify(current, [&](account_object &a) { - a.validators_vote_weight = current_fair_vote_weight; - }); - - //look validator object from invalid account - const auto &invalid_validator = find(current.name); - if(invalid_validator != nullptr){//found validator - //remove invalid validator account from penalty index - const auto &d8idx = get_index().indices().get(); - auto delete_itr8 = d8idx.lower_bound(invalid_validator->owner); - while(delete_itr8 != d8idx.end() && - delete_itr8->validator == invalid_validator->owner) { - const auto &delete_current = *delete_itr8; - ++delete_itr8; - remove(delete_current); - } - //remove invalid validator account from schedule - const validator_schedule_object &wso = get_validator_schedule_object(); - modify(wso, [&](validator_schedule_object &_wso) { - for (int i = 0; i < _wso.num_scheduled_validators; i+=CHAIN_BLOCK_VALIDATOR_REPEAT) { - if(_wso.current_shuffled_validators[i] == invalid_validator->owner){ - _wso.current_shuffled_validators[i] = account_name_type(); - } - } - }); - //recalc validators_vote_weight from all votes to invalid validator account (remove votes to invalid validator account) - const auto &vidx = get_index().indices().get(); - auto vitr = vidx.lower_bound(boost::make_tuple(invalid_validator->id, account_id_type())); - while (vitr != vidx.end() && vitr->validator == invalid_validator->id) { - const auto &voter_account = get(vitr->account); - const auto &vidx2 = get_index().indices().get(); - auto vitr2 = vidx2.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); - while (vitr2 != vidx2.end() && vitr2->account == voter_account.id) { - adjust_validator_vote(get(vitr2->validator), -voter_account.validators_vote_weight); - ++vitr2; - } - - remove(*vitr); - - modify(voter_account, [&](account_object &a) { - a.validators_voted_for--; - }); - - share_type fair_vote_weight = voter_account.validator_vote_fair_weight(); - modify(voter_account, [&](account_object &a) { - a.validators_vote_weight = fair_vote_weight; - }); - - const auto &vidx3 = get_index().indices().get(); - auto vitr3 = vidx3.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); - while (vitr3 != vidx3.end() && vitr3->account == voter_account.id) { - adjust_validator_vote(get(vitr3->validator), voter_account.validators_vote_weight); - ++vitr3; - } - ++vitr; - } - - //remove from validator_index invalid validator account - const auto &d9idx = get_index().indices().get(); - auto delete_itr9 = d9idx.lower_bound(current.name); - while(delete_itr9 != d9idx.end() && - delete_itr9->owner == current.name) { - const auto &delete_current = *delete_itr9; - ++delete_itr9; - remove(delete_current); - } - } - - //remove all committee votes - const auto &d11idx = get_index().indices().get(); - auto delete_itr11 = d11idx.lower_bound(current.name); - while(delete_itr11 != d11idx.end() && - delete_itr11->voter == current.name) { - const auto &delete_current = *delete_itr11; - ++delete_itr11; - remove(delete_current); - } - - //remove all committee requests as creator - const auto &d12idx = get_index().indices().get(); - auto delete_itr12 = d12idx.lower_bound(current.name); - while(delete_itr12 != d12idx.end() && - delete_itr12->creator == current.name) { - const auto &delete_current = *delete_itr12; - ++delete_itr12; - remove(delete_current); - } - - //remove all committee requests as worker - const auto &d13idx = get_index().indices().get(); - auto delete_itr13 = d13idx.lower_bound(current.name); - while(delete_itr13 != d13idx.end() && - delete_itr13->worker == current.name) { - const auto &delete_current = *delete_itr13; - ++delete_itr13; - remove(delete_current); - } - - //remove all withdraw routes from - const auto &d14idx = get_index().indices().get(); - auto delete_itr14 = d14idx.lower_bound(std::make_tuple(current.id,account_id_type())); - while(delete_itr14 != d14idx.end() && - delete_itr14->from_account == current.id) { - const auto &delete_current = *delete_itr14; - ++delete_itr14; - remove(delete_current); - } - //remove all withdraw routes to - const auto &d15idx = get_index().indices().get(); - auto delete_itr15 = d15idx.lower_bound(std::make_tuple(account_id_type(),current.id)); - while(delete_itr15 != d15idx.end() && - delete_itr15->to_account == current.id) { - const auto &delete_current = *delete_itr15; - ++delete_itr15; - remove(delete_current); - } - - //change all escrow routes from - const auto &d16idx = get_index().indices().get(); - auto itr16 = d16idx.lower_bound(std::make_tuple(current.name,0)); - while(itr16 != d16idx.end() && - itr16->from == current.name) { - const auto &e_current = *itr16; - ++itr16; - modify(e_current, [&](escrow_object &esc) { - esc.from = CHAIN_COMMITTEE_ACCOUNT; - }); - } - - //change all escrow routes from - const auto &d17idx = get_index().indices().get(); - auto itr17 = d17idx.lower_bound(std::make_tuple(current.name,0)); - while(itr17 != d17idx.end() && - itr17->to == current.name) { - const auto &e_current = *itr17; - ++itr17; - modify(e_current, [&](escrow_object &esc) { - esc.to = CHAIN_COMMITTEE_ACCOUNT; - }); - } - - //change all escrow routes from - const auto &d18idx = get_index().indices().get(); - auto itr18 = d18idx.lower_bound(std::make_tuple(current.name,0)); - while(itr18 != d18idx.end() && - itr18->agent == current.name) { - const auto &e_current = *itr18; - ++itr18; - modify(e_current, [&](escrow_object &esc) { - esc.agent = CHAIN_COMMITTEE_ACCOUNT; - }); - } - - //change all invites creator - const auto &d19idx = get_index().indices().get(); - auto itr19 = d19idx.lower_bound(current.name); - while(itr19 != d19idx.end() && - itr19->creator == current.name) { - const auto &i_current = *itr19; - ++itr19; - modify(i_current, [&](invite_object &i) { - i.creator = CHAIN_COMMITTEE_ACCOUNT; - }); - } - - //change all invites receiver - const auto &d19idx2 = get_index().indices().get(); - auto itr19_2 = d19idx2.lower_bound(current.name); - while(itr19_2 != d19idx2.end() && - itr19_2->receiver == current.name) { - const auto &i_current = *itr19_2; - ++itr19_2; - modify(i_current, [&](invite_object &i) { - i.receiver = CHAIN_COMMITTEE_ACCOUNT; - }); - } - - //remove all paid subscribes by subscriber - const auto &d20idx = get_index().indices().get(); - auto itr20 = d20idx.lower_bound(current.name); - while(itr20 != d20idx.end() && - itr20->subscriber == current.name) { - const auto &i_current = *itr20; - ++itr20; - remove(i_current); - } - - //remove all paid subscribes by creator - const auto &d21idx = get_index().indices().get(); - auto itr21 = d21idx.lower_bound(current.name); - while(itr21 != d21idx.end() && - itr21->creator == current.name) { - const auto &i_current = *itr21; - ++itr21; - remove(i_current); - } - - //remove all paid subscription by creator - const auto &d21idx2 = get_index().indices().get(); - auto itr21_2 = d21idx2.lower_bound(current.name); - while(itr21_2 != d21idx2.end() && - itr21_2->creator == current.name) { - const auto &i_current = *itr21_2; - ++itr21_2; - remove(i_current); - } - - //remove all proposals by account - const auto &d22idx = get_index().indices().get(); - auto itr22 = d22idx.lower_bound(current.name); - while(itr22 != d22idx.end() && - itr22->author == current.name) { - const auto &i_current = *itr22; - ++itr22; - remove(i_current); - } - - //remove all proposals required approval by account - const auto &d23idx = get_index().indices().get(); - auto itr23 = d23idx.lower_bound(current.name); - while(itr23 != d23idx.end() && - itr23->account == current.name) { - const auto &i_current = *itr23; - ++itr23; - remove(i_current); - } - - //remove invalid account - remove(current); - } - } - break; - } - case CHAIN_HARDFORK_8: - { - //refresh delegated_vesting_shares and received_vesting_shares - const auto &eidx = get_index().indices().get(); - auto itr = eidx.begin(); - while(itr != eidx.end()){ - const auto ¤t = *itr; - ++itr; - modify(current, [&](account_object& a) { - a.delegated_vesting_shares=asset(0, SHARES_SYMBOL); - a.received_vesting_shares=asset(0, SHARES_SYMBOL); - }); - } - - //recalc delegations - const auto& delegations = get_index().indices().get(); - for (auto itr = delegations.begin(); - itr != delegations.end(); - ++itr) { - modify(get_account(itr->delegator), [&](account_object& a) { - a.delegated_vesting_shares += itr->vesting_shares; - }); - modify(get_account(itr->delegatee), [&](account_object& a) { - a.received_vesting_shares += itr->vesting_shares; - }); - } - - //recalc expiring delegations - const auto& delegations_by_exp = get_index().indices().get(); - for (auto itr = delegations_by_exp.begin(); - itr != delegations_by_exp.end(); - ++itr) { - modify(get_account(itr->delegator), [&](account_object& a) { - a.delegated_vesting_shares += itr->vesting_shares; - }); - } - break; - } - case CHAIN_HARDFORK_9: - { - //remove validators without signed block - const auto &idx = get_index().indices().get(); - auto itr = idx.begin(); - while(itr != idx.end()){ - const auto ¤t = *itr; - ++itr; - if(0==current.last_confirmed_block_num){ - //MUST be corrected: the validator_vote_object must also be deleted - //remove invalid validator account from penalty index - const auto &d8idx = get_index().indices().get(); - auto delete_itr8 = d8idx.lower_bound(current.owner); - while(delete_itr8 != d8idx.end() && - delete_itr8->validator == current.owner) { - const auto &delete_current = *delete_itr8; - ++delete_itr8; - remove(delete_current); - } - //recalc validators_vote_weight from all votes to invalid validator account (remove votes to invalid validator account) - const auto &vidx = get_index().indices().get(); - auto vitr = vidx.lower_bound(boost::make_tuple(current.id, account_id_type())); - while (vitr != vidx.end() && vitr->validator == current.id) { - const auto &voter_account = get(vitr->account); - const auto &vidx2 = get_index().indices().get(); - auto vitr2 = vidx2.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); - while (vitr2 != vidx2.end() && vitr2->account == voter_account.id) { - adjust_validator_vote(get(vitr2->validator), -voter_account.validators_vote_weight); - ++vitr2; - } - - remove(*vitr); - - modify(voter_account, [&](account_object &a) { - a.validators_voted_for--; - a.valid=false; - }); - - share_type fair_vote_weight = voter_account.validator_vote_fair_weight(); - modify(voter_account, [&](account_object &a) { - a.validators_vote_weight = fair_vote_weight; - }); - - const auto &vidx3 = get_index().indices().get(); - auto vitr3 = vidx3.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); - while (vitr3 != vidx3.end() && vitr3->account == voter_account.id) { - adjust_validator_vote(get(vitr3->validator), voter_account.validators_vote_weight); - ++vitr3; - } - ++vitr; - } - elog("HF9 remove empty/spam validator ${a}", ("a", current.owner)); - remove(current); - } - } - - //remove committee requests without votes - const auto &idx2 = get_index().indices().get(); - auto itr2 = idx2.begin(); - while(itr2 != idx2.end()){ - const auto ¤t = *itr2; - ++itr2; - if(1==current.status){ - if(0==current.votes_count){ - remove(current); - } - } - } - - //remove all paid subscriptions without subscribes - const auto &idx3 = get_index().indices().get(); - auto itr3 = idx3.begin(); - while(itr3 != idx3.end()) { - const auto ¤t = *itr3; - ++itr3; - - bool find_subscribe=false; - - const auto &idx4 = get_index().indices().get(); - auto itr4 = idx4.find(current.creator); - if(itr4 != idx4.end()){ - find_subscribe=true; - } - - if(!find_subscribe){ - remove(current); - } - } - break; - } - case CHAIN_HARDFORK_10: - break; - case CHAIN_HARDFORK_11: - { - //fixed error from CHAIN_HARDFORK_9 by replay, but need toggle flag valid for spam accounts - const auto &eidx = get_index().indices().get(); - auto itr = eidx.begin(); - while(itr != eidx.end()){ - const auto ¤t = *itr; - ++itr; - if(!current.valid){ - //toggle invalid accounts to valid - modify(current, [&](account_object &a) { - a.valid=true; - }); - } - } - break; - } - case CHAIN_HARDFORK_12: - break; - case CHAIN_HARDFORK_13: - // Validator reward sharing: new fields sharing_rate and - // pending_stakeholder_reward on validator_object default to 0 (replay - // initialises them), no extra migration needed. - break; - default: - break; - } - - modify(get_hardfork_property_object(), [&](hardfork_property_object &hfp) { - FC_ASSERT(hardfork == hfp.last_hardfork + - 1, "Hardfork being applied out of order", ("hardfork", hardfork)("hfp.last_hardfork", hfp.last_hardfork)); - FC_ASSERT(hfp.processed_hardforks.size() == - hardfork, "Hardfork being applied out of order"); - hfp.processed_hardforks.push_back(_hardfork_times[hardfork]); - hfp.last_hardfork = hardfork; - hfp.current_hardfork_version = _hardfork_versions[hardfork]; - FC_ASSERT(hfp.processed_hardforks[hfp.last_hardfork] == - _hardfork_times[hfp.last_hardfork], "Hardfork processing failed sanity check..."); - }); - - push_virtual_operation(hardfork_operation(hardfork), true); - } + // NOTE: init_hardforks / reset_virtual_schedule_time / process_hardforks / + // has_hardfork / set_hardfork / apply_hardfork were moved to + // database_hardfork.cpp to shrink this translation unit. } } //graphene::chain diff --git a/libraries/chain/database_hardfork.cpp b/libraries/chain/database_hardfork.cpp new file mode 100644 index 0000000000..1b2a24f340 --- /dev/null +++ b/libraries/chain/database_hardfork.cpp @@ -0,0 +1,1045 @@ +/* + * Hardfork application for graphene::chain::database. + * + * Extracted verbatim from database.cpp to shrink that translation unit (the + * single longest-to-compile file in the project) and give the hardfork logic + * its own compilation unit. No behavior change: the method bodies below are + * byte-for-byte the ones that previously lived in database.cpp. + * + * These methods rely only on the database members declared in database.hpp + * (_hardfork_times, _hardfork_versions, _log_hardforks) and on the same object + * indexes and helpers the rest of the chain library uses. The include set below + * mirrors the graphene/chain/* headers database.cpp pulls in for the hardfork + * path (apply_hardfork touches proposal_index, required_approval_index, and the + * full range of core object indexes), so keep it in sync with database.cpp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // HF14: apply_hardfork instantiates pm_lazy_pool_object + +#include + +#include + +namespace graphene { namespace chain { + +// Virtual-schedule lap length, mirrored from database.cpp. The reset in +// reset_virtual_schedule_time (below) must use the same constant the scheduler +// in database.cpp uses; keep the two definitions in sync if either changes. +#define VIRTUAL_SCHEDULE_LAP_LENGTH ( fc::uint128_t(uint64_t(-1)) ) +#define VIRTUAL_SCHEDULE_LAP_LENGTH2 ( fc::uint128_t::max_value() ) + +// Local mirror of the u256 promotion helper used by apply_hardfork. Defined the +// same way in database.cpp and chain_evaluator.cpp; kept file-local here so this +// TU is self-contained. +inline u256 to256(const fc::uint128_t &t) { + u256 v(t.hi); + v <<= 64; + v += t.lo; + return v; +} + + void database::init_hardforks() { + const dynamic_global_property_object &dpo = get_dynamic_global_properties(); + _hardfork_times[0] = dpo.genesis_time; + _hardfork_versions[0] = hardfork_version(CHAIN_STARTUP_VERSION); + + _hardfork_times[CHAIN_HARDFORK_1] = fc::time_point_sec(CHAIN_HARDFORK_1_TIME); + _hardfork_versions[CHAIN_HARDFORK_1] = CHAIN_HARDFORK_1_VERSION; + + _hardfork_times[CHAIN_HARDFORK_2] = fc::time_point_sec(CHAIN_HARDFORK_2_TIME); + _hardfork_versions[CHAIN_HARDFORK_2] = CHAIN_HARDFORK_2_VERSION; + + _hardfork_times[CHAIN_HARDFORK_3] = fc::time_point_sec(CHAIN_HARDFORK_3_TIME); + _hardfork_versions[CHAIN_HARDFORK_3] = CHAIN_HARDFORK_3_VERSION; + + _hardfork_times[CHAIN_HARDFORK_4] = fc::time_point_sec(CHAIN_HARDFORK_4_TIME); + _hardfork_versions[CHAIN_HARDFORK_4] = CHAIN_HARDFORK_4_VERSION; + + _hardfork_times[CHAIN_HARDFORK_5] = fc::time_point_sec(CHAIN_HARDFORK_5_TIME); + _hardfork_versions[CHAIN_HARDFORK_5] = CHAIN_HARDFORK_5_VERSION; + + _hardfork_times[CHAIN_HARDFORK_6] = fc::time_point_sec(CHAIN_HARDFORK_6_TIME); + _hardfork_versions[CHAIN_HARDFORK_6] = CHAIN_HARDFORK_6_VERSION; + + _hardfork_times[CHAIN_HARDFORK_7] = fc::time_point_sec(CHAIN_HARDFORK_7_TIME); + _hardfork_versions[CHAIN_HARDFORK_7] = CHAIN_HARDFORK_7_VERSION; + + _hardfork_times[CHAIN_HARDFORK_8] = fc::time_point_sec(CHAIN_HARDFORK_8_TIME); + _hardfork_versions[CHAIN_HARDFORK_8] = CHAIN_HARDFORK_8_VERSION; + + _hardfork_times[CHAIN_HARDFORK_9] = fc::time_point_sec(CHAIN_HARDFORK_9_TIME); + _hardfork_versions[CHAIN_HARDFORK_9] = CHAIN_HARDFORK_9_VERSION; + + _hardfork_times[CHAIN_HARDFORK_10] = fc::time_point_sec(CHAIN_HARDFORK_10_TIME); + _hardfork_versions[CHAIN_HARDFORK_10] = CHAIN_HARDFORK_10_VERSION; + + _hardfork_times[CHAIN_HARDFORK_11] = fc::time_point_sec(CHAIN_HARDFORK_11_TIME); + _hardfork_versions[CHAIN_HARDFORK_11] = CHAIN_HARDFORK_11_VERSION; + + _hardfork_times[CHAIN_HARDFORK_12] = fc::time_point_sec(CHAIN_HARDFORK_12_TIME); + _hardfork_versions[CHAIN_HARDFORK_12] = CHAIN_HARDFORK_12_VERSION; + + _hardfork_times[CHAIN_HARDFORK_13] = fc::time_point_sec(CHAIN_HARDFORK_13_TIME); + _hardfork_versions[CHAIN_HARDFORK_13] = CHAIN_HARDFORK_13_VERSION; + + _hardfork_times[CHAIN_HARDFORK_14] = fc::time_point_sec(CHAIN_HARDFORK_14_TIME); + _hardfork_versions[CHAIN_HARDFORK_14] = CHAIN_HARDFORK_14_VERSION; + + const auto &hardforks = get_hardfork_property_object(); + FC_ASSERT( + hardforks.last_hardfork <= CHAIN_NUM_HARDFORKS, + "Chain knows of more hardforks than configuration", + ("hardforks.last_hardfork", hardforks.last_hardfork) + ("CHAIN_NUM_HARDFORKS", CHAIN_NUM_HARDFORKS)); + FC_ASSERT( + _hardfork_versions[hardforks.last_hardfork] <= CHAIN_VERSION, + "Blockchain version is older than last applied hardfork"); + FC_ASSERT(CHAIN_HARDFORK_VERSION == _hardfork_versions[CHAIN_NUM_HARDFORKS]); + } + + void database::reset_virtual_schedule_time() { + const validator_schedule_object &wso = get_validator_schedule_object(); + modify(wso, [&](validator_schedule_object &o) { + o.current_virtual_time = fc::uint128_t(); // reset it 0 + }); + + const auto &idx = get_index().indices(); + for (const auto &validator : idx) { + modify(validator, [&](validator_object &wobj) { + wobj.virtual_position = fc::uint128_t(); + wobj.virtual_last_update = wso.current_virtual_time; + wobj.virtual_scheduled_time = VIRTUAL_SCHEDULE_LAP_LENGTH2 / + (wobj.counted_votes.value + 1); + }); + } + } + + void database::process_hardforks() { + try { + // If there are upcoming hardforks and the next one is later, do nothing + const auto &hardforks = get_hardfork_property_object(); + + while (_hardfork_versions[hardforks.last_hardfork] < + hardforks.next_hardfork + && + hardforks.next_hardfork_time <= head_block_time()) { + if (hardforks.last_hardfork < CHAIN_NUM_HARDFORKS) { + apply_hardfork(hardforks.last_hardfork + 1); + } else { + throw unknown_hardfork_exception(); + } + } + } + FC_CAPTURE_AND_RETHROW() + } + + bool database::has_hardfork(uint32_t hardfork) const { + return get_hardfork_property_object().processed_hardforks.size() > + hardfork; + } + + void database::set_hardfork(uint32_t hardfork, bool apply_now) { + auto const &hardforks = get_hardfork_property_object(); + + for (uint32_t i = hardforks.last_hardfork + 1; + i <= hardfork && i <= CHAIN_NUM_HARDFORKS; i++) { + modify(hardforks, [&](hardfork_property_object &hpo) { + hpo.next_hardfork = _hardfork_versions[i]; + hpo.next_hardfork_time = head_block_time(); + }); + + if (apply_now) { + apply_hardfork(i); + } + } + } + + void database::apply_hardfork(uint32_t hardfork) { + if (_log_hardforks) { + elog("HARDFORK ${hf} at block ${b}", ("hf", hardfork)("b", head_block_num())); + } + + switch (hardfork) { + case CHAIN_HARDFORK_1: + break; + case CHAIN_HARDFORK_2: + break; + case CHAIN_HARDFORK_3: + break; + case CHAIN_HARDFORK_4: + { + const auto &props = get_dynamic_global_properties(); + u256 summary_awarded_rshares_u256 = 0; + const auto block_time = head_block_time(); + //need to calc summary content net_rshares for competition with summary_awarded_rshares from accounts + //also set cashout time for current block_time for init process_content_cashout after summary_awarded_rshares payouts + const auto &cidx = get_index().indices().get(); + + auto current = cidx.begin(); + while (current != cidx.end() && current->cashout_time > block_time) { + summary_awarded_rshares_u256 += to256(current->net_rshares.value); + modify(*current, [&](content_object &c) { + c.cashout_time = block_time; + }); + current = cidx.begin(); + } + + //recalc summary_awarded_rshares and split reward fund between all accounts contains awarded_rshares + const auto &sidx = get_index().indices().get(); + for (auto itr = sidx.begin(); itr != sidx.end(); ++itr) { + if(itr->awarded_rshares > 0){ + summary_awarded_rshares_u256 += to256(itr->awarded_rshares); + } + } + + u256 reward_fund = to256(props.total_reward_fund.amount.value); + u256 payout_u256 = 0; + u256 awarded_rshares_u256 = 0; + uint64_t payout = 0; + uint64_t summary_payout = 0; + + const auto &eidx = get_index().indices().get(); + for (auto itr = eidx.begin(); itr != eidx.end(); ++itr) { + if(itr->awarded_rshares > 0){ + awarded_rshares_u256 = to256(itr->awarded_rshares); + payout_u256 = (awarded_rshares_u256 * reward_fund) / summary_awarded_rshares_u256; + FC_ASSERT(payout_u256 <= u256(uint64_t(std::numeric_limits::max()))); + + payout = static_cast< uint64_t >( payout_u256 ); + summary_payout += payout; + share_type payout_tokens = payout; + asset account_payout=asset(payout_tokens,TOKEN_SYMBOL); + elog("HF4 awarded payment for ${a}: ${n}", ("a", itr->name)("n", payout_tokens)); + adjust_balance(get_account(itr->name), account_payout); + modify(*itr, [&](account_object &a) { + a.awarded_rshares = 0; + }); + } + } + modify(props, [&](dynamic_global_property_object &p) { + p.total_reward_fund.amount -= summary_payout; + }); + + //finally exec process_content_cashout for content objects (using remaining reward fund) + process_content_cashout(); + + //remove all content vote index objects + const auto &r1idx = get_index().indices(); + auto r1itr = r1idx.begin(); + while(r1itr != r1idx.end()) + { + const auto& current = *r1itr; + ++r1itr; + remove(current); + } + //remove all content index objects + const auto &r2idx = get_index().indices(); + auto r2itr = r2idx.begin(); + while(r2itr != r2idx.end()) + { + const auto& current = *r2itr; + ++r2itr; + remove(current); + } + //remove all content type index objects + const auto &r3idx = get_index().indices(); + auto r3itr = r3idx.begin(); + while(r3itr != r3idx.end()) + { + const auto& current = *r3itr; + ++r3itr; + remove(current); + } + + modify(props, [&](dynamic_global_property_object &p) { + p.total_reward_shares=0; + }); + + //recalc validator votes for fair DPOS + const auto &widx = get_index().indices(); + for(auto witr = widx.begin(); witr != widx.end(); ++witr) { + const auto &voter = get(witr->account); + share_type old_weight=voter.validator_vote_weight(); + share_type new_weight=voter.validator_vote_fair_weight_prehf5(); + adjust_validator_vote(get(witr->validator), -old_weight); + adjust_validator_vote(get(witr->validator), new_weight); + } + + break; + } + case CHAIN_HARDFORK_5: + { + //clear votes for each validator + const auto &widx = get_index().indices().get(); + for (auto itr = widx.begin(); + itr != widx.end(); + ++itr) { + modify(*itr, [&](validator_object &w) { + elog("HF5 validator ${a} was votes: ${n}", ("a", w.owner)("n", w.votes)); + w.votes = 0; + w.counted_votes = 0; + }); + } + //recalc validator votes for fair DPOS + const auto &widx2 = get_index().indices(); + for(auto witr = widx2.begin(); witr != widx2.end(); ++witr) { + const auto &voter = get(witr->account); + const auto &validator = get(witr->validator); + + share_type fair_weight=voter.validator_vote_fair_weight(); + modify(voter, [&](account_object &a) { + a.validators_vote_weight = fair_weight; + }); + elog("HF5 validator ${a} calc votes: ${n}", ("a", validator.owner)("n", fair_weight)); + + adjust_validator_vote(get(witr->validator), fair_weight); + } + break; + } + case CHAIN_HARDFORK_6: + { + const auto &props = get_dynamic_global_properties(); + //recalc shares to change ratio with VIZ token 1:1 + auto old_price=props.get_vesting_share_price(); + price new_price(asset(1000, TOKEN_SYMBOL), asset(1000000, SHARES_SYMBOL)); + asset new_total_vesting_shares(0, SHARES_SYMBOL); + + const auto &eidx = get_index().indices().get(); + for (auto itr = eidx.begin(); itr != eidx.end(); ++itr) { + elog("HF6 account shares recalc ${a}:", ("a", itr->name)); + elog(" - old vesting_shares: ${a}", ("a", itr->vesting_shares.amount)); + if(itr->proxy != CHAIN_PROXY_TO_SELF_ACCOUNT){ + elog("- proxy found: ${a}", ("a", itr->proxy)); + std::array delta; + delta[0] = -(itr->vesting_shares.amount); + for (int i = 0; i < CHAIN_MAX_PROXY_RECURSION_DEPTH; ++i) { + delta[i + 1] = -(itr->proxied_vsf_votes[i]); + } + adjust_proxied_validator_votes(get_account(itr->name), delta); + } + + auto old_shares=itr->vesting_shares; + old_shares=old_shares*1000; + auto tokens=old_shares*old_price; + auto new_shares=tokens*new_price; + new_shares=new_shares/1000; + new_total_vesting_shares+=new_shares; + + auto old_withdraw_rate=itr->vesting_withdraw_rate; + old_withdraw_rate=old_withdraw_rate*1000; + auto withdraw_tokens=old_withdraw_rate*old_price; + auto new_withdraw_rate=withdraw_tokens*new_price; + new_withdraw_rate=new_withdraw_rate/1000; + + modify(*itr, [&](account_object &a) { + a.vesting_shares = new_shares; + a.vesting_withdraw_rate = new_withdraw_rate; + a.delegated_vesting_shares = asset(0, SHARES_SYMBOL); + a.received_vesting_shares = asset(0, SHARES_SYMBOL); + }); + + elog(" - new vesting_shares: ${a}", ("a", itr->vesting_shares.amount)); + if(itr->proxy != CHAIN_PROXY_TO_SELF_ACCOUNT){ + elog("- proxy votes updated: ${a}", ("a", itr->proxy)); + std::array delta; + delta[0] = itr->vesting_shares.amount; + for (int i = 0; i < CHAIN_MAX_PROXY_RECURSION_DEPTH; ++i) { + delta[i + 1] = itr->proxied_vsf_votes[i]; + } + adjust_proxied_validator_votes(get_account(itr->name), delta); + } + } + + elog("New total_vesting_shares: ${a}", ("a", new_total_vesting_shares.amount)); + modify(props, [&](dynamic_global_property_object &p) { + p.total_vesting_shares = new_total_vesting_shares; + }); + + //recalc delegations + const auto& delegations = get_index().indices().get(); + for (auto itr = delegations.begin(); + itr != delegations.end(); + ++itr) { + auto old_delegation_shares=itr->vesting_shares; + //fix excess delegated_vesting_shares (need hf7 fix object) + create([&](fix_vesting_delegation_object& o) { + o.delegator = itr->delegator; + o.delegatee = itr->delegatee; + o.vesting_shares = old_delegation_shares; + elog("!!! HF6 delay fix decrease ${a} delegated_vesting_shares: ${n}", ("a", itr->delegator)("n", old_delegation_shares)); + elog("!!! HF6 delay fix decrease ${a} received_vesting_shares: ${n}", ("a", itr->delegatee)("n", old_delegation_shares)); + }); + /* + modify(get_account(itr->delegator), [&](account_object& a) { + a.delegated_vesting_shares -= old_delegation_shares; + }); + modify(get_account(itr->delegatee), [&](account_object& a) { + a.received_vesting_shares -= old_delegation_shares; + }); + */ + old_delegation_shares=old_delegation_shares*1000; + auto old_delegation_tokens=old_delegation_shares*old_price; + auto new_delegation_shares=old_delegation_tokens*new_price; + new_delegation_shares=new_delegation_shares/1000; + modify(*itr, [&](vesting_delegation_object &a) { + a.vesting_shares = new_delegation_shares; + }); + modify(get_account(itr->delegator), [&](account_object& a) { + a.delegated_vesting_shares += new_delegation_shares; + }); + modify(get_account(itr->delegatee), [&](account_object& a) { + a.received_vesting_shares += new_delegation_shares; + }); + } + + //recalc expiring delegations + const auto& delegations_by_exp = get_index().indices().get(); + for (auto itr = delegations_by_exp.begin(); + itr != delegations_by_exp.end(); + ++itr) { + auto old_delegation_shares=itr->vesting_shares; + //fix excess delegated_vesting_shares (need hf7 fix object) + create([&](fix_vesting_delegation_object& o) { + o.delegator = itr->delegator; + o.vesting_shares = old_delegation_shares; + elog("!!! HF6 delay fix decrease ${a} delegated_vesting_shares: ${n}", ("a", itr->delegator)("n", old_delegation_shares)); + }); + /* + modify(get_account(itr->delegator), [&](account_object& a) { + a.delegated_vesting_shares -= old_delegation_shares; + }); + */ + old_delegation_shares=old_delegation_shares*1000; + auto old_delegation_tokens=old_delegation_shares*old_price; + auto new_delegation_shares=old_delegation_tokens*new_price; + new_delegation_shares=new_delegation_shares/1000; + modify(*itr, [&](vesting_delegation_expiration_object &a) { + a.vesting_shares = new_delegation_shares; + }); + modify(get_account(itr->delegator), [&](account_object& a) { + a.delegated_vesting_shares += new_delegation_shares; + }); + } + + //clear votes for each validator + const auto &widx = get_index().indices().get(); + for (auto itr = widx.begin(); + itr != widx.end(); + ++itr) { + modify(*itr, [&](validator_object &w) { + elog("HF6 validator ${a} has votes: ${n}", ("a", w.owner)("n", w.votes)); + w.votes = 0; + w.counted_votes = 0; + }); + } + //recalc validator votes after shares ratio fix + const auto &widx2 = get_index().indices(); + for(auto witr = widx2.begin(); witr != widx2.end(); ++witr) { + const auto &voter = get(witr->account); + const auto &validator = get(witr->validator); + + share_type fair_weight=voter.validator_vote_fair_weight(); + modify(voter, [&](account_object &a) { + a.validators_vote_weight = fair_weight; + }); + elog("HF6 validator ${a} recalc votes from ${a}: ${n}", ("a", validator.owner)("n", fair_weight)); + + adjust_validator_vote(get(witr->validator), fair_weight); + } + break; + } + case CHAIN_HARDFORK_7: + { + const auto &committee_account = get_account(CHAIN_COMMITTEE_ACCOUNT); + + const auto &fix_idx = get_index().indices(); + auto fix_itr = fix_idx.begin(); + while(fix_itr != fix_idx.end()) { + const auto &fix_current = *fix_itr; + ++fix_itr; + if(fix_current.delegator!=""){ + modify(get_account(fix_current.delegator), [&](account_object& a) { + a.delegated_vesting_shares -= fix_current.vesting_shares; + elog("!!! HF7 delayed fix decrease ${a} delegated_vesting_shares: ${n}", ("a", fix_current.delegator)("n", fix_current.vesting_shares)); + }); + } + if(fix_current.delegatee!=""){ + modify(get_account(fix_current.delegatee), [&](account_object& a) { + a.received_vesting_shares -= fix_current.vesting_shares; + elog("!!! HF7 delayed fix decrease ${a} received_vesting_shares: ${n}", ("a", fix_current.delegatee)("n", fix_current.vesting_shares)); + }); + } + remove(fix_current); + } + + const auto &eidx = get_index().indices().get(); + auto itr = eidx.begin(); + while(itr != eidx.end()){ + const auto ¤t = *itr; + ++itr; + if(!current.valid){ + elog("HF7 found invalid account ${a}", ("a", current.name)); + if(current.proxy != CHAIN_PROXY_TO_SELF_ACCOUNT){ + elog("- remove proxy: ${a}", ("a", current.proxy)); + std::array delta; + delta[0] = -(current.vesting_shares.amount); + for (int i = 0; i < CHAIN_MAX_PROXY_RECURSION_DEPTH; ++i) { + delta[i + 1] = -(current.proxied_vsf_votes[i]); + } + adjust_proxied_validator_votes(get_account(current.name), delta); + } + //move shares and balance to committee + elog("- add to committee funds: ${a} SHARES, ${b} TOKEN", ("a", current.vesting_shares)("b", current.balance)); + modify(committee_account, [&](account_object &a) { + a.vesting_shares += current.vesting_shares; + a.balance += current.balance; + }); + + //remove from master_authority_history_index + const auto &d1idx = get_index().indices().get(); + auto delete_itr1 = d1idx.lower_bound(current.name); + while(delete_itr1 != d1idx.end() && + delete_itr1->account == current.name) { + const auto &delete_current = *delete_itr1; + ++delete_itr1; + remove(delete_current); + } + + //remove from account_metadata_index + const auto &d2idx = get_index().indices().get(); + auto delete_itr2 = d2idx.lower_bound(current.name); + while(delete_itr2 != d2idx.end() && + delete_itr2->account == current.name) { + const auto &delete_current = *delete_itr2; + ++delete_itr2; + remove(delete_current); + } + + //remove from account_authority_index + const auto &d3idx = get_index().indices().get(); + auto delete_itr3 = d3idx.lower_bound(current.name); + while(delete_itr3 != d3idx.end() && + delete_itr3->account == current.name) { + const auto &delete_current = *delete_itr3; + ++delete_itr3; + remove(delete_current); + } + + //remove from vesting_delegation_expiration_index by_account_expiration + const auto& d4idx = get_index().indices().get(); + auto delete_itr4 = d4idx.lower_bound(std::make_tuple(current.name, fc::time_point_sec())); + while(delete_itr4 != d4idx.end() && + delete_itr4->delegator == current.name) { + const auto &delete_current = *delete_itr4; + ++delete_itr4; + modify(get_account(delete_current.delegator), [&](account_object& a) { + a.delegated_vesting_shares -= delete_current.vesting_shares; + }); + remove(delete_current); + } + + //remove from vesting_delegation_index by_delegation + const auto &d5idx = get_index().indices().get(); + auto delete_itr5 = d5idx.lower_bound(std::make_tuple(current.name,account_name_type())); + while(delete_itr5 != d5idx.end() && + delete_itr5->delegator == current.name) { + const auto &delete_current = *delete_itr5; + ++delete_itr5; + modify(get_account(delete_current.delegator), [&](account_object& a) { + a.delegated_vesting_shares -= delete_current.vesting_shares; + }); + modify(get_account(delete_current.delegatee), [&](account_object& a) { + a.received_vesting_shares -= delete_current.vesting_shares; + }); + remove(delete_current); + } + + //remove from vesting_delegation_index by_received + const auto &d6idx = get_index().indices().get(); + auto delete_itr6 = d6idx.lower_bound(std::make_tuple(current.name,account_name_type())); + while(delete_itr6 != d6idx.end() && + delete_itr6->delegatee == current.name) { + const auto &delete_current = *delete_itr6; + ++delete_itr6; + modify(get_account(delete_current.delegator), [&](account_object& a) { + a.delegated_vesting_shares -= delete_current.vesting_shares; + }); + modify(get_account(delete_current.delegatee), [&](account_object& a) { + a.received_vesting_shares -= delete_current.vesting_shares; + }); + remove(delete_current); + } + + //remove from account_recovery_request_index + const auto &d7idx = get_index().indices().get(); + auto delete_itr7 = d7idx.lower_bound(std::make_tuple(current.name,account_recovery_request_id_type())); + while(delete_itr7 != d7idx.end() && + delete_itr7->account_to_recover == current.name) { + const auto &delete_current = *delete_itr7; + ++delete_itr7; + remove(delete_current); + } + + //remove from change_recovery_account_request_index + const auto &d8idx = get_index().indices().get(); + auto delete_itr8 = d8idx.lower_bound(std::make_tuple(current.name,change_recovery_account_request_id_type())); + while(delete_itr8 != d8idx.end() && + delete_itr8->account_to_recover == current.name) { + const auto &delete_current = *delete_itr8; + ++delete_itr8; + remove(delete_current); + } + + //decrease validators_vote_weight from all votes by invalid account + const auto &vidx = get_index().indices().get(); + auto vitr = vidx.lower_bound(boost::make_tuple(current.id, validator_id_type())); + while (vitr != vidx.end() && vitr->account == current.id) { + adjust_validator_vote(get(vitr->validator),-current.validators_vote_weight); + ++vitr; + } + + //remove from validator_vote_index by_account_validator (remove all votes from invalid account) + const auto &d10idx = get_index().indices().get(); + auto delete_itr10 = d10idx.lower_bound(boost::make_tuple(current.id, validator_id_type())); + while(delete_itr10 != d10idx.end() && + delete_itr10->account == current.id) { + const auto &delete_current = *delete_itr10; + adjust_validator_vote(get(delete_itr10->validator),-current.validators_vote_weight); + modify(current, [&](account_object &a) { + a.validators_voted_for--; + }); + ++delete_itr10; + remove(delete_current); + } + + //recalc validators_vote_weight (must be 0 after remove all validator votes from invalid account) + share_type current_fair_vote_weight = current.validator_vote_fair_weight(); + modify(current, [&](account_object &a) { + a.validators_vote_weight = current_fair_vote_weight; + }); + + //look validator object from invalid account + const auto &invalid_validator = find(current.name); + if(invalid_validator != nullptr){//found validator + //remove invalid validator account from penalty index + const auto &d8idx = get_index().indices().get(); + auto delete_itr8 = d8idx.lower_bound(invalid_validator->owner); + while(delete_itr8 != d8idx.end() && + delete_itr8->validator == invalid_validator->owner) { + const auto &delete_current = *delete_itr8; + ++delete_itr8; + remove(delete_current); + } + //remove invalid validator account from schedule + const validator_schedule_object &wso = get_validator_schedule_object(); + modify(wso, [&](validator_schedule_object &_wso) { + for (int i = 0; i < _wso.num_scheduled_validators; i+=CHAIN_BLOCK_VALIDATOR_REPEAT) { + if(_wso.current_shuffled_validators[i] == invalid_validator->owner){ + _wso.current_shuffled_validators[i] = account_name_type(); + } + } + }); + //recalc validators_vote_weight from all votes to invalid validator account (remove votes to invalid validator account) + const auto &vidx = get_index().indices().get(); + auto vitr = vidx.lower_bound(boost::make_tuple(invalid_validator->id, account_id_type())); + while (vitr != vidx.end() && vitr->validator == invalid_validator->id) { + const auto &voter_account = get(vitr->account); + const auto &vidx2 = get_index().indices().get(); + auto vitr2 = vidx2.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); + while (vitr2 != vidx2.end() && vitr2->account == voter_account.id) { + adjust_validator_vote(get(vitr2->validator), -voter_account.validators_vote_weight); + ++vitr2; + } + + remove(*vitr); + + modify(voter_account, [&](account_object &a) { + a.validators_voted_for--; + }); + + share_type fair_vote_weight = voter_account.validator_vote_fair_weight(); + modify(voter_account, [&](account_object &a) { + a.validators_vote_weight = fair_vote_weight; + }); + + const auto &vidx3 = get_index().indices().get(); + auto vitr3 = vidx3.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); + while (vitr3 != vidx3.end() && vitr3->account == voter_account.id) { + adjust_validator_vote(get(vitr3->validator), voter_account.validators_vote_weight); + ++vitr3; + } + ++vitr; + } + + //remove from validator_index invalid validator account + const auto &d9idx = get_index().indices().get(); + auto delete_itr9 = d9idx.lower_bound(current.name); + while(delete_itr9 != d9idx.end() && + delete_itr9->owner == current.name) { + const auto &delete_current = *delete_itr9; + ++delete_itr9; + remove(delete_current); + } + } + + //remove all committee votes + const auto &d11idx = get_index().indices().get(); + auto delete_itr11 = d11idx.lower_bound(current.name); + while(delete_itr11 != d11idx.end() && + delete_itr11->voter == current.name) { + const auto &delete_current = *delete_itr11; + ++delete_itr11; + remove(delete_current); + } + + //remove all committee requests as creator + const auto &d12idx = get_index().indices().get(); + auto delete_itr12 = d12idx.lower_bound(current.name); + while(delete_itr12 != d12idx.end() && + delete_itr12->creator == current.name) { + const auto &delete_current = *delete_itr12; + ++delete_itr12; + remove(delete_current); + } + + //remove all committee requests as worker + const auto &d13idx = get_index().indices().get(); + auto delete_itr13 = d13idx.lower_bound(current.name); + while(delete_itr13 != d13idx.end() && + delete_itr13->worker == current.name) { + const auto &delete_current = *delete_itr13; + ++delete_itr13; + remove(delete_current); + } + + //remove all withdraw routes from + const auto &d14idx = get_index().indices().get(); + auto delete_itr14 = d14idx.lower_bound(std::make_tuple(current.id,account_id_type())); + while(delete_itr14 != d14idx.end() && + delete_itr14->from_account == current.id) { + const auto &delete_current = *delete_itr14; + ++delete_itr14; + remove(delete_current); + } + //remove all withdraw routes to + const auto &d15idx = get_index().indices().get(); + auto delete_itr15 = d15idx.lower_bound(std::make_tuple(account_id_type(),current.id)); + while(delete_itr15 != d15idx.end() && + delete_itr15->to_account == current.id) { + const auto &delete_current = *delete_itr15; + ++delete_itr15; + remove(delete_current); + } + + //change all escrow routes from + const auto &d16idx = get_index().indices().get(); + auto itr16 = d16idx.lower_bound(std::make_tuple(current.name,0)); + while(itr16 != d16idx.end() && + itr16->from == current.name) { + const auto &e_current = *itr16; + ++itr16; + modify(e_current, [&](escrow_object &esc) { + esc.from = CHAIN_COMMITTEE_ACCOUNT; + }); + } + + //change all escrow routes from + const auto &d17idx = get_index().indices().get(); + auto itr17 = d17idx.lower_bound(std::make_tuple(current.name,0)); + while(itr17 != d17idx.end() && + itr17->to == current.name) { + const auto &e_current = *itr17; + ++itr17; + modify(e_current, [&](escrow_object &esc) { + esc.to = CHAIN_COMMITTEE_ACCOUNT; + }); + } + + //change all escrow routes from + const auto &d18idx = get_index().indices().get(); + auto itr18 = d18idx.lower_bound(std::make_tuple(current.name,0)); + while(itr18 != d18idx.end() && + itr18->agent == current.name) { + const auto &e_current = *itr18; + ++itr18; + modify(e_current, [&](escrow_object &esc) { + esc.agent = CHAIN_COMMITTEE_ACCOUNT; + }); + } + + //change all invites creator + const auto &d19idx = get_index().indices().get(); + auto itr19 = d19idx.lower_bound(current.name); + while(itr19 != d19idx.end() && + itr19->creator == current.name) { + const auto &i_current = *itr19; + ++itr19; + modify(i_current, [&](invite_object &i) { + i.creator = CHAIN_COMMITTEE_ACCOUNT; + }); + } + + //change all invites receiver + const auto &d19idx2 = get_index().indices().get(); + auto itr19_2 = d19idx2.lower_bound(current.name); + while(itr19_2 != d19idx2.end() && + itr19_2->receiver == current.name) { + const auto &i_current = *itr19_2; + ++itr19_2; + modify(i_current, [&](invite_object &i) { + i.receiver = CHAIN_COMMITTEE_ACCOUNT; + }); + } + + //remove all paid subscribes by subscriber + const auto &d20idx = get_index().indices().get(); + auto itr20 = d20idx.lower_bound(current.name); + while(itr20 != d20idx.end() && + itr20->subscriber == current.name) { + const auto &i_current = *itr20; + ++itr20; + remove(i_current); + } + + //remove all paid subscribes by creator + const auto &d21idx = get_index().indices().get(); + auto itr21 = d21idx.lower_bound(current.name); + while(itr21 != d21idx.end() && + itr21->creator == current.name) { + const auto &i_current = *itr21; + ++itr21; + remove(i_current); + } + + //remove all paid subscription by creator + const auto &d21idx2 = get_index().indices().get(); + auto itr21_2 = d21idx2.lower_bound(current.name); + while(itr21_2 != d21idx2.end() && + itr21_2->creator == current.name) { + const auto &i_current = *itr21_2; + ++itr21_2; + remove(i_current); + } + + //remove all proposals by account + const auto &d22idx = get_index().indices().get(); + auto itr22 = d22idx.lower_bound(current.name); + while(itr22 != d22idx.end() && + itr22->author == current.name) { + const auto &i_current = *itr22; + ++itr22; + remove(i_current); + } + + //remove all proposals required approval by account + const auto &d23idx = get_index().indices().get(); + auto itr23 = d23idx.lower_bound(current.name); + while(itr23 != d23idx.end() && + itr23->account == current.name) { + const auto &i_current = *itr23; + ++itr23; + remove(i_current); + } + + //remove invalid account + remove(current); + } + } + break; + } + case CHAIN_HARDFORK_8: + { + //refresh delegated_vesting_shares and received_vesting_shares + const auto &eidx = get_index().indices().get(); + auto itr = eidx.begin(); + while(itr != eidx.end()){ + const auto ¤t = *itr; + ++itr; + modify(current, [&](account_object& a) { + a.delegated_vesting_shares=asset(0, SHARES_SYMBOL); + a.received_vesting_shares=asset(0, SHARES_SYMBOL); + }); + } + + //recalc delegations + const auto& delegations = get_index().indices().get(); + for (auto itr = delegations.begin(); + itr != delegations.end(); + ++itr) { + modify(get_account(itr->delegator), [&](account_object& a) { + a.delegated_vesting_shares += itr->vesting_shares; + }); + modify(get_account(itr->delegatee), [&](account_object& a) { + a.received_vesting_shares += itr->vesting_shares; + }); + } + + //recalc expiring delegations + const auto& delegations_by_exp = get_index().indices().get(); + for (auto itr = delegations_by_exp.begin(); + itr != delegations_by_exp.end(); + ++itr) { + modify(get_account(itr->delegator), [&](account_object& a) { + a.delegated_vesting_shares += itr->vesting_shares; + }); + } + break; + } + case CHAIN_HARDFORK_9: + { + //remove validators without signed block + const auto &idx = get_index().indices().get(); + auto itr = idx.begin(); + while(itr != idx.end()){ + const auto ¤t = *itr; + ++itr; + if(0==current.last_confirmed_block_num){ + //MUST be corrected: the validator_vote_object must also be deleted + //remove invalid validator account from penalty index + const auto &d8idx = get_index().indices().get(); + auto delete_itr8 = d8idx.lower_bound(current.owner); + while(delete_itr8 != d8idx.end() && + delete_itr8->validator == current.owner) { + const auto &delete_current = *delete_itr8; + ++delete_itr8; + remove(delete_current); + } + //recalc validators_vote_weight from all votes to invalid validator account (remove votes to invalid validator account) + const auto &vidx = get_index().indices().get(); + auto vitr = vidx.lower_bound(boost::make_tuple(current.id, account_id_type())); + while (vitr != vidx.end() && vitr->validator == current.id) { + const auto &voter_account = get(vitr->account); + const auto &vidx2 = get_index().indices().get(); + auto vitr2 = vidx2.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); + while (vitr2 != vidx2.end() && vitr2->account == voter_account.id) { + adjust_validator_vote(get(vitr2->validator), -voter_account.validators_vote_weight); + ++vitr2; + } + + remove(*vitr); + + modify(voter_account, [&](account_object &a) { + a.validators_voted_for--; + a.valid=false; + }); + + share_type fair_vote_weight = voter_account.validator_vote_fair_weight(); + modify(voter_account, [&](account_object &a) { + a.validators_vote_weight = fair_vote_weight; + }); + + const auto &vidx3 = get_index().indices().get(); + auto vitr3 = vidx3.lower_bound(boost::make_tuple(voter_account.id, validator_id_type())); + while (vitr3 != vidx3.end() && vitr3->account == voter_account.id) { + adjust_validator_vote(get(vitr3->validator), voter_account.validators_vote_weight); + ++vitr3; + } + ++vitr; + } + elog("HF9 remove empty/spam validator ${a}", ("a", current.owner)); + remove(current); + } + } + + //remove committee requests without votes + const auto &idx2 = get_index().indices().get(); + auto itr2 = idx2.begin(); + while(itr2 != idx2.end()){ + const auto ¤t = *itr2; + ++itr2; + if(1==current.status){ + if(0==current.votes_count){ + remove(current); + } + } + } + + //remove all paid subscriptions without subscribes + const auto &idx3 = get_index().indices().get(); + auto itr3 = idx3.begin(); + while(itr3 != idx3.end()) { + const auto ¤t = *itr3; + ++itr3; + + bool find_subscribe=false; + + const auto &idx4 = get_index().indices().get(); + auto itr4 = idx4.find(current.creator); + if(itr4 != idx4.end()){ + find_subscribe=true; + } + + if(!find_subscribe){ + remove(current); + } + } + break; + } + case CHAIN_HARDFORK_10: + break; + case CHAIN_HARDFORK_11: + { + //fixed error from CHAIN_HARDFORK_9 by replay, but need toggle flag valid for spam accounts + const auto &eidx = get_index().indices().get(); + auto itr = eidx.begin(); + while(itr != eidx.end()){ + const auto ¤t = *itr; + ++itr; + if(!current.valid){ + //toggle invalid accounts to valid + modify(current, [&](account_object &a) { + a.valid=true; + }); + } + } + break; + } + case CHAIN_HARDFORK_12: + break; + case CHAIN_HARDFORK_13: + // Validator reward sharing: new fields sharing_rate and + // pending_stakeholder_reward on validator_object default to 0 (replay + // initialises them), no extra migration needed. + break; + case CHAIN_HARDFORK_14: + // Prediction Markets: instantiate the lazy-liquidity pool singleton + // (id 0) so pm_lazy_deposit and the allocation/leverage paths can rely + // on get. All balances start at zero (empty pool). + create([](pm_lazy_pool_object&) {}); + break; + default: + break; + } + + modify(get_hardfork_property_object(), [&](hardfork_property_object &hfp) { + FC_ASSERT(hardfork == hfp.last_hardfork + + 1, "Hardfork being applied out of order", ("hardfork", hardfork)("hfp.last_hardfork", hfp.last_hardfork)); + FC_ASSERT(hfp.processed_hardforks.size() == + hardfork, "Hardfork being applied out of order"); + hfp.processed_hardforks.push_back(_hardfork_times[hardfork]); + hfp.last_hardfork = hardfork; + hfp.current_hardfork_version = _hardfork_versions[hardfork]; + FC_ASSERT(hfp.processed_hardforks[hfp.last_hardfork] == + _hardfork_times[hfp.last_hardfork], "Hardfork processing failed sanity check..."); + }); + + push_virtual_operation(hardfork_operation(hardfork), true); + } +} } // graphene::chain diff --git a/libraries/chain/hardfork.d/0-preamble.hf b/libraries/chain/hardfork.d/0-preamble.hf index 848514a323..be8af26aac 100644 --- a/libraries/chain/hardfork.d/0-preamble.hf +++ b/libraries/chain/hardfork.d/0-preamble.hf @@ -52,4 +52,4 @@ FC_REFLECT((graphene::chain::hardfork_property_object), CHAINBASE_SET_INDEX_TYPE( graphene::chain::hardfork_property_object, graphene::chain::hardfork_property_index) #define CHAIN_STARTUP_HARDFORKS 0 -#define CHAIN_NUM_HARDFORKS 13 +#define CHAIN_NUM_HARDFORKS 14 diff --git a/libraries/chain/hardfork.d/14.hf b/libraries/chain/hardfork.d/14.hf new file mode 100644 index 0000000000..c6fac6d4ea --- /dev/null +++ b/libraries/chain/hardfork.d/14.hf @@ -0,0 +1,14 @@ +// 14 Hardfork — Prediction Markets (Onix): binary CPMM + multi LMSR, oracles, +// disputes, batch/commit-reveal, lazy liquidity pool. See plan Prediction_Market_HF14. +#ifndef CHAIN_HARDFORK_14 +#define CHAIN_HARDFORK_14 14 +// NOTE: placeholder activation time — MUST be finalized before release (see plan risk register: +// pm_processing_cap_per_block must be benchmarked under realistic load first). +#ifdef BUILD_TESTNET +// Testnet activates earlier so the full PM feature set can be exercised ahead of mainnet. +#define CHAIN_HARDFORK_14_TIME 1780000000 +#else +#define CHAIN_HARDFORK_14_TIME 1782849600 +#endif +#define CHAIN_HARDFORK_14_VERSION hardfork_version( version(4, 0, 0) ) +#endif diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 5e493a3d80..335caabf88 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -140,6 +140,14 @@ class account_object account_name_type subaccount_seller; asset subaccount_offer_price = asset(0, TOKEN_SYMBOL); bool subaccount_on_sale = false; + + // Display-only running aggregates: how much of this account's own funds is currently + // frozen in prediction markets. Maintained incrementally at every PM lock/unlock/move + // point and seeded once from existing objects on upgrade (no full replay). NEVER gate + // consensus on these — they are read-only telemetry for clients (get_accounts). + asset pm_liquidity_committed = asset(0, TOKEN_SYMBOL);///< own LP/creator liquidity in live markets + asset pm_bets_staked = asset(0, TOKEN_SYMBOL); ///< own stake locked in open/queued bets + asset pm_leverage_collateral = asset(0, TOKEN_SYMBOL);///< own collateral in active leverage positions }; class account_authority_object @@ -528,6 +536,7 @@ FC_REFLECT((graphene::chain::account_object), (reserved_balance) (target_buyer)(account_on_auction)(current_bid)(current_bidder)(current_bidder_key)(last_bid) (subaccount_seller)(subaccount_offer_price)(subaccount_on_sale) + (pm_liquidity_committed)(pm_bets_staked)(pm_leverage_collateral) ) CHAINBASE_SET_INDEX_TYPE(graphene::chain::account_object, graphene::chain::account_index) diff --git a/libraries/chain/include/graphene/chain/chain_object_types.hpp b/libraries/chain/include/graphene/chain/chain_object_types.hpp index a58b48fc44..c5bfaa4c11 100644 --- a/libraries/chain/include/graphene/chain/chain_object_types.hpp +++ b/libraries/chain/include/graphene/chain/chain_object_types.hpp @@ -73,7 +73,24 @@ namespace graphene { namespace chain { paid_subscription_object_type, paid_subscribe_object_type, validator_penalty_expire_object_type, - validator_confirmation_object_type + validator_confirmation_object_type, + + // HF14 Prediction Markets (Onix) — APPEND ONLY, never reorder existing ids. + pm_oracle_object_type, + pm_market_object_type, + pm_outcome_object_type, + pm_bet_object_type, + pm_liquidity_object_type, + pm_commit_object_type, + pm_dispute_object_type, + pm_dispute_vote_object_type, + pm_lazy_pool_object_type, + pm_lazy_deposit_object_type, + pm_lazy_allocation_object_type, + pm_leverage_position_object_type, + pm_creator_ban_object_type, + pm_lazy_withdraw_request_object_type, + pm_deferred_claim_object_type }; class dynamic_global_property_object; @@ -110,6 +127,23 @@ namespace graphene { namespace chain { class validator_penalty_expire_object; class validator_confirmation_object; + // HF14 Prediction Markets + class pm_oracle_object; + class pm_market_object; + class pm_outcome_object; + class pm_bet_object; + class pm_liquidity_object; + class pm_commit_object; + class pm_dispute_object; + class pm_dispute_vote_object; + class pm_lazy_pool_object; + class pm_lazy_deposit_object; + class pm_lazy_allocation_object; + class pm_leverage_position_object; + class pm_creator_ban_object; + class pm_lazy_withdraw_request_object; + class pm_deferred_claim_object; + typedef object_id dynamic_global_property_id_type; typedef object_id account_id_type; typedef object_id account_authority_id_type; @@ -143,6 +177,23 @@ namespace graphene { namespace chain { typedef object_id validator_penalty_expire_object_id_type; typedef object_id validator_confirmation_object_id_type; + // HF14 Prediction Markets + typedef object_id pm_oracle_id_type; + typedef object_id pm_market_id_type; + typedef object_id pm_outcome_id_type; + typedef object_id pm_bet_id_type; + typedef object_id pm_liquidity_id_type; + typedef object_id pm_commit_id_type; + typedef object_id pm_dispute_id_type; + typedef object_id pm_dispute_vote_id_type; + typedef object_id pm_lazy_pool_id_type; + typedef object_id pm_lazy_deposit_id_type; + typedef object_id pm_lazy_allocation_id_type; + typedef object_id pm_leverage_position_id_type; + typedef object_id pm_creator_ban_id_type; + typedef object_id pm_lazy_withdraw_request_id_type; + typedef object_id pm_deferred_claim_id_type; + } } //graphene::chain namespace fc { @@ -239,6 +290,21 @@ FC_REFLECT_ENUM(graphene::chain::object_type, (paid_subscribe_object_type) (validator_penalty_expire_object_type) (validator_confirmation_object_type) + (pm_oracle_object_type) + (pm_market_object_type) + (pm_outcome_object_type) + (pm_bet_object_type) + (pm_liquidity_object_type) + (pm_commit_object_type) + (pm_dispute_object_type) + (pm_dispute_vote_object_type) + (pm_lazy_pool_object_type) + (pm_lazy_deposit_object_type) + (pm_lazy_allocation_object_type) + (pm_leverage_position_object_type) + (pm_creator_ban_object_type) + (pm_lazy_withdraw_request_object_type) + (pm_deferred_claim_object_type) ) FC_REFLECT_TYPENAME((graphene::chain::shared_string)) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 326e2642e5..618d7ab7c3 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -434,6 +434,31 @@ namespace graphene { namespace chain { void adjust_balance(const account_object &a, const asset &delta); + // PM frozen-funds telemetry (display-only; account_object.pm_*). kind: + // 0 = liquidity_committed, 1 = bets_staked, 2 = leverage_collateral. + // NEVER gate consensus on these; they only feed get_accounts for clients. + void pm_adjust_frozen(const account_name_type &account, uint8_t kind, share_type delta); + // One-time seed of the pm_* counters from existing objects on the first block + // processed after the upgrade (guarded by dgpo.pm_frozen_counters_seeded). + void pm_seed_frozen_counters(); + // Debug drift-check: recompute the frozen counters straight from the live objects and + // compare against the stored per-account aggregates. Logs every mismatch and returns + // false if any is found. Read-only; safe to call any time (walks accounts + PM objects). + bool pm_verify_frozen_counters() const; + // One-time seed of pm_oracle_object.active_markets from live markets (guarded by + // dgpo.pm_active_markets_seeded). Display-only O(1) live-market count per oracle. + void pm_seed_oracle_active_markets(); + // Debug drift-check for active_markets: recompute per-oracle from live markets and diff + // against the stored counter. Read-only; logs mismatches; returns false if any is found. + bool pm_verify_oracle_active_markets() const; + // One-time seed of the per-oracle workload gauges (markets_in_dispute_window, + // disputes_awaiting_response, disputes_awaiting_decision) from live markets/disputes + // (guarded by dgpo.pm_oracle_gauges_seeded). Display-only; never gates consensus. + void pm_seed_oracle_gauges(); + // Debug drift-check for the workload gauges: recompute per-oracle from live state and + // diff against the stored counters. Read-only; logs mismatches; returns false on drift. + bool pm_verify_oracle_gauges() const; + void burn_asset(const asset &delta); void adjust_rshares(const content_object &content, fc::uint128_t old_rshares, fc::uint128_t new_rshares); @@ -478,6 +503,7 @@ namespace graphene { namespace chain { void process_validator_epoch_distribution(); ///< HF13: distribute accumulated delegator TOKEN rewards void committee_processing(); void paid_subscribe_processing(); + void process_pm_markets(); ///< HF14: bounded per-block PM cron void expire_award_shares_processing(); diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index ac7ad104f3..9deb4c1bb6 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -11,6 +11,8 @@ namespace graphene { template class evaluator { public: + virtual ~evaluator() = default; // polymorphic base: needed for delete via base ptr + virtual void apply(const OperationType &op) = 0; virtual int get_type() const = 0; diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index dba1c7cb88..1f48d47614 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -143,6 +143,48 @@ namespace graphene { * Zero if never activated. Used to compute duration and for exit condition. */ uint32_t emergency_consensus_start_block = 0; + + /** + * One-time flag: the PM per-account frozen-funds counters + * (account.pm_liquidity_committed / pm_bets_staked / pm_leverage_collateral) + * have been seeded from existing objects. Set on the first block processed + * after the upgrade so counters are correct without a full replay. + */ + bool pm_frozen_counters_seeded = false; + /** + * One-time CORRECTIVE re-seed of the frozen counters. The initial seed left the + * counters over-stated on the live testnet (they carried legacy/inflated state — see + * task #266: pm_liquidity_committed read ~3.76× a physically-impossible value). Because + * the counters are display-only (never gate consensus) they can be safely recomputed: + * on the first block after this upgrade, pm_seed_frozen_counters() zeroes every account's + * three counters and re-sums them from the live objects. Idempotent. + */ + bool pm_frozen_counters_reseeded_v1 = false; + /** + * One-time seed of the per-oracle live active-market counter + * (pm_oracle_object.active_markets) from existing markets. Set on the first block after + * this upgrade so get_oracle exposes the count without a full replay. Display-only; + * never gates consensus. pm_seed_oracle_active_markets() zeroes then re-sums — idempotent. + */ + bool pm_active_markets_seeded = false; + + /** + * One-time seed of the per-oracle workload gauges (markets_in_dispute_window, + * disputes_awaiting_response, disputes_awaiting_decision) from existing markets/disputes. + * Set on the first block after this upgrade so get_oracle exposes them without a full + * replay. Display-only; never gates consensus. pm_seed_oracle_gauges() zeroes then + * re-derives — idempotent. + */ + bool pm_oracle_gauges_seeded = false; + + /** + * Batch-epoch-settle round-robin cursor: the market id where the next + * epoch-boundary scan resumes after stopping on the per-block processing cap. + * 0 after a completed full pass. Consensus state (all nodes advance it + * identically); prevents ~cap always-busy low-id markets from permanently + * starving newer ones. + */ + uint64_t pm_batch_settle_cursor = 0; }; typedef multi_index_container < @@ -190,5 +232,10 @@ FC_REFLECT((graphene::chain::dynamic_global_property_object), (inflation_ratio) (emergency_consensus_active) (emergency_consensus_start_block) + (pm_frozen_counters_seeded) + (pm_frozen_counters_reseeded_v1) + (pm_active_markets_seeded) + (pm_oracle_gauges_seeded) + (pm_batch_settle_cursor) ) CHAINBASE_SET_INDEX_TYPE(graphene::chain::dynamic_global_property_object, graphene::chain::dynamic_global_property_index) diff --git a/libraries/chain/include/graphene/chain/pm/leverage.hpp b/libraries/chain/include/graphene/chain/pm/leverage.hpp new file mode 100644 index 0000000000..6e24b95e95 --- /dev/null +++ b/libraries/chain/include/graphene/chain/pm/leverage.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +// HF14 Prediction Markets — pure leverage (margin) math for binary CPMM markets, +// frozen against leverage-risk-off-strategy.md §4. Deterministic integer math only; +// `k = reserve_a × reserve_b` is 128-bit. Free of database types so it is unit-tested +// in isolation; the evaluators / atomic-liquidation hook feed live reserves in. +// +// Reserve convention matches the chain's pm_place_bet (NOT the PHP prototype): +// outcome 0 (A): bet adds to reserve_a; tokens = reserve_b − k/(reserve_a+amount) +// outcome 1 (B): bet adds to reserve_b; tokens = reserve_a − k/(reserve_b+amount) +// +// Leverage ships behind the `pm_leverage_enabled` kill-switch and is CPMM-binary only +// in HF14 (LMSR leverage is future work). + +namespace graphene { namespace chain { namespace pm { namespace leverage { + + struct cpmm_fill { + int64_t tokens = 0; ///< weight received + int64_t new_reserve_a = 0; + int64_t new_reserve_b = 0; + }; + + // §4.1 — place a bet of `amount` on `outcome`; returns tokens + post-bet reserves. + cpmm_fill cpmm_buy(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t amount, int outcome); + + // §4.2 — exit price: VIZ returned by selling `tokens` of `outcome` back to the curve + // at current reserves (floored at 0). + int64_t cancel_value(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t tokens, int outcome); + + // §4.3 — cancel value of `tokens` on `outcome` AFTER an opposing bet of `m` on the + // other side moves the price against the position (floored at 0). Monotonically + // decreasing in `m`; the worst case uses m = worst_opposing_bet(). + int64_t cancel_value_after_opposing(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t tokens, int outcome, int64_t m); + + // §4.3 — worst-case opposing bet size: min(reserve_a,reserve_b) × sl% /100 × m_factor% /100. + int64_t worst_opposing_bet(int64_t reserve_a, int64_t reserve_b, + uint16_t sl_percent, uint16_t m_factor_percent); + + // §4.4 — pool obligation / liquidation threshold = loan × (1 + r_percent/100). + int64_t liquidation_threshold(int64_t loan, uint16_t r_percent); + + // §4.6 Constraint 2 (API preview) — max loan L (50-iter binary search over [0, hi]) + // such that, after placing (collateral+L) on `outcome`, the worst-case cancel value + // ≥ liquidation_threshold(L) × (1 + s_percent/100). Reserves are the PRE-bet market + // reserves. Returns the loan (0 if none qualifies). + int64_t max_leverage_loan(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t collateral, int outcome, int64_t hi_loan, + uint16_t r_percent, uint16_t s_percent, + uint16_t sl_percent, uint16_t m_factor_percent); + +}}}} // graphene::chain::pm::leverage diff --git a/libraries/chain/include/graphene/chain/pm/lmsr_q96.hpp b/libraries/chain/include/graphene/chain/pm/lmsr_q96.hpp new file mode 100644 index 0000000000..ce4d05be47 --- /dev/null +++ b/libraries/chain/include/graphene/chain/pm/lmsr_q96.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +// HF14 — Deterministic fixed-point LMSR (Q96). C++ consensus port of the normative +// PHP/JS reference (.qoder/plans/pm-plan/reference/lmsr_fixed.php + market_math.js) and +// lmsr-fixed-point-spec.md. MUST reproduce reference/lmsr_fixed_vectors.json bit-for-bit. +// +// No / exp / log / pow / sqrt — pure big-integer arithmetic. The translation unit +// is compiled with -fno-fast-math -ffp-contract=off and CI greps the object for FP symbols. +// +// Rounding rules (frozen, spec §3.5): +// mul_q / shifts : arithmetic right shift = floor toward -inf +// div_q / term/n : truncation toward zero (C99 integer division) +// exp range-reduce: round to nearest, ties to even (the single non-truncating step) + +namespace graphene { namespace chain { namespace lmsr { + + using q96 = boost::multiprecision::int256_t; + + // Public precisions (unchanged from the reference API). + constexpr int64_t LMSR_PRECISION = 1000; // 1 VIZ = 1000 milli-VIZ + constexpr int64_t LMSR_PRICE_PRECISION = 1000000; // price x10^6 + + // Public API — all int64 milli-VIZ in/out. On domain violation these FC_ASSERT (fail the op). + int64_t lmsr_cost(const std::vector& q, int64_t b); + std::vector lmsr_prices(const std::vector& q, int64_t b); + int64_t lmsr_price(const std::vector& q, int64_t b, int i); + int64_t lmsr_buy_cost(const std::vector& q, int64_t b, int i, int64_t delta); + int64_t lmsr_sell_return(const std::vector& q, int64_t b, int i, int64_t delta); + int64_t lmsr_tokens_for_amount(const std::vector& q, int64_t b, int i, int64_t amount); + int64_t lmsr_b_from_liquidity(int64_t liquidity, int n); + + // Exposed for the vector-parity test (primitives + transcendentals). + namespace detail { + q96 mul_q(const q96& a, const q96& b); + q96 div_q(const q96& a, const q96& b); + q96 exp_q(const q96& x); + q96 ln_q(const q96& x); + } + +} } } // graphene::chain::lmsr diff --git a/libraries/chain/include/graphene/chain/pm/parimutuel.hpp b/libraries/chain/include/graphene/chain/pm/parimutuel.hpp new file mode 100644 index 0000000000..8cbad0e434 --- /dev/null +++ b/libraries/chain/include/graphene/chain/pm/parimutuel.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +// HF14 Prediction Markets — pure parimutuel settlement math (spec +// parimutuel-settlement.md §2). Kept free of database/chainbase types so it can +// be unit-tested in isolation; settle_market() in pm_evaluator.cpp is a thin +// wrapper that feeds chain state in and applies the result via adjust_balance. +// +// Strictly ZERO-SUM (no token emission): +// Σ winner_payout + oracle_take + creator_take + lp_bonus +// == Σ winner.amount + losers_sum + forfeit_pool + uncovered +// LP principal is returned separately by the caller. When winners_pool would go negative the +// shortfall is reported as `uncovered` — an EXTERNAL top-up the pot could not fund from +// losers_sum+forfeit — and the caller charges it to LP principal (F1). So it sits on the input +// side of the identity, which then holds unconditionally instead of the shortfall being emitted. + +namespace graphene { namespace chain { namespace pm { + + // One winning position fed into settlement (curve weight is the claim device). + struct winner_in { + int64_t amount = 0; ///< staked principal (mVIZ) + int64_t weight = 0; ///< curve weight (CPMM/LMSR tokens) + uint32_t time_penalty = 0; ///< 1e6-scaled late penalty applied to PROFIT only + }; + + struct settle_params { + int64_t losers_sum = 0; ///< Σ amount of losing bets + int64_t forfeit_pool = 0; ///< extra pot folded into winners_pool + int64_t oracle_fixed_fee = 0; ///< funded from the pool, capped ≥0 + uint16_t oracle_fee_percent = 0; + uint16_t creator_fee_percent = 0; + uint16_t liquidity_fee_percent = 0; + }; + + struct settle_result { + std::vector winner_payout; ///< amount + profit − penalty, parallel to winners + int64_t oracle_take = 0; ///< oracle_fee + oracle_fixed_paid + int64_t creator_take = 0; + int64_t lp_bonus = 0; ///< liq_fee + Σpenalty + rounding dust + undistributed pool + int64_t uncovered = 0; ///< F1: |negative winners_pool| the pot couldn't cover. + ///< Flooring winners_pool at 0 returns winners their principal + ///< but leaves this shortfall uncharged; the caller MUST net it + ///< off LP principal (the leverage counterparty) pro-rata so the + ///< zero-sum invariant above holds unconditionally. (PR #124 F1.) + }; + + /// Compute the parimutuel payout split. Winners are paid by weight out of the + /// losers' stakes; the remainder/penalties accrue to the LP bonus. + settle_result compute_settlement(const settle_params& p, + const std::vector& winners); + + // One LP position for the time-weighted fee split. + struct lp_in { + int64_t principal = 0; ///< staked liquidity (mVIZ) + int64_t seconds_active = 0; ///< settle_time − deposit_time (clamped ≥0) + }; + + /// Split `bonus` across LPs weighted by principal × time-in-market (minute + /// granularity, +1 floor so simultaneous deposits fall back to principal-pro-rata, + /// and the product stays within 128 bits). Returns each LP's bonus share (parallel + /// to `lps`); the last LP absorbs the rounding remainder so Σ shares == bonus. + std::vector distribute_lp(const std::vector& lps, int64_t bonus); + +}}} // graphene::chain::pm diff --git a/libraries/chain/include/graphene/chain/pm_evaluator.hpp b/libraries/chain/include/graphene/chain/pm_evaluator.hpp new file mode 100644 index 0000000000..82e1b6ac64 --- /dev/null +++ b/libraries/chain/include/graphene/chain/pm_evaluator.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +#define DEFINE_PM_EVALUATOR(X) \ +class pm_##X##_evaluator : public graphene::chain::evaluator_impl { \ +public: \ + typedef graphene::protocol::pm_##X##_operation operation_type; \ + pm_##X##_evaluator(database& db) \ + : graphene::chain::evaluator_impl(db) {} \ + void do_apply(const operation_type& o); \ +}; + +namespace graphene { namespace chain { + + DEFINE_PM_EVALUATOR(oracle_register) + DEFINE_PM_EVALUATOR(oracle_update) + DEFINE_PM_EVALUATOR(create_market) + DEFINE_PM_EVALUATOR(oracle_accept_market) + DEFINE_PM_EVALUATOR(place_bet) + DEFINE_PM_EVALUATOR(commit_bet) + DEFINE_PM_EVALUATOR(reveal_bet) + DEFINE_PM_EVALUATOR(cancel_bet) + DEFINE_PM_EVALUATOR(add_liquidity) + DEFINE_PM_EVALUATOR(withdraw_liquidity) + DEFINE_PM_EVALUATOR(resolve_market) + DEFINE_PM_EVALUATOR(no_contest) + DEFINE_PM_EVALUATOR(dispute_create) + DEFINE_PM_EVALUATOR(dispute_vote) + DEFINE_PM_EVALUATOR(dispute_resolve) + DEFINE_PM_EVALUATOR(transfer_position) + DEFINE_PM_EVALUATOR(lazy_deposit) + DEFINE_PM_EVALUATOR(lazy_withdraw) + DEFINE_PM_EVALUATOR(leverage_open) + DEFINE_PM_EVALUATOR(leverage_close) + DEFINE_PM_EVALUATOR(leverage_convert) + DEFINE_PM_EVALUATOR(dispute_oracle_respond) + DEFINE_PM_EVALUATOR(unban) + +}} // graphene::chain diff --git a/libraries/chain/include/graphene/chain/pm_meta_object.hpp b/libraries/chain/include/graphene/chain/pm_meta_object.hpp new file mode 100644 index 0000000000..c7bcb6a1bb --- /dev/null +++ b/libraries/chain/include/graphene/chain/pm_meta_object.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include +#include + +#include + +// HF14 Prediction-Market metadata index (NON-consensus state). Each market carries a free-form +// `metadata` JSON string; the prediction_market_api plugin extracts the keys it indexes (category / +// subcategory / tags / banned jurisdictions / title / image / event …), builds queryable indexes, +// and prunes them X days after the market's dispute window closes. Localization is a client concern; +// jurisdiction filtering is done here. +// +// The object/index TYPE lives in libraries/chain so shared consumers (notably the snapshot plugin, +// which serializes it into the snapshot when `snapshot-include-pm-meta` is on) can reference it +// WITHOUT a plugin-to-plugin dependency. Runtime REGISTRATION of the index (add_plugin_index) still +// happens in the prediction_market_api plugin — nodes that don't load that plugin simply never +// create the index (snapshot export/import guard on db.has_index<...>()). +// +// NB: this is a chainbase object at space id 30 (unchanged since it was a plugin-only type), so its +// numeric object-type id is stable across the move — existing shared_memory / snapshots keep working. + +#ifndef PM_META_SPACE_ID +#define PM_META_SPACE_ID 30 +#endif + +namespace graphene { namespace chain { + + enum pm_meta_object_types { + pm_market_meta_object_type = (PM_META_SPACE_ID << 8) + }; + + using namespace chainbase; + + class pm_market_meta_object final : public object { + public: + template + pm_market_meta_object(Constructor&& c, allocator a) + : category(a), subcategory(a), tags(a), banned_jurisdictions(a), + title(a), image(a), condition_id(a), description(a), event(a), event_title(a) { c(*this); } + + id_type id; + pm_market_id_type market; + shared_string category; + shared_string subcategory; + shared_string tags; ///< comma-joined + shared_string banned_jurisdictions; ///< comma-joined ISO codes; empty = allowed everywhere + shared_string title; ///< human-readable market question (for cards/detail) + shared_string image; ///< icon/cover URL (hotlinked by clients; not hosted) + shared_string condition_id; ///< source dedup id (e.g. Polymarket conditionId) for back-link + shared_string description; ///< short resolution rules (how the oracle resolves); url holds full legal terms + shared_string event; ///< parent grouping key (siblings of one match/game share it); empty = standalone + shared_string event_title; ///< human-readable event label (e.g. "Dota 2: A vs B") for event page/cards + bool child = false; ///< true = a child/prop market of a parent event; hidden from category/tag listings by default + time_point_sec expiry; ///< prune after: dispute window close + TTL + }; + + using pm_market_meta_id_type = object_id; + + struct by_meta_market; + struct by_meta_category; + struct by_meta_event; + struct by_meta_expiry; + typedef boost::multi_index_container< + pm_market_meta_object, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique, + boost::multi_index::member>, + boost::multi_index::ordered_unique, + boost::multi_index::member>, + boost::multi_index::ordered_unique, + boost::multi_index::composite_key, + boost::multi_index::member + >, + boost::multi_index::composite_key_compare> + >, + boost::multi_index::ordered_unique, + boost::multi_index::composite_key, + boost::multi_index::member + >, + boost::multi_index::composite_key_compare> + >, + boost::multi_index::ordered_unique, + boost::multi_index::composite_key, + boost::multi_index::member + >, + boost::multi_index::composite_key_compare, std::less> + > + >, + allocator + > pm_market_meta_index; + +} } // graphene::chain + +FC_REFLECT((graphene::chain::pm_market_meta_object), + (id)(market)(category)(subcategory)(tags)(banned_jurisdictions)(title)(image)(condition_id)(description)(event)(event_title)(child)(expiry)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_market_meta_object, + graphene::chain::pm_market_meta_index) diff --git a/libraries/chain/include/graphene/chain/pm_objects.hpp b/libraries/chain/include/graphene/chain/pm_objects.hpp new file mode 100644 index 0000000000..35ba60fa06 --- /dev/null +++ b/libraries/chain/include/graphene/chain/pm_objects.hpp @@ -0,0 +1,899 @@ +#pragma once + +#include +#include + +#include +#include +#include + +// HF14 Prediction Markets (Onix) — consensus ChainBase objects. All financial sums are +// share_type. Variable-length strings are shared_string capped at MAX_PM_* (enforced on +// insert in the evaluators — the cap is consensus-mechanical, see config.hpp). Time indexes +// are (status, time, id) so the bounded per-block cron scans oldest-first deterministically. + +namespace graphene { namespace chain { + + using protocol::string_less; + + // ───────────────────────────── 1.1 pm_oracle_object ───────────────────────────── + class pm_oracle_object : public object { + public: + pm_oracle_object() = delete; + template + pm_oracle_object(Constructor&& c, allocator a) : rules_url(a) { c(*this); } + + id_type id; + account_name_type owner; + share_type insurance; + uint16_t fee_percent = 0; + share_type fixed_fee; + shared_string rules_url; + time_point_sec active_since; + time_point_sec last_active_time; + time_point_sec banned_until; ///< 0 not banned; time_point_sec::maximum() = permanent + + // 14 reputation counters (score computed on read in the API plugin). + uint32_t markets_accepted = 0; + uint32_t markets_resolved = 0; + uint32_t no_contest_count = 0; + uint32_t missed_count = 0; + uint32_t disputes_received = 0; + uint32_t disputes_lost = 0; + uint32_t disputes_won = 0; + uint32_t disputes_auto_closed = 0; + uint32_t dispute_responses_missed = 0; + share_type total_volume_resolved; + share_type total_insurance_slashed; + uint32_t avg_resolution_time = 0; + uint32_t penalty_stamps = 0; + uint32_t bans_received = 0; + time_point_sec last_penalty_stamp_time; ///< time of most recent stamp (10-day decay) + // Auto-accept policy (anti-collusion): if `auto_accept`, a new market naming this oracle is + // accepted at creation ONLY when it matches this policy — so a creator cannot slip in a + // sham dispute resolver. `auto_accept_creator` empty = any creator. `auto_accept_resolver` + // empty = only committee-mode markets (dispute_mode 0); set = only account-mode markets + // whose dispute_resolver equals it. The oracle's profile fee terms must also be within the + // creator's offered ceiling, else the market stays pending for manual review. + account_name_type auto_accept_creator; + account_name_type auto_accept_resolver; + bool auto_accept = false; + account_name_type banned_by; ///< resolver that set banned_until (empty if unset); may pm_unban + // Live count of this oracle's markets currently in the active(1) state. Display-only O(1) + // read for get_oracle / watchdogs (avoids paging list_markets). Maintained by + // pm_oracle_inc/dec_active at create-active/accept and resolve/no_contest/missed-void; + // seeded from live markets on the first block after upgrade (dgpo.pm_active_markets_seeded). + // NEVER gates consensus. + uint32_t active_markets = 0; + + // Live workload gauges (display-only, O(1) reads for oracle dashboards/watchdogs; + // NEVER gate consensus). Maintained incrementally by pm_oracle_gauge_adj at the + // relevant state transitions, and seeded once on the first block after upgrade + // (dgpo.pm_oracle_gauges_seeded) with a drift-check. Invariant each tracks: + // markets_in_dispute_window — this oracle's resolved(3)+payout-pending(1) markets + // that have NO dispute filed yet (still disputable). + // disputes_awaiting_response — open(0) disputes whose oracle has NOT yet responded. + // disputes_awaiting_decision — open(0) disputes whose oracle HAS responded (awaiting + // the committee vote / account-resolver verdict). + // (markets_awaiting_resolution — status1 past betting close — is computed on read in the + // prediction_market_api from this oracle's small active set, not stored here.) + uint32_t markets_in_dispute_window = 0; + uint32_t disputes_awaiting_response = 0; + uint32_t disputes_awaiting_decision = 0; + + // Resolution-timeliness telemetry (display-only, forward-accumulating from this upgrade; + // no seed — increments deterministically at pm_resolve_market). `resolved_late_count` is + // the number of markets this oracle resolved AFTER their result_expiration (past the + // advertised deadline but before the missed-resolution void). `avg_resolution_time` + // (declared above, previously never maintained) now tracks the running mean latency from + // betting close to resolution. Oldest-unresolved age and median/p95 latency are exposed + // via the prediction_market_api (computed on read / histogram — see P5). + uint32_t resolved_late_count = 0; + // Resolution-latency histogram (P5 pt.2): 8 buckets by seconds from betting close to + // resolve — (≤1h, ≤6h, ≤24h, ≤3d, ≤7d, ≤14d, ≤30d, >30d]; open-ended resolves count as 0 + // (bucket 0), matching avg_resolution_time. share_type (not uint32) so the fc::array is + // guaranteed zero-initialized — the generic fc::array ctor leaves data + // indeterminate, which would be non-deterministic. p50/p95 are derived on read in the API. + fc::array resolution_time_hist; + }; + + struct by_owner; + struct by_status; + struct by_insurance; + typedef multi_index_container< + pm_oracle_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member, string_less>, + ordered_non_unique, member>, + ordered_non_unique, member> + >, + allocator + > pm_oracle_index; + + // ───────────────────────────── 1.2 pm_market_object ───────────────────────────── + class pm_market_object : public object { + public: + pm_market_object() = delete; + template + pm_market_object(Constructor&& c, allocator a) + : url(a), decision_url(a), decision_reason(a) { c(*this); } + + id_type id; + account_name_type creator; + account_name_type oracle; + uint8_t market_type = 0; ///< 0 binary (CPMM), 1 multi (LMSR) + uint8_t outcome_count = 2; + shared_string url; + // NB: the free-form client `metadata` is intentionally NOT stored in consensus state. + // It is opaque to consensus (never read here), so the prediction_market_api plugin + // ingests it off-chain from pm_create_market_operation into its own prunable index. + // This keeps chainbase lean and lets each node prune it (see --pmm-ttl-days). + int8_t status = 0; ///< -1 deleted, 0 waiting, 1 active, 2 closed, 3 resolved + uint8_t payout_status = 0; ///< 0 none, 1 pending, 2 disputed, 3 finalized (paid/closed) + time_point_sec created_time; + time_point_sec accept_deadline; ///< pending markets only: created_time + median + ///< pm_oracle_accept_window_sec. The cron voids the + ///< market (refund seed) if the oracle hasn't acted by + ///< then. 0 for markets active at creation (never scanned). + time_point_sec betting_expiration; + time_point_sec result_expiration; + time_point_sec finalized_time; ///< 0 while live; set to head-block time the moment the + ///< market becomes terminal (resolved+paid, void/no-contest, + ///< oracle-rejected, or accept-window expired). The cron GCs + ///< the whole market cluster pm_closed_market_retention_sec + ///< after this — deterministic, so every node prunes alike. + int16_t resolved_outcome = -1; + + // Binary CPMM + share_type reserve_a; + share_type reserve_b; + fc::uint128_t k = 0; ///< reserve_a * reserve_b + share_type a_bets_sum; + share_type b_bets_sum; + + // Multi LMSR + share_type lmsr_b; + share_type lmsr_subsidy; + + // Common + share_type bets_sum; + share_type liquidity_sum; + uint16_t oracle_fee_percent = 0; + uint16_t creator_fee_percent = 0; + uint16_t liquidity_fee_percent = 0; + share_type oracle_fixed_fee; + share_type liquidity_fee_earned; + share_type forfeit_pool; + uint8_t time_penalty_type = 0; + uint32_t time_penalty_value = 0; + uint8_t penalty_curve_type = 0; + bool allow_early_resolution = false; + bool allow_cancellation = false; + bool allow_batch = false; + bool allow_instant_bet = true; + uint8_t endogeneity_tier = 2; + uint32_t current_epoch = 0; + uint8_t dispute_mode = 0; ///< 0 committee / 1 account + account_name_type dispute_resolver; + int16_t dispute_penalty_percent = 0; ///< −10000..+10000: oracle penalty on a + ///< successful dispute (>0 slash % of + ///< insurance ×consensus; <0 good-faith + ///< oracle bonus from fee; 0 none) + // Oracle's resolution statement, stored on-chain (like an oracle/validator's rules_url): + // set by pm_resolve_market (both) or pm_no_contest (reason → decision_reason). Empty until + // resolved. Readable directly via get_market — no history scan needed. + shared_string decision_url; ///< evidence link the oracle cited when resolving + shared_string decision_reason; ///< oracle's free-text justification (or NO-CONTEST reason) + // #349: number of live early-exit deferred claims (F1/#300) recorded against this market. + // Bounds settle_market's claim-distribution loop: once this hits + // MAX_PM_DEFERRED_CLAIMS_PER_MARKET, further early-exits skip recording a claim (the tail + // value stays in the curve and simply pays 0 at settlement, exactly like bucket-exhaustion) + // so a griefer cannot force unbounded per-block work at settlement. Monotonic pre-settlement + // (claims are only created, never removed, before the market settles). + uint32_t deferred_claim_count = 0; + }; + + struct by_creator; + struct by_oracle; + struct by_oracle_status; + struct by_accept_deadline; + struct by_betting_expiration; + struct by_result_expiration; + struct by_payout_status; + struct by_finalized; + typedef multi_index_container< + pm_market_object, + indexed_by< + ordered_unique, member>, + ordered_non_unique, member, string_less>, + ordered_non_unique, member, string_less>, + // (status, id): consensus iterates status ranges in id order (deterministic) and the + // batch-epoch-settle cursor resumes mid-range with lower_bound((status, id)). + ordered_unique, + composite_key, + member>>, + // (oracle, status, id): one oracle's markets filtered by a single status in a bounded + // walk — e.g. its still-active (1) or already-resolved (3) rows — without scanning the + // oracle's entire (mostly resolved) history the way plain by_oracle does. + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less> + >, + // Pending-acceptance sweep: (status, accept_deadline, id). The cron lower_bounds at + // status 0 and stops at the first accept_deadline > now, so voiding never-accepted + // markets stays bounded. Accepted (1)/rejected (-1) markets sit in other status buckets. + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + >, + ordered_non_unique, member>, + // GC sweep: terminal markets ordered by finalized_time (0 = still live, sorts first + // and is skipped). The cron lower_bounds just past 0 and stops at the first + // finalized_time newer than the retention cutoff → bounded, deterministic pruning. + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + > + >, + allocator + > pm_market_index; + + // ───────────────────────────── 1.3 pm_outcome_object ───────────────────────────── + class pm_outcome_object : public object { + public: + pm_outcome_object() = delete; + template + pm_outcome_object(Constructor&& c, allocator a) : label(a) { c(*this); } + + id_type id; + pm_market_id_type market; + uint8_t outcome_index = 0; + shared_string label; + share_type q; ///< LMSR quantity + share_type bets_sum; + share_type weight_sum; + uint32_t bets_count = 0; + }; + + struct by_market_outcome; + typedef multi_index_container< + pm_outcome_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + > + >, + allocator + > pm_outcome_index; + + // ───────────────────────────── 1.4 pm_bet_object ───────────────────────────── + class pm_bet_object : public object { + public: + pm_bet_object() = delete; + template + pm_bet_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type account; + int8_t side = -1; + int16_t outcome_index = -1; + share_type amount; + share_type weight; + uint64_t price = 0; + uint32_t time_penalty = 0; + uint8_t mode = 0; + uint32_t epoch = 0; + uint8_t status = 0; ///< 0 active,1 cancelled,2 refunded,3 resolved,5 queued,6 revealed-pending + share_type min_tokens = 0; ///< slippage floor for queued batch bets + share_type resolved_amount; + time_point_sec created_time; + }; + + struct by_market; + struct by_account; + struct by_market_account; + struct by_market_outcome_bet; + struct by_epoch; + typedef multi_index_container< + pm_bet_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + >, + ordered_unique, + composite_key, + member + >, + composite_key_compare> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, string_less, std::less> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + > + >, + allocator + > pm_bet_index; + + // ───────────────────────────── 1.5 pm_liquidity_object ───────────────────────────── + class pm_liquidity_object : public object { + public: + pm_liquidity_object() = delete; + template + pm_liquidity_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type provider; ///< empty = Lazy Pool + share_type amount; + share_type weight_a; + share_type weight_b; + share_type b_share; + uint32_t sec_to_expiration = 0; + time_point_sec deposit_time; + share_type earned_fee; + uint8_t status = 0; ///< 0 active, 3 resolved/closed + }; + + struct by_provider; + typedef multi_index_container< + pm_liquidity_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + >, + ordered_unique, + composite_key, + member + >, + composite_key_compare> + > + >, + allocator + > pm_liquidity_index; + + // ───────────────────────────── 1.6 pm_commit_object ───────────────────────────── + class pm_commit_object : public object { + public: + pm_commit_object() = delete; + template + pm_commit_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type account; + fc::sha256 commitment; + share_type escrow_amount; + uint16_t no_reveal_fee_percent = 0; ///< snapshotted at commit (consensus-checked) + time_point_sec commit_time; + time_point_sec reveal_deadline; + uint8_t status = 0; ///< 0 committed, 1 revealed, 2 forfeited + }; + + struct by_reveal_deadline; + struct by_commit_account; + typedef multi_index_container< + pm_commit_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + >, + ordered_unique, + composite_key, + member + >, + composite_key_compare> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + > + >, + allocator + > pm_commit_index; + + // ───────────────────────────── 1.7 pm_dispute_object ───────────────────────────── + class pm_dispute_object : public object { + public: + pm_dispute_object() = delete; + template + pm_dispute_object(Constructor&& c, allocator a) : reason(a), oracle_response(a) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type disputer; + share_type dispute_fee; + shared_string reason; + time_point_sec filed_time; + time_point_sec oracle_response_deadline; + uint8_t dispute_mode = 0; + time_point_sec voting_end_time; + time_point_sec auto_close_time; + int16_t proposed_outcome = -1; + uint8_t status = 0; ///< 0 open,1 oracle-wrong,2 oracle-right,3 auto-closed + shared_string oracle_response; ///< oracle's public rebuttal (empty until it responds) + time_point_sec oracle_response_time; ///< when the rebuttal was posted (0 = none) + }; + + struct by_voting_end; + struct by_auto_close; + typedef multi_index_container< + pm_dispute_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member>, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + > + >, + allocator + > pm_dispute_index; + + // ───────────────────────────── 1.8 pm_dispute_vote_object ───────────────────────────── + class pm_dispute_vote_object : public object { + public: + pm_dispute_vote_object() = delete; + template + pm_dispute_vote_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type voter; + int16_t vote_outcome = -1; ///< -1 = uphold oracle + int16_t vote_percent = 0; + time_point_sec time; + }; + + struct by_market_voter; + struct by_voter; + typedef multi_index_container< + pm_dispute_vote_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, + member + >, + composite_key_compare, string_less> + >, + ordered_unique, + composite_key, + member + >, + composite_key_compare> + > + >, + allocator + > pm_dispute_vote_index; + + // ───────────────────────────── 1.9 Lazy Liquidity Pool (allocation-only) ───────────────────────────── + class pm_lazy_pool_object : public object { + public: + pm_lazy_pool_object() = delete; + template + pm_lazy_pool_object(Constructor&& c, allocator) { c(*this); } + + id_type id; ///< singleton (id == 0) + share_type total_shares; + share_type free_balance; + share_type allocated_balance; + share_type earned_balance; ///< monotonically non-decreasing + fc::uint128_t reward_per_share = 0; ///< LAZY_POOL_PRECISION (1e9) accumulator + share_type leverage_fund_used; ///< total active leverage loans (cap on free_balance) + share_type pending_withdrawals; ///< VIZ owed to queued withdrawers not yet paid (first claim on returning capital); free_balance never goes negative + }; + + typedef multi_index_container< + pm_lazy_pool_object, + indexed_by< + ordered_unique, member> + >, + allocator + > pm_lazy_pool_index; + + class pm_lazy_deposit_object : public object { + public: + pm_lazy_deposit_object() = delete; + template + pm_lazy_deposit_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + account_name_type account; + share_type shares; + share_type principal; + fc::uint128_t reward_snapshot = 0; + share_type pending_rewards; + time_point_sec unlock_time; + }; + + struct by_deposit_account; + typedef multi_index_container< + pm_lazy_deposit_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member, string_less> + >, + allocator + > pm_lazy_deposit_index; + + // Queued lazy-pool withdrawal: created when a withdrawal cannot be paid in full from + // free_balance at request time. Shares are already burned and rewards/penalty already + // applied; `amount` is the fixed VIZ still owed. Serviced FIFO (by id) as capital returns + // to free_balance, so the pool never pays out more than it holds liquid (free_balance + // never goes negative). A withdrawal that IS fully covered creates and clears its request + // within the same operation. + class pm_lazy_withdraw_request_object : public object { + public: + pm_lazy_withdraw_request_object() = delete; + template + pm_lazy_withdraw_request_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + account_name_type account; + share_type amount; ///< VIZ still owed to this account + time_point_sec created; ///< enqueue time (FIFO order == id) + }; + + struct by_request_account; + typedef multi_index_container< + pm_lazy_withdraw_request_object, + indexed_by< + ordered_unique, member>, + ordered_non_unique, member, string_less> + >, + allocator + > pm_lazy_withdraw_request_index; + + // Early-exit deferred claim (F1 / #300). Recorded when a bet is cancelled or a leverage + // position closes/liquidates BEFORE resolution: instead of paying curve profit out of LP + // depth, the profit becomes an OUTCOME-CONTINGENT claim. At settlement, claims on the + // WINNING outcome are paid FIFO by exit_time from a bounded slice + // (pm_early_exit_reward_cap_percent × losers_sum); a losing outcome pays nothing; any + // unused slice returns to the winners' pool. See early-exit-deferred-claim.md. + class pm_deferred_claim_object : public object { + public: + pm_deferred_claim_object() = delete; + template + pm_deferred_claim_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type account; + uint8_t kind = 0; ///< 0 = bet cancel, 1 = leverage close/liquidate + uint8_t outcome_index = 0; ///< claim pays ONLY if this outcome wins + share_type claim_amount; ///< profit competing for the bounded early-exit bucket + time_point_sec exit_time; ///< FIFO order at settlement (== id creation order) + }; + + struct by_claim_market; + typedef multi_index_container< + pm_deferred_claim_object, + indexed_by< + ordered_unique, member>, + // (market, id): iterate a market's claims in FIFO (creation/exit) order at settlement. + ordered_unique, composite_key, + member>> + >, + allocator + > pm_deferred_claim_index; + + class pm_lazy_allocation_object : public object { + public: + pm_lazy_allocation_object() = delete; + template + pm_lazy_allocation_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + share_type amount; + share_type original_amount; + share_type recalled_amount; + share_type returned_amount; + share_type bets_sum_at_check; + uint32_t check_step = 0; ///< 0 just allocated, 1..10 + time_point_sec last_check_time; + uint8_t status = 0; ///< 0 active, 1 returned + }; + + struct by_alloc_check; + typedef multi_index_container< + pm_lazy_allocation_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member>, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + > + >, + allocator + > pm_lazy_allocation_index; + + // ───────────────────────────── 1.12 pm_leverage_position (HF14 margin) ───────────────────────────── + class pm_leverage_position_object : public object { + public: + pm_leverage_position_object() = delete; + template + pm_leverage_position_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + pm_market_id_type market; + account_name_type account; + int16_t outcome_index = 0; + share_type collateral; + share_type loan; + share_type total_bet; + share_type tokens; ///< weight received from the AMM + pm_bet_id_type bet; ///< the underlying pm_bet + share_type pool_profit; ///< loan × R% / 100 + share_type liquidation_threshold; ///< loan × (1 + R%/100) + uint8_t status = 0; ///< 0 active,1 liquidated,2 resolved_won,3 resolved_lost,4 closed_voluntary,5 converted + time_point_sec liquidated_at; + pm_bet_id_type liquidated_by_bet; + share_type cancel_value_at_liquidation; + share_type pool_received; + share_type bettor_received; + time_point_sec created_time; + time_point_sec last_update; + share_type funding_paid; ///< cumulative funding charged from the bettor's equity → pool (raises effective obligation) + time_point_sec funding_due_time; ///< next 24h funding-period boundary (created_time + N×24h); 0 = none yet + }; + + struct by_lev_market_status; + struct by_lev_account; + struct by_lev_bet; + struct by_lev_funding_due; + typedef multi_index_container< + pm_leverage_position_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + >, + ordered_unique, + composite_key, + member + >, + composite_key_compare> + >, + // (bet, id): `bet` is left at its default (0) for every leverage position — the evaluator + // never links an underlying pm_bet — so a bare ordered_unique on `bet` alone let only ONE + // position exist chain-wide; the 2nd pm_leverage_open failed insert with a uniqueness + // violation. Append `id` as tiebreaker (like every sibling index here) so inserts always + // succeed while a bet-prefix lookup still works. + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::less, std::less> + > + >, + allocator + > pm_leverage_position_index; + + // ───────────────────────────── 1.13 pm_creator_ban_object ───────────────────────────── + // A creator may be temporarily (or permanently) barred from creating new markets as a + // sanction issued by an account-mode dispute resolver (scenario #14). One row per banned + // creator; create_market consults by_account and rejects while now < banned_until. + class pm_creator_ban_object : public object { + public: + pm_creator_ban_object() = delete; + template + pm_creator_ban_object(Constructor&& c, allocator) { c(*this); } + + id_type id; + account_name_type creator; + time_point_sec banned_until; ///< time_point_sec::maximum() = permanent; past = not banned + uint32_t ban_count = 0; + account_name_type banned_by; ///< resolver that set the current ban (may pm_unban) + }; + + struct by_ban_account; + struct by_ban_expiry; + typedef multi_index_container< + pm_creator_ban_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member, string_less>, + // (banned_until, id): the per-block cron sweeps expired temp bans oldest-first. Cleared + // bans have banned_until = 0 (past), permanent bans = maximum(), so the sweep skips both. + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> + > + >, + allocator + > pm_creator_ban_index; + +} } // graphene::chain + +FC_REFLECT((graphene::chain::pm_oracle_object), + (id)(owner)(insurance)(fee_percent)(fixed_fee)(rules_url)(active_since)(last_active_time)(banned_until) + (markets_accepted)(markets_resolved)(no_contest_count)(missed_count)(disputes_received)(disputes_lost) + (disputes_won)(disputes_auto_closed)(dispute_responses_missed)(total_volume_resolved)(total_insurance_slashed) + (avg_resolution_time)(penalty_stamps)(bans_received)(last_penalty_stamp_time) + (auto_accept_creator)(auto_accept_resolver)(auto_accept)(banned_by)(active_markets) + (markets_in_dispute_window)(disputes_awaiting_response)(disputes_awaiting_decision) + (resolved_late_count)(resolution_time_hist)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_oracle_object, graphene::chain::pm_oracle_index) + +FC_REFLECT((graphene::chain::pm_market_object), + (id)(creator)(oracle)(market_type)(outcome_count)(url)(status)(payout_status)(created_time)(accept_deadline) + (betting_expiration)(result_expiration)(finalized_time)(resolved_outcome)(reserve_a)(reserve_b)(k)(a_bets_sum)(b_bets_sum)(lmsr_b)(lmsr_subsidy) + (bets_sum)(liquidity_sum)(oracle_fee_percent)(creator_fee_percent)(liquidity_fee_percent)(oracle_fixed_fee) + (liquidity_fee_earned)(forfeit_pool)(time_penalty_type)(time_penalty_value)(penalty_curve_type) + (allow_early_resolution)(allow_cancellation)(allow_batch)(allow_instant_bet)(endogeneity_tier)(current_epoch) + (dispute_mode)(dispute_resolver)(dispute_penalty_percent)(decision_url)(decision_reason) + (deferred_claim_count)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_market_object, graphene::chain::pm_market_index) + +FC_REFLECT((graphene::chain::pm_outcome_object), + (id)(market)(outcome_index)(label)(q)(bets_sum)(weight_sum)(bets_count)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_outcome_object, graphene::chain::pm_outcome_index) + +FC_REFLECT((graphene::chain::pm_bet_object), + (id)(market)(account)(side)(outcome_index)(amount)(weight)(price)(time_penalty)(mode)(epoch)(status) + (min_tokens)(resolved_amount)(created_time)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_bet_object, graphene::chain::pm_bet_index) + +FC_REFLECT((graphene::chain::pm_liquidity_object), + (id)(market)(provider)(amount)(weight_a)(weight_b)(b_share)(sec_to_expiration)(deposit_time)(earned_fee)(status)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_liquidity_object, graphene::chain::pm_liquidity_index) + +FC_REFLECT((graphene::chain::pm_commit_object), + (id)(market)(account)(commitment)(escrow_amount)(no_reveal_fee_percent)(commit_time)(reveal_deadline)(status)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_commit_object, graphene::chain::pm_commit_index) + +FC_REFLECT((graphene::chain::pm_dispute_object), + (id)(market)(disputer)(dispute_fee)(reason)(filed_time)(oracle_response_deadline)(dispute_mode) + (voting_end_time)(auto_close_time)(proposed_outcome)(status)(oracle_response)(oracle_response_time)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_dispute_object, graphene::chain::pm_dispute_index) + +FC_REFLECT((graphene::chain::pm_dispute_vote_object), + (id)(market)(voter)(vote_outcome)(vote_percent)(time)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_dispute_vote_object, graphene::chain::pm_dispute_vote_index) + +FC_REFLECT((graphene::chain::pm_lazy_pool_object), + (id)(total_shares)(free_balance)(allocated_balance)(earned_balance)(reward_per_share)(leverage_fund_used)(pending_withdrawals)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_lazy_pool_object, graphene::chain::pm_lazy_pool_index) + +FC_REFLECT((graphene::chain::pm_lazy_withdraw_request_object), + (id)(account)(amount)(created)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_lazy_withdraw_request_object, graphene::chain::pm_lazy_withdraw_request_index) + +FC_REFLECT((graphene::chain::pm_deferred_claim_object), + (id)(market)(account)(kind)(outcome_index)(claim_amount)(exit_time)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_deferred_claim_object, graphene::chain::pm_deferred_claim_index) + +FC_REFLECT((graphene::chain::pm_lazy_deposit_object), + (id)(account)(shares)(principal)(reward_snapshot)(pending_rewards)(unlock_time)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_lazy_deposit_object, graphene::chain::pm_lazy_deposit_index) + +FC_REFLECT((graphene::chain::pm_lazy_allocation_object), + (id)(market)(amount)(original_amount)(recalled_amount)(returned_amount)(bets_sum_at_check)(check_step) + (last_check_time)(status)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_lazy_allocation_object, graphene::chain::pm_lazy_allocation_index) + +FC_REFLECT((graphene::chain::pm_leverage_position_object), + (id)(market)(account)(outcome_index)(collateral)(loan)(total_bet)(tokens)(bet)(pool_profit) + (liquidation_threshold)(status)(liquidated_at)(liquidated_by_bet)(cancel_value_at_liquidation) + (pool_received)(bettor_received)(created_time)(last_update)(funding_paid)(funding_due_time)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_leverage_position_object, graphene::chain::pm_leverage_position_index) + +FC_REFLECT((graphene::chain::pm_creator_ban_object), + (id)(creator)(banned_until)(ban_count)(banned_by)) +CHAINBASE_SET_INDEX_TYPE(graphene::chain::pm_creator_ban_object, graphene::chain::pm_creator_ban_index) diff --git a/libraries/chain/include/graphene/chain/validator_objects.hpp b/libraries/chain/include/graphene/chain/validator_objects.hpp index 2d8d0a362f..44b2a7dbb1 100644 --- a/libraries/chain/include/graphene/chain/validator_objects.hpp +++ b/libraries/chain/include/graphene/chain/validator_objects.hpp @@ -17,7 +17,7 @@ namespace graphene { namespace chain { using graphene::protocol::asset; using graphene::protocol::asset_symbol_type; - using chain_properties = graphene::protocol::chain_properties_hf13; + using chain_properties = graphene::protocol::chain_properties_pm; /** * All validators with at least 1% net positive approval and diff --git a/libraries/chain/pch.hpp b/libraries/chain/pch.hpp new file mode 100644 index 0000000000..23de8af663 --- /dev/null +++ b/libraries/chain/pch.hpp @@ -0,0 +1,41 @@ +// Precompiled header for graphene_chain. +// +// Opt-in via -DENABLE_PCH=ON. Lists only *stable* third-party headers (Boost, +// FC, C++ standard library) that are pulled in — directly or transitively — by +// most translation units in this library. Project headers are intentionally +// excluded: they change often, and putting a churning header in the PCH forces +// a full-library rebuild on every edit, defeating the purpose. +// +// Chosen from an include-frequency scan of libraries/chain/*.{hpp,cpp}; the +// dominant cost here is Boost.MultiIndex plus FC serialization/reflection. +#pragma once + +// Boost.MultiIndex — the chainbase object indexes instantiate these heavily. +#include +#include +#include +#include +#include +#include + +// Boost misc used across the library. +#include +#include + +// FC framework — serialization, reflection, exceptions, primitives. +#include +#include +#include +#include +#include +#include +#include + +// C++ standard library. +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/chain/pm/leverage.cpp b/libraries/chain/pm/leverage.cpp new file mode 100644 index 0000000000..652a97f54d --- /dev/null +++ b/libraries/chain/pm/leverage.cpp @@ -0,0 +1,92 @@ +#include + +namespace graphene { namespace chain { namespace pm { namespace leverage { + + namespace { + // k / x as int64 (k is 128-bit, x>0 → result fits for reachable reserves). + inline int64_t kdiv(fc::uint128_t k, int64_t x) { + if (x <= 0) return 0; + return (int64_t)(k / fc::uint128_t((uint64_t)x)).lo; + } + inline int64_t mul_pct(int64_t v, uint16_t pct) { // floor(v × (100+pct)/100) + return (int64_t)(fc::uint128_t((uint64_t)v) * fc::uint128_t(100u + pct) / fc::uint128_t(100u)).lo; + } + } + + cpmm_fill cpmm_buy(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t amount, int outcome) { + cpmm_fill r; + if (outcome == 0) { + r.new_reserve_a = reserve_a + amount; + r.new_reserve_b = kdiv(k, r.new_reserve_a); + r.tokens = reserve_b - r.new_reserve_b; + } else { + r.new_reserve_b = reserve_b + amount; + r.new_reserve_a = kdiv(k, r.new_reserve_b); + r.tokens = reserve_a - r.new_reserve_a; + } + return r; + } + + int64_t cancel_value(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t tokens, int outcome) { + int64_t cv; + if (outcome == 0) { // A-tokens sell back into reserve_b + int64_t new_rb = reserve_b + tokens; + cv = reserve_a - kdiv(k, new_rb); + } else { // B-tokens sell back into reserve_a + int64_t new_ra = reserve_a + tokens; + cv = reserve_b - kdiv(k, new_ra); + } + return cv > 0 ? cv : 0; + } + + int64_t cancel_value_after_opposing(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t tokens, int outcome, int64_t m) { + int64_t cv; + if (outcome == 0) { // position on A, opposing bet on B + int64_t ra_m = kdiv(k, reserve_b + m); // reserves after opposing bet + int64_t ra_mx = kdiv(k, (reserve_b + m) + tokens); // after also selling X A-tokens + cv = ra_m - ra_mx; + } else { // position on B, opposing bet on A + int64_t rb_m = kdiv(k, reserve_a + m); + int64_t rb_mx = kdiv(k, (reserve_a + m) + tokens); + cv = rb_m - rb_mx; + } + return cv > 0 ? cv : 0; + } + + int64_t worst_opposing_bet(int64_t reserve_a, int64_t reserve_b, + uint16_t sl_percent, uint16_t m_factor_percent) { + int64_t minr = reserve_a < reserve_b ? reserve_a : reserve_b; + if (minr <= 0) return 0; + // min(ra,rb) × sl% /100 × m_factor% /100 + return (int64_t)(fc::uint128_t((uint64_t)minr) + * fc::uint128_t(sl_percent) * fc::uint128_t(m_factor_percent) + / fc::uint128_t(10000u)).lo; + } + + int64_t liquidation_threshold(int64_t loan, uint16_t r_percent) { + return mul_pct(loan, r_percent); + } + + int64_t max_leverage_loan(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k, + int64_t collateral, int outcome, int64_t hi_loan, + uint16_t r_percent, uint16_t s_percent, + uint16_t sl_percent, uint16_t m_factor_percent) { + int64_t lo = 0, hi = hi_loan, best = 0; + for (int i = 0; i < 50; ++i) { + int64_t mid = (lo + hi) / 2; + if (mid <= lo) break; + cpmm_fill f = cpmm_buy(reserve_a, reserve_b, k, collateral + mid, outcome); + int64_t m = worst_opposing_bet(f.new_reserve_a, f.new_reserve_b, sl_percent, m_factor_percent); + int64_t cvw = cancel_value_after_opposing(f.new_reserve_a, f.new_reserve_b, k, + f.tokens, outcome, m); + int64_t thr_safe = mul_pct(liquidation_threshold(mid, r_percent), s_percent); + if (cvw >= thr_safe) { best = mid; lo = mid; } + else { hi = mid; } + } + return best; + } + +}}}} // graphene::chain::pm::leverage diff --git a/libraries/chain/pm/lmsr_q96.cpp b/libraries/chain/pm/lmsr_q96.cpp new file mode 100644 index 0000000000..611bd0a9af --- /dev/null +++ b/libraries/chain/pm/lmsr_q96.cpp @@ -0,0 +1,259 @@ +#include +#include + +// Deterministic fixed-point LMSR — C++ port of reference/lmsr_fixed.php (normative). +// Every numeric step mirrors the PHP/GMP reference so the C++ output is bit-identical +// to reference/lmsr_fixed_vectors.json. See lmsr-fixed-point-spec.md. + +namespace graphene { namespace chain { namespace lmsr { + + using boost::multiprecision::int256_t; + using boost::multiprecision::int512_t; + + namespace { + + // Q96 anchors (frozen). + const q96 ONE = q96(1) << 96; + // 0xB17217F7D1CF79ABC9E3B398 = floor(ln(2) * 2^96) + const q96 LN2 = q96("54916777467707473351141471128"); + const q96 NEG_LN2 = -LN2; + const q96 EXP_DOMAIN_LIMIT = q96(200) * ONE; + const q96 MAX_B = q96(1) << 53; + const q96 MAX_Q = q96(1) << 53; + + // Floor division (round toward -inf). d is always > 0 here. + template + inline T floor_div(const T& n, const T& d) { + T q = n / d; // boost: truncation toward zero + T r = n - q * d; + if (r != 0 && ((r < 0) != (d < 0))) q -= 1; + return q; + } + + // Arithmetic right shift by `bits` = floor(n / 2^bits). Spec rule for mul_q / scaling. + inline q96 asr(const q96& n, unsigned bits) { + return floor_div(n, q96(q96(1) << bits)); + } + + // mul_q(a,b) = (a*b) >> 96, arithmetic (sign-preserving). Wide 512-bit product. + inline q96 mul_q_impl(const q96& a, const q96& b) { + int512_t t = int512_t(a) * int512_t(b); + int512_t d = int512_t(1) << 96; + int512_t q = t / d; + int512_t r = t - q * d; + if (r != 0 && ((r < 0) != (d < 0))) q -= 1; // floor + return q96(q); + } + + // div_q(a,b) = (a << 96) / b, truncated toward zero. + inline q96 div_q_impl(const q96& a, const q96& b) { + int512_t t = int512_t(a) << 96; + return q96(t / int512_t(b)); // boost: trunc toward zero + } + + inline bool is_odd(const q96& q) { + q96 r = q % 2; + return r != 0; + } + + // Round x/LN2 to nearest integer, ties to even (the single non-truncating step, §3.5). + q96 round_div_ln2(const q96& x) { + q96 q = x / LN2; // truncated toward zero + q96 r = x - q * LN2; + q96 two_r = r * 2; + if (two_r > LN2 || (two_r == LN2 && is_odd(q))) q += 1; + if (two_r < NEG_LN2 || (two_r == NEG_LN2 && is_odd(q))) q -= 1; + return q; + } + + q96 exp_q(const q96& x) { + q96 abs_x = x < 0 ? q96(-x) : x; + FC_ASSERT(abs_x <= q96(130) * ONE, "LMSR_OVERFLOW: exp_q domain exceeded"); + + q96 k = round_div_ln2(x); + q96 r = x - k * LN2; + + q96 acc = ONE; + q96 term = ONE; + for (int n = 1; n <= 30; ++n) { // FIXED 30 iterations + term = mul_q_impl(term, r) / q96(n); // mul_q first, then trunc-div by n + acc += term; + } + + long long k_int = k.convert_to(); + if (k_int >= 0) { + return acc * (q96(1) << (unsigned)k_int); // exact left shift + } else { + return asr(acc, (unsigned)(-k_int)); // ASR (acc>0 -> == trunc) + } + } + + q96 ln_q(const q96& x) { + FC_ASSERT(x > 0, "LMSR_DOMAIN_LN: ln_q called with x <= 0"); + + int p = (int)boost::multiprecision::msb(x) - 96; + q96 m; + if (p >= 0) m = asr(x, (unsigned)p); + else m = x * (q96(1) << (unsigned)(-p)); + + q96 num = m - ONE; + q96 den = m + ONE; + q96 z = div_q_impl(num, den); + + q96 z2 = mul_q_impl(z, z); + q96 acc = z; + q96 term = z; + for (int k = 1; k <= 50; ++k) { // FIXED 50 iterations + term = mul_q_impl(term, z2); + q96 denom = q96(2 * k + 1); + acc += term / denom; // trunc-div + } + q96 ln_m = acc * 2; + return q96(p) * LN2 + ln_m; + } + + void validate_domain(const std::vector& q, int64_t b) { + FC_ASSERT(q.size() >= 2 && q.size() <= 128, "LMSR_INVALID_N: outcomes must be 2..128"); + FC_ASSERT(b > 0, "LMSR_INVALID_B: b must be > 0"); + FC_ASSERT(q96(b) <= MAX_B, "LMSR_INVALID_B: b exceeds MAX_B (2^53)"); + for (int64_t qi : q) { + FC_ASSERT(qi >= 0, "LMSR_INVALID_Q: q[i] < 0"); + FC_ASSERT(q96(qi) <= MAX_Q, "LMSR_INVALID_Q: q[i] exceeds MAX_Q (2^53)"); + } + } + + // ratios[i] = q[i] * ONE / b, truncated (one-step form, §3.4). + std::vector ratios_q96(const std::vector& q, int64_t b) { + std::vector out; + out.reserve(q.size()); + q96 bb(b); + for (int64_t qi : q) { + q96 num = q96(qi) * ONE; + out.push_back(num / bb); // trunc toward zero (qi,b >= 0) + } + return out; + } + + q96 lse_q(const std::vector& ratios) { + if (ratios.empty()) return q96(0); + q96 mx = ratios[0]; + for (const auto& r : ratios) if (r > mx) mx = r; + + q96 acc(0); + q96 cutoff = -EXP_DOMAIN_LIMIT; + for (const auto& r : ratios) { // input order — DO NOT sort + q96 d = r - mx; // d <= 0 + if (d < cutoff) continue; // strict < + acc += exp_q(d); + } + if (acc <= 0) return mx; // defensive (unreachable) + return mx + ln_q(acc); + } + + } // anonymous namespace + + // ───────────────────────── public API ───────────────────────── + + int64_t lmsr_cost(const std::vector& q, int64_t b) { + if (b <= 0) return 0; + validate_domain(q, b); + std::vector ratios = ratios_q96(q, b); + q96 lse = lse_q(ratios); + q96 product = q96(b) * lse; + q96 cost = product / ONE; // trunc toward zero + return cost.convert_to(); + } + + std::vector lmsr_prices(const std::vector& q, int64_t b) { + size_t n = q.size(); + if (b <= 0) return std::vector(n, 0); + validate_domain(q, b); + + std::vector ratios = ratios_q96(q, b); + q96 mx = ratios[0]; + for (const auto& r : ratios) if (r > mx) mx = r; + + q96 cutoff = -EXP_DOMAIN_LIMIT; + std::vector exps; + exps.reserve(n); + q96 sum(0); + for (const auto& r : ratios) { + q96 d = r - mx; + if (d < cutoff) { exps.push_back(q96(0)); } + else { q96 e = exp_q(d); exps.push_back(e); sum += e; } + } + if (sum <= 0) return std::vector(n, 0); + + std::vector out; + out.reserve(n); + for (const auto& e : exps) { + q96 pq = div_q_impl(e, sum); // Q96 + q96 scaled = pq * q96(LMSR_PRICE_PRECISION); + q96 v = scaled / ONE; // trunc + out.push_back(v.convert_to()); + } + return out; + } + + int64_t lmsr_price(const std::vector& q, int64_t b, int i) { + if (b <= 0 || i < 0 || (size_t)i >= q.size()) return 0; + validate_domain(q, b); + auto prices = lmsr_prices(q, b); + return prices[i]; + } + + int64_t lmsr_buy_cost(const std::vector& q, int64_t b, int i, int64_t delta) { + if (b <= 0 || delta <= 0 || i < 0 || (size_t)i >= q.size()) return 0; + std::vector q_after = q; + q_after[i] += delta; + int64_t d = lmsr_cost(q_after, b) - lmsr_cost(q, b); + return d > 0 ? d : 0; + } + + int64_t lmsr_sell_return(const std::vector& q, int64_t b, int i, int64_t delta) { + if (b <= 0 || delta <= 0 || i < 0 || (size_t)i >= q.size()) return 0; + if (q[i] - delta < 0) return 0; + std::vector q_after = q; + q_after[i] -= delta; + int64_t d = lmsr_cost(q, b) - lmsr_cost(q_after, b); + return d > 0 ? d : 0; + } + + int64_t lmsr_tokens_for_amount(const std::vector& q, int64_t b, int i, int64_t amount) { + if (b <= 0 || amount <= 0 || i < 0 || (size_t)i >= q.size()) return 0; + validate_domain(q, b); + + int64_t lo = 0; + int64_t hi = amount * 10; + int64_t max_q = (int64_t)1 << 53; + if (hi > max_q) hi = max_q; + int64_t best = 0; + + for (int iter = 0; iter < 100; ++iter) { // FIXED 100 iterations + if (hi - lo <= 1) break; + int64_t mid = (lo + hi) / 2; + int64_t cost = lmsr_buy_cost(q, b, i, mid); + if (cost <= amount) { best = mid; lo = mid; } + else { hi = mid; } + } + return best; + } + + int64_t lmsr_b_from_liquidity(int64_t liquidity, int n) { + if (liquidity <= 0 || n <= 1) return 0; + FC_ASSERT(n <= 128, "LMSR_INVALID_N"); + q96 n_q96 = q96(n) * ONE; + q96 ln_n = ln_q(n_q96); + q96 num = q96(liquidity) * ONE; + q96 b_q = num / ln_n; // trunc + return b_q.convert_to(); + } + + namespace detail { + q96 mul_q(const q96& a, const q96& b) { return mul_q_impl(a, b); } + q96 div_q(const q96& a, const q96& b) { return div_q_impl(a, b); } + q96 exp_q(const q96& x) { return graphene::chain::lmsr::exp_q(x); } + q96 ln_q(const q96& x) { return graphene::chain::lmsr::ln_q(x); } + } + +} } } // graphene::chain::lmsr diff --git a/libraries/chain/pm/parimutuel.cpp b/libraries/chain/pm/parimutuel.cpp new file mode 100644 index 0000000000..ebb203dd18 --- /dev/null +++ b/libraries/chain/pm/parimutuel.cpp @@ -0,0 +1,99 @@ +#include + +#include + +namespace graphene { namespace chain { namespace pm { + + settle_result compute_settlement(const settle_params& p, + const std::vector& winners) { + settle_result r; + + // Fees are bp OF losers_sum (10000 = 100%); oracle fixed fee is taken from the + // remainder (capped) so it is never minted. + int64_t oracle_fee = p.losers_sum * (int64_t)p.oracle_fee_percent / 10000; + int64_t creator_fee = p.losers_sum * (int64_t)p.creator_fee_percent / 10000; + int64_t liq_fee = p.losers_sum * (int64_t)p.liquidity_fee_percent / 10000; + int64_t avail = p.losers_sum - oracle_fee - creator_fee - liq_fee; + if (avail < 0) avail = 0; + int64_t fixed_paid = (p.oracle_fixed_fee < avail) ? p.oracle_fixed_fee : avail; + // forfeit_pool is a SIGNED accumulator: leverage positions that close in profit route + // a negative residual (cv > total_bet — the surplus was paid to the winner out of the + // pool, so it must reduce what parimutuel winners can share). If leverage wins exceed + // the losers' pool the sum goes negative; floor at 0 so winners simply get their + // principal back (line 44) and the negative NEVER reaches the uint64 casts below — an + // unclamped negative would wrap to ~1.8e19 and mint. (B3: PR #124 review.) + int64_t winners_pool = avail - fixed_paid + p.forfeit_pool; + if (winners_pool < 0) { + // F1: the floor stops the wrap but the shortfall (=|winners_pool|) is real — winners get + // their principal back out of tokens that the losers'+forfeit pot did not fund. Hand it to + // the caller as `uncovered` so it is charged to LP principal (the leverage counterparty), + // instead of being silently emitted. (PR #124 review, finding 1.) + r.uncovered = -winners_pool; + winners_pool = 0; + } + + r.oracle_take = oracle_fee + fixed_paid; + r.creator_take = creator_fee; + r.lp_bonus = liq_fee; + + fc::uint128_t total_weight = 0; + for (const auto& w : winners) + total_weight += fc::uint128_t((uint64_t)w.weight); + + r.winner_payout.reserve(winners.size()); + + if (total_weight == 0) { + // No winning tokens: the whole pool is undistributed → LP. + r.lp_bonus += winners_pool; + return r; + } + + int64_t distributed = 0; + for (const auto& w : winners) { + int64_t profit = (int64_t)(fc::uint128_t((uint64_t)winners_pool) + * fc::uint128_t((uint64_t)w.weight) / total_weight).lo; + int64_t penalty = (int64_t)(fc::uint128_t((uint64_t)profit) + * fc::uint128_t((uint64_t)w.time_penalty) + / fc::uint128_t((uint64_t)1000000)).lo; + // F3 defensive clamp: validate() bounds pm_max_time_penalty <= 1e6 so penalty <= profit + // already, but never let a (mis-configured / legacy) median push penalty past profit — + // that would make payout < principal and settle_market silently drop the winner's stake. + if (penalty > profit) penalty = profit; + r.winner_payout.push_back(w.amount + profit - penalty); + distributed += profit; + r.lp_bonus += penalty; + } + // Rounding dust from the weight split → LP (keeps Σ exact). + int64_t dust = winners_pool - distributed; + if (dust > 0) r.lp_bonus += dust; + + return r; + } + + std::vector distribute_lp(const std::vector& lps, int64_t bonus) { + std::vector out(lps.size(), 0); + if (lps.empty() || bonus <= 0) return out; + + std::vector w(lps.size()); + fc::uint128_t total = 0; + for (size_t i = 0; i < lps.size(); ++i) { + int64_t sec = lps[i].seconds_active < 0 ? 0 : lps[i].seconds_active; + // Minute granularity keeps principal×time within 128 bits; the +1 floor + // makes equal-time LPs reduce to a plain principal-pro-rata split. + w[i] = fc::uint128_t((uint64_t)lps[i].principal) + * fc::uint128_t((uint64_t)(sec / 60 + 1)); + total += w[i]; + } + if (total == 0) return out; // all-zero principal + + int64_t left = bonus; + for (size_t i = 0; i < lps.size(); ++i) { + if (i + 1 == lps.size()) { out[i] = left; break; } // last absorbs remainder + int64_t s = (int64_t)(fc::uint128_t((uint64_t)bonus) * w[i] / total).lo; + out[i] = s; + left -= s; + } + return out; + } + +}}} // graphene::chain::pm diff --git a/libraries/chain/pm_evaluator.cpp b/libraries/chain/pm_evaluator.cpp new file mode 100644 index 0000000000..2dcb4d9d73 --- /dev/null +++ b/libraries/chain/pm_evaluator.cpp @@ -0,0 +1,3490 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace graphene { namespace chain { + +using namespace graphene::protocol; + +// ─── Internal helpers ──────────────────────────────────────────────────────── + +namespace { + + const chain_properties_pm& median(const database& db) { + return db.get_validator_schedule_object().median_props; + } + + // ── B9 (spec §8): late-bet anti-sniping penalty ────────────────────────────── + // Returns the 1e6-scaled penalty applied ONLY to a winning bet's PROFIT at settlement + // (never principal — see compute_settlement). It is 0 outside the penalty window, when the + // market configured none (time_penalty_value == 0), or for open-ended markets (no deadline). + // `risk_time` is when the market exposure was actually taken — bet placement for instant bets, + // the COMMIT time for commit-reveal (blind, not the later reveal), the leverage OPEN time for a + // converted position — so honest early risk-takers are never penalised for later mechanics. + // Integer-only (consensus); the field was defined + read but never assigned before this fix. + uint32_t compute_time_penalty(const pm_market_object& mkt, time_point_sec risk_time, + uint32_t max_time_penalty) { + if (mkt.time_penalty_value == 0) return 0; + if (mkt.betting_expiration == time_point_sec()) return 0; // open-ended: no window + const int64_t be = (int64_t)mkt.betting_expiration.sec_since_epoch(); + const int64_t ct = (int64_t)mkt.created_time.sec_since_epoch(); + int64_t window; + if (mkt.time_penalty_type == 0) { + window = (int64_t)mkt.time_penalty_value; // fixed seconds before expiry + } else { + const int64_t duration = be - ct; // percentage of market lifetime + if (duration <= 0) return 0; + window = (int64_t)(fc::uint128_t((uint64_t)mkt.time_penalty_value) + * fc::uint128_t((uint64_t)duration) / fc::uint128_t(100u)).lo; + } + if (window <= 0) return 0; + int64_t tte = be - (int64_t)risk_time.sec_since_epoch(); // time left when risk was taken + if (tte < 0) tte = 0; + if (tte >= window) return 0; // placed before the window opened + const int64_t into = window - tte; // deeper into window = later = harsher + fc::uint128_t num, den; + if (mkt.penalty_curve_type == 1) { // quadratic + num = fc::uint128_t((uint64_t)into) * fc::uint128_t((uint64_t)into); + den = fc::uint128_t((uint64_t)window) * fc::uint128_t((uint64_t)window); + } else { // linear + num = fc::uint128_t((uint64_t)into); + den = fc::uint128_t((uint64_t)window); + } + int64_t penalty = (int64_t)(fc::uint128_t((uint64_t)max_time_penalty) * num / den).lo; + if (penalty < 0) penalty = 0; + if (penalty > (int64_t)max_time_penalty) penalty = max_time_penalty; + return (uint32_t)penalty; + } + + // ── Per-oracle live active-market counter (display-only, pm_oracle_object.active_markets) ── + // O(1) upkeep so get_oracle/watchdogs read the count without paging list_markets. NEVER gates + // consensus. A market is "active" == status 1; inc when it enters (create-active / accept), + // dec when it leaves (resolve / no_contest / missed-void). Both no-op when the oracle has no + // pm_oracle_object (self-oracle accounts that never registered) — such markets aren't counted + // anywhere, and are correctly excluded by the seed/verify walks too. + void pm_oracle_inc_active(database& db, const account_name_type& oracle) { + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(oracle); + if (it != oidx.end()) + db.modify(*it, [](pm_oracle_object& o) { o.active_markets++; }); + } + // Guarded: decrements only if the market is still active(1), so it is safe (idempotent) to call + // at any terminal transition even if the market was already non-active. + void pm_oracle_dec_active(database& db, const pm_market_object& mkt) { + if (mkt.status != 1) return; + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(mkt.oracle); + if (it != oidx.end()) + db.modify(*it, [](pm_oracle_object& o) { if (o.active_markets > 0) o.active_markets--; }); + } + + // ── Per-oracle workload gauges (display-only) ───────────────────────────────── + // Generic ±1 on any uint32_t gauge of the oracle named `oracle`. Decrement clamps at 0 so a + // stray double-dec can never wrap the counter. No-op when the oracle has no pm_oracle_object. + // NEVER gates consensus (cosmetic aggregate; a drift is caught by pm_verify_oracle_gauges). + void pm_oracle_gauge_adj(database& db, const account_name_type& oracle, + uint32_t pm_oracle_object::* field, int delta) { + if (delta == 0) return; + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(oracle); + if (it == oidx.end()) return; + db.modify(*it, [&](pm_oracle_object& o) { + if (delta > 0) (o.*field) += (uint32_t)delta; + else { uint32_t d = (uint32_t)(-delta); (o.*field) = (o.*field) > d ? (o.*field) - d : 0u; } + }); + } + // A dispute is leaving the open(0) state (responded/finalized/auto-closed): decrement whichever + // open-dispute gauge it currently sits in, keyed on whether the oracle had responded. Call while + // the dispute is still status 0 (oracle_response_time is stable across the transition either way). + void pm_oracle_dispute_left_open(database& db, const account_name_type& oracle, + const pm_dispute_object& d) { + pm_oracle_gauge_adj(db, oracle, + (d.oracle_response_time == time_point_sec()) + ? &pm_oracle_object::disputes_awaiting_response + : &pm_oracle_object::disputes_awaiting_decision, + -1); + } + + // Resolution-latency histogram bucket for rt seconds — 8 buckets (≤1h,≤6h,≤24h,≤3d,≤7d,≤14d,≤30d,>30d). + inline int pm_rt_bucket(uint64_t rt) { + static const uint64_t ub[7] = {3600u, 21600u, 86400u, 259200u, 604800u, 1209600u, 2592000u}; + for (int i = 0; i < 7; ++i) if (rt <= ub[i]) return i; + return 7; + } + + // Pay queued lazy-pool withdrawals FIFO (oldest id first) from whatever is liquid in + // free_balance right now. Each request is paid in full or in part; when free is exhausted we + // stop. Called at every point capital returns to free_balance (LP return, deposit, leverage + // repay) so queued withdrawers have first claim on returning capital and free_balance never + // goes negative — the ledger never hands out more than the pool holds liquid. + void service_lazy_withdraw_queue(database& db) { + const auto* poolp = db.find(pm_lazy_pool_id_type(0)); + if (!poolp) return; + for (;;) { + const auto& pool = db.get(pm_lazy_pool_id_type(0)); + if (pool.free_balance.value <= 0) break; + const auto& qidx = db.get_index().indices().get(); + if (qidx.empty()) break; + const auto& req = *qidx.begin(); // FIFO: lowest id == oldest + const int64_t pay = std::min(pool.free_balance.value, req.amount.value); + if (pay <= 0) break; + db.adjust_balance(db.get_account(req.account), asset(share_type(pay), TOKEN_SYMBOL)); + db.modify(pool, [&](pm_lazy_pool_object& p) { + p.free_balance -= share_type(pay); + p.pending_withdrawals -= share_type(pay); + }); + if (req.amount.value == pay) { + db.remove(req); // fully settled + } else { + db.modify(req, [&](pm_lazy_withdraw_request_object& r) { r.amount -= share_type(pay); }); + break; // free_balance exhausted + } + } + } + + // Return a lazy-pool LP position's capital to the pool: principal to free_balance, + // yield (its share of the LP bonus) into the MasterChef accumulator (reward_per_share + // ×1e9) so depositors can claim it. Mirrors the lazy deposit/withdraw accounting. + void route_pool_lp_return(database& db, int64_t principal, int64_t yield) { + const auto* pool = db.find(pm_lazy_pool_id_type(0)); + if (!pool) return; + db.modify(*pool, [&](pm_lazy_pool_object& p) { + // Principal leaves allocation; principal AND yield both land in free_balance so the + // yield is spendable on withdrawal (spec lazy-pool Transition 4: free += return). + p.free_balance += share_type(principal + yield); + p.allocated_balance -= share_type(principal); + if (yield > 0) { + p.earned_balance += share_type(yield); + if (p.total_shares.value > 0) + p.reward_per_share += fc::uint128_t((uint64_t)yield) + * fc::uint128_t((uint64_t)1000000000) + / fc::uint128_t((uint64_t)p.total_shares.value); + } + }); + service_lazy_withdraw_queue(db); // returning capital first pays queued withdrawers + } + + // Close the market's lazy allocation (recall-tracking object) once its LP position + // has been settled/returned, so the recall cron skips it. + void mark_alloc_settled(database& db, pm_market_id_type market) { + const auto& aidx = db.get_index().indices().get(); + auto it = aidx.find(market); + if (it != aidx.end() && it->status == 0) + db.modify(*it, [&](pm_lazy_allocation_object& a) { + a.returned_amount += a.amount; a.amount = 0; a.status = 1; + }); + } + + // ── Leverage funding (spec §7 — perpetual carry cost) ──────────────────────── + // Charge any whole 24h funding periods that have come due on an ACTIVE position. + // funding_per_period = loan × pm_leverage_funding_rate_ppm_per_day / 1e6, accrued into + // pos.funding_paid, which raises the effective pool obligation (liquidation_threshold + + // funding_paid) and therefore pulls the liquidation point up over time. Funding is realized + // out of the bettor's own equity (the collateral leg of the position value) at settlement. + // Idempotent: after charging, funding_due_time is advanced strictly past `now`. One-shot + // catch-up (multiplies by the number of whole periods due) so a cap-throttled sweep can never + // under-charge. rate 0 → no-op except advancing the clock (no retroactive charge on re-enable). + void accrue_leverage_funding(database& db, const pm_leverage_position_object& pos, + uint32_t rate_ppm_per_day, time_point_sec now) { + if (pos.status != 0) return; + if (pos.funding_due_time == time_point_sec()) return; // not initialized (pre-funding position) + if (pos.funding_due_time > now) return; + uint32_t due = ((now.sec_since_epoch() - pos.funding_due_time.sec_since_epoch()) / 86400u) + 1u; + int64_t add = 0; + if (rate_ppm_per_day > 0) + add = (int64_t)(fc::uint128_t((uint64_t)pos.loan.value) + * fc::uint128_t((uint64_t)rate_ppm_per_day) + * fc::uint128_t((uint64_t)due) + / fc::uint128_t((uint64_t)1000000u)).lo; + db.modify(pos, [&](pm_leverage_position_object& p) { + p.funding_paid += share_type(add); + p.funding_due_time = time_point_sec(p.funding_due_time.sec_since_epoch() + 86400u * due); + p.last_update = now; + }); + } + + // ── Leverage liquidation (spec §5/§6) ──────────────────────────────────────── + // Liquidate one position at CURRENT reserves: sell its tokens back, the pool recovers + // min(cancel_value, obligation), the bettor gets any remainder. Zero-sum: the C+L that + // entered the curve at open returns as cancel_value (split pool/bettor); the price-impact + // difference accrues to the rest of the market. reason: 0 opposing-bet, 1 cancel-bet, 2 expiry, + // 3 funding (position pulled underwater by accrued carry cost), 4 terminal void/no-contest + // (refund the residual immediately — no outcome to defer a claim to). See F1/#300. + void liquidate_position(database& db, const pm_leverage_position_object& pos, uint8_t reason) { + // Bring funding current before settling so the obligation reflects carry owed to now. + accrue_leverage_funding(db, pos, + db.get_validator_schedule_object().median_props.pm_leverage_funding_rate_ppm_per_day, + db.head_block_time()); + const auto& mkt = db.get(pos.market); + int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + pos.tokens.value, (int)pos.outcome_index); + // Effective obligation = base pool markup (loan×(1+R%)) + accrued funding. The funding + // portion is captured by pool_profit = pool_received − loan and thus flows to LP yield. + int64_t obligation = pos.liquidation_threshold.value + pos.funding_paid.value; + int64_t pool_received = cv < obligation ? cv : obligation; + int64_t bettor_received = cv - pool_received; // ≥ 0 + int64_t pool_profit = pool_received - pos.loan.value; + + // F1/#300 deferred-claim conservation. (C+L)=total_bet entered the curve at open. The pool + // recovers its obligation from cv; everything the pool does NOT take stays in the market pot + // (forfeit_pool). The bettor's residual (cv − pool_received) is NOT paid against the curve now — + // it becomes an OUTCOME-CONTINGENT deferred claim paid at settlement from a bounded slice of the + // losing pool (or nothing if its outcome loses). So forfeit gets `total_bet − pool_received` + // (≥ 0 for normal closes → no negative-forfeit / uncovered mint, no LP hit). Model validated: + // early-exit-deferred-claim.md. EXCEPTION reason 4 = terminal void/no-contest: there is no + // outcome, so the bettor is REFUNDED immediately (old path) and forfeit gets total_bet − cv. + const bool voiding = (reason == 4); + // Clamp pot_retained at 0: accrued funding can push obligation above total_bet for a + // long-lived position (funding_paid grows unbounded in accrue_leverage_funding), which + // would drive forfeit_pool negative and manufacture an `uncovered` shortfall (LP hit / + // mint) at settlement — defeating the F1/#300 no-uncovered guarantee. The pool still + // recovers its obligation from cv (reserves); the pot simply never goes negative. + const int64_t pot_retained = voiding ? (pos.total_bet.value - cv) + : (pos.total_bet.value - pool_received); + const int64_t pot_retained_capped = pot_retained > 0 ? pot_retained : 0; + db.modify(mkt, [&](pm_market_object& m) { // unwind tokens (k preserved) + if (pos.outcome_index == 0) { + int64_t new_rb = m.reserve_b.value + pos.tokens.value; + m.reserve_b = share_type(new_rb); + m.reserve_a = share_type((int64_t)(m.k / fc::uint128_t((uint64_t)new_rb)).lo); + } else { + int64_t new_ra = m.reserve_a.value + pos.tokens.value; + m.reserve_a = share_type(new_ra); + m.reserve_b = share_type((int64_t)(m.k / fc::uint128_t((uint64_t)new_ra)).lo); + } + m.forfeit_pool += share_type(pot_retained_capped); + }); + db.modify(db.get(pm_lazy_pool_id_type(0)), [&](pm_lazy_pool_object& p) { + p.free_balance += share_type(pool_received); + p.leverage_fund_used -= pos.loan; + if (pool_profit > 0) { + p.earned_balance += share_type(pool_profit); + if (p.total_shares.value > 0) + p.reward_per_share += fc::uint128_t((uint64_t)pool_profit) + * fc::uint128_t((uint64_t)1000000000) / fc::uint128_t((uint64_t)p.total_shares.value); + } + }); + service_lazy_withdraw_queue(db); // returning leverage capital first pays queued withdrawers + if (bettor_received > 0) { + if (voiding) { + // Terminal void/no-contest: refund the residual immediately (no outcome to defer to). + db.adjust_balance(db.get_account(pos.account), asset(share_type(bettor_received), TOKEN_SYMBOL)); + } else if (mkt.deferred_claim_count < MAX_PM_DEFERRED_CLAIMS_PER_MARKET) { + // Defer as an outcome-contingent claim (F1/#300) — paid at settlement from the bounded + // early-exit bucket iff pos.outcome_index wins; otherwise it pays nothing. #349: skip + // once the per-market cap is hit (the residual stays in the curve and pays 0, exactly + // like bucket-exhaustion) so settle_market's claim loop stays bounded. + db.create([&](pm_deferred_claim_object& c) { + c.market = pos.market; c.account = pos.account; c.kind = 1; + c.outcome_index = pos.outcome_index; c.claim_amount = share_type(bettor_received); + c.exit_time = db.head_block_time(); + }); + db.modify(mkt, [](pm_market_object& m) { m.deferred_claim_count++; }); + } + } + + // reason 2 = resolving, 4 = voiding → SETTLEMENT (pm_leverage_resolve); 0/1/3 = mid-market + // liquidation (pm_leverage_liquidate). `won` = the position was solvent. + const bool settle = (reason == 2 || reason == 4); + const bool won = (cv >= obligation); + db.modify(pos, [&](pm_leverage_position_object& p) { + p.status = settle ? (won ? (uint8_t)2 : (uint8_t)3) : (uint8_t)1; + p.cancel_value_at_liquidation = share_type(cv); + p.pool_received = share_type(pool_received); + p.bettor_received = share_type(bettor_received); + p.last_update = db.head_block_time(); + }); + db.pm_adjust_frozen(pos.account, 2, -pos.collateral); // UNLOCK: collateral leaves active leverage (liquidate/settle) + if (settle) { + uint16_t lev = pos.collateral.value > 0 + ? (uint16_t)(pos.total_bet.value / pos.collateral.value) : (uint16_t)0; + db.push_virtual_operation(pm_leverage_resolve_operation( + pos.account, pos.id._id, pos.market._id, won, + asset(share_type(pool_received), TOKEN_SYMBOL), + asset(share_type(bettor_received), TOKEN_SYMBOL), + pos.outcome_index, lev)); + } else { + db.push_virtual_operation(pm_leverage_liquidate_operation( + pos.account, pos.id._id, pos.market._id, + asset(share_type(cv), TOKEN_SYMBOL), asset(share_type(pool_received), TOKEN_SYMBOL), + asset(share_type(bettor_received), TOKEN_SYMBOL), reason)); + } + } + + // Cascade: liquidate EVERY active position on `market` (restricted to `side`, -1 = any) + // whose cancel_value ≤ threshold, re-evaluating after each liquidation (reserves change). + // No iteration cap — the pool must never be left exposed by a partial cascade. Termination + // is guaranteed: each liquidation flips a position to status 1 (excluded from the scan), + // so the loop runs at most once per position. One-directional (only same-side worsen). + void cascade_liquidate(database& db, pm_market_id_type market, int16_t side, uint8_t reason) { + for (;;) { + const auto& mkt = db.get(market); + const auto& idx = db.get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple(market, (uint8_t)0, pm_leverage_position_id_type())); + const pm_leverage_position_object* victim = nullptr; + for (; it != idx.end() && it->market == market && it->status == 0; ++it) { + if (side >= 0 && it->outcome_index != side) continue; + int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + it->tokens.value, (int)it->outcome_index); + if (cv <= it->liquidation_threshold.value + it->funding_paid.value) { victim = &*it; break; } + } + if (!victim) break; + liquidate_position(db, *victim, reason); + } + } + + // Force-close ALL active positions on a market (terminal: market resolving/voided). + // Unbounded by design — every position must be closed; terminates because each + // liquidation flips status 0→1 (excluded from the next lower_bound). + // reason 2 = normal resolution (defer residuals as claims); reason 4 = terminal void/no-contest + // (refund residuals immediately, no outcome to defer to). See liquidate_position. + void force_close_positions(database& db, pm_market_id_type market, uint8_t reason = 2) { + for (;;) { + const auto& idx = db.get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple(market, (uint8_t)0, pm_leverage_position_id_type())); + if (it == idx.end() || it->market != market || it->status != 0) break; + liquidate_position(db, *it, reason); + } + } + + // Delete every deferred early-exit claim of a market WITHOUT paying (terminal void/no-contest: + // no winning outcome, so outcome-contingent claims are worthless). F1/#300. + void purge_deferred_claims(database& db, pm_market_id_type market) { + const auto& cidx = db.get_index().indices().get(); + std::vector consumed; + for (auto it = cidx.lower_bound(boost::make_tuple(market, pm_deferred_claim_id_type())); + it != cidx.end() && it->market == market; ++it) consumed.push_back(&*it); + for (const auto* c : consumed) db.remove(*c); + } + + // Graduated recall: withdraw `amount` of the lazy pool's LP position from an idle + // market and return it to free_balance. Mirrors pm_withdraw_liquidity — shrink + // liquidity_sum and shrink the pricing curve price-neutrally (LMSR b for multi, + // both CPMM reserves proportionally for binary) so depth tracks the remaining + // capital while the odds stay put. Returns the amount actually recalled. + share_type recall_pool_liquidity(database& db, const pm_market_object& mkt, share_type amount) { + if (amount.value <= 0) return share_type(0); + const auto& lidx = db.get_index().indices().get(); + const pm_liquidity_object* poollp = nullptr; + for (auto it = lidx.lower_bound(boost::make_tuple(mkt.id, pm_liquidity_id_type())); + it != lidx.end() && it->market == mkt.id; ++it) { + if (it->status == 0 && it->provider.size() == 0) { poollp = &*it; break; } + } + if (!poollp) return share_type(0); + + share_type w = amount; + if (w.value > poollp->amount.value) w = poollp->amount; + share_type b_remove = 0; + if (mkt.market_type == 1 && poollp->b_share.value > 0 && poollp->amount.value > 0) { + b_remove = (w == poollp->amount) ? poollp->b_share : + share_type((int64_t)(fc::uint128_t((uint64_t)poollp->b_share.value) + * fc::uint128_t((uint64_t)w.value) / fc::uint128_t((uint64_t)poollp->amount.value)).lo); + } + db.modify(mkt, [&](pm_market_object& m) { + const int64_t L = m.liquidity_sum.value; // capital BEFORE this recall + m.liquidity_sum -= w; + if (m.market_type == 1) { + m.lmsr_b -= b_remove; + } else if (L > 0) { + // CPMM: price-neutral shrink, mirroring pm_withdraw_liquidity so the + // reserve ratio (odds) is preserved and depth tracks liquidity_sum. + const fc::uint128_t num((uint64_t)(L - w.value)); + const fc::uint128_t den((uint64_t)L); + m.reserve_a = share_type((int64_t)(fc::uint128_t((uint64_t)m.reserve_a.value) * num / den).lo); + m.reserve_b = share_type((int64_t)(fc::uint128_t((uint64_t)m.reserve_b.value) * num / den).lo); + m.k = fc::uint128_t((uint64_t)m.reserve_a.value) * fc::uint128_t((uint64_t)m.reserve_b.value); + } + }); + if (w == poollp->amount) + db.modify(*poollp, [](pm_liquidity_object& l) { l.status = 3; }); + else + db.modify(*poollp, [&](pm_liquidity_object& l) { l.amount -= w; l.b_share -= b_remove; }); + route_pool_lp_return(db, w.value, 0); // principal back to pool, no yield + return w; + } + + // ── Liquidity settlement ──────────────────────────────────────────────────── + // Returns every active LP's principal UNCONDITIONALLY and distributes `bonus` + // (liquidity fee + time-penalty pool + undistributed winners' pool) weighted by + // principal × time-in-market (pure, unit-tested distribute_lp; last entry absorbs + // rounding). The lazy pool is a real LP (pm_liquidity_object with empty provider); + // its principal + yield route back into the pool instead of to an account. + // `uncovered` (F1): the parimutuel shortfall compute_settlement could not fund from the + // losers'+forfeit pot (see settle_result::uncovered). LPs are the leverage counterparty, so the + // shortfall is charged against LP principal pro-rata here — otherwise flooring winners_pool at 0 + // would silently emit exactly `uncovered` tokens. Default 0 for void/refund callers. + void settle_liquidity(database& db, const pm_market_object& mkt, share_type bonus, + share_type uncovered = share_type(0)) { + const auto& lidx = db.get_index().indices().get(); + auto first = lidx.lower_bound(boost::make_tuple(mkt.id, pm_liquidity_id_type())); + + const fc::time_point_sec now = db.head_block_time(); + + // #348 / goal #290: F1 guarantees `uncovered == 0` by construction (winners_pool ≥ (1−cap)·losers + // − fees ≥ 0). If it is ever > 0, some invariant broke (e.g. a new unbounded accrual like the + // #141 funding path) and LP principal is being silently eroded (or tokens emitted when no LP). + // Emit ONE always-on loud signal here — regardless of LP presence — so a regression is visible in + // node logs / acceptance instead of slipping through silently the way #141 did. Log-only: + // deterministic, no consensus effect, and does NOT halt (a hard assert would take the chain down + // on an unforeseen edge, worse than charging LP). + if (uncovered.value > 0) + elog("PM F1 INVARIANT VIOLATED: uncovered shortfall ${u} on market ${m} — winners_pool<0 was " + "floored, shortfall charged to LP principal (or emitted if no LP). This must be 0 by " + "construction; investigate the exit/settle path.", ("u", uncovered.value)("m", mkt.id._id)); + + std::vector active; + std::vector lps; + for (auto it = first; it != lidx.end() && it->market == mkt.id; ++it) { + if (it->status != 0) continue; + active.push_back(&*it); + int64_t sec = (int64_t)now.sec_since_epoch() - (int64_t)it->deposit_time.sec_since_epoch(); + lps.push_back(pm::lp_in{ it->amount.value, sec }); + } + if (active.empty()) { + // No LP principal to absorb the F1 shortfall → 100% of it is emitted. This is the worst + // case and must be logged here, above the early return, since the diagnostic in the charge + // block below is unreachable when there are no LPs. (PR #124 review.) + if (uncovered.value > 0) + wlog("PM settle: no active LP to absorb uncovered ${u} on market ${m} — fully emitted", + ("u", uncovered.value)("m", mkt.id._id)); + return; + } + + const std::vector shares = pm::distribute_lp(lps, bonus.value); + + // F1: split `uncovered` across LP principal pro-rata (by principal, not time — it's a capital + // loss, not a fee). Each floor charge is <= its LP's principal (to_charge <= total_principal); + // the rounding remainder is then spread ONLY over LPs that still have headroom, so no single + // LP is ever charged above its principal (a naive "remainder → last LP" could exceed a small + // last LP's principal, clamp to 0, and re-emit the difference — PR #124 review). + std::vector charge(active.size(), 0); + if (uncovered.value > 0) { + int64_t total_principal = 0; + for (auto* lp : active) total_principal += lp->amount.value; + if (total_principal > 0) { + int64_t to_charge = uncovered.value < total_principal ? uncovered.value : total_principal; + int64_t assigned = 0; + for (size_t i = 0; i < active.size(); ++i) { + int64_t c = (int64_t)(fc::uint128_t((uint64_t)to_charge) + * fc::uint128_t((uint64_t)active[i]->amount.value) + / fc::uint128_t((uint64_t)total_principal)).lo; + charge[i] = c; assigned += c; + } + // Distribute the floor remainder over LPs with headroom (charge < principal). Total + // headroom (total_principal - assigned) >= (to_charge - assigned), so it always fits. + int64_t remainder = to_charge - assigned; + for (size_t i = 0; i < active.size() && remainder > 0; ++i) { + int64_t room = active[i]->amount.value - charge[i]; + int64_t add = remainder < room ? remainder : room; + charge[i] += add; remainder -= add; + } + // uncovered beyond the whole pool cannot be charged to anyone — it is a real (bounded) + // over-emission the strict supply invariant (#126) would only catch on a later import, + // so flag it here where it happens. pos_cap should make this unreachable. + if (uncovered.value > total_principal) + wlog("PM settle: uncovered ${u} exceeds LP principal ${p} on market ${m} — ${e} emitted", + ("u", uncovered.value)("p", total_principal)("m", mkt.id._id)("e", uncovered.value - total_principal)); + } else if (uncovered.value > 0) { + // active LPs exist but their principals sum to zero → nothing to charge, so 100% of + // uncovered is emitted. Same over-emission as the no-active-LP case above, one level + // deeper; log it here too so the supply-invariant trail is never silent. (PR #124 review.) + wlog("PM settle: active LPs hold zero principal — uncovered ${u} fully emitted on market ${m}", + ("u", uncovered.value)("m", mkt.id._id)); + } + } + + for (size_t i = 0; i < active.size(); ++i) { + const pm_liquidity_object& lp = *active[i]; + share_type share(shares[i]); + int64_t principal_ret = lp.amount.value - charge[i]; + if (principal_ret < 0) principal_ret = 0; // defensive; charge[i] ≤ amount by construction + if (lp.provider.size() > 0) { + share_type ret = share_type(principal_ret + share.value); + if (ret.value > 0) + db.adjust_balance(db.get_account(lp.provider), asset(ret, TOKEN_SYMBOL)); + db.pm_adjust_frozen(lp.provider, 0, -lp.amount); // UNLOCK: full committed principal releases + } else { + route_pool_lp_return(db, principal_ret, share.value); // lazy pool LP + } + db.modify(lp, [&](pm_liquidity_object& l) { l.earned_fee += share; l.status = 3; }); + } + + mark_alloc_settled(db, mkt.id); + } + + // ── Market settlement (unified parimutuel, spec parimutuel-settlement.md §2) ── + // Winners are paid by curve WEIGHT out of the losers' stakes: + // losers_sum = Σ amount of losing bets + // winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee − oracle_fixed + forfeit_pool + // payout_i = bet_amount_i + floor(winners_pool × weight_i / Σweight) − time_penalty(profit) + // LP principal is returned unconditionally; liq_fee + time-penalty pool + any + // undistributed winners_pool form the LP bonus. Money is conserved exactly: + // Σ outputs == Σ all bet amounts + LP principal + forfeit_pool. + // oracle_fixed_fee is funded FROM the pool (capped), never minted. + void settle_market(database& db, const pm_market_object& mkt) { + const bool binary = (mkt.market_type == 0); + const int16_t win = mkt.resolved_outcome; + + // Any leveraged positions still open at settlement are force-closed first (normally the + // expiration buffer prevents this; terminal safety net). On a normal resolution (win≥0) the + // residuals defer as outcome-contingent claims; on void/no-contest (win<0) they refund now. + force_close_positions(db, mkt.id, win < 0 ? (uint8_t)4 : (uint8_t)2); + + // Zero-volume resolution → fault stamp on the oracle (§4.10 lazy-pool defense: + // discourages spam markets that lock pool capital with no betting volume). + if (win >= 0 && mkt.bets_sum.value == 0) { + const auto& oidx = db.get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + if (oit != oidx.end()) + db.modify(*oit, [&](pm_oracle_object& o) { + o.penalty_stamps++; + o.last_penalty_stamp_time = db.head_block_time(); + }); + } + + const auto& bidx = db.get_index().indices().get(); + auto first = bidx.lower_bound(boost::make_tuple(mkt.id, pm_bet_id_type())); + + // Void / no-contest finalization: refund every active bet, return LP, and slash the + // no-contest penalty (pm_no_contest_penalty_percent of the dispute fee) from the oracle, + // distributed pro-rata to the refunded bettors as compensation (spec §3.9). Only the + // no-contest path reaches settle with win<0 (missed/auto-close refund in their own crons). + if (win < 0) { + const auto& mp = median(db); + std::vector> participants; + int64_t total_bets = 0; + for (auto it = first; it != bidx.end() && it->market == mkt.id; ++it) + if (it->status == 0) { participants.emplace_back(it->account, it->amount.value); total_bets += it->amount.value; } + + for (auto it = first; it != bidx.end() && it->market == mkt.id; ) { + const auto& bet = *it; ++it; + if (bet.status != 0) continue; + db.adjust_balance(db.get_account(bet.account), asset(bet.amount, TOKEN_SYMBOL)); + db.pm_adjust_frozen(bet.account, 1, -bet.amount); // UNLOCK: stake refunded on void + db.modify(bet, [](pm_bet_object& b) { b.status = 2; }); + } + settle_liquidity(db, mkt, 0); + + share_type penalty(0); + if (total_bets > 0) { + const auto& oidx = db.get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + if (oit != oidx.end()) { + int64_t want = (int64_t)(fc::uint128_t((uint64_t)mp.pm_dispute_fee.amount.value) + * fc::uint128_t((uint64_t)mp.pm_no_contest_penalty_percent) / fc::uint128_t(10000)).lo; + penalty = share_type(want > oit->insurance.value ? oit->insurance.value : want); + if (penalty.value > 0) + db.modify(*oit, [&](pm_oracle_object& o) { + o.insurance -= penalty; + o.total_insurance_slashed += penalty; + }); + } + } + if (penalty.value > 0) { + int64_t paid = 0; + for (size_t i = 0; i < participants.size(); ++i) { + int64_t share = (i + 1 == participants.size()) + ? penalty.value - paid + : (int64_t)(fc::uint128_t((uint64_t)penalty.value) + * fc::uint128_t((uint64_t)participants[i].second) / fc::uint128_t((uint64_t)total_bets)).lo; + if (share > 0) { + db.adjust_balance(db.get_account(participants[i].first), asset(share_type(share), TOKEN_SYMBOL)); + paid += share; + } + } + } + purge_deferred_claims(db, mkt.id); // F1/#300: no winning outcome → early-exit claims pay 0 + db.modify(mkt, [](pm_market_object& m) { m.forfeit_pool = 0; }); + return; + } + + auto is_winner = [&](const pm_bet_object& b) { + return binary ? (b.side == win) : (b.outcome_index == win); + }; + + // Pass 1: collect winners (curve weight = claim) and losing stakes, then + // delegate the money split to the pure, unit-tested parimutuel math. + pm::settle_params sp; + sp.oracle_fee_percent = mkt.oracle_fee_percent; + sp.creator_fee_percent = mkt.creator_fee_percent; + sp.liquidity_fee_percent = mkt.liquidity_fee_percent; + sp.forfeit_pool = mkt.forfeit_pool.value; + sp.oracle_fixed_fee = mkt.oracle_fixed_fee.value; + + std::vector winners; + std::vector winner_bets; + std::vector loser_bets; + int64_t losers_sum = 0; + for (auto it = first; it != bidx.end() && it->market == mkt.id; ++it) { + if (it->status != 0) continue; + if (is_winner(*it)) { + winners.push_back(pm::winner_in{it->amount.value, it->weight.value, it->time_penalty}); + winner_bets.push_back(&*it); + } else { + losers_sum += it->amount.value; + loser_bets.push_back(&*it); + } + } + sp.losers_sum = losers_sum; + + // F1/#300: pay outcome-contingent deferred claims (early bet-cancels + leverage closes) BEFORE + // the parimutuel split, from a BOUNDED slice of the losing pool. Only claims on the winning + // outcome are funded, FIFO by exit order (== id via by_claim_market), capped so the total drawn + // from losers ≤ pm_early_exit_reward_cap_percent × losers_sum. The unfunded remainder is a + // haircut; the unused slice simply stays in the pot (we subtract only what is PAID from + // winners_pool, so leftover flows to winners). Losing-outcome claims pay 0. Every claim for this + // market is then consumed. Conservation model: docs/prediction-markets/early-exit-deferred-claim.md. + // (Inert until the exit paths record claims; a market with no claims iterates nothing.) + int64_t paid_claims = 0; + { + const auto& mp = median(db); + int64_t bucket = (int64_t)(fc::uint128_t((uint64_t)losers_sum) + * fc::uint128_t(mp.pm_early_exit_reward_cap_percent) / fc::uint128_t(10000u)).lo; + // ADVERSARIAL FIX (own F1 review, goal #350 paper Theorem 2): the reward CAP alone does + // NOT bound solvency. Per-market fees are capped only by oracle+creator+liquidity ≤ 100% + // at creation (pm_operations.cpp:64) — NOT by any chain param, so validate() can't guard + // it — and a valid market can push fees near 100%. With the default early-exit cap (33%) + // that makes fees + bucket exceed losers_sum, and compute_settlement would floor + // winners_pool at 0 and charge the shortfall to LP principal (`uncovered`, F1) — reachable + // with VALID default params, not just extreme medians. Clamp the bucket to the settlement + // headroom (== winners_pool BEFORE claims) so paid_claims can never drive winners_pool + // negative → Theorem 2 holds unconditionally, no LP hit, no mint. This only ever REDUCES + // the bucket (min), so it is strictly more conservative than before. Fee math MUST mirror + // parimutuel.cpp:13-25 exactly (same int64 order/flooring) so headroom == the pot the + // split will actually see. + { + const int64_t oracle_fee = losers_sum * (int64_t)mkt.oracle_fee_percent / 10000; + const int64_t creator_fee = losers_sum * (int64_t)mkt.creator_fee_percent / 10000; + const int64_t liq_fee = losers_sum * (int64_t)mkt.liquidity_fee_percent / 10000; + int64_t avail = losers_sum - oracle_fee - creator_fee - liq_fee; + if (avail < 0) avail = 0; + const int64_t fixed_paid = (mkt.oracle_fixed_fee.value < avail) + ? mkt.oracle_fixed_fee.value : avail; + int64_t headroom = avail - fixed_paid + mkt.forfeit_pool.value; + if (headroom < 0) headroom = 0; + if (bucket > headroom) bucket = headroom; + } + const auto& cidx = db.get_index().indices().get(); + auto cit = cidx.lower_bound(boost::make_tuple(mkt.id, pm_deferred_claim_id_type())); + std::vector consumed; + for (; cit != cidx.end() && cit->market == mkt.id; ++cit) { + const pm_deferred_claim_object& c = *cit; + if ((int16_t)c.outcome_index == win) { + int64_t remaining = bucket - paid_claims; + int64_t pay = c.claim_amount.value < remaining ? c.claim_amount.value : remaining; + if (pay > 0) { + db.adjust_balance(db.get_account(c.account), asset(share_type(pay), TOKEN_SYMBOL)); + paid_claims += pay; + // Put the credit in the early-exiter's account history (adjust_balance alone + // leaves no trace); `claimed` vs `paid` exposes any bucket-exhaustion haircut. + db.push_virtual_operation(pm_early_exit_claim_paid_operation( + c.account, mkt.id._id, c.kind, c.outcome_index, + asset(c.claim_amount, TOKEN_SYMBOL), asset(share_type(pay), TOKEN_SYMBOL))); + } + } + consumed.push_back(&c); + } + for (const auto* c : consumed) db.remove(*c); + } + // Fold the paid claims into forfeit_pool so the parimutuel winners' pool drops by exactly what + // early-exiters were paid (never below (1−cap)·losers − fees ≥ 0 → no uncovered mint, no LP hit). + sp.forfeit_pool = mkt.forfeit_pool.value - paid_claims; + + const pm::settle_result res = pm::compute_settlement(sp, winners); + + if (res.oracle_take > 0) + db.adjust_balance(db.get_account(mkt.oracle), asset(share_type(res.oracle_take), TOKEN_SYMBOL)); + if (res.creator_take > 0) + db.adjust_balance(db.get_account(mkt.creator), asset(share_type(res.creator_take), TOKEN_SYMBOL)); + + // Per-bettor settlement record (winners + losers) so history parsers see each result. + for (const auto* lb : loser_bets) { + db.pm_adjust_frozen(lb->account, 1, -lb->amount); // UNLOCK: losing stake leaves the bet set at settle + db.modify(*lb, [](pm_bet_object& b) { b.status = 3; b.resolved_amount = 0; }); + db.push_virtual_operation(pm_payout_operation( + lb->account, mkt.id._id, lb->id._id, lb->side, lb->outcome_index, + asset(lb->amount, TOKEN_SYMBOL), asset(share_type(0), TOKEN_SYMBOL))); + } + + for (size_t i = 0; i < winner_bets.size(); ++i) { + share_type payout(res.winner_payout[i]); + if (payout.value > 0) + db.adjust_balance(db.get_account(winner_bets[i]->account), asset(payout, TOKEN_SYMBOL)); + db.pm_adjust_frozen(winner_bets[i]->account, 1, -winner_bets[i]->amount); // UNLOCK: original stake leaves the bet set (payout is winnings) + db.modify(*winner_bets[i], [&](pm_bet_object& b) { b.status = 3; b.resolved_amount = payout; }); + db.push_virtual_operation(pm_payout_operation( + winner_bets[i]->account, mkt.id._id, winner_bets[i]->id._id, + winner_bets[i]->side, winner_bets[i]->outcome_index, + asset(winner_bets[i]->amount, TOKEN_SYMBOL), asset(payout, TOKEN_SYMBOL))); + } + + settle_liquidity(db, mkt, share_type(res.lp_bonus), share_type(res.uncovered)); // F1: charge shortfall to LP principal + db.modify(mkt, [](pm_market_object& m) { m.forfeit_pool = 0; }); + } + + // Garbage-collect a fully-finalized market and its whole object cluster. A settled market + // (status 3 / payout_status 3) can no longer be acted on — no betting, dispute, resolve or + // payout is possible — it only lingers "for history". process_pm_markets() calls this a few + // days after closure. Deletion is driven purely by chain time, so every node prunes exactly + // the same markets at the same block → shared-memory state and snapshots stay identical + // network-wide. Nothing else holds an id-reference to a settled market, so no dangling refs. + void gc_market(database& db, const pm_market_object& mkt) { + const pm_market_id_type mid = mkt.id; + // Composite (market, …)-keyed indexes: drop the whole market range. + auto drop_range = [&](const auto& idx) { + for (auto it = idx.lower_bound(boost::make_tuple(mid)); + it != idx.end() && it->market == mid; ) { + const auto& obj = *it; ++it; db.remove(obj); + } + }; + // Single-key (unique per market) by_market indexes: at most one row. + auto drop_unique = [&](const auto& idx) { + auto it = idx.find(mid); + if (it != idx.end()) db.remove(*it); + }; + drop_range(db.get_index().indices().get()); + drop_range(db.get_index().indices().get()); + drop_range(db.get_index().indices().get()); + drop_range(db.get_index().indices().get()); + drop_range(db.get_index().indices().get()); + drop_range(db.get_index().indices().get()); + // Backstop: every terminal path that can hold deferred claims already clears them — + // settle_market consumes them, refund_all_bets purges them on void/no-contest/missed- + // resolution, and accept-expired markets are pending (never had bets). This drop is a + // defensive net so no overlooked or future finalize path can leave a claim row dangling + // in shared memory / drift the snapshot. Money-neutral: the range is normally empty here. + drop_range(db.get_index().indices().get()); + drop_unique(db.get_index().indices().get()); + drop_unique(db.get_index().indices().get()); + db.remove(mkt); + } + + void refund_all_bets(database& db, const pm_market_object& mkt) { + const auto& bidx = db.get_index().indices().get(); + auto it = bidx.lower_bound(boost::make_tuple(mkt.id, pm_bet_id_type())); + while (it != bidx.end() && it->market == mkt.id) { + if (it->status == 0) { + db.adjust_balance(db.get_account(it->account), asset(it->amount, TOKEN_SYMBOL)); + db.pm_adjust_frozen(it->account, 1, -it->amount); // UNLOCK: stake refunded (missed-resolution/auto-close) + db.modify(*it, [](pm_bet_object& b) { b.status = 2; }); + } + ++it; + } + } + + void return_liquidity(database& db, const pm_market_object& mkt) { + // Void/refund terminal: force-close open leveraged positions (reason 4 = refund residuals now, + // no outcome to defer to) and discard any deferred early-exit claims (no winning outcome). + force_close_positions(db, mkt.id, 4); + purge_deferred_claims(db, mkt.id); + const auto& lidx = db.get_index().indices().get(); + auto it = lidx.lower_bound(boost::make_tuple(mkt.id, pm_liquidity_id_type())); + while (it != lidx.end() && it->market == mkt.id) { + if (it->status == 0) { + if (it->provider.size() > 0) { + db.adjust_balance(db.get_account(it->provider), asset(it->amount, TOKEN_SYMBOL)); + db.pm_adjust_frozen(it->provider, 0, -it->amount); // UNLOCK: LP principal on void/refund + } else + route_pool_lp_return(db, it->amount.value, 0); // lazy pool LP, no yield on refund + db.modify(*it, [](pm_liquidity_object& l) { l.status = 3; }); + } + ++it; + } + // Void/refund paths (no_contest, missed, auto-close) bypass settle_liquidity, so + // close the lazy allocation's recall-tracking object here. + mark_alloc_settled(db, mkt.id); + } + + // Allocate a slice of idle lazy-pool capital to a newly-active market as a REAL LP + // position (enters CPMM reserves / LMSR b, provider = pool) — spec lazy-pool-properties + // Transition 2. Triggered at activation (status 0→1, before bets). One allocation per + // market, bounded by pm_lazy_alloc_percent of free_balance and pm_lazy_max_total_alloc_percent + // of total pool capital, minus the §4.10 per-oracle exposure penalties. + void maybe_allocate_lazy(database& db, const pm_market_object& mkt) { + const auto& mp = median(db); + if (!mp.pm_lazy_pool_enabled) return; + // Reward floor: the pool only subsidizes markets whose LP fee pays it enough. Below the + // governed minimum it stays out entirely (spec lazy-pool §min-fee gate). + if (mkt.liquidity_fee_percent < mp.pm_lazy_min_liquidity_fee_percent) return; + const auto* pool = db.find(pm_lazy_pool_id_type(0)); + if (!pool || pool->free_balance.value <= 0) return; + + const auto& aidx = db.get_index().indices().get(); + if (aidx.find(mkt.id) != aidx.end()) return; // already allocated + + int64_t total_capital = pool->free_balance.value + pool->allocated_balance.value; + int64_t max_total = (int64_t)(fc::uint128_t((uint64_t)total_capital) + * fc::uint128_t(mp.pm_lazy_max_total_alloc_percent) / fc::uint128_t(10000)).lo; + int64_t headroom = max_total - pool->allocated_balance.value; + if (headroom <= 0) return; + + int64_t alloc = (int64_t)(fc::uint128_t((uint64_t)pool->free_balance.value) + * fc::uint128_t(mp.pm_lazy_alloc_percent) / fc::uint128_t(10000)).lo; + if (alloc > pool->free_balance.value) alloc = pool->free_balance.value; + if (alloc > headroom) alloc = headroom; + if (alloc <= 0) return; + + const auto now = db.head_block_time(); + + // Per-oracle exposure penalties (security-threat-model §4.10) — limit how much + // pool capital a single oracle can attract into idle/abusive markets. + const auto& oidx = db.get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + { + // (a) Active-market penalty: 5% recursive reduction per OTHER active market + // of this oracle (bounded scan; cap 20 markets). + const auto& midx = db.get_index().indices().get(); + uint32_t active_cnt = 0, scanned = 0; + for (auto mit = midx.lower_bound(mkt.oracle); + mit != midx.end() && mit->oracle == mkt.oracle && scanned < 128 && active_cnt < 20; + ++mit, ++scanned) { + if (mit->status == 1 && mit->id != mkt.id) ++active_cnt; + } + for (uint32_t i = 0; i < active_cnt; ++i) alloc = alloc * 95 / 100; + + // (b) Fault penalty stamps: zero-volume resolutions stamp the oracle; each + // non-expired stamp halves the allocation (cap 4). Stamps expire 10 days + // after the most recent one (coarse, deterministic decay). + if (oit != oidx.end() && oit->penalty_stamps > 0 && + now < oit->last_penalty_stamp_time + fc::seconds(864000)) { + uint32_t st = oit->penalty_stamps > 4 ? 4 : oit->penalty_stamps; + for (uint32_t i = 0; i < st; ++i) alloc /= 2; + } + } + if (alloc <= 0) return; + + // Deploy the allocation into the curve as a REAL LP position (provider = pool), + // exactly like pm_add_liquidity. This happens at activation (status 0→1, before + // any bets) — the genesis of the active curve, not a mid-life edit. + share_type b_share = 0; + if (mkt.market_type == 1 && mkt.liquidity_sum.value > 0) { + b_share = share_type((int64_t)(fc::uint128_t((uint64_t)mkt.lmsr_b.value) + * fc::uint128_t((uint64_t)alloc) / fc::uint128_t((uint64_t)mkt.liquidity_sum.value)).lo); + } + db.modify(mkt, [&](pm_market_object& m) { + m.liquidity_sum += share_type(alloc); + if (m.market_type == 1) { + m.lmsr_b += b_share; + m.lmsr_subsidy += share_type(alloc); + } else { + share_type half = share_type(alloc / 2); + m.reserve_a += half; + m.reserve_b += share_type(alloc - half.value); + m.k = fc::uint128_t((uint64_t)m.reserve_a.value) * fc::uint128_t((uint64_t)m.reserve_b.value); + } + }); + db.create([&](pm_liquidity_object& lp) { + lp.market = mkt.id; + lp.provider = account_name_type(); // empty = lazy pool + lp.amount = share_type(alloc); + lp.deposit_time = now; + lp.status = 0; + lp.b_share = b_share; + }); + db.create([&](pm_lazy_allocation_object& a) { + a.market = mkt.id; + a.amount = share_type(alloc); + a.original_amount = share_type(alloc); + a.bets_sum_at_check = mkt.bets_sum; + a.last_check_time = now; + a.check_step = 0; + a.status = 0; + }); + db.modify(*pool, [&](pm_lazy_pool_object& p) { + p.free_balance -= share_type(alloc); + p.allocated_balance += share_type(alloc); + }); + } + + bool verify_commit(const pm_commit_object& commit, + int8_t side, int16_t outcome_index, + share_type amount, share_type min_tokens, const std::string& salt) { + fc::sha256::encoder enc; + int64_t mid = commit.market._id; + enc.write(reinterpret_cast(&mid), (uint32_t)sizeof(mid)); + enc.write(reinterpret_cast(&commit.account.data), (uint32_t)sizeof(commit.account.data)); + enc.write(reinterpret_cast(&side), (uint32_t)sizeof(side)); + enc.write(reinterpret_cast(&outcome_index),(uint32_t)sizeof(outcome_index)); + int64_t amt = amount.value; + enc.write(reinterpret_cast(&amt), (uint32_t)sizeof(amt)); + int64_t mnt = min_tokens.value; + enc.write(reinterpret_cast(&mnt), (uint32_t)sizeof(mnt)); + enc.write(salt.data(), (uint32_t)salt.size()); + return enc.result() == commit.commitment; + } + + // Get pm_market_object by raw int64 id + const pm_market_object& get_market(const database& db, int64_t market_id) { + return db.get(pm_market_id_type(market_id)); + } + +} // anonymous namespace + +// ─── 1. pm_oracle_register ─────────────────────────────────────────────────── + +void pm_oracle_register_evaluator::do_apply(const pm_oracle_register_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + + const auto& owner = db.get_account(o.owner); + FC_ASSERT(o.insurance.symbol == TOKEN_SYMBOL, "Insurance must be VIZ"); + FC_ASSERT(o.insurance.amount >= mp.pm_min_oracle_insurance.amount, "Insurance below minimum"); + FC_ASSERT(o.fee_percent <= mp.pm_max_oracle_fee_percent, "fee_percent exceeds maximum"); + FC_ASSERT(o.fixed_fee.symbol == TOKEN_SYMBOL, "fixed_fee must be VIZ"); + FC_ASSERT(o.fixed_fee.amount >= 0, "fixed_fee cannot be negative"); + FC_ASSERT(o.rules_url.size() <= MAX_PM_PROFILE_URL_LEN, "rules_url too long"); + + const auto& oidx = db.get_index().indices().get(); + FC_ASSERT(oidx.find(o.owner) == oidx.end(), "Oracle already registered"); + + if (mp.pm_oracle_registration_fee.amount > 0) { + FC_ASSERT(owner.balance >= mp.pm_oracle_registration_fee, "Insufficient balance for registration fee"); + db.adjust_balance(owner, -mp.pm_oracle_registration_fee); + db.modify(db.get_dynamic_global_properties(), [&](dynamic_global_property_object& dgp) { + dgp.committee_fund += mp.pm_oracle_registration_fee; // protocol fee → DAO fund + }); + } + + FC_ASSERT(owner.balance >= o.insurance, "Insufficient balance for insurance"); + db.adjust_balance(owner, -o.insurance); + + db.create([&](pm_oracle_object& oracle) { + oracle.owner = o.owner; + oracle.insurance = o.insurance.amount; + oracle.fee_percent = o.fee_percent; + oracle.fixed_fee = o.fixed_fee.amount; + from_string(oracle.rules_url, o.rules_url); + oracle.active_since = db.head_block_time(); + oracle.last_active_time = db.head_block_time(); + oracle.auto_accept_creator = o.auto_accept_creator; + oracle.auto_accept_resolver = o.auto_accept_resolver; + oracle.auto_accept = o.auto_accept; + }); +} + +// ─── 2. pm_oracle_update ───────────────────────────────────────────────────── + +void pm_oracle_update_evaluator::do_apply(const pm_oracle_update_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + + const auto& owner = db.get_account(o.owner); + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(o.owner); + FC_ASSERT(it != oidx.end(), "Oracle not found"); + const auto& oracle = *it; + + if (o.fee_percent.valid()) + FC_ASSERT(*o.fee_percent <= mp.pm_max_oracle_fee_percent, "fee_percent exceeds maximum"); + if (o.fixed_fee.valid()) { + FC_ASSERT(o.fixed_fee->symbol == TOKEN_SYMBOL, "fixed_fee must be VIZ"); + FC_ASSERT(o.fixed_fee->amount >= 0, "fixed_fee cannot be negative"); + } + if (o.rules_url.valid()) + FC_ASSERT(o.rules_url->size() <= MAX_PM_PROFILE_URL_LEN, "rules_url too long"); + + if (o.insurance_delta.valid()) { + const asset& delta = *o.insurance_delta; + FC_ASSERT(delta.symbol == TOKEN_SYMBOL, "insurance_delta must be VIZ"); + if (delta.amount > 0) { + FC_ASSERT(owner.balance >= delta, "Insufficient balance for insurance top-up"); + db.adjust_balance(owner, -delta); + } else if (delta.amount < 0) { + share_type withdraw = share_type(-delta.amount); + FC_ASSERT(oracle.insurance.value - withdraw.value >= mp.pm_min_oracle_insurance.amount.value, + "Withdrawal would push insurance below minimum"); + db.adjust_balance(owner, asset(withdraw, TOKEN_SYMBOL)); + } + } + + db.modify(oracle, [&](pm_oracle_object& ora) { + if (o.insurance_delta.valid()) { + ora.insurance += o.insurance_delta->amount; + } + if (o.fee_percent.valid()) ora.fee_percent = *o.fee_percent; + if (o.fixed_fee.valid()) ora.fixed_fee = o.fixed_fee->amount; + if (o.rules_url.valid()) from_string(ora.rules_url, *o.rules_url); + if (o.auto_accept_creator.valid()) ora.auto_accept_creator = *o.auto_accept_creator; + if (o.auto_accept_resolver.valid()) ora.auto_accept_resolver = *o.auto_accept_resolver; + if (o.auto_accept.valid()) ora.auto_accept = *o.auto_accept; + ora.last_active_time = db.head_block_time(); + }); +} + +// ─── 3. pm_create_market ───────────────────────────────────────────────────── + +void pm_create_market_evaluator::do_apply(const pm_create_market_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + const auto now = db.head_block_time(); + + FC_ASSERT(o.liquidity.symbol == TOKEN_SYMBOL, "Liquidity must be VIZ"); + FC_ASSERT(o.liquidity.amount >= mp.pm_min_liquidity.amount, "Liquidity below minimum"); + FC_ASSERT(o.url.size() <= MAX_PM_MARKET_TITLE_LEN, "Market url too long"); + // Open-ended market: betting_expiration == 0 (epoch) means betting stays open until the + // oracle resolves. result_expiration then acts purely as the emergency backstop deadline + // (<= now + pm_max_market_duration, i.e. <= 1 year): if the oracle never resolves by then, + // process_pm_markets() refunds every bet and slashes the oracle insurance (missed-resolution + // path). Requires allow_early_resolution so the oracle can resolve at any time (with + // betting_expiration == 0, now >= betting_expiration is always true, so the early-resolution + // branch in pm_resolve_market is the only way to resolve before the backstop fires). + if (o.betting_expiration == time_point_sec()) { + FC_ASSERT(o.allow_early_resolution, + "open-ended market (betting_expiration=0) requires allow_early_resolution"); + FC_ASSERT(o.result_expiration > now, "result_expiration must be in the future"); + FC_ASSERT(o.result_expiration <= now + fc::seconds(mp.pm_max_market_duration), + "Market duration exceeds maximum"); + } else { + FC_ASSERT(o.betting_expiration > now, "betting_expiration must be in the future"); + FC_ASSERT(o.result_expiration > o.betting_expiration, "result_expiration must be after betting_expiration"); + FC_ASSERT(o.result_expiration <= now + fc::seconds(mp.pm_max_market_duration), + "Market duration exceeds maximum"); + } + + // Fee solvency (sum of bp fees <= 100%) is enforced statically in validate(). The oracle terms + // in this op are only the creator's OFFER CEILING; the governed cap (pm_max_oracle_fee_percent) + // is checked against the oracle's actual quote at accept (or, for a self-oracle, just below). + + if (o.market_type == 0) { + FC_ASSERT(o.outcomes.size() == 2, "Binary market must have exactly 2 outcomes"); + } else { + FC_ASSERT(o.market_type == 1, "Invalid market_type"); + FC_ASSERT(o.outcomes.size() >= 3 && (int)o.outcomes.size() <= mp.pm_max_outcomes, + "Multi-outcome count out of range"); + } + for (const auto& label : o.outcomes) + FC_ASSERT(label.size() <= MAX_PM_OUTCOME_LABEL_LEN, "Outcome label too long"); + + if (o.dispute_mode == 1) { + FC_ASSERT(o.dispute_resolver.size() > 0, "dispute_resolver required"); + FC_ASSERT(o.dispute_resolver != o.oracle, "dispute_resolver must differ from oracle"); + FC_ASSERT(o.dispute_resolver != o.creator, "dispute_resolver must differ from creator"); + db.get_account(o.dispute_resolver); + } + + const auto& oidx = db.get_index().indices().get(); + auto oracle_it = oidx.find(o.oracle); + bool self_oracle = (o.oracle == o.creator); + if (!self_oracle) { + FC_ASSERT(oracle_it != oidx.end(), "Oracle not registered"); + FC_ASSERT(oracle_it->banned_until < now, "Oracle is banned"); + // The oracle's actual fee is quoted (<= this offer) at accept; nothing to bind here. + } + + const auto& creator = db.get_account(o.creator); + + // Creator ban (scenario #14): an account-mode dispute resolver may bar a creator from + // opening new markets for a period (or permanently). Honour an unexpired ban here. + { + const auto& cbidx = db.get_index().indices().get(); + auto cb = cbidx.find(o.creator); + FC_ASSERT(cb == cbidx.end() || cb->banned_until < now, "Creator is banned from creating markets"); + } + + share_type market_fee = mp.pm_market_creation_fee.amount; + share_type total_need = share_type(market_fee.value + o.liquidity.amount.value); + FC_ASSERT(creator.balance.amount >= total_need, "Insufficient balance"); + + if (market_fee.value > 0) { + db.adjust_balance(creator, -mp.pm_market_creation_fee); + db.modify(db.get_dynamic_global_properties(), [&](dynamic_global_property_object& dgp) { + dgp.committee_fund += mp.pm_market_creation_fee; // protocol fee → DAO fund + }); + } + db.adjust_balance(creator, -o.liquidity); + db.pm_adjust_frozen(o.creator, 0, o.liquidity.amount); // LOCK: creator liquidity → live market + + share_type lmsr_b_val = 0; + if (o.market_type == 1) { + int64_t expected_b = lmsr::lmsr_b_from_liquidity(o.liquidity.amount.value, (int)o.outcomes.size()); + FC_ASSERT(o.lmsr_b == expected_b, "lmsr_b mismatch"); + lmsr_b_val = expected_b; + } + + bool is_self = self_oracle || (oracle_it == oidx.end()); + // Self-oracle auto-accepts at creation, so its oracle fee is final now and must satisfy the + // governed cap immediately. (External markets defer this check to accept.) + if (is_self) + FC_ASSERT(o.oracle_fee_percent <= mp.pm_max_oracle_fee_percent, "oracle_fee_percent exceeds cap"); + + // Auto-accept (anti-collusion): an external oracle that opted into auto-accept takes the market + // live at creation ONLY if it matches the oracle's pre-set policy — allowed creator + allowed + // resolver (empty resolver ⇒ committee-mode only), and the oracle's own profile fee terms within + // the creator's offered ceiling + the governed cap. Otherwise the market stays pending (status 0) + // for the oracle to review manually. This stops a creator from slipping in a sham resolver. + bool auto_accept = false; + uint16_t aa_fee = 0; share_type aa_fixed = 0; + if (!is_self && oracle_it != oidx.end()) { + const auto& ora = *oracle_it; + const bool creator_ok = (ora.auto_accept_creator == account_name_type()) + || (ora.auto_accept_creator == o.creator); + const bool resolver_ok = (ora.auto_accept_resolver == account_name_type()) + ? (o.dispute_mode == 0) + : (o.dispute_mode == 1 && o.dispute_resolver == ora.auto_accept_resolver); + const bool terms_ok = (ora.fee_percent <= o.oracle_fee_percent) + && (ora.fixed_fee <= o.oracle_fixed_fee.amount) + && (ora.fee_percent <= mp.pm_max_oracle_fee_percent); + const bool eligible = (ora.insurance >= mp.pm_min_oracle_insurance.amount) + && (ora.banned_until < now); + auto_accept = ora.auto_accept && creator_ok && resolver_ok && terms_ok && eligible; + aa_fee = ora.fee_percent; aa_fixed = ora.fixed_fee; + } + const bool active_at_create = is_self || auto_accept; + + const auto& mkt = db.create([&](pm_market_object& m) { + m.creator = o.creator; + m.oracle = o.oracle; + m.market_type = o.market_type; + m.outcome_count = (uint8_t)o.outcomes.size(); + from_string(m.url, o.url); + // o.metadata is intentionally NOT persisted in consensus state — the + // prediction_market_api plugin ingests it off-chain (prunable). See pm_objects.hpp. + m.status = active_at_create ? 1 : 0; + m.created_time = now; + // Pending markets get an acceptance deadline; markets that are live at creation (self-oracle / + // auto-accept) never enter the pending sweep, so leave it at 0. + m.accept_deadline = active_at_create + ? time_point_sec() + : time_point_sec(now + fc::seconds(mp.pm_oracle_accept_window_sec)); + m.betting_expiration = o.betting_expiration; + m.result_expiration = o.result_expiration; + m.resolved_outcome = -1; + m.lmsr_b = lmsr_b_val; + m.lmsr_subsidy = o.liquidity.amount; + m.liquidity_sum = o.liquidity.amount; + // Oracle terms = creator's offer ceiling (final immediately for a self-oracle; an external + // oracle narrows these down at accept). creator/liquidity fees are final at creation. + m.oracle_fee_percent = auto_accept ? aa_fee : o.oracle_fee_percent; + m.oracle_fixed_fee = auto_accept ? aa_fixed : o.oracle_fixed_fee.amount; + m.creator_fee_percent = o.creator_fee_percent; + m.liquidity_fee_percent = o.liquidity_fee_percent; + m.time_penalty_type = o.time_penalty_type; + m.time_penalty_value = o.time_penalty_value; + m.penalty_curve_type = o.penalty_curve_type; + m.allow_early_resolution = o.allow_early_resolution; + m.allow_cancellation = o.allow_cancellation; + m.allow_batch = o.allow_batch && mp.pm_commit_reveal_enabled; + m.allow_instant_bet = (o.market_type == 1) ? true : o.allow_instant_bet; + m.endogeneity_tier = o.endogeneity_tier; + m.dispute_mode = o.dispute_mode; + m.dispute_resolver = o.dispute_resolver; + m.dispute_penalty_percent = o.dispute_penalty_percent; + + if (o.market_type == 0) { + m.reserve_a = share_type(o.liquidity.amount.value / 2); + m.reserve_b = share_type(o.liquidity.amount.value - m.reserve_a.value); + m.k = fc::uint128_t((uint64_t)m.reserve_a.value) * fc::uint128_t((uint64_t)m.reserve_b.value); + } + }); + + for (uint8_t i = 0; i < (uint8_t)o.outcomes.size(); ++i) { + db.create([&](pm_outcome_object& out) { + out.market = mkt.id; + out.outcome_index = i; + from_string(out.label, o.outcomes[i]); + }); + } + + db.create([&](pm_liquidity_object& lp) { + lp.market = mkt.id; + lp.provider = o.creator; + lp.amount = o.liquidity.amount; + lp.deposit_time = now; + lp.status = 0; + if (o.market_type == 1) lp.b_share = lmsr_b_val; + }); + + if (!is_self && !auto_accept && oracle_it != oidx.end()) { + db.modify(*oracle_it, [&](pm_oracle_object& ora) { ora.last_active_time = now; }); + } + + if (is_self) { + maybe_allocate_lazy(db, mkt); // self-oracle markets are active at creation + // Auto-accepted: announce the launch + frozen terms so history parsers see it go live. + db.push_virtual_operation(pm_market_accepted_operation( + o.oracle, o.creator, mkt.id._id, + o.oracle_fee_percent, asset(o.oracle_fixed_fee.amount, TOKEN_SYMBOL), true)); + } else if (auto_accept && oracle_it != oidx.end()) { + // External oracle auto-accepted the market at creation under its policy. Same effect as a manual + // accept: count it, allocate lazy-pool subsidy, and emit pm_market_accepted with the frozen quote. + db.modify(*oracle_it, [&](pm_oracle_object& ora) { + ora.markets_accepted++; + ora.last_active_time = now; + }); + maybe_allocate_lazy(db, mkt); + db.push_virtual_operation(pm_market_accepted_operation( + o.oracle, o.creator, mkt.id._id, + aa_fee, asset(aa_fixed, TOKEN_SYMBOL), false)); + } + + // A market that is live at creation (self-oracle or auto-accepted) enters the active set now. + if (active_at_create) pm_oracle_inc_active(db, o.oracle); +} + +// ─── 4. pm_oracle_accept_market ────────────────────────────────────────────── + +void pm_oracle_accept_market_evaluator::do_apply(const pm_oracle_accept_market_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + + const auto& mp = median(db); + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.oracle == o.oracle, "Not the market oracle"); + FC_ASSERT(mkt.status == 0, "Market not pending acceptance"); + + if (o.accept) { + // The oracle quotes its actual terms now. They must not exceed the creator's offer ceiling + // (the values currently on the market) nor the governed cap; the quote is then frozen and + // the market goes live. The worst-case solvency was already validated at creation, and the + // quote only lowers the oracle fee, so the sum stays <= 100%. + FC_ASSERT(o.oracle_fee_percent <= mkt.oracle_fee_percent, + "oracle_fee_percent exceeds the creator's offer"); + FC_ASSERT(o.oracle_fixed_fee.amount <= mkt.oracle_fixed_fee, + "oracle_fixed_fee exceeds the creator's offer"); + FC_ASSERT(o.oracle_fee_percent <= mp.pm_max_oracle_fee_percent, "oracle_fee_percent exceeds cap"); + + db.modify(mkt, [&](pm_market_object& m) { + m.status = 1; + m.oracle_fee_percent = o.oracle_fee_percent; // freeze the agreed terms + m.oracle_fixed_fee = o.oracle_fixed_fee.amount; + }); + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(o.oracle); + if (it != oidx.end()) + db.modify(*it, [&](pm_oracle_object& ora) { + ora.markets_accepted++; + ora.active_markets++; // market goes live (status 0 → 1) + ora.last_active_time = db.head_block_time(); + }); + maybe_allocate_lazy(db, mkt); // pool subsidy on activation + db.push_virtual_operation(pm_market_accepted_operation( + o.oracle, mkt.creator, mkt.id._id, + o.oracle_fee_percent, o.oracle_fixed_fee, false)); + } else { + // Refund the creator's seed exactly once: return_liquidity already credits the seed + // LP object (provider == creator, created in pm_create_market). A pending market + // cannot have received pm_add_liquidity (that requires status==1), so liquidity_sum + // equals that single LP amount — crediting liquidity_sum here too would emit tokens. + return_liquidity(db, mkt); + db.modify(mkt, [&](pm_market_object& m) { m.status = -1; m.finalized_time = db.head_block_time(); }); + } +} + +// ─── 5. pm_place_bet ───────────────────────────────────────────────────────── + +void pm_place_bet_evaluator::do_apply(const pm_place_bet_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + const auto& mp = median(db); // for B9 late-bet penalty scale (pm_max_time_penalty) + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(mkt.betting_expiration == time_point_sec() || now < mkt.betting_expiration, "Betting period ended"); + + // Betting-mode gate (scenario #55): a market may disable instant bets (allow_instant_bet=false) + // to force the front-run-resistant batch / commit-reveal flow. mode 0 = instant, mode 1 = batch. + if (o.mode == 0) FC_ASSERT(mkt.allow_instant_bet, "Instant betting is disabled for this market"); + else FC_ASSERT(mkt.allow_batch, "Batch betting is not enabled for this market"); + + FC_ASSERT(o.amount.symbol == TOKEN_SYMBOL, "Amount must be VIZ"); + FC_ASSERT(o.amount.amount > 0, "Amount must be positive"); + + const auto& acct = db.get_account(o.account); + FC_ASSERT(acct.balance >= o.amount, "Insufficient balance"); + + if (mkt.market_type == 0) { + FC_ASSERT(o.side == 0 || o.side == 1, "Binary market requires side 0 or 1"); + FC_ASSERT(o.outcome_index == -1, "Binary market does not use outcome_index"); + + // Case A (spec §5): an opposing bet moves price against leveraged positions on the + // OTHER side — cascade-liquidate them at PRE-bet reserves so the pool stays whole. + // NOT gated by pm_leverage_enabled: that flag only blocks NEW opens (pm_leverage_open). + // Once a loan is out, governance toggling leverage off must never strip the pool's + // liquidation protection. No-op (cheap index probe) when the market has no positions. + cascade_liquidate(db, mkt.id, (int16_t)(1 - o.side), 0); + + share_type delta = o.amount.amount; + share_type reserve_in = (o.side == 0) ? mkt.reserve_a : mkt.reserve_b; + share_type reserve_out = (o.side == 0) ? mkt.reserve_b : mkt.reserve_a; + + fc::uint128_t denom = fc::uint128_t((uint64_t)(reserve_in.value + delta.value)); + FC_ASSERT(denom.lo > 0 || denom.hi > 0, "CPMM overflow"); + fc::uint128_t new_out_u128 = mkt.k / denom; + share_type new_reserve_out = share_type((int64_t)new_out_u128.lo); + share_type tokens_out = share_type(reserve_out.value - new_reserve_out.value); + FC_ASSERT(tokens_out.value > 0, "Zero tokens out"); + FC_ASSERT(tokens_out.value >= o.min_tokens, "Slippage: tokens below min_tokens"); + + db.adjust_balance(acct, -o.amount); + db.pm_adjust_frozen(o.account, 1, o.amount.amount); // LOCK: stake → open bet (binary/CPMM) + db.modify(mkt, [&](pm_market_object& m) { + if (o.side == 0) { + m.reserve_a += delta; m.reserve_b = new_reserve_out; m.a_bets_sum += delta; + } else { + m.reserve_b += delta; m.reserve_a = new_reserve_out; m.b_bets_sum += delta; + } + m.bets_sum += delta; + }); + + db.create([&](pm_bet_object& bet) { + bet.market = mkt.id; + bet.account = o.account; + bet.side = o.side; + bet.outcome_index = -1; + bet.amount = delta; + bet.weight = tokens_out; + bet.mode = o.mode; + bet.status = 0; + bet.created_time = now; + bet.time_penalty = compute_time_penalty(mkt, now, mp.pm_max_time_penalty); // B9 + }); + + } else { + FC_ASSERT(o.side == -1, "Multi market does not use side"); + FC_ASSERT(o.outcome_index >= 0 && o.outcome_index < (int16_t)mkt.outcome_count, + "outcome_index out of range"); + + const auto& oidx_out = db.get_index().indices().get(); + std::vector q; + q.reserve(mkt.outcome_count); + for (uint8_t i = 0; i < mkt.outcome_count; ++i) { + auto it = oidx_out.lower_bound(boost::make_tuple(mkt.id, i)); + FC_ASSERT(it != oidx_out.end() && it->market == mkt.id && it->outcome_index == i, "Outcome missing"); + q.push_back(it->q.value); + } + + int64_t tokens = lmsr::lmsr_tokens_for_amount(q, mkt.lmsr_b.value, (int)o.outcome_index, o.amount.amount.value); + FC_ASSERT(tokens > 0, "Zero LMSR tokens"); + FC_ASSERT(tokens >= o.min_tokens, "Slippage: LMSR tokens below min_tokens"); + + db.adjust_balance(acct, -o.amount); + db.pm_adjust_frozen(o.account, 1, o.amount.amount); // LOCK: stake → open bet (LMSR) + db.modify(mkt, [&](pm_market_object& m) { m.bets_sum += o.amount.amount; }); + + auto it = oidx_out.lower_bound(boost::make_tuple(mkt.id, (uint8_t)o.outcome_index)); + db.modify(*it, [&](pm_outcome_object& out) { + out.q += tokens; + out.bets_sum += o.amount.amount; + out.bets_count++; + }); + + db.create([&](pm_bet_object& bet) { + bet.market = mkt.id; + bet.account = o.account; + bet.side = -1; + bet.outcome_index = o.outcome_index; + bet.amount = o.amount.amount; + bet.weight = tokens; + bet.mode = o.mode; + bet.status = 0; + bet.created_time = now; + bet.time_penalty = compute_time_penalty(mkt, now, mp.pm_max_time_penalty); // B9 + }); + } +} + +// ─── 6. pm_commit_bet ──────────────────────────────────────────────────────── + +void pm_commit_bet_evaluator::do_apply(const pm_commit_bet_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + FC_ASSERT(mp.pm_commit_reveal_enabled, "Commit-reveal not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(mkt.allow_batch, "Batch mode not enabled"); + FC_ASSERT(mkt.betting_expiration == time_point_sec() || now < mkt.betting_expiration, "Betting period ended"); + + FC_ASSERT(o.escrow_amount.symbol == TOKEN_SYMBOL, "Escrow must be VIZ"); + FC_ASSERT(o.escrow_amount.amount >= mp.pm_min_batch_bet.amount, "Escrow below minimum batch bet"); + FC_ASSERT(o.no_reveal_fee_percent == mp.pm_commit_no_reveal_penalty_percent, + "no_reveal_fee_percent must equal current consensus value"); + + const auto& acct = db.get_account(o.account); + FC_ASSERT(acct.balance >= o.escrow_amount, "Insufficient balance for escrow"); + db.adjust_balance(acct, -o.escrow_amount); + + uint32_t epoch_blocks = mp.pm_batch_epoch_blocks; + uint32_t reveal_window = mp.pm_reveal_window_blocks; + uint32_t cur_block = db.head_block_num(); + uint32_t epoch_end = ((cur_block / epoch_blocks) + 1) * epoch_blocks; + time_point_sec reveal_dl = now + fc::seconds((int64_t)(epoch_end - cur_block + reveal_window) * CHAIN_BLOCK_INTERVAL); + + db.create([&](pm_commit_object& c) { + c.market = mkt.id; + c.account = o.account; + c.commitment = o.commitment; + c.escrow_amount = o.escrow_amount.amount; + c.no_reveal_fee_percent = o.no_reveal_fee_percent; + c.commit_time = now; + c.reveal_deadline = reveal_dl; + c.status = 0; + }); +} + +// ─── 7. pm_reveal_bet ──────────────────────────────────────────────────────── + +void pm_reveal_bet_evaluator::do_apply(const pm_reveal_bet_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& commit = db.get(pm_commit_id_type(o.commit_id)); + FC_ASSERT(commit.account == o.account, "Not your commitment"); + FC_ASSERT(commit.status == 0, "Already revealed or forfeited"); + FC_ASSERT(now <= commit.reveal_deadline, "Reveal window passed"); + + FC_ASSERT(o.amount.symbol == TOKEN_SYMBOL, "Amount must be VIZ"); + FC_ASSERT(o.amount.amount > 0 && o.amount.amount <= commit.escrow_amount, "Invalid reveal amount"); + + FC_ASSERT(verify_commit(commit, o.side, o.outcome_index, o.amount.amount, o.min_tokens, o.salt), + "Commitment hash mismatch"); + + const auto& mkt = get_market(db, commit.market._id); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(mkt.betting_expiration == time_point_sec() || now < mkt.betting_expiration, "Betting period ended"); + + share_type surplus = share_type(commit.escrow_amount.value - o.amount.amount.value); + if (surplus.value > 0) + db.adjust_balance(db.get_account(o.account), asset(surplus, TOKEN_SYMBOL)); + + db.modify(commit, [](pm_commit_object& c) { c.status = 1; }); + + db.create([&](pm_bet_object& bet) { + bet.market = commit.market; + bet.account = o.account; + bet.side = o.side; + bet.outcome_index = o.outcome_index; + bet.amount = o.amount.amount; + bet.weight = 0; + bet.min_tokens = o.min_tokens; + bet.mode = 1; + bet.epoch = mkt.current_epoch; + bet.status = 5; // queued + bet.created_time = now; + // B9: penalise by the blind COMMIT time, not the reveal — honest commit-reveal bettors + // are not punished for revealing late within the window. + bet.time_penalty = compute_time_penalty(mkt, commit.commit_time, median(db).pm_max_time_penalty); + }); + // LOCK: revealed stake becomes a queued bet. The escrow left balance at commit but is not a + // tracked category; count it as frozen from the moment it materializes as a bet object. + db.pm_adjust_frozen(o.account, 1, o.amount.amount); +} + +// ─── 8. pm_cancel_bet ──────────────────────────────────────────────────────── + +void pm_cancel_bet_evaluator::do_apply(const pm_cancel_bet_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + + const auto& bet = db.get(pm_bet_id_type(o.bet_id)); + FC_ASSERT(bet.account == o.account, "Not your bet"); + FC_ASSERT(bet.status == 0, "Bet not active"); + + const auto& mkt = db.get(bet.market); + FC_ASSERT(mkt.allow_cancellation, "Cancellation not allowed"); + FC_ASSERT(mkt.status == 1, "Market not active"); + // B7: cancellation is a pre-close action only. Once betting closes the outcome starts + // becoming known, so a late cancel would be a free option to unwind a losing bet. + // Open-ended markets (no betting deadline) stay cancellable while active. + const auto now = db.head_block_time(); + FC_ASSERT(mkt.betting_expiration == time_point_sec() || now < mkt.betting_expiration, + "Cannot cancel bets after betting closes"); + + // Binary markets re-price the refund on the current curve (F2, below); type-1/LMSR stays + // nominal. min_return is checked once the final refund is known (after the branch). + share_type refund = bet.amount; + + if (mkt.market_type == 0) { + // F2: a cancel is the MIRROR OF THE BUY — sell bet.weight back at the CURRENT reserves so + // k stays invariant. The old nominal reversal (refund bet.amount, hand back bet.weight at + // today's price) corrupted k on any curve that had moved since the bet — and an opposing bet + // is enough to move it — leaving the retained position an inflated claim weight (up to 103×) + // that settles as real money, plus a free option to unwind a losing bet at 100%. Curve-priced: + // new_reserve_in = k / (reserve_out + weight); refund = reserve_in − new_reserve_in. + // new_reserve_in < reserve_in always (weight > 0 ⇒ larger denominator), so refund > 0 and no + // reserve can underflow — this subsumes the B6 guard. min_return (checked below) protects the + // bettor from an adverse move. The gap between the original stake and the curve-priced refund + // routes to forfeit_pool (signed): a loss accrues to the rest of the market, a gain is charged + // to LP principal at settlement via the F1 shortfall path. (PR #124 finding 2.) + const bool a = (bet.side == 0); + share_type reserve_in = a ? mkt.reserve_a : mkt.reserve_b; + share_type reserve_out = a ? mkt.reserve_b : mkt.reserve_a; + fc::uint128_t denom = fc::uint128_t((uint64_t)(reserve_out.value + bet.weight.value)); + FC_ASSERT(denom.lo > 0 || denom.hi > 0, "CPMM overflow"); + share_type new_reserve_in = share_type((int64_t)(mkt.k / denom).lo); + const int64_t curve_refund = reserve_in.value - new_reserve_in.value; // > 0 by construction + FC_ASSERT(curve_refund >= 0, "curve-priced refund underflow"); + // F1/#300: pay at most the stake (min(curve_refund, stake)) — a cancel cuts losses or breaks + // even but never realizes curve PROFIT against LP depth. The profit tail becomes an OUTCOME- + // CONTINGENT deferred claim, paid from the bounded early-exit bucket at settlement (winning side + // only). residual = stake − paid ≥ 0, so forfeit_pool never goes negative on a cancel. + const int64_t capped = curve_refund < bet.amount.value ? curve_refund : bet.amount.value; + const int64_t tail = curve_refund - capped; // ≥ 0: curve profit, deferred as a claim + refund = share_type(capped); + const int64_t residual = bet.amount.value - capped; // ≥ 0 → forfeit_pool + db.modify(mkt, [&](pm_market_object& m) { + if (a) { m.reserve_a = new_reserve_in; m.reserve_b += bet.weight; m.a_bets_sum -= bet.amount; } + else { m.reserve_b = new_reserve_in; m.reserve_a += bet.weight; m.b_bets_sum -= bet.amount; } + m.bets_sum -= bet.amount; + m.forfeit_pool += residual; // stake = refund + residual; k unchanged (mirror of buy) + }); + if (tail > 0 && mkt.deferred_claim_count < MAX_PM_DEFERRED_CLAIMS_PER_MARKET) { + // #349: skip once the per-market cap is hit — the tail stays in the curve and pays 0 at + // settlement (like bucket-exhaustion), keeping settle_market's claim loop bounded. + db.create([&](pm_deferred_claim_object& c) { + c.market = mkt.id; c.account = bet.account; c.kind = 0; + c.outcome_index = (uint8_t)bet.side; c.claim_amount = share_type(tail); c.exit_time = now; + }); + db.modify(mkt, [](pm_market_object& m) { m.deferred_claim_count++; }); + } + } else { + const auto& oidx_out = db.get_index().indices().get(); + auto it = oidx_out.lower_bound(boost::make_tuple(mkt.id, (uint8_t)bet.outcome_index)); + if (it != oidx_out.end() && it->market == mkt.id && it->outcome_index == (uint8_t)bet.outcome_index) { + db.modify(*it, [&](pm_outcome_object& out) { + out.q -= bet.weight; + out.bets_sum -= bet.amount; + if (out.bets_count > 0) out.bets_count--; + }); + } + db.modify(mkt, [&](pm_market_object& m) { m.bets_sum -= bet.amount; }); + } + + FC_ASSERT(refund.value >= o.min_return, "Refund below min_return"); + db.adjust_balance(db.get_account(o.account), asset(refund, TOKEN_SYMBOL)); + db.pm_adjust_frozen(o.account, 1, -bet.amount); // UNLOCK: original stake leaves the bet set on cancel + db.modify(bet, [](pm_bet_object& b) { b.status = 1; }); + + // Case B (spec §5): the cancel-bettor was paid first at current reserves; now cascade- + // liquidate same-side leveraged positions the cancel pushed below threshold (bad debt, + // if any, is borne by the pool — the initiator is never penalized). NOT gated by + // pm_leverage_enabled — existing positions stay protected even if new leverage was disabled. + if (mkt.market_type == 0 && bet.side >= 0) + cascade_liquidate(db, mkt.id, (int16_t)bet.side, 1); +} + +// ─── 9. pm_add_liquidity ───────────────────────────────────────────────────── + +void pm_add_liquidity_evaluator::do_apply(const pm_add_liquidity_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(mkt.betting_expiration == time_point_sec() || now < mkt.betting_expiration, "Cannot add liquidity after betting ends"); + FC_ASSERT(o.amount.symbol == TOKEN_SYMBOL, "Amount must be VIZ"); + FC_ASSERT(o.amount.amount > 0, "Amount must be positive"); + + const auto& provider = db.get_account(o.provider); + FC_ASSERT(provider.balance >= o.amount, "Insufficient balance"); + db.adjust_balance(provider, -o.amount); + db.pm_adjust_frozen(o.provider, 0, o.amount.amount); // LOCK: LP liquidity → live market + + share_type b_share = 0; + if (mkt.market_type == 1 && mkt.liquidity_sum.value > 0) { + b_share = share_type((int64_t)(fc::uint128_t((uint64_t)mkt.lmsr_b.value) * + fc::uint128_t((uint64_t)o.amount.amount.value) / + fc::uint128_t((uint64_t)mkt.liquidity_sum.value)).lo); + } + + db.modify(mkt, [&](pm_market_object& m) { + if (m.market_type == 1) { + m.liquidity_sum += o.amount.amount; + m.lmsr_b += b_share; + m.lmsr_subsidy += o.amount.amount; + } else { + // CPMM: price-neutral add. Scale both reserves by (L + amount) / L so the + // reserve ratio (the odds) is unchanged and only depth grows with capital. + // A round-trip (add then withdraw the same amount) restores the reserves. + const int64_t L = m.liquidity_sum.value; // capital BEFORE this deposit + if (L > 0) { + const fc::uint128_t num((uint64_t)(L + o.amount.amount.value)); + const fc::uint128_t den((uint64_t)L); + m.reserve_a = share_type((int64_t)(fc::uint128_t((uint64_t)m.reserve_a.value) * num / den).lo); + m.reserve_b = share_type((int64_t)(fc::uint128_t((uint64_t)m.reserve_b.value) * num / den).lo); + } else { + // No prior capital: seed a balanced curve (matches market genesis). + const share_type half = share_type(o.amount.amount.value / 2); + m.reserve_a += half; + m.reserve_b += share_type(o.amount.amount.value - half.value); + } + m.liquidity_sum += o.amount.amount; + m.k = fc::uint128_t((uint64_t)m.reserve_a.value) * fc::uint128_t((uint64_t)m.reserve_b.value); + } + }); + + db.create([&](pm_liquidity_object& lp) { + lp.market = mkt.id; + lp.provider = o.provider; + lp.amount = o.amount.amount; + lp.deposit_time = now; + lp.status = 0; + lp.b_share = b_share; + }); +} + +// ─── 10. pm_withdraw_liquidity ─────────────────────────────────────────────── + +void pm_withdraw_liquidity_evaluator::do_apply(const pm_withdraw_liquidity_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& lp = db.get(pm_liquidity_id_type(o.liquidity_id)); + FC_ASSERT(lp.provider == o.provider, "Not your liquidity position"); + FC_ASSERT(lp.status == 0, "Position already closed"); + + const auto& mkt = db.get(lp.market); + // LP positions may be withdrawn early *during* the betting window (Early Withdrawal, + // spec §9) but are locked once betting closes — for open-ended markets (no betting + // deadline) the lock instead begins at resolution — and stay locked until SETTLEMENT, + // not merely until resolution: this keeps the LP set stable for the time-weighted fee + // settlement and backs the pending F1 charge. The lock is keyed on finalized_time and the + // betting window below, never on a status>=2 sentinel (status is only ever 0/1/-1/3). The + // withdrawal shrinks the reserves price-neutrally below, so an add→withdraw round-trip + // is exploit-free (this is what B4 fixed; the earlier assert *message* wrongly implied + // withdrawal was blocked during betting — the condition itself is correct). + // F1-escape fix (PR #124): resolution is the lock trigger, settlement is the unlock. Settlement + // runs from the deferred cron sweep (result_expiration + ≥12h dispute grace; resolution moves + // result_expiration to the report time — early OR late — so it always settles ~grace after the + // report and disputers always get the full window), + // and resolution is exactly when the F1 `uncovered` LP charge + // becomes computable+public (forfeit_pool is on get_market). If an LP could withdraw in that + // window it would empty settle_liquidity's `active` set and dodge its share of the charge. + // Withdrawable iff (a) already finalized (finalized_time stamped — settle/void/expire), or + // (b) still pre-resolution AND inside the betting window (status < 2 gates out resolved markets; + // the betting-window clause keeps EARLY withdrawal working, incl. open-ended betting_expiration==0 + // — clause 1 alone would have left open-ended markets withdrawable at status 3, floored only to + // pm_min_liquidity, re-emitting uncovered − 100 VIZ; and open-ended is where uncovered is MOST + // likely, as the leverage expiration buffer is waived there). + FC_ASSERT(mkt.finalized_time != time_point_sec() + || (mkt.status < 2 + && (mkt.betting_expiration == time_point_sec() || now < mkt.betting_expiration)), + "LP positions are locked once betting closes or the market resolves, until settlement"); + + share_type withdraw = (o.amount.amount == 0) ? lp.amount : o.amount.amount; + FC_ASSERT(withdraw.value > 0 && withdraw.value <= lp.amount.value, "Invalid withdrawal amount"); + + // A live market's pricing curve must stay funded above the minimum: the reserves + // (CPMM) / lmsr_b (Multi) now track liquidity_sum, so an unchecked full exit could + // drain them to zero and brick pricing. Only already-finalized markets (finalized_time + // stamped) skip the floor — a resolved-but-unsettled market is still locked above (its + // curve is dead but its principal backs the pending F1 charge). Positions that cannot + // exit early settle in full at settlement. + if (mkt.finalized_time == time_point_sec()) { + const auto& mp = median(db); + FC_ASSERT(mkt.liquidity_sum.value - withdraw.value >= mp.pm_min_liquidity.amount.value, + "Withdrawal would drop market liquidity below the minimum"); + } + + share_type total = share_type(withdraw.value + lp.earned_fee.value); + db.adjust_balance(db.get_account(o.provider), asset(total, TOKEN_SYMBOL)); + db.pm_adjust_frozen(o.provider, 0, -withdraw); // UNLOCK: principal back to free (earned_fee is profit, not frozen) + + db.modify(mkt, [&](pm_market_object& m) { + const int64_t L = m.liquidity_sum.value; // capital BEFORE this withdrawal + m.liquidity_sum -= withdraw; + if (m.market_type == 1) { + if (lp.b_share.value > 0 && lp.amount.value > 0) { + share_type b_remove = (withdraw == lp.amount) ? lp.b_share : + share_type((int64_t)(fc::uint128_t((uint64_t)lp.b_share.value) * + fc::uint128_t((uint64_t)withdraw.value) / + fc::uint128_t((uint64_t)lp.amount.value)).lo); + m.lmsr_b -= b_remove; + } + } else if (L > 0) { + // CPMM: price-neutral withdraw. Shrink both reserves by (L - withdraw) / L so + // the reserve ratio (the odds) is unchanged and depth falls with capital. + // Mirrors the proportional add — a round-trip leaves the curve untouched. + const fc::uint128_t num((uint64_t)(L - withdraw.value)); + const fc::uint128_t den((uint64_t)L); + m.reserve_a = share_type((int64_t)(fc::uint128_t((uint64_t)m.reserve_a.value) * num / den).lo); + m.reserve_b = share_type((int64_t)(fc::uint128_t((uint64_t)m.reserve_b.value) * num / den).lo); + m.k = fc::uint128_t((uint64_t)m.reserve_a.value) * fc::uint128_t((uint64_t)m.reserve_b.value); + } + }); + + if (withdraw == lp.amount) { + db.modify(lp, [](pm_liquidity_object& l) { l.status = 3; l.earned_fee = 0; }); + } else { + db.modify(lp, [&](pm_liquidity_object& l) { l.amount -= withdraw; l.earned_fee = 0; }); + } +} + +// ─── 11. pm_resolve_market ─────────────────────────────────────────────────── + +void pm_resolve_market_evaluator::do_apply(const pm_resolve_market_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.oracle == o.oracle, "Not the market oracle"); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(o.winning_outcome >= 0 && o.winning_outcome < (int16_t)mkt.outcome_count, + "Invalid winning_outcome"); + FC_ASSERT(o.decision_url.size() <= MAX_PM_DECISION_URL_LEN, "decision_url too long"); + + bool can_resolve_early = mkt.allow_early_resolution && now >= mkt.betting_expiration; + FC_ASSERT(can_resolve_early || now >= mkt.result_expiration, "Cannot resolve yet"); + + // P5 timeliness telemetry, captured BEFORE the modify below rewrites result_expiration on an + // early resolve. `late` = resolved past the advertised deadline. `rt` = latency from betting + // close to now (0 for open-ended, which has no forced wait). + const bool late = (now > mkt.result_expiration); + const uint64_t rt = (mkt.betting_expiration != time_point_sec() && now > mkt.betting_expiration) + ? (uint64_t)(now.sec_since_epoch() - mkt.betting_expiration.sec_since_epoch()) : 0; + + // Anchor the whole downstream schedule (dispute window + LP-principal lock + settle wait) to the + // moment the result is ANNOUNCED: result_expiration = now, unconditionally — exactly like + // pm_no_contest. This guarantees disputers ALWAYS get the full pm_dispute_grace_sec window + // measured from the announcement, whether the oracle reported early OR late. + // * Early report → pulls the schedule forward (collapses the LP lock / settle wait to + // now + grace instead of a far-future result_expiration; open-ended markets could otherwise + // stay locked ~pm_max_market_duration). + // * Late report → pushes result_expiration forward to the report time. Previously we only ever + // shifted EARLIER, which let an oracle GRIEF disputers: by stalling until ~result_expiration + // + grace and only then reporting, the dispute deadline (result_expiration + grace) was + // already spent, leaving a near-zero (or zero) window. Anchoring to `now` closes that hole — + // the settle wait extends by however late the report was, which is the necessary price of a + // fair, always-full dispute window. `late`/`rt` telemetry above was captured pre-shift, so + // resolved_late_count and latency stats stay honest. + db.modify(mkt, [&](pm_market_object& m) { + m.status = 3; + m.payout_status = 1; + m.resolved_outcome = o.winning_outcome; + m.result_expiration = now; // dispute/settle grace ALWAYS starts from the announcement + from_string(m.decision_url, o.decision_url); + from_string(m.decision_reason, o.decision_reason); + }); + + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(o.oracle); + if (it != oidx.end()) + db.modify(*it, [&](pm_oracle_object& ora) { + ora.markets_resolved++; + if (ora.active_markets > 0) ora.active_markets--; // leaves active set (1 → 3) + ora.markets_in_dispute_window++; // enters disputable window (status3, payout1, no dispute) + ora.total_volume_resolved += mkt.bets_sum; + if (late) ora.resolved_late_count++; + // Running mean resolution latency over all resolves (n just incremented above). + // uint128 intermediate: avg_resolution_time (uint32) * (n-1) can overflow uint64 + // once n grows large (e.g. ~4.3e9 markets), so accumulate in 128 bits. + { + const uint64_t n = ora.markets_resolved; + const uint64_t sum = + ((fc::uint128_t((uint64_t)ora.avg_resolution_time) * (uint64_t)(n - 1) + + fc::uint128_t(rt)) / n).lo; + ora.avg_resolution_time = (uint32_t)sum; + } + ora.resolution_time_hist[pm_rt_bucket(rt)] += share_type(1); // P5 latency distribution + ora.last_active_time = now; + }); +} + +// ─── 12. pm_no_contest ─────────────────────────────────────────────────────── + +void pm_no_contest_evaluator::do_apply(const pm_no_contest_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.oracle == o.oracle, "Not the market oracle"); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(o.reason.size() <= MAX_PM_DISPUTE_REASON_LEN, "Reason too long"); + + // Declaring no-contest does NOT settle immediately: it records an unresolved (-1) outcome and + // opens the normal dispute window (spec §3.9 "Disputable", 3-outcome A/B/no-contest). Refund + + // penalty are applied at settlement (settle_market, win<0) once the grace elapses — unless a + // dispute overrides the no-contest to a real outcome first. + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(o.oracle); + if (it != oidx.end()) + db.modify(*it, [&](pm_oracle_object& ora) { + ora.no_contest_count++; + if (ora.active_markets > 0) ora.active_markets--; // leaves active set (1 → 3) + ora.markets_in_dispute_window++; // enters disputable window (status3, payout1, no dispute) + ora.last_active_time = now; + }); + db.modify(mkt, [&](pm_market_object& m) { + m.status = 3; + m.payout_status = 1; + m.resolved_outcome = -1; + m.result_expiration = now; // start the dispute/settle grace from here + from_string(m.decision_reason, o.reason); // NO-CONTEST rationale, readable via get_market + }); +} + +// ─── 13. pm_dispute_create ─────────────────────────────────────────────────── + +void pm_dispute_create_evaluator::do_apply(const pm_dispute_create_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.status == 3, "Market not resolved"); + FC_ASSERT(mkt.payout_status == 1, "Payout not in pending state"); + FC_ASSERT(o.reason.size() <= MAX_PM_DISPUTE_REASON_LEN, "Reason too long"); + FC_ASSERT(o.proposed_outcome >= -1 && o.proposed_outcome < (int16_t)mkt.outcome_count, + "proposed_outcome out of range"); + FC_ASSERT(now <= mkt.result_expiration + fc::seconds(mp.pm_dispute_grace_sec), "Grace period passed"); + + const auto& didx = db.get_index().indices().get(); + FC_ASSERT(didx.find(mkt.id) == didx.end(), "Dispute already filed"); + + const auto& disputer = db.get_account(o.disputer); + FC_ASSERT(disputer.balance >= mp.pm_dispute_fee, "Insufficient balance for dispute fee"); + db.adjust_balance(disputer, -mp.pm_dispute_fee); + + time_point_sec oracle_dl = now + fc::seconds(mp.pm_oracle_dispute_response_sec); + time_point_sec voting_end = oracle_dl + fc::seconds(mp.pm_dispute_vote_period_sec); + time_point_sec auto_close = now + fc::seconds(mp.pm_dispute_auto_close_sec); + + db.create([&](pm_dispute_object& d) { + d.market = mkt.id; + d.disputer = o.disputer; + d.dispute_fee = mp.pm_dispute_fee.amount; + from_string(d.reason, o.reason); + d.filed_time = now; + d.oracle_response_deadline = oracle_dl; + d.dispute_mode = mkt.dispute_mode; + d.voting_end_time = voting_end; + d.auto_close_time = auto_close; + d.proposed_outcome = o.proposed_outcome; + d.status = 0; + }); + + db.modify(mkt, [](pm_market_object& m) { m.payout_status = 2; }); + + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(mkt.oracle); + if (it != oidx.end()) + db.modify(*it, [](pm_oracle_object& ora) { + ora.disputes_received++; + if (ora.markets_in_dispute_window > 0) ora.markets_in_dispute_window--; // leaves disputable → disputed + ora.disputes_awaiting_response++; // new open dispute, oracle has not responded yet + }); + + // P1 oracle-metrics: surface the filing in the oracle's (and disputer's) history. + db.push_virtual_operation(pm_dispute_opened_operation( + mkt.oracle, o.disputer, mkt.id._id, o.proposed_outcome)); +} + +// ─── 14. pm_dispute_vote ───────────────────────────────────────────────────── + +void pm_dispute_vote_evaluator::do_apply(const pm_dispute_vote_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.dispute_mode == 0, "Vote only for committee-mode disputes"); + + const auto& didx = db.get_index().indices().get(); + auto dit = didx.find(mkt.id); + FC_ASSERT(dit != didx.end(), "No active dispute"); + FC_ASSERT(dit->status == 0, "Dispute already resolved"); + FC_ASSERT(now <= dit->voting_end_time, "Voting period ended"); + FC_ASSERT(o.vote_outcome >= -1 && o.vote_outcome < (int16_t)mkt.outcome_count, + "vote_outcome out of range"); + FC_ASSERT(o.vote_percent >= -10000 && o.vote_percent <= 10000, "vote_percent out of range"); + + // A committee dispute is a PUBLIC hearing (no commit-reveal — by design, see pm spec + // §dispute-transparency): the running tally is visible so the DAO resolves it as truthfully + // as possible. Consequently a voter may REVISE their ballot at any time while voting is open, + // in case new evidence or arguments surface before voting_end_time. A repeat vote overwrites + // the previous one (latest ballot wins) rather than being rejected. + const auto& vidx = db.get_index().indices().get(); + auto vit = vidx.find(boost::make_tuple(mkt.id, o.voter)); + if (vit != vidx.end()) { + db.modify(*vit, [&](pm_dispute_vote_object& v) { + v.vote_outcome = o.vote_outcome; + v.vote_percent = o.vote_percent; + v.time = now; + }); + } else { + db.create([&](pm_dispute_vote_object& v) { + v.market = mkt.id; + v.voter = o.voter; + v.vote_outcome = o.vote_outcome; + v.vote_percent = o.vote_percent; + v.time = now; + }); + } +} + +// ─── 15. pm_dispute_resolve ────────────────────────────────────────────────── + +void pm_dispute_resolve_evaluator::do_apply(const pm_dispute_resolve_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.dispute_mode == 1, "resolve only for account-mode disputes"); + FC_ASSERT(mkt.dispute_resolver == o.resolver, "Not the market dispute resolver"); + FC_ASSERT(o.correct_outcome >= -1 && o.correct_outcome < (int16_t)mkt.outcome_count, + "correct_outcome out of range"); + FC_ASSERT(o.penalty_amount.symbol == TOKEN_SYMBOL, "penalty_amount must be VIZ"); + + const auto& didx = db.get_index().indices().get(); + auto dit = didx.find(mkt.id); + FC_ASSERT(dit != didx.end() && dit->status == 0, "No open dispute"); + FC_ASSERT(now <= dit->oracle_response_deadline + fc::seconds(mp.pm_dispute_vote_period_sec), + "Resolution window passed"); + + // Dispute leaves the open state either way (verdict below): drop it from the open-dispute gauge. + pm_oracle_dispute_left_open(db, mkt.oracle, *dit); + + // Same post-verdict canon as committee mode (spec §3.9 "both modes converge"); only the + // penalty size differs — here it is the resolver-specified penalty_amount (no vote to scale). + if (o.correct_outcome != mkt.resolved_outcome) { + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(mkt.oracle); + share_type slash(0); + if (it != oidx.end()) { + slash = share_type(std::min(o.penalty_amount.amount.value, it->insurance.value)); + db.modify(*it, [&](pm_oracle_object& ora) { + if (slash.value > 0) { ora.insurance -= slash; ora.total_insurance_slashed += slash; } + ora.disputes_lost++; + if (o.ban_oracle) { ora.banned_until = o.ban_oracle_until; ora.banned_by = o.resolver; } + }); + } + // Disputer was right: refund fee + reward carve-out from the slash; remainder → winners. + int64_t fee = dit->dispute_fee.value > 0 ? dit->dispute_fee.value : 0; + int64_t reward_target = (int64_t)(fc::uint128_t((uint64_t)fee) + * fc::uint128_t((uint64_t)mp.pm_dispute_reward_multiplier) / fc::uint128_t(10000)).lo; + int64_t bonus = reward_target - fee; + if (bonus < 0) bonus = 0; + if (bonus > slash.value) bonus = slash.value; + if (fee + bonus > 0) + db.adjust_balance(db.get_account(dit->disputer), asset(share_type(fee + bonus), TOKEN_SYMBOL)); + db.modify(mkt, [&](pm_market_object& m) { + m.forfeit_pool += share_type(slash.value - bonus); + m.resolved_outcome = o.correct_outcome; + m.status = 3; + m.payout_status = 1; + }); + db.modify(*dit, [](pm_dispute_object& d) { d.status = 1; }); // oracle wrong + } else { + // Uphold: the dispute fee compensates the oracle. + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(mkt.oracle); + if (it != oidx.end()) + db.modify(*it, [](pm_oracle_object& ora) { ora.disputes_won++; }); + if (dit->dispute_fee.value > 0) + db.adjust_balance(db.get_account(mkt.oracle), asset(dit->dispute_fee, TOKEN_SYMBOL)); + db.modify(mkt, [&](pm_market_object& m) { m.payout_status = 1; }); + db.modify(*dit, [](pm_dispute_object& d) { d.status = 2; }); // oracle right + } + + // Creator ban (scenario #14): an independent resolver sanction, applied regardless of the + // verdict on the outcome. Upsert a pm_creator_ban row (one per creator) with the resolver's + // ban_until; create_market consults it and rejects new markets while the ban is in force. + if (o.ban_creator) { + const auto& cbidx = db.get_index().indices().get(); + auto cb = cbidx.find(mkt.creator); + if (cb == cbidx.end()) { + db.create([&](pm_creator_ban_object& b) { + b.creator = mkt.creator; b.banned_until = o.ban_creator_until; b.ban_count = 1; + b.banned_by = o.resolver; + }); + } else { + db.modify(*cb, [&](pm_creator_ban_object& b) { + if (o.ban_creator_until > b.banned_until) b.banned_until = o.ban_creator_until; + b.ban_count++; + b.banned_by = o.resolver; + }); + } + } +} + +// ─── 16. pm_transfer_position ──────────────────────────────────────────────── + +void pm_transfer_position_evaluator::do_apply(const pm_transfer_position_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + + const auto& bet = db.get(pm_bet_id_type(o.bet_id)); + FC_ASSERT(bet.account == o.from, "Not your bet"); + FC_ASSERT(bet.status == 0, "Bet not active"); + db.get_account(o.to); + + share_type transfer_weight = (o.amount == 0) ? bet.weight : share_type(o.amount); + FC_ASSERT(transfer_weight.value > 0 && transfer_weight.value <= bet.weight.value, "Invalid transfer amount"); + + const auto& mkt = db.get(bet.market); + FC_ASSERT(mkt.status == 1, "Market not active"); + + if (transfer_weight == bet.weight) { + const share_type moved = bet.amount; + db.modify(bet, [&](pm_bet_object& b) { b.account = o.to; }); + db.pm_adjust_frozen(o.from, 1, -moved); // MOVE: whole stake changes owner + db.pm_adjust_frozen(o.to, 1, moved); + } else { + share_type transferred_amount = share_type((int64_t)( + fc::uint128_t((uint64_t)bet.amount.value) * + fc::uint128_t((uint64_t)transfer_weight.value) / + fc::uint128_t((uint64_t)bet.weight.value)).lo); + + db.modify(bet, [&](pm_bet_object& b) { + b.weight -= transfer_weight; + b.amount -= transferred_amount; + }); + db.pm_adjust_frozen(o.from, 1, -transferred_amount); // MOVE: partial stake to recipient + db.pm_adjust_frozen(o.to, 1, transferred_amount); + + db.create([&](pm_bet_object& nb) { + nb.market = bet.market; + nb.account = o.to; + nb.side = bet.side; + nb.outcome_index = bet.outcome_index; + nb.amount = transferred_amount; + nb.weight = transfer_weight; + nb.mode = bet.mode; + nb.epoch = bet.epoch; + nb.status = 0; + nb.created_time = bet.created_time; + nb.time_penalty = bet.time_penalty; // B9: inherit — same risk taken at the same time + }); + } +} + +// ─── 17. pm_lazy_deposit ───────────────────────────────────────────────────── + +void pm_lazy_deposit_evaluator::do_apply(const pm_lazy_deposit_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + FC_ASSERT(mp.pm_lazy_pool_enabled, "Lazy pool not enabled"); + const auto now = db.head_block_time(); + + FC_ASSERT(o.amount.symbol == TOKEN_SYMBOL, "Amount must be VIZ"); + FC_ASSERT(o.amount.amount > 0, "Amount must be positive"); + + const auto& acct = db.get_account(o.account); + FC_ASSERT(acct.balance >= o.amount, "Insufficient balance"); + db.adjust_balance(acct, -o.amount); + + const auto& pool = db.get(pm_lazy_pool_id_type(0)); + + share_type new_shares; + // Share price is backed by the pool's LP EQUITY = free + allocated − pending_withdrawals — the + // capital that actually backs outstanding shares — NOT just free_balance. Pricing off + // free_balance alone over-issued shares whenever capital was deployed in markets (allocated>0) + // or owed to queued withdrawers, letting a new depositor mint a disproportionate reward weight. + const int64_t pool_equity = + pool.free_balance.value + pool.allocated_balance.value - pool.pending_withdrawals.value; + if (pool.total_shares.value == 0 || pool_equity <= 0) { + new_shares = o.amount.amount; // empty/insolvent pool → 1:1 reset + } else { + new_shares = share_type((int64_t)( + fc::uint128_t((uint64_t)o.amount.amount.value) * + fc::uint128_t((uint64_t)pool.total_shares.value) / + fc::uint128_t((uint64_t)pool_equity)).lo); + } + FC_ASSERT(new_shares.value > 0, "Zero shares minted"); + + fc::uint128_t rps = pool.reward_per_share; + + db.modify(pool, [&](pm_lazy_pool_object& p) { + p.total_shares += new_shares; + p.free_balance += o.amount.amount; + }); + + const auto& didx = db.get_index().indices().get(); + auto it = didx.find(o.account); + if (it != didx.end()) { + fc::uint128_t pend = (rps - it->reward_snapshot) * + fc::uint128_t((uint64_t)it->shares.value) / + fc::uint128_t((uint64_t)1000000000ULL); + db.modify(*it, [&](pm_lazy_deposit_object& d) { + d.pending_rewards += share_type((int64_t)pend.lo); + d.shares += new_shares; + d.principal += o.amount.amount; + d.reward_snapshot = rps; + }); + } else { + db.create([&](pm_lazy_deposit_object& d) { + d.account = o.account; + d.shares = new_shares; + d.principal = o.amount.amount; + d.reward_snapshot = rps; + d.unlock_time = now + fc::seconds(mp.pm_lazy_lock_sec); + }); + } + service_lazy_withdraw_queue(db); // fresh capital first pays anyone already queued to withdraw +} + +// ─── 18. pm_lazy_withdraw ──────────────────────────────────────────────────── + +void pm_lazy_withdraw_evaluator::do_apply(const pm_lazy_withdraw_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + FC_ASSERT(mp.pm_lazy_pool_enabled, "Lazy pool not enabled"); + const auto now = db.head_block_time(); + + const auto& didx = db.get_index().indices().get(); + auto it = didx.find(o.account); + FC_ASSERT(it != didx.end(), "No lazy deposit found"); + const auto& dep = *it; + + const auto& pool = db.get(pm_lazy_pool_id_type(0)); + FC_ASSERT(pool.total_shares.value > 0, "Pool empty"); + + // Settle the depositor's full accrued rewards (MasterChef: rps delta × shares + carried). + fc::uint128_t pend_u = (pool.reward_per_share - dep.reward_snapshot) * + fc::uint128_t((uint64_t)dep.shares.value) / + fc::uint128_t((uint64_t)1000000000ULL); + share_type pending = share_type((int64_t)pend_u.lo + dep.pending_rewards.value); + + // A planned withdrawal is only allowed once the lock has elapsed; an emergency withdrawal is + // allowed any time (penalised while still locked). Both support a PARTIAL amount: o.shares==0 + // withdraws the whole position, otherwise exactly o.shares are burned. + const bool locked = now < dep.unlock_time; + if (!o.emergency) + FC_ASSERT(!locked, "Deposit is locked; use emergency withdrawal or wait for unlock"); + share_type burn_shares = (o.shares == 0) ? dep.shares : share_type(o.shares); + FC_ASSERT(burn_shares.value > 0 && burn_shares.value <= dep.shares.value, "Invalid shares amount"); + + // Pro-rate principal and accrued rewards to the shares being burned. + share_type principal_out = share_type((int64_t)( + fc::uint128_t((uint64_t)dep.principal.value) * + fc::uint128_t((uint64_t)burn_shares.value) / + fc::uint128_t((uint64_t)dep.shares.value)).lo); + share_type pending_out = share_type((int64_t)( + fc::uint128_t((uint64_t)pending.value) * + fc::uint128_t((uint64_t)burn_shares.value) / + fc::uint128_t((uint64_t)dep.shares.value)).lo); + + // Emergency-while-locked penalty applies to the withdrawn PROFIT only (never principal); it + // stays in the pool and is redistributed to the remaining LPs via reward_per_share. + share_type penalty(0); + if (o.emergency && locked) + penalty = share_type(pending_out.value * mp.pm_lazy_emergency_penalty_percent / 10000); + share_type owed = share_type(principal_out.value + pending_out.value - penalty.value); + + // Burn the shares now, register the amount owed as a first-claim liability, and redistribute the + // penalty to the remaining LPs. The payout itself is NOT made here — it is queued and serviced + // FIFO from free_balance, so the pool never hands out capital it doesn't hold liquid. + const share_type remaining = share_type(pool.total_shares.value - burn_shares.value); + db.modify(pool, [&](pm_lazy_pool_object& p) { + p.total_shares -= burn_shares; + p.pending_withdrawals += owed; + if (penalty.value > 0 && remaining.value > 0) + p.reward_per_share += fc::uint128_t((uint64_t)penalty.value) + * fc::uint128_t((uint64_t)1000000000ULL) + / fc::uint128_t((uint64_t)remaining.value); + }); + + if (burn_shares == dep.shares) { + db.remove(dep); + } else { + const auto& pool2 = db.get(pm_lazy_pool_id_type(0)); // penalty-bumped rps + db.modify(dep, [&](pm_lazy_deposit_object& d) { + d.shares -= burn_shares; + d.principal -= principal_out; + d.pending_rewards = share_type(pending.value - pending_out.value); + d.reward_snapshot = pool2.reward_per_share; + }); + } + + // Queue the owed amount (FIFO by id) and pay out as much as free_balance covers right now. Any + // uncovered remainder waits in the queue and is paid as capital returns to the pool — older + // queued withdrawers are always paid first and free_balance never goes negative. + if (owed.value > 0) { + db.create([&](pm_lazy_withdraw_request_object& r) { + r.account = o.account; + r.amount = owed; + r.created = now; + }); + } + service_lazy_withdraw_queue(db); +} + +// ─── 19. pm_leverage_open (margin position via lazy-pool loan) ─────────────── + +void pm_leverage_open_evaluator::do_apply(const pm_leverage_open_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + FC_ASSERT(mp.pm_leverage_enabled, "Leverage not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.status == 1, "Market not active"); + FC_ASSERT(mkt.market_type == 0, "Leverage is CPMM-binary only"); + FC_ASSERT(o.outcome_index == 0 || o.outcome_index == 1, "outcome_index must be 0/1"); + // The expiration buffer only applies to markets with a fixed betting deadline. Open-ended + // markets (betting_expiration == 0) have no deadline to buffer against, so leverage stays + // available for the market's entire active life — the extra volatility on an open-ended risk + // is the bettor's own choice, not a consensus concern. Positions are force-closed on + // resolve/void either way (settle_market / return_liquidity). Guarding the subtraction also + // avoids the epoch-0 underflow of (betting_expiration - buffer). + if (mkt.betting_expiration != time_point_sec()) { + FC_ASSERT(now < mkt.betting_expiration - fc::seconds(mp.pm_leverage_expiration_buffer_sec), + "Too close to betting expiration for leverage"); + } + FC_ASSERT(mkt.liquidity_sum >= mp.pm_leverage_min_market_liquidity.amount, + "Market liquidity below leverage minimum"); + FC_ASSERT(o.collateral.symbol == TOKEN_SYMBOL && o.loan.symbol == TOKEN_SYMBOL, "Must be VIZ"); + + const auto& acct = db.get_account(o.account); + FC_ASSERT(acct.balance >= o.collateral, "Insufficient balance for collateral"); + + const auto& pool = db.get(pm_lazy_pool_id_type(0)); + const int64_t loan = o.loan.amount.value; + const int64_t collateral = o.collateral.amount.value; + + // Constraint 1: leverage-fund availability + per-position cap. + int64_t free_amount = pool.free_balance.value - pool.leverage_fund_used.value; + FC_ASSERT(loan <= free_amount, "Loan exceeds pool free capital"); + int64_t fund_total = (int64_t)(fc::uint128_t((uint64_t)pool.free_balance.value) + * fc::uint128_t(mp.pm_leverage_fund_percent) / fc::uint128_t(100u)).lo; + int64_t fund_available = fund_total - pool.leverage_fund_used.value; + FC_ASSERT(fund_available > 0, "Leverage fund exhausted"); + int64_t per_pos_cap = (int64_t)(fc::uint128_t((uint64_t)fund_available) + * fc::uint128_t(mp.pm_leverage_max_per_position_bp) / fc::uint128_t(10000u)).lo; + FC_ASSERT(loan <= per_pos_cap, "Loan exceeds per-position cap"); + + // Constraint 3: max position size relative to market. + const int64_t total_bet = collateral + loan; + int64_t pos_cap = (int64_t)(fc::uint128_t((uint64_t)mkt.liquidity_sum.value) + * fc::uint128_t(mp.pm_leverage_max_position_ratio_percent) / fc::uint128_t(100u)).lo; + FC_ASSERT(total_bet <= pos_cap, "Position exceeds market size limit"); + + // Place (C+L) into the CPMM (k preserved). + pm::leverage::cpmm_fill fill = pm::leverage::cpmm_buy(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + total_bet, (int)o.outcome_index); + FC_ASSERT(fill.tokens > 0, "Zero tokens"); + FC_ASSERT(fill.tokens >= o.min_tokens.value, "Slippage: tokens below min_tokens"); + + // Constraint 2 / Rule 8: worst-case solvency guarantee for the pool. + int64_t m = pm::leverage::worst_opposing_bet(fill.new_reserve_a, fill.new_reserve_b, + mp.pm_leverage_max_slippage_percent, mp.pm_leverage_m_factor_percent); + int64_t cvw = pm::leverage::cancel_value_after_opposing(fill.new_reserve_a, fill.new_reserve_b, mkt.k, + fill.tokens, (int)o.outcome_index, m); + int64_t threshold = pm::leverage::liquidation_threshold(loan, mp.pm_leverage_pool_profit_percent); + int64_t threshold_safe = (int64_t)(fc::uint128_t((uint64_t)threshold) + * fc::uint128_t(100u + mp.pm_leverage_safety_margin_percent) / fc::uint128_t(100u)).lo; + FC_ASSERT(cvw >= threshold_safe, "Position fails worst-case safety check"); + + // Apply: bettor pays collateral, pool fronts the loan, capital enters the curve. + db.adjust_balance(acct, -o.collateral); + db.pm_adjust_frozen(o.account, 2, o.collateral.amount); // LOCK: collateral → active leverage (loan is pool capital, not counted) + db.modify(pool, [&](pm_lazy_pool_object& p) { + p.free_balance -= share_type(loan); + p.leverage_fund_used += share_type(loan); + }); + db.modify(mkt, [&](pm_market_object& mm) { + mm.reserve_a = share_type(fill.new_reserve_a); + mm.reserve_b = share_type(fill.new_reserve_b); + }); + db.create([&](pm_leverage_position_object& pos) { + pos.market = mkt.id; + pos.account = o.account; + pos.outcome_index = o.outcome_index; + pos.collateral = share_type(collateral); + pos.loan = share_type(loan); + pos.total_bet = share_type(total_bet); + pos.tokens = share_type(fill.tokens); + pos.pool_profit = share_type(threshold - loan); + pos.liquidation_threshold = share_type(threshold); + pos.status = 0; + pos.created_time = now; + pos.last_update = now; + pos.funding_paid = share_type(0); + pos.funding_due_time = time_point_sec(now + fc::seconds(86400)); // first 24h funding period + }); +} + +// ─── 20. pm_leverage_close (voluntary, only when cancel_value >= threshold) ──── + +void pm_leverage_close_evaluator::do_apply(const pm_leverage_close_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& pos = db.get(pm_leverage_position_id_type(o.position_id)); + FC_ASSERT(pos.account == o.account, "Not your position"); + FC_ASSERT(pos.status == 0, "Position not active"); + accrue_leverage_funding(db, pos, + db.get_validator_schedule_object().median_props.pm_leverage_funding_rate_ppm_per_day, now); + const auto& mkt = db.get(pos.market); + + int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + pos.tokens.value, (int)pos.outcome_index); + const int64_t obligation = pos.liquidation_threshold.value + pos.funding_paid.value; + FC_ASSERT(cv >= obligation, "Position underwater: cannot voluntarily close"); + int64_t bettor_received = cv - obligation; + FC_ASSERT(bettor_received >= o.min_return, "Return below min_return"); + + // F1/#300: the pool recovers `obligation` from cv; everything it does not take stays in the pot + // (forfeit_pool gets total_bet − obligation, ≥ 0). The bettor's profit (cv − obligation) is NOT + // paid against the curve now — it becomes an OUTCOME-CONTINGENT deferred claim, paid at settlement + // from the bounded early-exit bucket iff this outcome wins. Keeps forfeit ≥ 0 (no LP hit / mint). + const int64_t pot_retained = pos.total_bet.value - obligation; + // Clamp at 0 (see liquidate_position): long-lived positions accrue unbounded funding, so + // obligation can exceed total_bet; a negative pot_retained would push forfeit_pool negative + // and create an `uncovered` shortfall at settlement. The pool recovers obligation from cv + // either way; the pot just never goes negative. + const int64_t pot_retained_capped = pot_retained > 0 ? pot_retained : 0; + // Unwind the tokens from the curve (k preserved). + db.modify(mkt, [&](pm_market_object& m) { + if (pos.outcome_index == 0) { + int64_t new_rb = m.reserve_b.value + pos.tokens.value; + m.reserve_b = share_type(new_rb); + m.reserve_a = share_type((int64_t)(m.k / fc::uint128_t((uint64_t)new_rb)).lo); + } else { + int64_t new_ra = m.reserve_a.value + pos.tokens.value; + m.reserve_a = share_type(new_ra); + m.reserve_b = share_type((int64_t)(m.k / fc::uint128_t((uint64_t)new_ra)).lo); + } + m.forfeit_pool += share_type(pot_retained_capped); + }); + int64_t pool_yield = pos.pool_profit.value + pos.funding_paid.value; // R-markup + accrued funding → LP yield + db.modify(db.get(pm_lazy_pool_id_type(0)), [&](pm_lazy_pool_object& p) { + p.free_balance += share_type(obligation); + p.leverage_fund_used -= pos.loan; + p.earned_balance += share_type(pool_yield); + if (p.total_shares.value > 0) + p.reward_per_share += fc::uint128_t((uint64_t)pool_yield) + * fc::uint128_t((uint64_t)1000000000) / fc::uint128_t((uint64_t)p.total_shares.value); + }); + service_lazy_withdraw_queue(db); // returning leverage capital first pays queued withdrawers + if (bettor_received > 0 && mkt.deferred_claim_count < MAX_PM_DEFERRED_CLAIMS_PER_MARKET) { + // #349: skip once the per-market cap is hit — the residual stays in the curve and pays 0 at + // settlement (like bucket-exhaustion), keeping settle_market's claim loop bounded. + db.create([&](pm_deferred_claim_object& c) { + c.market = pos.market; c.account = o.account; c.kind = 1; + c.outcome_index = pos.outcome_index; c.claim_amount = share_type(bettor_received); c.exit_time = now; + }); + db.modify(mkt, [](pm_market_object& m) { m.deferred_claim_count++; }); + } + db.modify(pos, [&](pm_leverage_position_object& p) { + p.status = 4; p.pool_received = share_type(obligation); + p.bettor_received = share_type(bettor_received); p.last_update = now; + }); + db.pm_adjust_frozen(o.account, 2, -pos.collateral); // UNLOCK: collateral leaves active leverage (voluntary close) +} + +// ─── 21. pm_leverage_convert (pay off loan, keep position as a normal bet) ───── + +void pm_leverage_convert_evaluator::do_apply(const pm_leverage_convert_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto& mp = median(db); + FC_ASSERT(o.conversion_profit_cost == mp.pm_conversion_profit_cost_percent, + "conversion_profit_cost must equal current consensus value"); + const auto now = db.head_block_time(); + + const auto& pos = db.get(pm_leverage_position_id_type(o.position_id)); + FC_ASSERT(pos.account == o.account, "Not your position"); + FC_ASSERT(pos.status == 0, "Position not active"); + accrue_leverage_funding(db, pos, mp.pm_leverage_funding_rate_ppm_per_day, now); + const auto& mkt = db.get(pos.market); + + int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + pos.tokens.value, (int)pos.outcome_index); + const int64_t obligation = pos.liquidation_threshold.value + pos.funding_paid.value; + FC_ASSERT(cv >= obligation, "Position underwater"); + int64_t current_profit = cv - obligation; + FC_ASSERT(current_profit > 0, "No profit to convert"); + int64_t conversion_fee = (int64_t)(fc::uint128_t((uint64_t)current_profit) + * fc::uint128_t(o.conversion_profit_cost) / fc::uint128_t(100u)).lo; + const int64_t total_payment = obligation + conversion_fee; + + const auto& acct = db.get_account(o.account); + FC_ASSERT(acct.balance.amount >= total_payment, "Insufficient balance for conversion"); + db.adjust_balance(acct, -asset(share_type(total_payment), TOKEN_SYMBOL)); + + int64_t pool_profit_total = pos.pool_profit.value + conversion_fee + pos.funding_paid.value; + db.modify(db.get(pm_lazy_pool_id_type(0)), [&](pm_lazy_pool_object& p) { + p.free_balance += share_type(total_payment); + p.leverage_fund_used -= pos.loan; + p.earned_balance += share_type(pool_profit_total); + if (p.total_shares.value > 0) + p.reward_per_share += fc::uint128_t((uint64_t)pool_profit_total) + * fc::uint128_t((uint64_t)1000000000) / fc::uint128_t((uint64_t)p.total_shares.value); + }); + service_lazy_withdraw_queue(db); // returning leverage capital first pays queued withdrawers + + // Position becomes a normal parimutuel bet, 100% bettor-owned (reserves unchanged). + db.create([&](pm_bet_object& b) { + b.market = mkt.id; + b.account = o.account; + b.side = (int8_t)pos.outcome_index; + b.outcome_index = -1; + b.amount = pos.total_bet; + b.weight = pos.tokens; + b.status = 0; + b.created_time = now; + // B9: the exposure was taken when the leverage position was OPENED, not at conversion — + // penalise by pos.created_time (leverage opens are gated ≥24h before expiry, so this is + // virtually always 0 unless the market runs a very wide percentage window). + b.time_penalty = compute_time_penalty(mkt, pos.created_time, mp.pm_max_time_penalty); + }); + db.modify(mkt, [&](pm_market_object& m) { + if (pos.outcome_index == 0) m.a_bets_sum += pos.total_bet; + else m.b_bets_sum += pos.total_bet; + m.bets_sum += pos.total_bet; + }); + db.modify(pos, [&](pm_leverage_position_object& p) { p.status = 5; p.last_update = now; }); + // MOVE: position converts to a plain bet — collateral leaves leverage, total_bet enters bets. + db.pm_adjust_frozen(o.account, 2, -pos.collateral); + db.pm_adjust_frozen(o.account, 1, pos.total_bet); +} + +// ─── 22. pm_dispute_oracle_respond ─────────────────────────────────────────── +// The oracle posts a public rebuttal onto the open dispute. Disputes are public hearings, so the +// text is stored on the dispute object (read by every voter/resolver via get_dispute). Allowed +// only while the dispute is open and within the oracle_response_deadline; re-posting overwrites. +void pm_dispute_oracle_respond_evaluator::do_apply(const pm_dispute_oracle_respond_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + + const auto& mkt = get_market(db, o.market_id); + FC_ASSERT(mkt.oracle == o.oracle, "Not the market oracle"); + FC_ASSERT(o.response.size() <= MAX_PM_DISPUTE_REASON_LEN, "response too long"); + + const auto& didx = db.get_index().indices().get(); + auto dit = didx.find(mkt.id); + FC_ASSERT(dit != didx.end() && dit->status == 0, "No open dispute"); + FC_ASSERT(now <= dit->oracle_response_deadline, "Oracle response window passed"); + + // First response moves the open dispute from awaiting-response to awaiting-decision. A repeat + // response (re-posting overwrites) leaves it in awaiting-decision — do not double-count. + if (dit->oracle_response_time == time_point_sec()) { + pm_oracle_gauge_adj(db, mkt.oracle, &pm_oracle_object::disputes_awaiting_response, -1); + pm_oracle_gauge_adj(db, mkt.oracle, &pm_oracle_object::disputes_awaiting_decision, +1); + } + + db.modify(*dit, [&](pm_dispute_object& d) { + from_string(d.oracle_response, o.response); + d.oracle_response_time = now; + }); +} + +// ─── 23. pm_unban ──────────────────────────────────────────────────────────── +// Reverse a ban set by an account-mode pm_dispute_resolve. Only the resolver recorded in +// banned_by may lift it; the ban is set to epoch (past ⇒ not banned) and banned_by cleared. +void pm_unban_evaluator::do_apply(const pm_unban_operation& o) { + auto& db = _db; + FC_ASSERT(db.has_hardfork(CHAIN_HARDFORK_14), "PM not enabled"); + const auto now = db.head_block_time(); + bool did = false; + + if (o.unban_oracle) { + const auto& oidx = db.get_index().indices().get(); + auto it = oidx.find(o.target); + FC_ASSERT(it != oidx.end(), "Oracle not found"); + FC_ASSERT(it->banned_until > now, "Oracle is not currently banned"); + FC_ASSERT(it->banned_by == o.resolver, "Only the resolver that imposed the ban may lift it"); + db.modify(*it, [&](pm_oracle_object& ora) { + ora.banned_until = time_point_sec(0); + ora.banned_by = account_name_type(); + }); + did = true; + } + if (o.unban_creator) { + const auto& cbidx = db.get_index().indices().get(); + auto it = cbidx.find(o.target); + FC_ASSERT(it != cbidx.end(), "No creator ban for target"); + FC_ASSERT(it->banned_until > now, "Creator is not currently banned"); + FC_ASSERT(it->banned_by == o.resolver, "Only the resolver that imposed the ban may lift it"); + db.modify(*it, [&](pm_creator_ban_object& b) { + b.banned_until = time_point_sec(0); + b.banned_by = account_name_type(); + }); + did = true; + } + FC_ASSERT(did, "Nothing to unban"); +} + +// ─── Cron: process_pm_markets (called once per block) ─────────────────────── +// +// Processes PM state-machine transitions bounded by pm_processing_cap_per_block. +// Uses the anonymous-namespace helpers (settle_market, refund_all_bets, +// return_liquidity) that are visible within this translation unit. + +// PM frozen-funds telemetry helper (display-only). kind: 0=liquidity, 1=bets, 2=leverage. +// Clamped at zero so a stray double-decrement can never wrap a share_type negative — these +// are cosmetic aggregates, never consensus inputs. +void database::pm_adjust_frozen(const account_name_type& account, uint8_t kind, share_type delta) { + if (delta == 0) return; + const auto* a = find_account(account); + if (a == nullptr) return; + modify(*a, [&](account_object& acc) { + asset* f = (kind == 0) ? &acc.pm_liquidity_committed + : (kind == 1) ? &acc.pm_bets_staked + : &acc.pm_leverage_collateral; + f->amount += delta; + if (f->amount < 0) f->amount = 0; + }); +} + +// (Re)seed the per-account frozen counters from existing PM objects. Runs once, on the first block +// after the upgrade (guarded by dgpo flags), so the counters are correct without a full replay (VIZ +// testnet is DLT-only, no genesis). **Idempotent:** every account's three counters are zeroed first, +// then re-summed from the live objects — so re-running it (the task #266 corrective re-seed) always +// converges to the exact object totals regardless of any prior over/under-count. +void database::pm_seed_frozen_counters() { + // Reset first — makes the (re)seed idempotent. Counters are display-only (never gate consensus), + // so recomputing them is safe. Without this a second run would double-count the objects. + const auto& aidx = get_index().indices().get(); + for (auto it = aidx.begin(); it != aidx.end(); ++it) { + if (it->pm_liquidity_committed.amount.value != 0 || + it->pm_bets_staked.amount.value != 0 || + it->pm_leverage_collateral.amount.value != 0) { + modify(*it, [&](account_object& a) { + a.pm_liquidity_committed = asset(0, TOKEN_SYMBOL); + a.pm_bets_staked = asset(0, TOKEN_SYMBOL); + a.pm_leverage_collateral = asset(0, TOKEN_SYMBOL); + }); + } + } + // liquidity: own provider liquidity in live markets (skip empty-provider lazy-pool rows) + const auto& lidx = get_index().indices().get(); + for (auto it = lidx.begin(); it != lidx.end(); ++it) { + if (it->status == 0 && it->provider.size() > 0) + pm_adjust_frozen(it->provider, 0, it->amount.value); + } + // bets: own stake still held on-chain — status 0 active, 5 queued, 6 revealed-pending + // (1 cancelled / 2 refunded / 3 resolved have already returned to free balance) + const auto& bidx = get_index().indices().get(); + for (auto it = bidx.begin(); it != bidx.end(); ++it) { + if (it->status == 0 || it->status == 5 || it->status == 6) + pm_adjust_frozen(it->account, 1, it->amount.value); + } + // leverage: own collateral in active positions + const auto& vidx = get_index().indices().get(); + for (auto it = vidx.begin(); it != vidx.end(); ++it) { + if (it->status == 0) + pm_adjust_frozen(it->account, 2, it->collateral.value); + } +} + +// Debug drift-check for the frozen counters (see header). Independently re-derives the expected +// per-account totals from the live objects and diffs them against the stored aggregates. O(accounts +// + PM objects); intended for tests / manual invocation, not per-block. Returns true when clean. +bool database::pm_verify_frozen_counters() const { + std::map> exp; + const auto& lidx = get_index().indices().get(); + for (auto it = lidx.begin(); it != lidx.end(); ++it) + if (it->status == 0 && it->provider.size() > 0) exp[it->provider][0] += it->amount.value; + const auto& bidx = get_index().indices().get(); + for (auto it = bidx.begin(); it != bidx.end(); ++it) + if (it->status == 0 || it->status == 5 || it->status == 6) exp[it->account][1] += it->amount.value; + const auto& vidx = get_index().indices().get(); + for (auto it = vidx.begin(); it != vidx.end(); ++it) + if (it->status == 0) exp[it->account][2] += it->collateral.value; + + bool ok = true; + const auto& aidx = get_index().indices().get(); + for (auto it = aidx.begin(); it != aidx.end(); ++it) { + auto e = exp.find(it->name); + int64_t el = e == exp.end() ? 0 : e->second[0]; + int64_t eb = e == exp.end() ? 0 : e->second[1]; + int64_t ev = e == exp.end() ? 0 : e->second[2]; + if (it->pm_liquidity_committed.amount.value != el || + it->pm_bets_staked.amount.value != eb || + it->pm_leverage_collateral.amount.value != ev) { + ok = false; + elog("pm frozen-counter drift ${a}: liq ${cl}!=${el} bets ${cb}!=${eb} lev ${cv}!=${ev}", + ("a", it->name)("cl", it->pm_liquidity_committed.amount.value)("el", el) + ("cb", it->pm_bets_staked.amount.value)("eb", eb) + ("cv", it->pm_leverage_collateral.amount.value)("ev", ev)); + } + } + return ok; +} + +// One-time seed of pm_oracle_object.active_markets from live markets (see header). Zeroes every +// oracle's counter then re-sums the status==1 markets per registered oracle — idempotent, display-only. +void database::pm_seed_oracle_active_markets() { + const auto& obyid = get_index().indices().get(); + for (auto it = obyid.begin(); it != obyid.end(); ++it) + if (it->active_markets != 0) + modify(*it, [](pm_oracle_object& o) { o.active_markets = 0; }); + const auto& obyowner = get_index().indices().get(); + const auto& midx = get_index().indices().get(); + for (auto it = midx.lower_bound((int8_t)1); it != midx.end() && it->status == 1; ++it) { + auto oit = obyowner.find(it->oracle); + if (oit != obyowner.end()) + modify(*oit, [](pm_oracle_object& o) { o.active_markets++; }); + } +} + +// Debug drift-check for active_markets: re-derive the live count per oracle and diff the stored +// counter. O(active markets + oracles); read-only. Logs every mismatch, returns true when clean. +bool database::pm_verify_oracle_active_markets() const { + std::map exp; + const auto& midx = get_index().indices().get(); + for (auto it = midx.lower_bound((int8_t)1); it != midx.end() && it->status == 1; ++it) + exp[it->oracle]++; + bool ok = true; + const auto& oidx = get_index().indices().get(); + for (auto it = oidx.begin(); it != oidx.end(); ++it) { + auto e = exp.find(it->owner); + uint32_t ev = (e == exp.end()) ? 0u : e->second; + if (it->active_markets != ev) { + ok = false; + elog("pm active_markets drift ${o}: ${c}!=${e}", + ("o", it->owner)("c", it->active_markets)("e", ev)); + } + } + return ok; +} + +// One-time seed of the per-oracle workload gauges (see header/pm_objects.hpp). Zeroes every +// oracle's three counters, then re-derives them from live state — idempotent, display-only. +// markets_in_dispute_window = status3 + payout_status1 markets with NO dispute row. +// disputes_awaiting_response = open(0) disputes with oracle_response_time == epoch. +// disputes_awaiting_decision = open(0) disputes with oracle_response_time > epoch. +void database::pm_seed_oracle_gauges() { + const auto& obyid = get_index().indices().get(); + for (auto it = obyid.begin(); it != obyid.end(); ++it) + if (it->markets_in_dispute_window || it->disputes_awaiting_response || it->disputes_awaiting_decision) + modify(*it, [](pm_oracle_object& o) { + o.markets_in_dispute_window = 0; + o.disputes_awaiting_response = 0; + o.disputes_awaiting_decision = 0; + }); + + const auto& obyowner = get_index().indices().get(); + const auto& didx = get_index().indices().get(); + const auto& midx = get_index().indices().get(); + for (auto it = midx.lower_bound((int8_t)3); it != midx.end() && it->status == 3; ++it) { + if (it->payout_status != 1) continue; + if (didx.find(it->id) != didx.end()) continue; // disputed → not in the disputable window + auto oit = obyowner.find(it->oracle); + if (oit != obyowner.end()) + modify(*oit, [](pm_oracle_object& o) { o.markets_in_dispute_window++; }); + } + + const auto& dclose = get_index().indices().get(); + for (auto it = dclose.lower_bound(boost::make_tuple((uint8_t)0, time_point_sec(0), pm_dispute_id_type())); + it != dclose.end() && it->status == 0; ++it) { + const auto& mbyid = get_index().indices().get(); + auto mit = mbyid.find(it->market); + if (mit == mbyid.end()) continue; + auto oit = obyowner.find(mit->oracle); + if (oit == obyowner.end()) continue; + const bool responded = (it->oracle_response_time != time_point_sec()); + modify(*oit, [&](pm_oracle_object& o) { + if (responded) o.disputes_awaiting_decision++; else o.disputes_awaiting_response++; + }); + } +} + +// Debug drift-check for the workload gauges: re-derive per-oracle from live state and diff the +// stored counters. O(resolved-pending markets + open disputes + oracles); read-only. Logs each +// mismatch, returns true when clean. +bool database::pm_verify_oracle_gauges() const { + std::map win, resp, dec; + const auto& didx = get_index().indices().get(); + const auto& midx = get_index().indices().get(); + const auto& mbyid = get_index().indices().get(); + for (auto it = midx.lower_bound((int8_t)3); it != midx.end() && it->status == 3; ++it) { + if (it->payout_status != 1) continue; + if (didx.find(it->id) != didx.end()) continue; + win[it->oracle]++; + } + const auto& dclose = get_index().indices().get(); + for (auto it = dclose.lower_bound(boost::make_tuple((uint8_t)0, time_point_sec(0), pm_dispute_id_type())); + it != dclose.end() && it->status == 0; ++it) { + auto mit = mbyid.find(it->market); + if (mit == mbyid.end()) continue; + if (it->oracle_response_time != time_point_sec()) dec[mit->oracle]++; + else resp[mit->oracle]++; + } + bool ok = true; + const auto& oidx = get_index().indices().get(); + for (auto it = oidx.begin(); it != oidx.end(); ++it) { + auto fw = win.find(it->owner); uint32_t ew = (fw == win.end()) ? 0u : fw->second; + auto fr = resp.find(it->owner); uint32_t er = (fr == resp.end()) ? 0u : fr->second; + auto fd = dec.find(it->owner); uint32_t ed = (fd == dec.end()) ? 0u : fd->second; + if (it->markets_in_dispute_window != ew || it->disputes_awaiting_response != er + || it->disputes_awaiting_decision != ed) { + ok = false; + elog("pm oracle gauges drift ${o}: win ${a}/${ea} resp ${b}/${eb} dec ${c}/${ec}", + ("o", it->owner)("a", it->markets_in_dispute_window)("ea", ew) + ("b", it->disputes_awaiting_response)("eb", er) + ("c", it->disputes_awaiting_decision)("ec", ed)); + } + } + return ok; +} + +void database::process_pm_markets() { + if (!has_hardfork(CHAIN_HARDFORK_14)) return; + + // First seed OR the task #266 corrective re-seed (fixes the over-counted live counters). The + // (re)seed zeroes then re-sums from objects, so running it again converges to the exact totals. + if (!get_dynamic_global_properties().pm_frozen_counters_reseeded_v1) { + const bool first = !get_dynamic_global_properties().pm_frozen_counters_seeded; + pm_seed_frozen_counters(); + modify(get_dynamic_global_properties(), [](dynamic_global_property_object& d) { + d.pm_frozen_counters_seeded = true; + d.pm_frozen_counters_reseeded_v1 = true; + }); + ilog("pm frozen-counters ${w}; drift-check ${r}", + ("w", first ? "seeded" : "re-seeded (#266 correction)") + ("r", pm_verify_frozen_counters() ? "clean" : "MISMATCH")); + } + + // One-time seed of the per-oracle live active-market counter (display-only). Runs once on the + // first block after this upgrade; thereafter maintained incrementally by pm_oracle_inc/dec_active. + if (!get_dynamic_global_properties().pm_active_markets_seeded) { + pm_seed_oracle_active_markets(); + modify(get_dynamic_global_properties(), [](dynamic_global_property_object& d) { + d.pm_active_markets_seeded = true; + }); + ilog("pm oracle active_markets seeded; drift-check ${r}", + ("r", pm_verify_oracle_active_markets() ? "clean" : "MISMATCH")); + } + + // One-time seed of the per-oracle workload gauges (display-only). Same pattern as above. + if (!get_dynamic_global_properties().pm_oracle_gauges_seeded) { + pm_seed_oracle_gauges(); + modify(get_dynamic_global_properties(), [](dynamic_global_property_object& d) { + d.pm_oracle_gauges_seeded = true; + }); + ilog("pm oracle workload gauges seeded; drift-check ${r}", + ("r", pm_verify_oracle_gauges() ? "clean" : "MISMATCH")); + } + + const auto now = head_block_time(); + const auto& mp = get_validator_schedule_object().median_props; + uint32_t cap = (mp.pm_processing_cap_per_block > 0) ? (uint32_t)mp.pm_processing_cap_per_block : 20u; + uint32_t done = 0; + + // ── 1. Commit forfeits ──────────────────────────────────────────────────── + // Commitments that were never revealed within the reveal window. + { + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (uint8_t)0, time_point_sec(0), pm_commit_id_type())); + while (it != idx.end() && it->status == 0 && it->reveal_deadline <= now && done < cap) { + const auto& commit = *it; ++it; + + share_type penalty = share_type(commit.escrow_amount.value * + commit.no_reveal_fee_percent / 10000); + share_type refund = share_type(commit.escrow_amount.value - penalty.value); + + if (refund.value > 0) + adjust_balance(get_account(commit.account), asset(refund, TOKEN_SYMBOL)); + + if (penalty.value > 0) { + const auto* mkt_ptr = find(commit.market); + if (mkt_ptr) + modify(*mkt_ptr, [&](pm_market_object& m) { m.forfeit_pool += penalty; }); + } + + push_virtual_operation(pm_commit_forfeit_operation( + commit.account, commit.id._id, commit.market._id, + asset(penalty, TOKEN_SYMBOL), asset(refund, TOKEN_SYMBOL))); + + modify(commit, [](pm_commit_object& c) { c.status = 2; }); // forfeited + ++done; + } + } + + // ── 2. Oracle missed resolution deadline ────────────────────────────────── + // Active markets whose result_expiration passed with no oracle report, plus a resolution grace. + // The grace is not optional politeness: process_pm_markets() runs at the END of a block, after + // dgp.time has advanced to this block's timestamp (database.cpp update_global_dynamic_data), + // whereas in-block transactions still saw the PREVIOUS block's time. A zero-slack void + // (result_expiration <= now) therefore fires one block before any resolve transaction's clock + // can first reach result_expiration, making pm_resolve_market unreachable for a fixed-deadline + // market without allow_early_resolution — it would always die here as missed-resolution and slash + // the oracle for a deadline it had no reachable block to meet. Voiding only once + // result_expiration + pm_dispute_grace_sec has elapsed (the same cutoff the settle sweep in §5 + // uses) leaves the oracle a real window [result_expiration, result_expiration + grace] to report. + { + const time_point_sec cutoff( + (now.sec_since_epoch() > mp.pm_dispute_grace_sec) + ? (now.sec_since_epoch() - mp.pm_dispute_grace_sec) + : 0u); + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (int8_t)1, time_point_sec(0), pm_market_id_type())); + while (it != idx.end() && it->status == 1 && it->result_expiration <= cutoff && done < cap) { + const auto& mkt = *it; ++it; + + share_type slashed(0); + const auto& oidx = get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + if (oit != oidx.end() && oit->insurance.value > 0) { + slashed = share_type(oit->insurance.value * mp.pm_oracle_penalty_percent / 10000); + if (slashed.value > 0) { + modify(*oit, [&](pm_oracle_object& o) { + o.insurance -= slashed; + o.missed_count++; + o.total_insurance_slashed += slashed; + }); + adjust_balance(get_account(CHAIN_COMMITTEE_ACCOUNT), + asset(slashed, TOKEN_SYMBOL)); + } + } + + refund_all_bets(*this, mkt); + return_liquidity(*this, mkt); + + pm_oracle_dec_active(*this, mkt); // leaves active set (1 → 3, missed-resolution void) + modify(mkt, [&](pm_market_object& m) { + m.status = 3; + m.payout_status = 3; // closed — no payout + m.resolved_outcome = -1; + m.finalized_time = now; + }); + + push_virtual_operation(pm_oracle_missed_penalty_operation( + mkt.oracle, mkt.id._id, asset(slashed, TOKEN_SYMBOL))); + ++done; + } + } + + // ── 2b. Oracle acceptance window expired ─────────────────────────────────── + // Pending markets (status 0) the named oracle never accepted nor rejected within + // pm_oracle_accept_window_sec. Refund the creator's seed liquidity (return_liquidity) + // and void the market (status -1). The non-refundable creation fee already went to the + // DAO fund at creation and is NOT returned. Mirrors the oracle-reject path, minus the + // oracle action. liquidity_sum == the single seed LP (a pending market cannot receive + // pm_add_liquidity), so it is the amount reported as refunded. + { + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (int8_t)0, time_point_sec(0), pm_market_id_type())); + while (it != idx.end() && it->status == 0 && it->accept_deadline <= now && done < cap) { + const auto& mkt = *it; ++it; + share_type refunded = mkt.liquidity_sum; + return_liquidity(*this, mkt); + modify(mkt, [&](pm_market_object& m) { + m.status = -1; + m.payout_status = 3; // closed — no payout + m.finalized_time = now; + }); + push_virtual_operation(pm_market_expired_operation( + mkt.oracle, mkt.creator, mkt.id._id, asset(refunded, TOKEN_SYMBOL))); + ++done; + } + } + + // ── 2c. Leverage funding accrual + liquidation check ────────────────────── + // Active leverage positions whose 24h funding period has come due: charge the carry cost + // (raising the effective obligation via accrue_leverage_funding), then re-price at current + // reserves — if the higher obligation now exceeds cancel_value, liquidate (reason 3). This is + // the "recompute the liquidation point + maybe liquidate" step. by_lev_funding_due keeps the + // due set contiguous (status 0, funding_due_time ascending); accrue advances the clock so a + // position is revisited only once per period. funding_due_time > 0 sentinel skips legacy rows. + { + const uint32_t frate = mp.pm_leverage_funding_rate_ppm_per_day; + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple((uint8_t)0, time_point_sec(1), pm_leverage_position_id_type())); + while (it != idx.end() && it->status == 0 && it->funding_due_time <= now && done < cap) { + const auto& pos = *it; ++it; + accrue_leverage_funding(*this, pos, frate, now); + const auto& mkt = get(pos.market); + int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + pos.tokens.value, (int)pos.outcome_index); + if (cv <= pos.liquidation_threshold.value + pos.funding_paid.value) + liquidate_position(*this, pos, 3); // reason 3 = funding pulled the position underwater + ++done; + } + } + + // ── 2d. Force-close leverage once betting can no longer happen ───────────── + // A leveraged position is a bet on the market PRICE (crowd sentiment) and settles at its + // cancel_value — it does NOT depend on the oracle outcome. So it must not linger open through + // resolution + dispute grace, bleeding carry (funding still accrues, secs 2c) and staying + // funding-liquidatable while nobody can even bet anymore. Close it the moment NEW betting is + // impossible: at betting_expiration for fixed-deadline markets (before the oracle resolves), or + // at resolve/void (status >= 3) for open-ended markets (betting_expiration == 0, no deadline). + // settle_market / return_liquidity still force-close as a backstop; both are idempotent (a + // position already out of status 0 is skipped). Anchored on the OPEN-position set (status 0) + // via by_lev_funding_due so each position is force-closed exactly once and, once it flips out + // of status 0, never re-scanned. done is charged per close only (cap = settlements/block); the + // status-0 working set is bounded by pool free capital, so a full scan per block is cheap. + { + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (uint8_t)0, time_point_sec(), pm_leverage_position_id_type())); + while (it != idx.end() && it->status == 0 && done < cap) { + const auto& pos = *it; ++it; + const auto& mkt = get(pos.market); + const bool betting_over = + (mkt.betting_expiration != time_point_sec() && mkt.betting_expiration <= now) + || mkt.status >= 3; + if (betting_over) { + liquidate_position(*this, pos, 2); // reason 2 = settlement force-close (pm_leverage_resolve) + ++done; + } + } + } + + // ── 3. Dispute auto-close (priority over voting finalize) ───────────────── + { + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (uint8_t)0, time_point_sec(0), pm_dispute_id_type())); + while (it != idx.end() && it->status == 0 && it->auto_close_time <= now && done < cap) { + const auto& disp = *it; ++it; + const auto& mkt = get(disp.market); + + share_type slashed(0); + const auto& oidx = get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + if (oit != oidx.end() && oit->insurance.value > 0) { + slashed = share_type(oit->insurance.value * mp.pm_oracle_penalty_percent / 10000); + if (slashed.value > 0) { + modify(*oit, [&](pm_oracle_object& o) { + o.insurance -= slashed; + o.dispute_responses_missed++; + o.disputes_auto_closed++; + o.total_insurance_slashed += slashed; + }); + adjust_balance(get_account(CHAIN_COMMITTEE_ACCOUNT), + asset(slashed, TOKEN_SYMBOL)); + } + } + + refund_all_bets(*this, mkt); + return_liquidity(*this, mkt); + + // Anti-freeze: the dispute stalled through no fault of the disputer → return the + // escrowed dispute fee (spec §4 pm_dispute_auto_close — "disputer fee return"). + if (disp.dispute_fee.value > 0) + adjust_balance(get_account(disp.disputer), + asset(disp.dispute_fee, TOKEN_SYMBOL)); + + modify(mkt, [&](pm_market_object& m) { + m.status = 3; + m.payout_status = 3; + m.resolved_outcome = -1; + m.finalized_time = now; + }); + pm_oracle_dispute_left_open(*this, mkt.oracle, disp); // drop from open-dispute gauge + modify(disp, [](pm_dispute_object& d) { d.status = 3; }); // auto-closed + + push_virtual_operation(pm_dispute_auto_close_operation( + mkt.oracle, mkt.id._id, asset(slashed, TOKEN_SYMBOL))); + ++done; + } + } + + // ── 4. Dispute voting finalize ──────────────────────────────────────────── + { + // HF14 governance bridge: VIZ parked in the lazy pool would otherwise lose its committee-vote + // weight (only vested SHARES count). A depositor's claim on the pool — its net asset value + // (free + allocated + loans-out) times their share of total pool shares — is converted to + // vesting-SHARES with the SAME token↔shares price as create_vesting (drifts with dust), and + // added to their dispute-vote weight. The pool's whole NAV is likewise folded into the + // participation-threshold denominator so the quorum scales with the full electorate. + const auto& gpo = get_dynamic_global_properties(); + const auto vprice = gpo.get_vesting_share_price(); + const pm_lazy_pool_object* lpool = find(pm_lazy_pool_id_type(0)); + const int64_t pool_nav = (lpool && lpool->total_shares.value > 0) + ? lpool->free_balance.value + lpool->allocated_balance.value + lpool->leverage_fund_used.value : 0; + const int64_t pool_total_shares = (lpool ? lpool->total_shares.value : 0); + const int64_t pool_nav_shares = (pool_nav > 0) + ? (asset(share_type(pool_nav), TOKEN_SYMBOL) * vprice).amount.value : 0; + auto lazy_vote_weight = [&](const account_name_type& acct) -> int64_t { + if (pool_nav <= 0 || pool_total_shares <= 0) return 0; + const auto& ldidx = get_index().indices().get(); + auto d = ldidx.find(acct); + if (d == ldidx.end() || d->shares.value <= 0) return 0; + int64_t viz = (int64_t)(fc::uint128_t((uint64_t)pool_nav) + * fc::uint128_t((uint64_t)d->shares.value) + / fc::uint128_t((uint64_t)pool_total_shares)).lo; + if (viz <= 0) return 0; + return (asset(share_type(viz), TOKEN_SYMBOL) * vprice).amount.value; + }; + + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (uint8_t)0, time_point_sec(0), pm_dispute_id_type())); + while (it != idx.end() && it->status == 0 && it->voting_end_time <= now && done < cap) { + const auto& disp = *it; ++it; + const auto& mkt = get(disp.market); + + // Dispute is being finalized (uphold or override below): drop from the open-dispute gauge. + pm_oracle_dispute_left_open(*this, mkt.oracle, disp); + + // Step 1 — stake-weighted tally (mirrors committee-request finalize). Weight is the + // voter's vesting shares. vote_percent>0 on a real outcome supports that change; + // anything else (negative conviction, or vote_outcome<0) defends the oracle. + // committee-dao-and-prediction-markets.md §Resolution. + share_type max_rshares(0), oracle_defense(0), total_change(0); + std::vector outcome_rshares(mkt.outcome_count, share_type(0)); + { + const auto& vidx = + get_index().indices().get(); + auto vit = vidx.lower_bound(boost::make_tuple(disp.market, account_name_type())); + for (; vit != vidx.end() && vit->market == disp.market; ++vit) { + int64_t w = get_account(vit->voter).effective_vesting_shares().amount.value + + lazy_vote_weight(vit->voter); // + lazy-pool stake as vesting-shares + max_rshares += w; + int32_t pct = (int32_t)vit->vote_percent; + if (pct > 0 && vit->vote_outcome >= 0 && + vit->vote_outcome < (int16_t)mkt.outcome_count) { + share_type r = share_type(w * pct / CHAIN_100_PERCENT); + outcome_rshares[vit->vote_outcome] += r; + total_change += r; + } else { + int32_t a = pct < 0 ? -pct : pct; + oracle_defense += share_type(w * a / CHAIN_100_PERCENT); + } + } + } + + // Step 2 — participation threshold; Step 3 — oracle defense vs change votes. + share_type approve_min = share_type((int64_t)( + fc::uint128_t((uint64_t)(gpo.total_vesting_shares.amount.value + pool_nav_shares)) + * mp.pm_dispute_approve_min_percent / fc::uint128_t(CHAIN_100_PERCENT)).lo); + bool uphold = (max_rshares < approve_min) || + (total_change.value <= 0) || + (oracle_defense >= total_change); + + // Step 4 — winning outcome among change votes; Step 5 — consensus strength. + int16_t winning_outcome = mkt.resolved_outcome; + share_type winning_rshares(0); + if (!uphold) { + int best = 0; + for (uint8_t i = 1; i < mkt.outcome_count; ++i) + if (outcome_rshares[i] > outcome_rshares[best]) best = (int)i; + winning_outcome = (int16_t)best; + winning_rshares = outcome_rshares[best]; + } + + asset oracle_penalty(0, TOKEN_SYMBOL); + if (uphold) { + // Oracle upheld: original outcome stands; the escrowed dispute fee compensates + // the oracle (committee-dao §Resolution step 3 — "fee goes to oracle"). + const auto& oidx = get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + if (oit != oidx.end()) + modify(*oit, [](pm_oracle_object& o) { o.disputes_won++; }); + if (disp.dispute_fee.value > 0) + adjust_balance(get_account(mkt.oracle), asset(disp.dispute_fee, TOKEN_SYMBOL)); + modify(mkt, [&](pm_market_object& m) { + m.payout_status = 1; + m.result_expiration = time_point_sec(1); + }); + modify(disp, [](pm_dispute_object& d) { d.status = 2; }); // oracle right + } else { + // Override. The oracle penalty policy is per-market (dispute_penalty_percent), + // scaled by consensus strength (winning / total participation): + // >0 → slash that % of insurance; fund the disputer reward + winners' forfeit; + // <0 → good-faith oracle: no slash, oracle keeps a carve-out of the fee; + // 0 → outcome corrected, nobody penalized (disputer just refunded). + fc::uint128_t cs = max_rshares.value > 0 + ? fc::uint128_t((uint64_t)winning_rshares.value) * fc::uint128_t(CHAIN_100_PERCENT) + / fc::uint128_t((uint64_t)max_rshares.value) + : fc::uint128_t(0); + int32_t pp = (int32_t)mkt.dispute_penalty_percent; + int64_t fee = disp.dispute_fee.value > 0 ? disp.dispute_fee.value : 0; + + const auto& oidx = get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + + if (pp < 0) { + // Good-faith oracle: keep a slice of the dispute fee; refund the rest to the + // disputer. Insurance untouched. + int64_t oracle_bonus = (int64_t)(fc::uint128_t((uint64_t)fee) + * fc::uint128_t((uint64_t)(-(int64_t)pp)) / fc::uint128_t(10000)).lo; + if (oracle_bonus > fee) oracle_bonus = fee; + if (oracle_bonus > 0) + adjust_balance(get_account(mkt.oracle), asset(share_type(oracle_bonus), TOKEN_SYMBOL)); + if (fee - oracle_bonus > 0) + adjust_balance(get_account(disp.disputer), + asset(share_type(fee - oracle_bonus), TOKEN_SYMBOL)); + if (oit != oidx.end()) + modify(*oit, [](pm_oracle_object& o) { o.disputes_lost++; }); + } else { + share_type slash(0); + if (pp > 0 && oit != oidx.end() && oit->insurance.value > 0) { + int64_t base = (int64_t)(fc::uint128_t((uint64_t)oit->insurance.value) + * fc::uint128_t((uint64_t)pp) / fc::uint128_t(10000)).lo; + slash = share_type((int64_t)(fc::uint128_t((uint64_t)base) * cs + / fc::uint128_t(CHAIN_100_PERCENT)).lo); + if (slash.value > oit->insurance.value) slash = oit->insurance; + if (slash.value > 0) { + modify(*oit, [&](pm_oracle_object& o) { + o.insurance -= slash; + o.disputes_lost++; + o.total_insurance_slashed += slash; + }); + oracle_penalty = asset(slash, TOKEN_SYMBOL); + } + } else if (oit != oidx.end()) { + modify(*oit, [](pm_oracle_object& o) { o.disputes_lost++; }); + } + + // Disputer: refund the escrowed fee + a reward carve-out + // (pm_dispute_reward_multiplier ‰ — total target fee×mult/1000) drawn from the + // slash; the remainder of the slash boosts the winners via forfeit_pool. + int64_t reward_target = (int64_t)(fc::uint128_t((uint64_t)fee) + * fc::uint128_t((uint64_t)mp.pm_dispute_reward_multiplier) / fc::uint128_t(10000)).lo; + int64_t bonus = reward_target - fee; + if (bonus < 0) bonus = 0; + if (bonus > slash.value) bonus = slash.value; + if (fee + bonus > 0) + adjust_balance(get_account(disp.disputer), + asset(share_type(fee + bonus), TOKEN_SYMBOL)); + modify(mkt, [&](pm_market_object& m) { + m.forfeit_pool += share_type(slash.value - bonus); + }); + } + + modify(mkt, [&](pm_market_object& m) { + m.resolved_outcome = winning_outcome; + m.payout_status = 1; + m.result_expiration = time_point_sec(1); + }); + modify(disp, [](pm_dispute_object& d) { d.status = 1; }); // oracle wrong + } + + push_virtual_operation(pm_dispute_finalize_operation( + mkt.oracle, mkt.id._id, mkt.resolved_outcome, oracle_penalty)); + ++done; + } + } + + // ── 5. Auto-payouts (dispute grace elapsed) ─────────────────────────────── + // Cutoff = now - pm_dispute_grace_sec; markets with result_expiration <= cutoff + // and payout_status==1 have their dispute window closed and are ready to settle. + { + const time_point_sec cutoff( + (now.sec_since_epoch() > mp.pm_dispute_grace_sec) + ? (now.sec_since_epoch() - mp.pm_dispute_grace_sec) + : 0u); + + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (int8_t)3, time_point_sec(0), pm_market_id_type())); + while (it != idx.end() && it->status == 3 && it->result_expiration <= cutoff && done < cap) { + const auto& mkt = *it; ++it; + + if (mkt.payout_status != 1) continue; + + // If this market was never disputed it is still counted in markets_in_dispute_window; + // settling closes that window. A market whose dispute finalized (payout returned to 1) + // already left the gauge at dispute_create and carries a dispute row — don't double-dec. + { + const auto& didx = get_index().indices().get(); + if (didx.find(mkt.id) == didx.end()) + pm_oracle_gauge_adj(*this, mkt.oracle, + &pm_oracle_object::markets_in_dispute_window, -1); + } + + settle_market(*this, mkt); + modify(mkt, [&](pm_market_object& m) { m.payout_status = 3; m.finalized_time = now; }); + + push_virtual_operation(pm_auto_payout_operation( + mkt.oracle, mkt.id._id, -1LL, asset(mkt.bets_sum, TOKEN_SYMBOL))); + ++done; + } + } + + // ── 5b. Garbage-collect terminal markets ────────────────────────────────── + // Any market that has become terminal — resolved+paid, void/no-contest, oracle-rejected, + // or accept-window-expired — carries a non-zero finalized_time and can never be acted on + // again; it only lingers "for history". Reclaim its whole object cluster + // pm_closed_market_retention_sec after that moment. The retention is median-voted, so it is + // identical on every node at any block → pruning is deterministic: every node deletes the + // same markets at the same block, keeping shared-memory state and snapshots in lock-step + // network-wide (a node syncing from a snapshot ends up with the same market set as all). + { + const uint32_t retention = mp.pm_closed_market_retention_sec; // median-voted, 5 d default + const time_point_sec cutoff( + (now.sec_since_epoch() > retention) ? (uint32_t)(now.sec_since_epoch() - retention) : 0u); + + // finalized_time == 0 for every live market (sorts first) — start just past them. + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple(time_point_sec(1), pm_market_id_type())); + while (it != idx.end() && it->finalized_time <= cutoff && done < cap) { + const auto& mkt = *it; ++it; + gc_market(*this, mkt); + ++done; + } + } + + // ── 6. Batch epoch settle ───────────────────────────────────────────────── + // At global epoch boundary execute all queued (status=5) bets for batch markets. + if (mp.pm_commit_reveal_enabled && mp.pm_batch_epoch_blocks > 0 && + (head_block_num() % (uint32_t)mp.pm_batch_epoch_blocks == 0)) { + + const auto& midx = get_index().indices().get(); + const auto& bidx = get_index().indices().get(); + + // Round-robin: resume where the previous boundary scan stopped on the cap, wrap + // once. A fixed scan start would let ~cap always-busy low-id markets permanently + // starve newer ones. The walk itself stays O(active batch markets) per boundary + // (one bet-index probe each); if that ever hurts, index queued bets by + // (status, market) and drive the scan from that instead. + const uint64_t start_id = get_dynamic_global_properties().pm_batch_settle_cursor; + bool second_pass = false; + auto mit = midx.lower_bound(boost::make_tuple((int8_t)1, pm_market_id_type(start_id))); + + while (done < cap) { + if (mit == midx.end() || mit->status != 1) { + if (second_pass || start_id == 0) break; // full circle + second_pass = true; + mit = midx.lower_bound((int8_t)1); // wrap to the lowest active id + continue; + } + if (second_pass && mit->id._id >= start_id) break; // full circle + const auto& mkt = *mit; ++mit; + if (!mkt.allow_batch) continue; + + // Idle fast-path: nothing queued at this epoch — skip before the LMSR + // q-vector snapshot, so an idle market costs one index probe, keeps its + // epoch, and does not consume the cap. + auto bit = bidx.lower_bound(boost::make_tuple( + mkt.id, (uint32_t)mkt.current_epoch, pm_bet_id_type())); + if (bit == bidx.end() || bit->market != mkt.id || + bit->epoch != (uint32_t)mkt.current_epoch) + continue; + + // Snapshot LMSR q-vector + std::vector q_vec; + if (mkt.market_type == 1) { + const auto& oidx = get_index().indices().get(); + for (uint8_t i = 0; i < mkt.outcome_count; i++) { + auto oit = oidx.find(boost::make_tuple(mkt.id, i)); + q_vec.push_back(oit != oidx.end() ? oit->q.value : 0LL); + } + } + + uint32_t settled = 0; + bool had_queued = false; + + while (bit != bidx.end() && + bit->market == mkt.id && + bit->epoch == (uint32_t)mkt.current_epoch) { + const auto& bet = *bit; ++bit; + if (bet.status != 5) continue; + had_queued = true; + + share_type tokens(0); + + if (mkt.market_type == 0) { // CPMM + share_type ra = mkt.reserve_a, rb = mkt.reserve_b; + if (ra.value > 0 && rb.value > 0) { + fc::uint128_t k128 = fc::uint128_t((uint64_t)ra.value) * + fc::uint128_t((uint64_t)rb.value); + // Side convention MUST match pm_place_bet: side 0 adds VIZ to reserve_a + // (tokens = reserve_b drop); side 1 adds to reserve_b (tokens = reserve_a + // drop). Otherwise batch and instant bets on the same side move the curve + // in opposite directions and their weights become incomparable at settle. + if (bet.side == 0) { + share_type new_ra = share_type(ra.value + bet.amount.value); + share_type new_rb = share_type((int64_t) + (k128 / fc::uint128_t((uint64_t)new_ra.value)).lo); + tokens = share_type(rb.value - new_rb.value); + } else { + share_type new_rb = share_type(rb.value + bet.amount.value); + share_type new_ra = share_type((int64_t) + (k128 / fc::uint128_t((uint64_t)new_rb.value)).lo); + tokens = share_type(ra.value - new_ra.value); + } + } + } else { // LMSR + if ((size_t)bet.outcome_index < q_vec.size()) { + int64_t t = lmsr::lmsr_tokens_for_amount( + q_vec, mkt.lmsr_b.value, (int)bet.outcome_index, bet.amount.value); + if (t > 0) { + tokens = share_type(t); + q_vec[(size_t)bet.outcome_index] += t; // update in-memory + } + } + } + + if (tokens.value > 0 && tokens.value >= bet.min_tokens.value) { + // Execute + if (mkt.market_type == 0) { + modify(mkt, [&](pm_market_object& m) { + if (bet.side == 0) { + m.reserve_a += bet.amount; m.reserve_b -= tokens; + m.a_bets_sum += bet.amount; + } else { + m.reserve_b += bet.amount; m.reserve_a -= tokens; + m.b_bets_sum += bet.amount; + } + m.bets_sum += bet.amount; + m.k = fc::uint128_t((uint64_t)m.reserve_a.value) * + fc::uint128_t((uint64_t)m.reserve_b.value); + }); + } else { + const auto& oidx = get_index().indices().get(); + auto oit = oidx.find(boost::make_tuple(mkt.id, (uint8_t)bet.outcome_index)); + if (oit != oidx.end()) + modify(*oit, [&](pm_outcome_object& out) { + out.q = share_type(q_vec[(size_t)bet.outcome_index]); + out.bets_sum += bet.amount; + out.bets_count++; + }); + modify(mkt, [&](pm_market_object& m) { m.bets_sum += bet.amount; }); + } + modify(bet, [&](pm_bet_object& b) { b.status = 0; b.weight = tokens; }); + settled++; + } else { + // Slippage: refund + adjust_balance(get_account(bet.account), asset(bet.amount, TOKEN_SYMBOL)); + pm_adjust_frozen(bet.account, 1, -bet.amount); // UNLOCK: queued stake refunded (slippage) + modify(bet, [](pm_bet_object& b) { b.status = 2; }); + } + } + + if (settled > 0) + push_virtual_operation(pm_batch_settle_operation( + mkt.id._id, mkt.current_epoch, settled)); + + // Idle markets keep their epoch and don't consume the cap, so the scan can + // reach markets with queued bets past the cap. + if (had_queued) { + modify(mkt, [](pm_market_object& m) { m.current_epoch++; }); + ++done; + } + } + + // Persist the resume point: the next unvisited active market when the cap cut + // the scan short, 0 after a completed full circle. + uint64_t next_cursor = 0; + if (done >= cap && mit != midx.end() && mit->status == 1 && + !(second_pass && mit->id._id >= start_id)) + next_cursor = mit->id._id; + if (next_cursor != start_id) + modify(get_dynamic_global_properties(), [&](dynamic_global_property_object& d) { + d.pm_batch_settle_cursor = next_cursor; + }); + } + + // ── 7. Lazy pool recall step ────────────────────────────────────────────── + if (mp.pm_lazy_pool_enabled) { + const auto& idx = get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple( + (uint8_t)0, time_point_sec(0), pm_lazy_allocation_id_type())); + + while (it != idx.end() && it->status == 0 && done < cap) { + const auto& alloc = *it; ++it; + + const auto* mkt_ptr = find(alloc.market); + if (!mkt_ptr || mkt_ptr->status == -1) { + // Market rejected/gone (never settled) — pull the LP position fully. + if (mkt_ptr && alloc.amount.value > 0) { + share_type r = recall_pool_liquidity(*this, *mkt_ptr, alloc.amount); + if (r.value > 0) + push_virtual_operation(pm_lazy_recall_operation( + alloc.market._id, asset(r, TOKEN_SYMBOL))); + } + modify(alloc, [](pm_lazy_allocation_object& a) { a.status = 1; }); + } else if (mkt_ptr->status >= 2) { + // Resolved/closed: settle_liquidity / return_liquidity returns the LP + // position (with yield). Defer the scan so we don't pre-empt it. + modify(alloc, [&](pm_lazy_allocation_object& a) { a.last_check_time = now; }); + } else { + const auto& mkt = *mkt_ptr; + bool idle = (mkt.bets_sum.value <= alloc.bets_sum_at_check.value); + + // Graduated recall is spread across the market's lifetime: up to 10 steps, + // each gated by ~10% of the (creation → result_expiration) window. This cron + // runs every block, so without a time gate an idle market's whole subsidy + // would be drained within ~10 blocks instead of over its full duration + // (spec lazy-pool-properties: check_step = "10% duration step", last_check_time). + int64_t window = (int64_t)mkt.result_expiration.sec_since_epoch() + - (int64_t)mkt.created_time.sec_since_epoch(); + int64_t step_dur = window > 0 ? window / 10 : 0; + bool step_due = (int64_t)now.sec_since_epoch() + >= (int64_t)alloc.last_check_time.sec_since_epoch() + step_dur; + + if (!idle) { + // New bets since the last check → market is live; reset the recall schedule. + modify(alloc, [&](pm_lazy_allocation_object& a) { + a.bets_sum_at_check = mkt.bets_sum; + a.last_check_time = now; + a.check_step = 0; + }); + } else if (alloc.check_step >= 10 || alloc.amount.value <= 0) { + modify(alloc, [](pm_lazy_allocation_object& a) { a.status = 1; }); + } else if (step_due) { + share_type want = share_type( + alloc.amount.value * mp.pm_lazy_recall_step_percent / 10000); + if (want.value <= 0) want = share_type(1); + if (want.value > alloc.amount.value) want = alloc.amount; + + share_type recalled = recall_pool_liquidity(*this, mkt, want); + if (recalled.value > 0) { + modify(alloc, [&](pm_lazy_allocation_object& a) { + a.amount -= recalled; + a.recalled_amount += recalled; + a.bets_sum_at_check = mkt.bets_sum; + a.last_check_time = now; + a.check_step++; + }); + push_virtual_operation(pm_lazy_recall_operation( + mkt.id._id, asset(recalled, TOKEN_SYMBOL))); + } else { + modify(alloc, [](pm_lazy_allocation_object& a) { a.status = 1; }); + } + } + // else: idle, steps remain, but this step isn't due yet → leave untouched. + } + ++done; + } + } + + // ── 8. Ban expiry sweep ─────────────────────────────────────────────────── + // Temporary oracle/creator bans lapse at banned_until. We clear the expired ones (banned_until + // → 0) and emit pm_ban_expired so history/indexers see the lift; cleared bans fall into the + // 0-bucket and are never re-swept, permanent bans (maximum()) sort past `now` and are skipped. + // lower_bound at epoch+1 skips the huge never-banned/cleared 0-cluster. + { + const auto& oidx = get_index().indices().get(); + auto it = oidx.lower_bound(time_point_sec(1)); + while (it != oidx.end() && it->banned_until <= now && done < cap) { + const auto& ora = *it; ++it; + const account_name_type owner = ora.owner; + modify(ora, [](pm_oracle_object& o) { o.banned_until = time_point_sec(0); o.banned_by = account_name_type(); }); + push_virtual_operation(pm_ban_expired_operation(owner, true, false)); + ++done; + } + } + { + const auto& cbidx = get_index().indices().get(); + auto it = cbidx.lower_bound(boost::make_tuple(time_point_sec(1), pm_creator_ban_id_type())); + while (it != cbidx.end() && it->banned_until <= now && done < cap) { + const auto& cb = *it; ++it; + const account_name_type who = cb.creator; + modify(cb, [](pm_creator_ban_object& b) { b.banned_until = time_point_sec(0); b.banned_by = account_name_type(); }); + push_virtual_operation(pm_ban_expired_operation(who, false, true)); + ++done; + } + } +} + +}} // graphene::chain diff --git a/libraries/network/CMakeLists.txt b/libraries/network/CMakeLists.txt index 05c9ee4913..338439a994 100644 --- a/libraries/network/CMakeLists.txt +++ b/libraries/network/CMakeLists.txt @@ -14,17 +14,10 @@ list(APPEND ${CURRENT_TARGET}_SOURCES dlt_p2p_node.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${${CURRENT_TARGET}_HEADERS} ${${CURRENT_TARGET}_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${${CURRENT_TARGET}_HEADERS} - ${${CURRENT_TARGET}_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index b35c204b2b..0cae865edf 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -17,6 +17,8 @@ list(APPEND ${CURRENT_TARGET}_HEADERS include/graphene/protocol/sign_state.hpp include/graphene/protocol/chain_operations.hpp include/graphene/protocol/chain_virtual_operations.hpp + include/graphene/protocol/pm_operations.hpp + include/graphene/protocol/pm_virtual_operations.hpp include/graphene/protocol/transaction.hpp include/graphene/protocol/types.hpp include/graphene/protocol/version.hpp @@ -32,22 +34,16 @@ list(APPEND ${CURRENT_TARGET}_SOURCES proposal_operations.cpp sign_state.cpp chain_operations.cpp + pm_operations.cpp transaction.cpp types.cpp version.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${${CURRENT_TARGET}_HEADERS} ${${CURRENT_TARGET}_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${${CURRENT_TARGET}_HEADERS} - ${${CURRENT_TARGET}_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) @@ -56,6 +52,10 @@ target_link_libraries(graphene_${CURRENT_TARGET} fc) target_include_directories(graphene_${CURRENT_TARGET} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include") +if(ENABLE_PCH) + target_precompile_headers(graphene_${CURRENT_TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/pch.hpp") +endif() + if(WITH_COVERAGE) target_compile_options(graphene_${CURRENT_TARGET} PRIVATE --coverage) target_link_options(graphene_${CURRENT_TARGET} PUBLIC --coverage) diff --git a/libraries/protocol/include/graphene/protocol/chain_operations.hpp b/libraries/protocol/include/graphene/protocol/chain_operations.hpp index c638b2766b..9e111e3d56 100644 --- a/libraries/protocol/include/graphene/protocol/chain_operations.hpp +++ b/libraries/protocol/include/graphene/protocol/chain_operations.hpp @@ -627,6 +627,148 @@ namespace graphene { namespace protocol { chain_properties_hf13& operator=(const chain_properties_hf13&) = default; }; + // HF14 Prediction Markets (Onix). Inherits all hf13 fields and appends the PM consensus + // params (spec §5). Defaults are inline (single protocol definition → mainnet and testnet + // share identical consensus values, which is required). Variant index 5; index 4 is hf13. + struct chain_properties_pm: public chain_properties_hf13 { + // Oracle / market economics + asset pm_oracle_registration_fee = asset(10000, TOKEN_SYMBOL); ///< 10.000 VIZ → committee fund + asset pm_min_oracle_insurance = asset(5000000, TOKEN_SYMBOL); ///< 5000.000 VIZ bond floor + asset pm_market_creation_fee = asset(5000, TOKEN_SYMBOL); ///< 5.000 VIZ → committee fund + asset pm_min_liquidity = asset(100000, TOKEN_SYMBOL); ///< 100.000 VIZ seed floor + uint8_t pm_max_outcomes = 64; + uint32_t pm_max_market_duration = 31536000; ///< ≤ 1 year (s) + uint16_t pm_max_oracle_fee_percent = 500; ///< bp cap on the oracle % (5%) + uint32_t pm_oracle_accept_window_sec = 3600; ///< 1 h for the named oracle to accept/reject a + ///< pending market; on expiry the cron refunds the + ///< seed liquidity (NOT the creation fee) and voids it + // Oracle-insurance coverage floors, as percent of a market's betting volume (100 = 1.0x). + // Listing: markets below it are hidden from the default catalog (revealed via show_risky) — + // enforced by the prediction_market_api plugin. Betting: advisory threshold below which a + // client should require an explicit risk confirmation (not enforced on-chain). + uint16_t pm_listing_min_coverage_percent = 250; ///< hide if insurance < 2.5x bets + uint16_t pm_betting_min_coverage_percent = 150; ///< client risk-confirm below 1.5x (advisory) + uint16_t pm_default_time_penalty_percent = 50; + uint32_t pm_max_time_penalty = 1000000; ///< 100% of profit (1e6 precision) + // Disputes + asset pm_dispute_fee = asset(1000000, TOKEN_SYMBOL); ///< 1000.000 VIZ + uint32_t pm_dispute_grace_sec = 43200; ///< 12 h + uint32_t pm_oracle_dispute_response_sec = 43200; ///< 12 h + uint32_t pm_dispute_auto_close_sec = 1209600; ///< 14 d (anti-freeze) + uint32_t pm_dispute_vote_period_sec = 259200; ///< 3 d (committee mode) + uint16_t pm_dispute_approve_min_percent = 1000; ///< participation threshold (bp) + uint16_t pm_oracle_penalty_percent = 500; ///< insurance slashed on missed deadline (bp) + uint16_t pm_no_contest_penalty_percent = 5000; ///< bp of dispute fee (50%) + uint32_t pm_dispute_reward_multiplier = 30000; ///< bp multiplier (10000=1x; default 3x) + // Batch / commit-reveal + uint32_t pm_batch_epoch_blocks = 20; ///< ~60 s + uint32_t pm_reveal_window_blocks = 200; ///< ~10 min liveness + uint16_t pm_commit_no_reveal_penalty_percent = 2000; ///< bp (20%) → winners' pool + asset pm_min_batch_bet = asset(1000, TOKEN_SYMBOL); ///< 1.000 VIZ anti-dust + bool pm_commit_reveal_enabled = true; ///< kill-switch (median-voted) + // Cron / fairness + uint32_t pm_processing_cap_per_block = 200; ///< bounded per-block virtual-op work + // Lazy pool (allocation-only; leverage out of scope for HF14) + bool pm_lazy_pool_enabled = true; ///< kill-switch (median-voted) + uint16_t pm_lazy_alloc_percent = 2000; ///< bp of free_balance allocated per market + uint16_t pm_lazy_max_total_alloc_percent = 7000; ///< bp cap on total allocation + uint32_t pm_lazy_lock_sec = 604800; ///< 7 d deposit lock + uint16_t pm_lazy_recall_step_percent = 1000; ///< bp recalled per idle step + uint16_t pm_lazy_emergency_penalty_percent = 5000; ///< bp of profit slashed on emergency + ///< withdraw before unlock (→ reward_per_share) + uint16_t pm_lazy_min_liquidity_fee_percent = 200; ///< bp; the pool refuses to subsidize markets + ///< whose liquidity_fee_percent is below this + ///< reward floor (2% default) + // Leverage (margin via lazy-pool loans; CPMM-binary only). Kill-switch ON by + // default on this pm/testnet branch so a fresh chain enables leverage out of the + // box; on an already-running chain the median persists, so validators must still + // vote it on. See leverage-risk-off-strategy.md §8. + bool pm_leverage_enabled = true; ///< kill-switch (median-voted) + uint16_t pm_leverage_fund_percent = 10; ///< % of free_balance usable for loans (F) + uint16_t pm_leverage_max_per_position_bp = 20; ///< bp of leverage-fund-available per position (P=0.2%) + uint16_t pm_leverage_pool_profit_percent = 10; ///< pool profit per loan (R) + uint16_t pm_leverage_safety_margin_percent = 1; ///< open-time safety buffer (S) + uint16_t pm_leverage_max_slippage_percent = 10; ///< max price impact per bet (SL) + asset pm_leverage_min_market_liquidity = asset(5000000, TOKEN_SYMBOL); ///< min liquidity for leverage + uint16_t pm_leverage_max_position_ratio_percent = 5; ///< max position as % of liquidity_sum (POS) + uint32_t pm_leverage_expiration_buffer_sec = 86400; ///< leverage disabled N sec before expiration + uint16_t pm_leverage_m_factor_percent = 50; ///< M_effective = M_max × this% (VIZ DLT relaxation) + uint32_t pm_leverage_funding_rate_ppm_per_day = 100; ///< funding on the loan per 24h, in ppm (1e6). 0.01%/day = 100 ppm (~3.65%/yr); 0 disables + uint16_t pm_conversion_profit_cost_percent = 50; ///< fee % of unrealized profit on convert + // Garbage collection of terminal markets + uint32_t pm_closed_market_retention_sec = 432000; ///< 5 d: a market and its whole object + ///< cluster are pruned from state this long + ///< after it becomes terminal (finalized_time). + ///< Median-voted → identical on every node, so + ///< pruning stays deterministic / snapshot-safe. + // Early-exit reward cap (F1/#300). A bet or leverage position that exits BEFORE + // resolution no longer extracts curve value from LPs. Its outcome-contingent profit + // is paid at settlement from a BOUNDED slice of the losing pool (FIFO by exit time, + // no per-position cap); a losing outcome earns nothing; any unused slice returns to + // the winners' pool. This is the bp cap of that slice (3300 = 33% of losers_sum). + // Median-voted (validator param). See early-exit-deferred-claim.md. + uint16_t pm_early_exit_reward_cap_percent = 3300; ///< bp of losers_sum for early-exit claims + + void validate() const { + chain_properties_hf13::validate(); + auto check_token = [](const asset& a, const char* n) { + FC_ASSERT(a.symbol == TOKEN_SYMBOL, "${n} must be VIZ", ("n", n)); + FC_ASSERT(a.amount > 0, "${n} must be positive", ("n", n)); + }; + check_token(pm_oracle_registration_fee, "pm_oracle_registration_fee"); + check_token(pm_min_oracle_insurance, "pm_min_oracle_insurance"); + check_token(pm_market_creation_fee, "pm_market_creation_fee"); + check_token(pm_min_liquidity, "pm_min_liquidity"); + check_token(pm_dispute_fee, "pm_dispute_fee"); + check_token(pm_min_batch_bet, "pm_min_batch_bet"); + FC_ASSERT(pm_max_outcomes >= 2 && pm_max_outcomes <= MAX_PM_OUTCOMES_PER_MARKET, + "pm_max_outcomes must be in [2, ${m}]", ("m", MAX_PM_OUTCOMES_PER_MARKET)); + FC_ASSERT(pm_max_market_duration > 0, "pm_max_market_duration must be positive"); + FC_ASSERT(pm_max_oracle_fee_percent <= 10000, "pm_max_oracle_fee_percent out of range"); + FC_ASSERT(pm_oracle_accept_window_sec > 0, "pm_oracle_accept_window_sec must be positive"); + FC_ASSERT(pm_betting_min_coverage_percent <= pm_listing_min_coverage_percent, + "pm_betting_min_coverage_percent must be <= pm_listing_min_coverage_percent"); + FC_ASSERT(pm_default_time_penalty_percent <= 10000, "pm_default_time_penalty_percent out of range"); + // B9 wired compute_time_penalty to spend this as profit*penalty/1e6 in compute_settlement, + // so it is only sound while <= 1e6 (100% of profit). Bound it like every sibling ratio. + FC_ASSERT(pm_max_time_penalty <= 1000000, "pm_max_time_penalty out of range (<= 1000000 = 100% of profit)"); + FC_ASSERT(pm_dispute_approve_min_percent <= 10000, "pm_dispute_approve_min_percent out of range"); + FC_ASSERT(pm_oracle_penalty_percent <= 10000, "pm_oracle_penalty_percent out of range"); + FC_ASSERT(pm_no_contest_penalty_percent <= 10000, "pm_no_contest_penalty_percent out of range"); + // Reward multiplier is a bp multiplier (10000 = 1x). Floor at 10000 so a vindicated + // disputer at least recovers the fee; cap at 100x. + FC_ASSERT(pm_dispute_reward_multiplier >= 10000 && pm_dispute_reward_multiplier <= 1000000, + "pm_dispute_reward_multiplier must be in [10000, 1000000]"); + FC_ASSERT(pm_commit_no_reveal_penalty_percent <= 10000, "pm_commit_no_reveal_penalty_percent out of range"); + FC_ASSERT(pm_batch_epoch_blocks > 0, "pm_batch_epoch_blocks must be positive"); + FC_ASSERT(pm_reveal_window_blocks > 0, "pm_reveal_window_blocks must be positive"); + FC_ASSERT(pm_processing_cap_per_block > 0, "pm_processing_cap_per_block must be positive"); + FC_ASSERT(pm_lazy_alloc_percent <= 10000, "pm_lazy_alloc_percent out of range"); + FC_ASSERT(pm_lazy_max_total_alloc_percent <= 10000, "pm_lazy_max_total_alloc_percent out of range"); + FC_ASSERT(pm_lazy_recall_step_percent <= 10000, "pm_lazy_recall_step_percent out of range"); + FC_ASSERT(pm_lazy_emergency_penalty_percent <= 10000, "pm_lazy_emergency_penalty_percent out of range"); + FC_ASSERT(pm_lazy_min_liquidity_fee_percent <= 10000, "pm_lazy_min_liquidity_fee_percent out of range"); + FC_ASSERT(pm_leverage_fund_percent <= 100, "pm_leverage_fund_percent out of range"); + FC_ASSERT(pm_leverage_max_per_position_bp <= 10000, "pm_leverage_max_per_position_bp out of range"); + FC_ASSERT(pm_leverage_pool_profit_percent <= 100, "pm_leverage_pool_profit_percent out of range"); + FC_ASSERT(pm_leverage_safety_margin_percent <= 100, "pm_leverage_safety_margin_percent out of range"); + FC_ASSERT(pm_leverage_max_slippage_percent <= 100, "pm_leverage_max_slippage_percent out of range"); + FC_ASSERT(pm_leverage_max_position_ratio_percent <= 100, "pm_leverage_max_position_ratio_percent out of range"); + FC_ASSERT(pm_leverage_m_factor_percent <= 100, "pm_leverage_m_factor_percent out of range"); + FC_ASSERT(pm_leverage_funding_rate_ppm_per_day <= 1000000, "pm_leverage_funding_rate_ppm_per_day out of range (<= 100%/day)"); + FC_ASSERT(pm_conversion_profit_cost_percent <= 100, "pm_conversion_profit_cost_percent out of range"); + FC_ASSERT(pm_early_exit_reward_cap_percent <= 10000, "pm_early_exit_reward_cap_percent out of range"); + check_token(pm_leverage_min_market_liquidity, "pm_leverage_min_market_liquidity"); + } + + chain_properties_pm& operator=(const chain_properties_init& src) { chain_properties_init::operator=(src); return *this; } + chain_properties_pm& operator=(const chain_properties_hf4& src) { chain_properties_hf4::operator=(src); return *this; } + chain_properties_pm& operator=(const chain_properties_hf6& src) { chain_properties_hf6::operator=(src); return *this; } + chain_properties_pm& operator=(const chain_properties_hf9& src) { chain_properties_hf9::operator=(src); return *this; } + chain_properties_pm& operator=(const chain_properties_hf13& src) { chain_properties_hf13::operator=(src); return *this; } + chain_properties_pm& operator=(const chain_properties_pm&) = default; + }; + inline chain_properties_init& chain_properties_init::operator=(const chain_properties_hf13& src) { account_creation_fee = src.account_creation_fee; maximum_block_size = src.maximum_block_size; @@ -696,7 +838,8 @@ namespace graphene { namespace protocol { chain_properties_hf4, chain_properties_hf6, chain_properties_hf9, - chain_properties_hf13 + chain_properties_hf13, + chain_properties_pm // index 5 (HF14) — APPEND ONLY >; /** @@ -1210,6 +1353,25 @@ FC_REFLECT_DERIVED( FC_REFLECT_DERIVED( (graphene::protocol::chain_properties_hf13),((graphene::protocol::chain_properties_hf9)), (distribution_epoch_length)) +FC_REFLECT_DERIVED( + (graphene::protocol::chain_properties_pm),((graphene::protocol::chain_properties_hf13)), + (pm_oracle_registration_fee)(pm_min_oracle_insurance)(pm_market_creation_fee)(pm_min_liquidity) + (pm_max_outcomes)(pm_max_market_duration)(pm_max_oracle_fee_percent)(pm_oracle_accept_window_sec) + (pm_listing_min_coverage_percent)(pm_betting_min_coverage_percent) + (pm_default_time_penalty_percent)(pm_max_time_penalty)(pm_dispute_fee)(pm_dispute_grace_sec) + (pm_oracle_dispute_response_sec)(pm_dispute_auto_close_sec)(pm_dispute_vote_period_sec) + (pm_dispute_approve_min_percent)(pm_oracle_penalty_percent)(pm_no_contest_penalty_percent) + (pm_dispute_reward_multiplier)(pm_batch_epoch_blocks)(pm_reveal_window_blocks) + (pm_commit_no_reveal_penalty_percent)(pm_min_batch_bet)(pm_commit_reveal_enabled) + (pm_processing_cap_per_block)(pm_lazy_pool_enabled)(pm_lazy_alloc_percent) + (pm_lazy_max_total_alloc_percent)(pm_lazy_lock_sec)(pm_lazy_recall_step_percent) + (pm_lazy_emergency_penalty_percent)(pm_lazy_min_liquidity_fee_percent) + (pm_leverage_enabled)(pm_leverage_fund_percent)(pm_leverage_max_per_position_bp) + (pm_leverage_pool_profit_percent)(pm_leverage_safety_margin_percent)(pm_leverage_max_slippage_percent) + (pm_leverage_min_market_liquidity)(pm_leverage_max_position_ratio_percent) + (pm_leverage_expiration_buffer_sec)(pm_leverage_m_factor_percent)(pm_leverage_funding_rate_ppm_per_day) + (pm_conversion_profit_cost_percent) + (pm_closed_market_retention_sec)(pm_early_exit_reward_cap_percent)) FC_REFLECT_TYPENAME((graphene::protocol::versioned_chain_properties)) diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index 8ba45bcf0e..17e91b414d 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -2,7 +2,7 @@ #define CHAIN_STARTUP_VERSION (version(1, 0, 0)) #define CHAIN_HARDFORK_STARTUP_VERSION (hardfork_version(CHAIN_STARTUP_VERSION)) -#define CHAIN_VERSION (version(3, 2, 0)) +#define CHAIN_VERSION (version(4, 0, 0)) #define CHAIN_HARDFORK_VERSION (hardfork_version(CHAIN_VERSION)) #define CHAIN_NAME "VIZ" @@ -208,7 +208,27 @@ /// 0 — pre-HF13 (implicit, file absent) /// 13 — HF13: added sharing_rate, pending_stakeholder_reward to witness_object; /// added vote_created_block to witness_vote_object -#define CHAIN_SCHEMA_VERSION uint32_t(13) +/// 14 — HF14: prediction-market chainbase objects (pm_oracle, pm_market, pm_outcome, +/// pm_bet, pm_liquidity, pm_commit, pm_dispute, pm_dispute_vote, pm_lazy_*) +#define CHAIN_SCHEMA_VERSION uint32_t(14) + +// HF14 prediction-market byte-length caps for variable-length on-chain strings. +// These are CONSENSUS-MECHANICAL, not anti-spam (spec §7.12): without a hard cap two +// nodes could pick different chainbase string sizes -> divergent fc::raw::pack length +// -> divergent object hash -> fork. They MUST be identical on mainnet and testnet — +// only the HF activation TIME differs between builds, never these caps. +#define MAX_PM_DECISION_URL_LEN 256 +#define MAX_PM_PROFILE_URL_LEN 256 +#define MAX_PM_DISPUTE_REASON_LEN 1024 +#define MAX_PM_MARKET_TITLE_LEN 256 +#define MAX_PM_OUTCOME_LABEL_LEN 64 +#define MAX_PM_OUTCOMES_PER_MARKET 128 +// #349: cap on live early-exit deferred claims (F1/#300) per market. Bounds settle_market's +// claim-distribution loop against a griefer spamming cancels/leverage-closes to force unbounded +// per-block work at settlement. Beyond the cap, an early-exit skips recording its contingent claim +// (the tail stays in the curve, pays 0 like bucket-exhaustion) — the exit itself still succeeds. +// Generous: far above any count the bounded reward bucket (≤ cap%·losers) could ever fund. +#define MAX_PM_DEFERRED_CLAIMS_PER_MARKET 10000 // Deprecated defines #define CHAIN_CASHOUT_WINDOW_SECONDS (60*60*24) // 1 day diff --git a/libraries/protocol/include/graphene/protocol/config_testnet.hpp b/libraries/protocol/include/graphene/protocol/config_testnet.hpp index 9af21a9156..7149dd03f5 100644 --- a/libraries/protocol/include/graphene/protocol/config_testnet.hpp +++ b/libraries/protocol/include/graphene/protocol/config_testnet.hpp @@ -2,7 +2,7 @@ #define CHAIN_STARTUP_VERSION (version(1, 0, 0)) #define CHAIN_HARDFORK_STARTUP_VERSION (hardfork_version(CHAIN_STARTUP_VERSION)) -#define CHAIN_VERSION (version(3, 0, 0)) +#define CHAIN_VERSION (version(4, 0, 0)) #define CHAIN_HARDFORK_VERSION (hardfork_version(CHAIN_VERSION)) #define CHAIN_NAME "VIZTEST" @@ -194,7 +194,16 @@ #define CHAIN_MIN_STAKEHOLDER_REWARD_PAYOUT int64_t(1) /// Chainbase schema version — must match CHAIN_SCHEMA_VERSION in config.hpp. -#define CHAIN_SCHEMA_VERSION uint32_t(13) +#define CHAIN_SCHEMA_VERSION uint32_t(14) + +// HF14 prediction-market byte-length caps — IDENTICAL to config.hpp (consensus-mechanical, +// see the note there). Only the HF14 activation TIME differs between mainnet/testnet. +#define MAX_PM_DECISION_URL_LEN 256 +#define MAX_PM_PROFILE_URL_LEN 256 +#define MAX_PM_DISPUTE_REASON_LEN 1024 +#define MAX_PM_MARKET_TITLE_LEN 256 +#define MAX_PM_OUTCOME_LABEL_LEN 64 +#define MAX_PM_OUTCOMES_PER_MARKET 128 // Deprecated defines #define CHAIN_CASHOUT_WINDOW_SECONDS (60*60*24) // 1 day diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index 29c97c0819..7dd5a0aecb 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace graphene { namespace protocol { @@ -103,7 +105,54 @@ namespace graphene { namespace protocol { // VIZ HF 13: Validator reward sharing set_reward_sharing_operation, // virtual operations: - stakeholder_reward_operation + stakeholder_reward_operation, + + // VIZ HF 14: Prediction Markets (Onix) — APPEND ONLY, never reorder. + // The variant index is the consensus op-id; order is frozen by the plan. + pm_oracle_register_operation, + pm_oracle_update_operation, + pm_create_market_operation, + pm_oracle_accept_market_operation, + pm_place_bet_operation, + pm_commit_bet_operation, + pm_reveal_bet_operation, + pm_cancel_bet_operation, + pm_add_liquidity_operation, + pm_withdraw_liquidity_operation, + pm_resolve_market_operation, + pm_no_contest_operation, + pm_dispute_create_operation, + pm_dispute_vote_operation, + pm_dispute_resolve_operation, + pm_transfer_position_operation, + pm_lazy_deposit_operation, + pm_lazy_withdraw_operation, + // virtual operations: + pm_batch_settle_operation, + pm_commit_forfeit_operation, + pm_auto_payout_operation, + pm_dispute_finalize_operation, + pm_dispute_auto_close_operation, + pm_oracle_missed_penalty_operation, + pm_lazy_recall_operation, + pm_leverage_open_operation, + pm_leverage_close_operation, + pm_leverage_convert_operation, + pm_leverage_liquidate_operation, + pm_leverage_resolve_operation, + pm_market_accepted_operation, + pm_payout_operation, + + // HF14 PM follow-ups (appended after the initial block — indices stay stable): + pm_dispute_oracle_respond_operation, + pm_unban_operation, + // virtual operation: + pm_ban_expired_operation, + pm_market_expired_operation, + // P1 oracle-metrics: dispute filed -> oracle+disputer history (virtual) + pm_dispute_opened_operation, + // F1/#300 early-exit deferred claim paid at settlement (virtual) + pm_early_exit_claim_paid_operation > operation; /*void operation_get_required_authorities( const operation& op, diff --git a/libraries/protocol/include/graphene/protocol/pm_operations.hpp b/libraries/protocol/include/graphene/protocol/pm_operations.hpp new file mode 100644 index 0000000000..68739a617d --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/pm_operations.hpp @@ -0,0 +1,431 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace graphene { namespace protocol { + + // HF14 Prediction Markets (Onix). All financial params are `asset` (VIZ, 3 decimals); + // stored internally as share_type. Object references are passed as plain int64 ids that + // the evaluator resolves to the corresponding pm_* chainbase object. + // + // Op-id NOTE: these are appended to the single `operation` static_variant (see operations.hpp); + // the variant index IS the consensus op-id. The spec's "ids start at 64" numbering does NOT + // apply to VIZ and is intentionally ignored. + + using pm_object_id_type = int64_t; + + /// 1. Register a new oracle with a bonded insurance deposit. + struct pm_oracle_register_operation : public base_operation { + account_name_type owner; + asset insurance; ///< VIZ, locked from owner; >= pm_min_oracle_insurance + uint16_t fee_percent = 0; ///< <= pm_max_oracle_fee_percent + asset fixed_fee; ///< VIZ, per-market fixed fee (>= 0) + string rules_url; ///< <= MAX_PM_PROFILE_URL_LEN + // Auto-accept policy (anti-collusion). When `auto_accept`, a new market naming this oracle is + // accepted at creation only if its creator matches `auto_accept_creator` (empty = any) and its + // dispute routing matches `auto_accept_resolver` (empty = committee-mode only; set = only + // account-mode markets whose `dispute_resolver` equals it) — otherwise it stays pending for + // manual accept. Prevents a creator from slipping in a colluding sham resolver. + account_name_type auto_accept_creator; + account_name_type auto_accept_resolver; + bool auto_accept = false; + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(owner); } + }; + + /// 2. Top-up / withdraw insurance, change fee policy or rules url. + struct pm_oracle_update_operation : public base_operation { + account_name_type owner; + optional insurance_delta; ///< signed: >0 top-up, <0 withdraw (blocked while active markets / below min) + optional fee_percent; + optional fixed_fee; + optional rules_url; + optional auto_accept_creator; ///< auto-accept policy (see register op) + optional auto_accept_resolver; + optional auto_accept; + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(owner); } + }; + + /// 3. Create a market (binary CPMM or multi LMSR). Creator becomes the first LP. + struct pm_create_market_operation : public base_operation { + account_name_type creator; + account_name_type oracle; ///< registered oracle (or creator for self-oracle) + uint8_t market_type = 0; ///< 0 binary (CPMM), 1 multi (LMSR) + vector outcomes; ///< size 2 (binary) or 3..pm_max_outcomes (multi) + string url; ///< resolution criteria, <= MAX_PM_MARKET_TITLE_LEN + // Oracle terms here are the creator's OFFER CEILING (max the maker will pay the oracle). + // The oracle locks in its actual quote (<= these) at accept; a self-oracle freezes them + // as-is at creation. All percents are bp (10000 = 100.00%). + uint16_t oracle_fee_percent = 0; ///< offered max oracle % of losers' pool + asset oracle_fixed_fee; ///< offered max oracle fixed fee (VIZ, >= 0) + uint16_t creator_fee_percent = 0; + uint16_t liquidity_fee_percent = 0; + asset liquidity; ///< VIZ seed; >= pm_min_liquidity + share_type lmsr_b = 0; ///< multi only: client-computed b; node checks floor(liquidity/ln_q(N))==b (lmsr-spec §7.7) + time_point_sec betting_expiration; + time_point_sec result_expiration; ///< > betting_expiration; <= now + pm_max_market_duration + uint8_t time_penalty_type = 0; + uint32_t time_penalty_value = 0; + uint8_t penalty_curve_type = 0; + bool allow_early_resolution = false; + bool allow_cancellation = false; + bool allow_batch = false; + bool allow_instant_bet = true; ///< multi forces true (no LMSR batch yet); see evaluator + uint8_t endogeneity_tier = 2; ///< 1 econ-data / 2 sports / 3 political + uint8_t dispute_mode = 0; ///< 0 committee / 1 account + account_name_type dispute_resolver; ///< required & must exist iff dispute_mode==1; must NOT equal oracle/creator + int16_t dispute_penalty_percent = 0; ///< −10000..+10000 oracle penalty policy on a + ///< successful dispute (>0 slash, <0 good-faith bonus) + string metadata; ///< free-form client JSON (no length cap, like custom_op); + ///< consensus-opaque, parsed off-chain by the meta plugin + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(creator); } + }; + + /// 4. Oracle accepts or rejects a pending market. On accept the oracle quotes its actual + /// terms (<= the creator's offered ceiling and <= the median cap); these are frozen onto + /// the market and a pm_market_accepted virtual op is emitted. Quote fields are ignored on + /// reject and for self-oracle markets (which auto-accept at creation). + struct pm_oracle_accept_market_operation : public base_operation { + account_name_type oracle; + pm_object_id_type market_id = 0; + bool accept = true; + uint16_t oracle_fee_percent = 0; ///< oracle's quoted % (bp); <= market offer & median cap + asset oracle_fixed_fee; ///< oracle's quoted fixed fee; <= market offer + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(oracle); } + }; + + /// 5. Place a bet (instant or batch). + struct pm_place_bet_operation : public base_operation { + account_name_type account; + pm_object_id_type market_id = 0; + int8_t side = -1; ///< binary: 0/1; multi: -1 + int16_t outcome_index = -1; ///< multi: 0..N-1; binary: -1 + asset amount; ///< VIZ, > 0 + share_type min_tokens = 0; ///< slippage floor (0 = none) + uint8_t mode = 0; ///< 0 instant, 1 batch + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 6. Commit-reveal phase 1: hidden bet commitment. + struct pm_commit_bet_operation : public base_operation { + account_name_type account; + pm_object_id_type market_id = 0; + fc::sha256 commitment; ///< H(market||account||side/outcome||amount||min_tokens||salt) + asset escrow_amount; ///< VIZ, locked; >= pm_min_batch_bet + uint16_t no_reveal_fee_percent = 0; ///< MUST equal median(pm_commit_no_reveal_penalty_percent) — consensus check + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 7. Commit-reveal phase 2: reveal & enqueue for the next batch epoch. + struct pm_reveal_bet_operation : public base_operation { + account_name_type account; + pm_object_id_type commit_id = 0; + int8_t side = -1; + int16_t outcome_index = -1; + asset amount; ///< <= escrow_amount; surplus refunded + string salt; ///< entropy bound into the commitment hash + share_type min_tokens = 0; + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 8. Cancel an open / queued bet (requires allow_cancellation). + struct pm_cancel_bet_operation : public base_operation { + account_name_type account; + pm_object_id_type bet_id = 0; + share_type min_return = 0; ///< slippage floor on the refund + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 9. Add liquidity to a market. + struct pm_add_liquidity_operation : public base_operation { + account_name_type provider; + pm_object_id_type market_id = 0; + asset amount; ///< VIZ, > 0 + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(provider); } + }; + + /// 10. Withdraw liquidity (principal-safe; locked after betting_expiration until resolution). + struct pm_withdraw_liquidity_operation : public base_operation { + account_name_type provider; + pm_object_id_type liquidity_id = 0; + asset amount; ///< VIZ to withdraw; 0 = full position + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(provider); } + }; + + /// 11. Oracle resolves the market to a winning outcome. + struct pm_resolve_market_operation : public base_operation { + account_name_type oracle; + pm_object_id_type market_id = 0; + int16_t winning_outcome = -1; + string decision_url; ///< <= MAX_PM_DECISION_URL_LEN + string decision_reason; ///< free-text justification, <= MAX_PM_DISPUTE_REASON_LEN + ///< (lives in operation history alongside decision_url) + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(oracle); } + }; + + /// 12. Oracle declares the market void (refund all; disputable). + struct pm_no_contest_operation : public base_operation { + account_name_type oracle; + pm_object_id_type market_id = 0; + string reason; ///< <= MAX_PM_DISPUTE_REASON_LEN + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(oracle); } + }; + + /// 13. File a dispute against a resolution (escrows pm_dispute_fee). + struct pm_dispute_create_operation : public base_operation { + account_name_type disputer; + pm_object_id_type market_id = 0; + int16_t proposed_outcome = -1; + string reason; ///< <= MAX_PM_DISPUTE_REASON_LEN + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(disputer); } + }; + + /// 14. Committee-mode dispute vote (regular auth, like committee_vote_request). + struct pm_dispute_vote_operation : public base_operation { + account_name_type voter; + pm_object_id_type market_id = 0; + int16_t vote_outcome = -1; ///< correct outcome, or -1 to uphold oracle + int16_t vote_percent = 0; ///< conviction/penalty intensity [-10000, 10000] + + extensions_type extensions; + + void validate() const; + void get_required_regular_authorities(flat_set& a) const { a.insert(voter); } + }; + + /// 15. Account-mode dispute verdict by the market's configured resolver. + struct pm_dispute_resolve_operation : public base_operation { + account_name_type resolver; + pm_object_id_type market_id = 0; + int16_t correct_outcome = -1; + asset penalty_amount; ///< insurance to slash + bool ban_oracle = false; + time_point_sec ban_oracle_until; + bool ban_creator = false; + time_point_sec ban_creator_until; + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(resolver); } + }; + + /// 16. Transfer all/part of a bet's weight to another account (no market impact). + struct pm_transfer_position_operation : public base_operation { + account_name_type from; + pm_object_id_type bet_id = 0; + account_name_type to; + share_type amount = 0; ///< weight to reassign (0 = full) + string memo; ///< plaintext or '#'-prefixed ECIES (same as VIZ memos) + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(from); } + }; + + /// 17. Deposit into the lazy liquidity pool (allocation-only; no leverage in HF14). + struct pm_lazy_deposit_operation : public base_operation { + account_name_type account; + asset amount; ///< VIZ, > 0 + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 18. Withdraw from the lazy liquidity pool (planned or emergency w/ penalty). + struct pm_lazy_withdraw_operation : public base_operation { + account_name_type account; + share_type shares = 0; ///< pool shares to burn (0 = all) + bool emergency = false; ///< true => penalty on locked-share profit + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 19. Open a leveraged (boosted) CPMM position: collateral C + pool loan L. + struct pm_leverage_open_operation : public base_operation { + account_name_type account; + pm_object_id_type market_id = 0; + int16_t outcome_index = 0; ///< binary: 0/1 + asset collateral; ///< bettor's own stake, VIZ + asset loan; ///< pool loan, VIZ + share_type min_tokens = 0; ///< slippage floor (consensus check) + uint16_t max_slippage_percent = 0; ///< user-facing front-run guard + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 20. Voluntarily close a leveraged position (only if cancel_value >= threshold). + struct pm_leverage_close_operation : public base_operation { + account_name_type account; + pm_object_id_type position_id = 0; + share_type min_return = 0; ///< slippage floor on the bettor's return + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 21. Convert a leveraged position to a normal bet (pay off the loan + fee). + struct pm_leverage_convert_operation : public base_operation { + account_name_type account; + pm_object_id_type position_id = 0; + uint16_t conversion_profit_cost = 0; ///< MUST equal median(pm_conversion_profit_cost_percent) + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(account); } + }; + + /// 22. Oracle posts a public rebuttal to an open dispute filed against its resolution. + /// Disputes are public hearings: the response is stored on the dispute object so every + /// viewer (committee voters / account resolver) can read it before deciding. Only the + /// market's oracle may respond, only while the dispute is open and within the response + /// window (oracle_response_deadline). + struct pm_dispute_oracle_respond_operation : public base_operation { + account_name_type oracle; + pm_object_id_type market_id = 0; + string response; ///< rebuttal text, <= MAX_PM_DISPUTE_REASON_LEN + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(oracle); } + }; + + /// 23. Lift an oracle and/or creator ban early. A ban set by an account-mode dispute + /// resolve (pm_dispute_resolve) records its issuer in `banned_by`; only that same resolver + /// may reverse it here. Bans otherwise expire only when now >= banned_until. + struct pm_unban_operation : public base_operation { + account_name_type resolver; ///< the account that imposed the ban (banned_by) + account_name_type target; ///< the banned oracle / creator account + bool unban_oracle = false; + bool unban_creator = false; + + extensions_type extensions; + + void validate() const; + void get_required_active_authorities(flat_set& a) const { a.insert(resolver); } + }; + +} } // graphene::protocol + +FC_REFLECT((graphene::protocol::pm_oracle_register_operation), + (owner)(insurance)(fee_percent)(fixed_fee)(rules_url) + (auto_accept_creator)(auto_accept_resolver)(auto_accept)(extensions)) +FC_REFLECT((graphene::protocol::pm_oracle_update_operation), + (owner)(insurance_delta)(fee_percent)(fixed_fee)(rules_url) + (auto_accept_creator)(auto_accept_resolver)(auto_accept)(extensions)) +FC_REFLECT((graphene::protocol::pm_create_market_operation), + (creator)(oracle)(market_type)(outcomes)(url)(oracle_fee_percent)(oracle_fixed_fee)(creator_fee_percent) + (liquidity_fee_percent)(liquidity)(lmsr_b)(betting_expiration)(result_expiration) + (time_penalty_type)(time_penalty_value)(penalty_curve_type)(allow_early_resolution) + (allow_cancellation)(allow_batch)(allow_instant_bet)(endogeneity_tier)(dispute_mode) + (dispute_resolver)(dispute_penalty_percent)(metadata)(extensions)) +FC_REFLECT((graphene::protocol::pm_oracle_accept_market_operation), + (oracle)(market_id)(accept)(oracle_fee_percent)(oracle_fixed_fee)(extensions)) +FC_REFLECT((graphene::protocol::pm_place_bet_operation), + (account)(market_id)(side)(outcome_index)(amount)(min_tokens)(mode)(extensions)) +FC_REFLECT((graphene::protocol::pm_commit_bet_operation), + (account)(market_id)(commitment)(escrow_amount)(no_reveal_fee_percent)(extensions)) +FC_REFLECT((graphene::protocol::pm_reveal_bet_operation), + (account)(commit_id)(side)(outcome_index)(amount)(salt)(min_tokens)(extensions)) +FC_REFLECT((graphene::protocol::pm_cancel_bet_operation), + (account)(bet_id)(min_return)(extensions)) +FC_REFLECT((graphene::protocol::pm_add_liquidity_operation), + (provider)(market_id)(amount)(extensions)) +FC_REFLECT((graphene::protocol::pm_withdraw_liquidity_operation), + (provider)(liquidity_id)(amount)(extensions)) +FC_REFLECT((graphene::protocol::pm_resolve_market_operation), + (oracle)(market_id)(winning_outcome)(decision_url)(decision_reason)(extensions)) +FC_REFLECT((graphene::protocol::pm_no_contest_operation), + (oracle)(market_id)(reason)(extensions)) +FC_REFLECT((graphene::protocol::pm_dispute_create_operation), + (disputer)(market_id)(proposed_outcome)(reason)(extensions)) +FC_REFLECT((graphene::protocol::pm_dispute_vote_operation), + (voter)(market_id)(vote_outcome)(vote_percent)(extensions)) +FC_REFLECT((graphene::protocol::pm_dispute_resolve_operation), + (resolver)(market_id)(correct_outcome)(penalty_amount)(ban_oracle)(ban_oracle_until) + (ban_creator)(ban_creator_until)(extensions)) +FC_REFLECT((graphene::protocol::pm_transfer_position_operation), + (from)(bet_id)(to)(amount)(memo)(extensions)) +FC_REFLECT((graphene::protocol::pm_lazy_deposit_operation), + (account)(amount)(extensions)) +FC_REFLECT((graphene::protocol::pm_lazy_withdraw_operation), + (account)(shares)(emergency)(extensions)) +FC_REFLECT((graphene::protocol::pm_leverage_open_operation), + (account)(market_id)(outcome_index)(collateral)(loan)(min_tokens)(max_slippage_percent)(extensions)) +FC_REFLECT((graphene::protocol::pm_leverage_close_operation), + (account)(position_id)(min_return)(extensions)) +FC_REFLECT((graphene::protocol::pm_leverage_convert_operation), + (account)(position_id)(conversion_profit_cost)(extensions)) +FC_REFLECT((graphene::protocol::pm_dispute_oracle_respond_operation), + (oracle)(market_id)(response)(extensions)) +FC_REFLECT((graphene::protocol::pm_unban_operation), + (resolver)(target)(unban_oracle)(unban_creator)(extensions)) diff --git a/libraries/protocol/include/graphene/protocol/pm_virtual_operations.hpp b/libraries/protocol/include/graphene/protocol/pm_virtual_operations.hpp new file mode 100644 index 0000000000..42c9c86bc0 --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/pm_virtual_operations.hpp @@ -0,0 +1,267 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace protocol { + + // HF14 Prediction-Market virtual operations: deterministic, block-generated by the + // bounded per-block cron in database_pm.cpp. They derive from virtual_operation, so + // is_virtual_operation() returns true automatically (no manual registration needed). + + using pm_vop_object_id_type = int64_t; + + /// Epoch-boundary batch settlement of queued/revealed bets at a uniform price. + struct pm_batch_settle_operation : public virtual_operation { + pm_batch_settle_operation() {} + pm_batch_settle_operation(pm_vop_object_id_type m, uint32_t e, uint32_t n) + : market_id(m), epoch(e), settled_bets(n) {} + + pm_vop_object_id_type market_id = 0; + uint32_t epoch = 0; + uint32_t settled_bets = 0; + }; + + /// Unrevealed commitment forfeits its penalty into the market's forfeit_pool. + struct pm_commit_forfeit_operation : public virtual_operation { + pm_commit_forfeit_operation() {} + pm_commit_forfeit_operation(const account_name_type& a, pm_vop_object_id_type c, + pm_vop_object_id_type m, const asset& p, const asset& r) + : account(a), commit_id(c), market_id(m), penalty(p), refund(r) {} + + account_name_type account; + pm_vop_object_id_type commit_id = 0; + pm_vop_object_id_type market_id = 0; + asset penalty; ///< -> market.forfeit_pool + asset refund; ///< returned to account + }; + + /// Deferred payout credited to a winner after the dispute grace elapsed. + struct pm_auto_payout_operation : public virtual_operation { + pm_auto_payout_operation() {} + pm_auto_payout_operation(const account_name_type& a, pm_vop_object_id_type m, + pm_vop_object_id_type b, const asset& p) + : account(a), market_id(m), bet_id(b), payout(p) {} + + account_name_type account; + pm_vop_object_id_type market_id = 0; + pm_vop_object_id_type bet_id = 0; + asset payout; + }; + + /// A dispute was filed against a resolved market. Carries the `oracle` (and `disputer`) + /// so the event lands in BOTH their account histories — the oracle's timeline shows + /// "a dispute was opened against my resolution", not just the disputer's side. + struct pm_dispute_opened_operation : public virtual_operation { + pm_dispute_opened_operation() {} + pm_dispute_opened_operation(const account_name_type& orc, const account_name_type& disp, + pm_vop_object_id_type m, int16_t po) + : oracle(orc), disputer(disp), market_id(m), proposed_outcome(po) {} + + account_name_type oracle; + account_name_type disputer; + pm_vop_object_id_type market_id = 0; + int16_t proposed_outcome = -1; + }; + + /// Committee-mode tally finalized at voting_end_time. Carries `oracle` so the verdict + /// (and any penalty) is visible in the oracle's own account history. + struct pm_dispute_finalize_operation : public virtual_operation { + pm_dispute_finalize_operation() {} + pm_dispute_finalize_operation(const account_name_type& orc, pm_vop_object_id_type m, + int16_t o, const asset& p) + : oracle(orc), market_id(m), winning_outcome(o), oracle_penalty(p) {} + + account_name_type oracle; + pm_vop_object_id_type market_id = 0; + int16_t winning_outcome = -1; + asset oracle_penalty; + }; + + /// Anti-freeze fallback: dispute unresolved at auto_close_time -> full refund. + /// Carries `oracle` so the auto-close (and its penalty) shows in the oracle's history. + struct pm_dispute_auto_close_operation : public virtual_operation { + pm_dispute_auto_close_operation() {} + pm_dispute_auto_close_operation(const account_name_type& orc, pm_vop_object_id_type m, + const asset& p) + : oracle(orc), market_id(m), oracle_penalty(p) {} + + account_name_type oracle; + pm_vop_object_id_type market_id = 0; + asset oracle_penalty; + }; + + /// Oracle missed result_expiration -> slash insurance, refund all. + struct pm_oracle_missed_penalty_operation : public virtual_operation { + pm_oracle_missed_penalty_operation() {} + pm_oracle_missed_penalty_operation(const account_name_type& o, pm_vop_object_id_type m, const asset& s) + : oracle(o), market_id(m), slashed(s) {} + + account_name_type oracle; + pm_vop_object_id_type market_id = 0; + asset slashed; + }; + + /// Lazy-pool graduated recall step (idle market allocation returned to free_balance). + struct pm_lazy_recall_operation : public virtual_operation { + pm_lazy_recall_operation() {} + pm_lazy_recall_operation(pm_vop_object_id_type m, const asset& r) + : market_id(m), recalled(r) {} + + pm_vop_object_id_type market_id = 0; + asset recalled; + }; + + /// A leveraged position force-closed by the protocol (opposing bet / cancel-bet / expiration). + struct pm_leverage_liquidate_operation : public virtual_operation { + pm_leverage_liquidate_operation() {} + pm_leverage_liquidate_operation(const account_name_type& a, pm_vop_object_id_type p, + pm_vop_object_id_type m, const asset& cv, + const asset& pr, const asset& br, uint8_t r) + : account(a), position_id(p), market_id(m), cancel_value(cv), + pool_received(pr), bettor_received(br), reason(r) {} + + account_name_type account; + pm_vop_object_id_type position_id = 0; + pm_vop_object_id_type market_id = 0; + asset cancel_value; + asset pool_received; + asset bettor_received; + uint8_t reason = 0; ///< 0 opposing_bet, 1 cancel_bet, 2 expiration + }; + + /// Market launched: the oracle accepted (or a self-oracle auto-accepted at creation), + /// freezing the agreed oracle terms onto the market. Lets history-parsing scripts react + /// that the market moved from pending to active and on what terms. + struct pm_market_accepted_operation : public virtual_operation { + pm_market_accepted_operation() {} + pm_market_accepted_operation(const account_name_type& o, const account_name_type& c, + pm_vop_object_id_type m, uint16_t fp, const asset& ff, bool self) + : oracle(o), creator(c), market_id(m), + oracle_fee_percent(fp), oracle_fixed_fee(ff), self_oracle(self) {} + + account_name_type oracle; + account_name_type creator; + pm_vop_object_id_type market_id = 0; + uint16_t oracle_fee_percent = 0; ///< frozen oracle % (bp, 10000=100%) + asset oracle_fixed_fee; ///< frozen oracle fixed fee + bool self_oracle = false; ///< true = auto-accepted at creation + }; + + /// A leveraged position settled at market resolution (force-closed at the final reserves). + /// `won` = the position was solvent (cancel_value >= obligation, so the bettor got money back); + /// `bettor_received == 0` ⇒ a negative outcome (collateral lost). `leverage` is the integer + /// multiple total_bet/collateral (e.g. 10 for ×10). + struct pm_leverage_resolve_operation : public virtual_operation { + pm_leverage_resolve_operation() {} + pm_leverage_resolve_operation(const account_name_type& a, pm_vop_object_id_type p, + pm_vop_object_id_type m, bool w, + const asset& pr, const asset& br, + int16_t oi, uint16_t lev) + : account(a), position_id(p), market_id(m), won(w), + pool_received(pr), bettor_received(br), outcome_index(oi), leverage(lev) {} + + account_name_type account; + pm_vop_object_id_type position_id = 0; + pm_vop_object_id_type market_id = 0; + bool won = false; + asset pool_received; + asset bettor_received; + int16_t outcome_index = 0; ///< side/outcome the position was on + uint16_t leverage = 0; ///< total_bet / collateral (integer multiple) + }; + + /// Per-bettor settlement of a parimutuel market: the bet's stake, the side/outcome it was on, + /// and the realized payout (`0` on a loss). Emitted once per active bet inside settle_market, + /// alongside the single market-level pm_auto_payout marker. + struct pm_payout_operation : public virtual_operation { + pm_payout_operation() {} + pm_payout_operation(const account_name_type& a, pm_vop_object_id_type m, pm_vop_object_id_type b, + int8_t s, int16_t oi, const asset& amt, const asset& p) + : account(a), market_id(m), bet_id(b), side(s), outcome_index(oi), amount(amt), payout(p) {} + + account_name_type account; + pm_vop_object_id_type market_id = 0; + pm_vop_object_id_type bet_id = 0; + int8_t side = -1; ///< binary: 0/1; multi: -1 + int16_t outcome_index = -1; ///< multi: 0..N-1; binary: -1 + asset amount; ///< the stake + asset payout; ///< credited at settle (0 on a loss) + }; + + /// Acceptance window expired: the named oracle neither accepted nor rejected a pending market + /// within pm_oracle_accept_window_sec. The cron voids the market (status -1) and refunds the + /// creator's seed liquidity — the non-refundable creation fee already went to the DAO fund at + /// creation and is NOT returned. Distinct from an explicit oracle reject (a signed op). + struct pm_market_expired_operation : public virtual_operation { + pm_market_expired_operation() {} + pm_market_expired_operation(const account_name_type& o, const account_name_type& c, + pm_vop_object_id_type m, const asset& r) + : oracle(o), creator(c), market_id(m), refunded_liquidity(r) {} + + account_name_type oracle; + account_name_type creator; + pm_vop_object_id_type market_id = 0; + asset refunded_liquidity; ///< seed returned to creator (creation fee NOT refunded) + }; + + /// A temporary oracle and/or creator ban lapsed (now >= banned_until): the per-block cron + /// cleared it and emits this so history/indexers observe the lift. An *early* manual lift is + /// the signed pm_unban op instead (already visible in history), so this fires only for + /// automatic time-expiry. + struct pm_ban_expired_operation : public virtual_operation { + pm_ban_expired_operation() {} + pm_ban_expired_operation(const account_name_type& a, bool o, bool c) + : account(a), oracle(o), creator(c) {} + + account_name_type account; + bool oracle = false; ///< an oracle ban expired + bool creator = false; ///< a creator ban expired + }; + + /// F1/#300: an early-exit deferred claim was paid at settlement. An early bet-cancel or + /// leverage-close on the WINNING outcome draws from the bounded early-exit bucket + /// (pm_early_exit_reward_cap_percent × losers_sum), FIFO by exit order. `claimed` is the + /// full outcome-contingent claim that was recorded at exit; `paid` is what the bucket could + /// actually fund (paid ≤ claimed — a shortfall is a haircut once the slice is exhausted). + /// Emitting this puts the credit in the early-exiter's account history (adjust_balance alone + /// leaves no trace) and lets indexers reconcile the settlement's token flow. + struct pm_early_exit_claim_paid_operation : public virtual_operation { + pm_early_exit_claim_paid_operation() {} + pm_early_exit_claim_paid_operation(const account_name_type& a, pm_vop_object_id_type m, + uint8_t k, uint8_t o, const asset& c, const asset& p) + : account(a), market_id(m), kind(k), outcome_index(o), claimed(c), paid(p) {} + + account_name_type account; + pm_vop_object_id_type market_id = 0; + uint8_t kind = 0; ///< 0 = bet cancel, 1 = leverage close/liquidate + uint8_t outcome_index = 0; ///< the winning outcome this claim was contingent on + asset claimed; ///< full claim recorded at exit + asset paid; ///< amount the bounded bucket funded (≤ claimed) + }; + +} } // graphene::protocol + +FC_REFLECT((graphene::protocol::pm_batch_settle_operation), (market_id)(epoch)(settled_bets)) +FC_REFLECT((graphene::protocol::pm_commit_forfeit_operation), (account)(commit_id)(market_id)(penalty)(refund)) +FC_REFLECT((graphene::protocol::pm_auto_payout_operation), (account)(market_id)(bet_id)(payout)) +FC_REFLECT((graphene::protocol::pm_dispute_opened_operation), (oracle)(disputer)(market_id)(proposed_outcome)) +FC_REFLECT((graphene::protocol::pm_dispute_finalize_operation), (oracle)(market_id)(winning_outcome)(oracle_penalty)) +FC_REFLECT((graphene::protocol::pm_dispute_auto_close_operation), (oracle)(market_id)(oracle_penalty)) +FC_REFLECT((graphene::protocol::pm_oracle_missed_penalty_operation), (oracle)(market_id)(slashed)) +FC_REFLECT((graphene::protocol::pm_lazy_recall_operation), (market_id)(recalled)) +FC_REFLECT((graphene::protocol::pm_leverage_liquidate_operation), + (account)(position_id)(market_id)(cancel_value)(pool_received)(bettor_received)(reason)) +FC_REFLECT((graphene::protocol::pm_leverage_resolve_operation), + (account)(position_id)(market_id)(won)(pool_received)(bettor_received)(outcome_index)(leverage)) +FC_REFLECT((graphene::protocol::pm_market_accepted_operation), + (oracle)(creator)(market_id)(oracle_fee_percent)(oracle_fixed_fee)(self_oracle)) +FC_REFLECT((graphene::protocol::pm_payout_operation), + (account)(market_id)(bet_id)(side)(outcome_index)(amount)(payout)) +FC_REFLECT((graphene::protocol::pm_market_expired_operation), + (oracle)(creator)(market_id)(refunded_liquidity)) +FC_REFLECT((graphene::protocol::pm_ban_expired_operation), + (account)(oracle)(creator)) +FC_REFLECT((graphene::protocol::pm_early_exit_claim_paid_operation), + (account)(market_id)(kind)(outcome_index)(claimed)(paid)) diff --git a/libraries/protocol/pch.hpp b/libraries/protocol/pch.hpp new file mode 100644 index 0000000000..82094de261 --- /dev/null +++ b/libraries/protocol/pch.hpp @@ -0,0 +1,28 @@ +// Precompiled header for graphene_protocol. +// +// Opt-in via -DENABLE_PCH=ON. See libraries/chain/pch.hpp for the rationale: +// only stable third-party headers, no project headers. Selected from an +// include-frequency scan of libraries/protocol/*.{hpp,cpp}; the dominant cost +// here is FC reflection / static_variant plus the operation type machinery. +#pragma once + +// FC framework — reflection, serialization, primitives, crypto. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// C++ standard library. +#include +#include +#include +#include +#include diff --git a/libraries/protocol/pm_operations.cpp b/libraries/protocol/pm_operations.cpp new file mode 100644 index 0000000000..f6615cf66d --- /dev/null +++ b/libraries/protocol/pm_operations.cpp @@ -0,0 +1,235 @@ +#include + +namespace graphene { namespace protocol { + + // Local validation helpers (mirror chain_operations.cpp conventions; kept TU-local). + inline void pm_validate_account_name(const string& name) { + FC_ASSERT(is_valid_account_name(name), "Account name ${n} is invalid", ("n", name)); + } + inline bool pm_is_token(const asset& a) { return a.symbol == TOKEN_SYMBOL; } + inline void pm_validate_utf8_cap(const string& s, size_t cap, const char* field) { + FC_ASSERT(s.size() <= cap, "${f} exceeds byte cap ${c}", ("f", field)("c", cap)); + FC_ASSERT(fc::is_utf8(s), "${f} not formatted in UTF8", ("f", field)); + } + + void pm_oracle_register_operation::validate() const { + pm_validate_account_name(owner); + FC_ASSERT(pm_is_token(insurance), "insurance must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(insurance.amount > 0, "insurance must be positive"); + FC_ASSERT(pm_is_token(fixed_fee), "fixed_fee must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(fixed_fee.amount >= 0, "fixed_fee cannot be negative"); + FC_ASSERT(fee_percent <= 10000, "fee_percent out of range (bp, max 10000)"); + pm_validate_utf8_cap(rules_url, MAX_PM_PROFILE_URL_LEN, "rules_url"); + } + + void pm_oracle_update_operation::validate() const { + pm_validate_account_name(owner); + FC_ASSERT(insurance_delta || fee_percent || fixed_fee || rules_url, + "pm_oracle_update must change at least one field"); + if (insurance_delta) FC_ASSERT(pm_is_token(*insurance_delta), "insurance_delta must be VIZ"); + if (fixed_fee) { + FC_ASSERT(pm_is_token(*fixed_fee), "fixed_fee must be VIZ"); + FC_ASSERT(fixed_fee->amount >= 0, "fixed_fee cannot be negative"); + } + if (fee_percent) FC_ASSERT(*fee_percent <= 10000, "fee_percent out of range (bp, max 10000)"); + if (rules_url) pm_validate_utf8_cap(*rules_url, MAX_PM_PROFILE_URL_LEN, "rules_url"); + } + + void pm_create_market_operation::validate() const { + pm_validate_account_name(creator); + pm_validate_account_name(oracle); + FC_ASSERT(market_type == 0 || market_type == 1, "market_type must be 0 (binary) or 1 (multi)"); + if (market_type == 0) { + FC_ASSERT(outcomes.size() == 2, "binary market must have exactly 2 outcomes"); + FC_ASSERT(lmsr_b == 0, "lmsr_b must be 0 for binary markets"); + } else { + FC_ASSERT(outcomes.size() >= 3 && outcomes.size() <= MAX_PM_OUTCOMES_PER_MARKET, + "multi market must have 3..${m} outcomes", ("m", MAX_PM_OUTCOMES_PER_MARKET)); + FC_ASSERT(lmsr_b > 0, "lmsr_b must be positive for multi markets"); + FC_ASSERT(allow_instant_bet, "multi markets must keep allow_instant_bet=true (no LMSR batch yet)"); + } + for (const auto& label : outcomes) { + FC_ASSERT(label.size() > 0, "outcome label cannot be empty"); + pm_validate_utf8_cap(label, MAX_PM_OUTCOME_LABEL_LEN, "outcome label"); + } + pm_validate_utf8_cap(url, MAX_PM_MARKET_TITLE_LEN, "url"); + FC_ASSERT(pm_is_token(liquidity), "liquidity must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(liquidity.amount > 0, "liquidity must be positive"); + FC_ASSERT(result_expiration > betting_expiration, "result_expiration must be after betting_expiration"); + // All percents are bp (10000 = 100.00%). The oracle fee here is the OFFER CEILING; the + // worst-case sum (offer + creator + liquidity) must stay solvent (<= 100%), so the + // winners' pool can never go negative regardless of the oracle's later quote. + FC_ASSERT(oracle_fee_percent <= 10000 && creator_fee_percent <= 10000 && liquidity_fee_percent <= 10000, + "individual fee_percent out of range (bp, max 10000)"); + FC_ASSERT(uint32_t(oracle_fee_percent) + creator_fee_percent + liquidity_fee_percent <= 10000, + "sum of fees exceeds 100% (10000 bp)"); + FC_ASSERT(pm_is_token(oracle_fixed_fee), "oracle_fixed_fee must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(oracle_fixed_fee.amount >= 0, "oracle_fixed_fee cannot be negative"); + FC_ASSERT(endogeneity_tier >= 1 && endogeneity_tier <= 3, "endogeneity_tier must be 1..3"); + FC_ASSERT(dispute_penalty_percent >= -10000 && dispute_penalty_percent <= 10000, + "dispute_penalty_percent must be in [-10000, 10000]"); + FC_ASSERT(dispute_mode == 0 || dispute_mode == 1, "dispute_mode must be 0 (committee) or 1 (account)"); + FC_ASSERT(allow_instant_bet || allow_batch, "market would be unbettable: need allow_instant_bet or allow_batch"); + if (dispute_mode == 1) { + pm_validate_account_name(dispute_resolver); + // Static anti-self-judging guard (threat-model §4.11); also re-checked statefully in evaluator. + FC_ASSERT(dispute_resolver != oracle, "dispute_resolver must not be the oracle"); + FC_ASSERT(dispute_resolver != creator, "dispute_resolver must not be the creator"); + } else { + FC_ASSERT(dispute_resolver == account_name_type(), "dispute_resolver must be empty for committee mode"); + } + } + + void pm_oracle_accept_market_operation::validate() const { + pm_validate_account_name(oracle); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(oracle_fee_percent <= 10000, "oracle_fee_percent out of range (bp, max 10000)"); + FC_ASSERT(pm_is_token(oracle_fixed_fee), "oracle_fixed_fee must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(oracle_fixed_fee.amount >= 0, "oracle_fixed_fee cannot be negative"); + } + + void pm_place_bet_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(pm_is_token(amount), "amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(amount.amount > 0, "bet amount must be positive"); + FC_ASSERT(min_tokens >= 0, "min_tokens cannot be negative"); + FC_ASSERT(mode == 0 || mode == 1, "mode must be 0 (instant) or 1 (batch)"); + FC_ASSERT(side >= -1 && side <= 1, "side must be -1, 0 or 1"); + FC_ASSERT(outcome_index >= -1, "outcome_index must be >= -1"); + // Exactly one selector populated: binary uses side (0/1), multi uses outcome_index (>=0). + FC_ASSERT((side >= 0) != (outcome_index >= 0), "set exactly one of side / outcome_index"); + } + + void pm_commit_bet_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(commitment != fc::sha256(), "commitment cannot be empty"); + FC_ASSERT(pm_is_token(escrow_amount), "escrow_amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(escrow_amount.amount > 0, "escrow_amount must be positive"); + FC_ASSERT(no_reveal_fee_percent <= 10000, "no_reveal_fee_percent out of range (bp, max 10000)"); + } + + void pm_reveal_bet_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(commit_id >= 0, "commit_id must be non-negative"); + FC_ASSERT(pm_is_token(amount), "amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(amount.amount > 0, "reveal amount must be positive"); + FC_ASSERT(min_tokens >= 0, "min_tokens cannot be negative"); + FC_ASSERT(side >= -1 && side <= 1, "side must be -1, 0 or 1"); + FC_ASSERT(outcome_index >= -1, "outcome_index must be >= -1"); + FC_ASSERT((side >= 0) != (outcome_index >= 0), "set exactly one of side / outcome_index"); + pm_validate_utf8_cap(salt, MAX_PM_PROFILE_URL_LEN, "salt"); + } + + void pm_cancel_bet_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(bet_id >= 0, "bet_id must be non-negative"); + FC_ASSERT(min_return >= 0, "min_return cannot be negative"); + } + + void pm_add_liquidity_operation::validate() const { + pm_validate_account_name(provider); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(pm_is_token(amount), "amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(amount.amount > 0, "liquidity amount must be positive"); + } + + void pm_withdraw_liquidity_operation::validate() const { + pm_validate_account_name(provider); + FC_ASSERT(liquidity_id >= 0, "liquidity_id must be non-negative"); + FC_ASSERT(pm_is_token(amount), "amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(amount.amount >= 0, "amount cannot be negative (0 = full position)"); + } + + void pm_resolve_market_operation::validate() const { + pm_validate_account_name(oracle); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(winning_outcome >= -1, "winning_outcome must be >= -1"); + pm_validate_utf8_cap(decision_url, MAX_PM_DECISION_URL_LEN, "decision_url"); + pm_validate_utf8_cap(decision_reason, MAX_PM_DISPUTE_REASON_LEN, "decision_reason"); + } + + void pm_no_contest_operation::validate() const { + pm_validate_account_name(oracle); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + pm_validate_utf8_cap(reason, MAX_PM_DISPUTE_REASON_LEN, "reason"); + } + + void pm_dispute_create_operation::validate() const { + pm_validate_account_name(disputer); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(proposed_outcome >= -1, "proposed_outcome must be >= -1"); + pm_validate_utf8_cap(reason, MAX_PM_DISPUTE_REASON_LEN, "reason"); + } + + void pm_dispute_vote_operation::validate() const { + pm_validate_account_name(voter); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(vote_outcome >= -1, "vote_outcome must be >= -1"); + FC_ASSERT(vote_percent >= -10000 && vote_percent <= 10000, "vote_percent must be in [-10000, 10000]"); + } + + void pm_dispute_resolve_operation::validate() const { + pm_validate_account_name(resolver); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(correct_outcome >= -1, "correct_outcome must be >= -1"); + FC_ASSERT(pm_is_token(penalty_amount), "penalty_amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(penalty_amount.amount >= 0, "penalty_amount cannot be negative"); + } + + void pm_transfer_position_operation::validate() const { + pm_validate_account_name(from); + pm_validate_account_name(to); + FC_ASSERT(from != to, "cannot transfer a position to yourself"); + FC_ASSERT(bet_id >= 0, "bet_id must be non-negative"); + FC_ASSERT(amount >= 0, "amount cannot be negative (0 = full position)"); + pm_validate_utf8_cap(memo, MAX_PM_DISPUTE_REASON_LEN, "memo"); + } + + void pm_lazy_deposit_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(pm_is_token(amount), "amount must be VIZ (TOKEN_SYMBOL)"); + FC_ASSERT(amount.amount > 0, "deposit amount must be positive"); + } + + void pm_lazy_withdraw_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(shares >= 0, "shares cannot be negative (0 = all)"); + } + + void pm_leverage_open_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(outcome_index == 0 || outcome_index == 1, "leverage is CPMM-binary only (outcome 0/1)"); + FC_ASSERT(collateral.symbol == TOKEN_SYMBOL, "collateral must be VIZ"); + FC_ASSERT(loan.symbol == TOKEN_SYMBOL, "loan must be VIZ"); + FC_ASSERT(collateral.amount > 0, "collateral must be positive"); + FC_ASSERT(loan.amount > 0, "loan must be positive"); + FC_ASSERT(min_tokens >= 0, "min_tokens cannot be negative"); + FC_ASSERT(max_slippage_percent <= 100, "max_slippage_percent out of range"); + } + + void pm_leverage_close_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(min_return >= 0, "min_return cannot be negative"); + } + + void pm_leverage_convert_operation::validate() const { + pm_validate_account_name(account); + FC_ASSERT(conversion_profit_cost <= 100, "conversion_profit_cost out of range"); + } + + void pm_dispute_oracle_respond_operation::validate() const { + pm_validate_account_name(oracle); + FC_ASSERT(market_id >= 0, "market_id must be non-negative"); + FC_ASSERT(response.size() > 0, "response cannot be empty"); + pm_validate_utf8_cap(response, MAX_PM_DISPUTE_REASON_LEN, "response"); + } + + void pm_unban_operation::validate() const { + pm_validate_account_name(resolver); + pm_validate_account_name(target); + FC_ASSERT(unban_oracle || unban_creator, "nothing to unban: set unban_oracle and/or unban_creator"); + } + +} } // graphene::protocol diff --git a/libraries/time/CMakeLists.txt b/libraries/time/CMakeLists.txt index 5e34f97814..0bbdde0a62 100644 --- a/libraries/time/CMakeLists.txt +++ b/libraries/time/CMakeLists.txt @@ -1,14 +1,8 @@ file(GLOB HEADERS "include/graphene/time/*.hpp") -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_time SHARED +add_library(graphene_time ${VIZ_LIBRARY_TYPE} time.cpp ) -else() - add_library(graphene_time STATIC - time.cpp - ) -endif() target_link_libraries(graphene_time fc) target_include_directories(graphene_time diff --git a/libraries/utilities/CMakeLists.txt b/libraries/utilities/CMakeLists.txt index 1b9747ce1a..89d0e21531 100644 --- a/libraries/utilities/CMakeLists.txt +++ b/libraries/utilities/CMakeLists.txt @@ -26,15 +26,9 @@ set(sources configure_file("${CMAKE_CURRENT_SOURCE_DIR}/git_revision.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp" @ONLY) list(APPEND sources "${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp") -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_utilities SHARED +add_library(graphene_utilities ${VIZ_LIBRARY_TYPE} ${sources} ${HEADERS}) -else() - add_library(graphene_utilities STATIC - ${sources} - ${HEADERS}) -endif() target_link_libraries(graphene_utilities fc) target_include_directories(graphene_utilities PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/libraries/wallet/CMakeLists.txt b/libraries/wallet/CMakeLists.txt index b1c42f093d..759929c9e9 100644 --- a/libraries/wallet/CMakeLists.txt +++ b/libraries/wallet/CMakeLists.txt @@ -19,17 +19,10 @@ list(APPEND ${CURRENT_TARGET}_SOURCES wallet.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${${CURRENT_TARGET}_HEADERS} ${${CURRENT_TARGET}_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${${CURRENT_TARGET}_HEADERS} - ${${CURRENT_TARGET}_SOURCES} - ) -endif() # I don't know why graphene_app is required twice in the following line, I just know the linker breaks if it isn't. target_link_libraries( graphene_${CURRENT_TARGET} @@ -42,6 +35,7 @@ target_link_libraries( graphene::operation_history graphene::account_by_key graphene::validator_api + graphene::prediction_market_api graphene_protocol graphene_utilities fc @@ -50,6 +44,10 @@ target_link_libraries( ) target_include_directories( graphene_${CURRENT_TARGET} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +if(ENABLE_PCH) + target_precompile_headers(graphene_${CURRENT_TARGET} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/pch.hpp") +endif() + if(MSVC) set_source_files_properties(wallet.cpp PROPERTIES COMPILE_FLAGS "/bigobj") endif(MSVC) diff --git a/libraries/wallet/include/graphene/wallet/remote_node_api.hpp b/libraries/wallet/include/graphene/wallet/remote_node_api.hpp index d105655f17..a9fcd995a0 100644 --- a/libraries/wallet/include/graphene/wallet/remote_node_api.hpp +++ b/libraries/wallet/include/graphene/wallet/remote_node_api.hpp @@ -103,6 +103,50 @@ struct remote_validator_api { uint64_t get_validator_count(); }; +/** + * Dummy signature class for the HF14 prediction_market_api plugin (Onix). Each method mirrors the + * node's msg_pack read API by name; fc::api serialises the positional args into the JSON-RPC array + * the plugin unpacks. Not used to execute calls — only to format them. + */ +struct remote_prediction_market_api { + // Every read method returns fc::variant rather than the node's typed object. The chainbase state + // objects (pm_market_object, pm_bet_object, …) and the API DTOs that embed them are intentionally + // NOT default-constructible (deleted default ctor / shared_string members need a segment manager), + // so fc::api's client-side deserializer (`T tmp; var.as()`) cannot instantiate them. The node + // already emits fully-formed JSON; the wallet just forwards it as a variant (cli_wallet prints it). + fc::variant get_market( int64_t ); + fc::variant list_markets( int8_t, uint32_t, uint32_t ); + fc::variant list_markets_by_oracle( account_name_type, uint32_t, uint32_t ); + fc::variant list_markets_by_oracle_status( account_name_type, int8_t, uint32_t, uint32_t ); + fc::variant list_markets_by_creator( account_name_type, uint32_t, uint32_t ); + fc::variant get_market_outcomes( int64_t ); + fc::variant get_market_weight_sums( int64_t ); + fc::variant get_market_bets( int64_t, uint32_t, uint32_t ); + fc::variant get_account_positions( account_name_type, uint32_t, uint32_t ); + fc::variant get_market_liquidity( int64_t, uint32_t, uint32_t ); + fc::variant get_account_leverage_positions( account_name_type, uint32_t, uint32_t ); + fc::variant get_market_leverage_positions( int64_t, uint32_t, uint32_t ); + fc::variant get_creator_ban( account_name_type ); + fc::variant get_oracle( account_name_type ); + fc::variant list_oracles( uint32_t, uint32_t ); + fc::variant get_dispute( int64_t ); + fc::variant get_dispute_votes( int64_t ); + fc::variant get_lazy_pool(); + fc::variant get_lazy_deposit( account_name_type ); + fc::variant get_pm_chain_properties(); + fc::variant get_market_meta( int64_t ); + fc::variant list_markets_by_category( string, uint32_t, uint32_t ); + fc::variant list_markets_by_event( string, uint32_t, uint32_t ); + fc::variant get_market_kline( int64_t, uint32_t, uint32_t ); + fc::variant get_leverage_quote( int64_t, int16_t, int64_t ); + fc::variant get_leverage_close_preview( int64_t ); + fc::variant get_leverage_convert_preview( int64_t ); + fc::variant get_market_categories(); + fc::variant get_market_full( int64_t, account_name_type ); + fc::variant get_lazy_allocations( uint32_t, uint32_t ); + fc::variant get_market_lazy_allocation( int64_t ); +}; + } } /** @@ -175,4 +219,41 @@ FC_API( graphene::wallet::remote_validator_api, (get_validator_count) (get_validator_by_account) (lookup_validator_accounts) +) + +/** + * Declaration of remote API formatter to prediction_market_api plugin on remote node + */ +FC_API( graphene::wallet::remote_prediction_market_api, + (get_market) + (list_markets) + (list_markets_by_oracle) + (list_markets_by_oracle_status) + (list_markets_by_creator) + (get_market_outcomes) + (get_market_weight_sums) + (get_market_bets) + (get_account_positions) + (get_market_liquidity) + (get_account_leverage_positions) + (get_market_leverage_positions) + (get_creator_ban) + (get_oracle) + (list_oracles) + (get_dispute) + (get_dispute_votes) + (get_lazy_pool) + (get_lazy_deposit) + (get_pm_chain_properties) + (get_market_meta) + (list_markets_by_category) + (list_markets_by_event) + (get_market_kline) + (get_leverage_quote) + (get_leverage_close_preview) + (get_leverage_convert_preview) + (get_market_categories) + (get_market_full) + (get_lazy_allocations) + (get_market_lazy_allocation) ) \ No newline at end of file diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index c04408b5d0..88bf8b3449 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1297,6 +1297,69 @@ namespace graphene { namespace wallet { bool broadcast = false ); + // ========== HF14 Prediction Market Helpers ========== + + annotated_signed_transaction pm_oracle_register(string owner, asset insurance, uint16_t fee_percent, asset fixed_fee, string rules_url, string auto_accept_creator, string auto_accept_resolver, bool auto_accept, bool broadcast = false); + annotated_signed_transaction pm_oracle_update(string owner, optional insurance_delta, optional fee_percent, optional fixed_fee, optional rules_url, optional auto_accept_creator, optional auto_accept_resolver, optional auto_accept, bool broadcast = false); + annotated_signed_transaction pm_create_market(string creator, string oracle, uint8_t market_type, vector outcomes, string url, uint16_t oracle_fee_percent, asset oracle_fixed_fee, uint16_t creator_fee_percent, uint16_t liquidity_fee_percent, asset liquidity, share_type lmsr_b, time_point_sec betting_expiration, time_point_sec result_expiration, uint8_t dispute_mode, string dispute_resolver, bool broadcast = false); + annotated_signed_transaction pm_oracle_accept_market(string oracle, int64_t market_id, bool accept, uint16_t oracle_fee_percent, asset oracle_fixed_fee, bool broadcast = false); + annotated_signed_transaction pm_place_bet(string account, int64_t market_id, int8_t side, int16_t outcome_index, asset amount, share_type min_tokens, uint8_t mode, bool broadcast = false); + + /// Deterministic commitment hash, byte-identical to the node's verify_commit(). + fc::sha256 pm_commitment_hash(int64_t market_id, string account, int8_t side, int16_t outcome_index, asset amount, share_type min_tokens, string salt) const; + annotated_signed_transaction pm_commit_bet(string account, int64_t market_id, int8_t side, int16_t outcome_index, asset amount, share_type min_tokens, string salt, uint16_t no_reveal_fee_percent, asset escrow_amount, bool broadcast = false); + annotated_signed_transaction pm_reveal_bet(string account, int64_t commit_id, int8_t side, int16_t outcome_index, asset amount, string salt, share_type min_tokens, bool broadcast = false); + annotated_signed_transaction pm_cancel_bet(string account, int64_t bet_id, share_type min_return, bool broadcast = false); + annotated_signed_transaction pm_add_liquidity(string provider, int64_t market_id, asset amount, bool broadcast = false); + annotated_signed_transaction pm_withdraw_liquidity(string provider, int64_t liquidity_id, asset amount, bool broadcast = false); + annotated_signed_transaction pm_resolve_market(string oracle, int64_t market_id, int16_t winning_outcome, string decision_url, bool broadcast = false); + annotated_signed_transaction pm_no_contest(string oracle, int64_t market_id, string reason, bool broadcast = false); + annotated_signed_transaction pm_dispute_create(string disputer, int64_t market_id, int16_t proposed_outcome, string reason, bool broadcast = false); + annotated_signed_transaction pm_dispute_vote(string voter, int64_t market_id, int16_t vote_outcome, int16_t vote_percent, bool broadcast = false); + annotated_signed_transaction pm_dispute_resolve(string resolver, int64_t market_id, int16_t correct_outcome, asset penalty_amount, bool ban_oracle, time_point_sec ban_oracle_until, bool ban_creator, time_point_sec ban_creator_until, bool broadcast = false); + annotated_signed_transaction pm_transfer_position(string from, int64_t bet_id, string to, share_type amount, string memo, bool broadcast = false); + annotated_signed_transaction pm_lazy_deposit(string account, asset amount, bool broadcast = false); + annotated_signed_transaction pm_lazy_withdraw(string account, share_type shares, bool emergency, bool broadcast = false); + annotated_signed_transaction pm_leverage_open(string account, int64_t market_id, int16_t outcome_index, asset collateral, asset loan, share_type min_tokens, uint16_t max_slippage_percent, bool broadcast = false); + annotated_signed_transaction pm_leverage_close(string account, int64_t position_id, share_type min_return, bool broadcast = false); + annotated_signed_transaction pm_leverage_convert(string account, int64_t position_id, uint16_t conversion_profit_cost, bool broadcast = false); + + // ========== HF14 Prediction Market read API (prediction_market_api plugin) ========== + // Thin pass-throughs to the node's prediction_market_api plugin so cli_wallet can inspect + // markets, oracles, positions, disputes and the lazy pool. Require the node to run that plugin. + + // These read pass-throughs return fc::variant (raw JSON from the node) rather than the + // node's typed objects: the underlying chainbase state objects and the API DTOs that embed + // them are not default-constructible, so the fc::api client proxy cannot deserialize them + // into concrete types. cli_wallet renders the variant as JSON, so the surface is unchanged. + fc::variant pm_get_market(int64_t market_id) const; + fc::variant pm_list_markets(int8_t status, uint32_t from, uint32_t limit) const; + fc::variant pm_list_markets_by_oracle(string oracle, uint32_t from, uint32_t limit) const; + fc::variant pm_list_markets_by_oracle_status(string oracle, int8_t status, uint32_t from, uint32_t limit) const; + fc::variant pm_list_markets_by_creator(string creator, uint32_t from, uint32_t limit) const; + fc::variant pm_get_market_outcomes(int64_t market_id) const; + fc::variant pm_get_market_weight_sums(int64_t market_id) const; + fc::variant pm_get_market_bets(int64_t market_id, uint32_t from, uint32_t limit) const; + fc::variant pm_get_account_positions(string account, uint32_t from, uint32_t limit) const; + fc::variant pm_get_market_liquidity(int64_t market_id, uint32_t from, uint32_t limit) const; + fc::variant pm_get_account_leverage_positions(string account, uint32_t from, uint32_t limit) const; + fc::variant pm_get_market_leverage_positions(int64_t market_id, uint32_t from, uint32_t limit) const; + fc::variant pm_get_creator_ban(string account) const; + fc::variant pm_get_oracle(string owner) const; + fc::variant pm_list_oracles(uint32_t from, uint32_t limit) const; + fc::variant pm_get_dispute(int64_t market_id) const; + fc::variant pm_get_dispute_votes(int64_t market_id) const; + fc::variant pm_get_lazy_pool() const; + fc::variant pm_get_lazy_deposit(string account) const; + fc::variant pm_get_chain_properties() const; + fc::variant pm_get_market_meta(int64_t market_id) const; + fc::variant pm_list_markets_by_category(string category, uint32_t from, uint32_t limit) const; + /// Sibling markets sharing a parent event key (one match/game); full cards, oldest-first. + fc::variant pm_list_markets_by_event(string event, uint32_t from, uint32_t limit) const; + /// Kline / weight-over-time history for charting. Pagination is offset-from-newest: + /// (from=0,limit=1000) = latest ≤1000 changes; (from=1000,…) steps another 1000 back. + fc::variant pm_get_market_kline(int64_t market_id, uint32_t from, uint32_t limit) const; + // ========== VIZ DNS Nameserver Helpers ========== /** @@ -1515,6 +1578,56 @@ FC_API( graphene::wallet::wallet_api, (buy_account) (target_account_sale) + /// HF14 prediction market helpers + (pm_oracle_register) + (pm_oracle_update) + (pm_create_market) + (pm_oracle_accept_market) + (pm_place_bet) + (pm_commitment_hash) + (pm_commit_bet) + (pm_reveal_bet) + (pm_cancel_bet) + (pm_add_liquidity) + (pm_withdraw_liquidity) + (pm_resolve_market) + (pm_no_contest) + (pm_dispute_create) + (pm_dispute_vote) + (pm_dispute_resolve) + (pm_transfer_position) + (pm_lazy_deposit) + (pm_lazy_withdraw) + (pm_leverage_open) + (pm_leverage_close) + (pm_leverage_convert) + + /// HF14 prediction market read API (prediction_market_api plugin) + (pm_get_market) + (pm_list_markets) + (pm_list_markets_by_oracle) + (pm_list_markets_by_oracle_status) + (pm_list_markets_by_creator) + (pm_get_market_outcomes) + (pm_get_market_weight_sums) + (pm_get_market_bets) + (pm_get_account_positions) + (pm_get_market_liquidity) + (pm_get_account_leverage_positions) + (pm_get_market_leverage_positions) + (pm_get_creator_ban) + (pm_get_oracle) + (pm_list_oracles) + (pm_get_dispute) + (pm_get_dispute_votes) + (pm_get_lazy_pool) + (pm_get_lazy_deposit) + (pm_get_chain_properties) + (pm_get_market_meta) + (pm_list_markets_by_category) + (pm_list_markets_by_event) + (pm_get_market_kline) + /// NS DNS helpers (ns_validate_ipv4) (ns_validate_sha256_hash) diff --git a/libraries/wallet/pch.hpp b/libraries/wallet/pch.hpp new file mode 100644 index 0000000000..c887e888c4 --- /dev/null +++ b/libraries/wallet/pch.hpp @@ -0,0 +1,34 @@ +// Precompiled header for graphene_wallet. +// +// Opt-in via -DENABLE_PCH=ON. See libraries/chain/pch.hpp for the rationale: +// only stable third-party headers, no project headers. Selected from an +// include-frequency scan of libraries/wallet/*.{hpp,cpp}; the cost here is +// Boost.MultiIndex, Boost.Range/algorithm, and FC's api/reflection headers. +#pragma once + +// Boost.MultiIndex. +#include +#include +#include +#include + +// Boost string / range algorithms used through the wallet. +#include +#include +#include + +// FC framework. +#include +#include +#include +#include + +// C++ standard library. +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 196a2a963e..747bae7b75 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -8,6 +8,7 @@ #else #include #endif +#include #include #include #include @@ -222,10 +223,25 @@ namespace graphene { namespace wallet { _wallet.ws_server = initial_data.ws_server; chain_id = _chain_id; + + // The prediction_market_api plugin is optional; bind it only if the node serves it + // so a wallet pointed at a node without HF14 PM read API still starts normally. + try { + _remote_prediction_market_api = con.get_remote_api< remote_prediction_market_api >( 0, "prediction_market_api" ); + } catch ( const fc::exception& ) { + ilog( "prediction_market_api not available on this node; pm_get_* read methods are disabled." ); + } } virtual ~wallet_api_impl() {} + /// Returns the prediction_market_api proxy, asserting the node actually serves it. + fc::api< remote_prediction_market_api >& pm_api() { + FC_ASSERT( _remote_prediction_market_api.valid(), + "This node does not run the prediction_market_api plugin; pm_get_* calls are unavailable." ); + return *_remote_prediction_market_api; + } + void encrypt_keys() { if( !is_locked() ) { plain_keys data; @@ -886,6 +902,8 @@ namespace graphene { namespace wallet { fc::api< remote_network_broadcast_api> _remote_network_broadcast_api; fc::api< remote_account_by_key > _remote_account_by_key; fc::api< remote_validator_api > _remote_validator_api; + /// Optional: only bound if the node runs the HF14 prediction_market_api plugin. + fc::optional< fc::api< remote_prediction_market_api > > _remote_prediction_market_api; uint32_t _tx_expiration_seconds = 30; flat_map _prototype_ops; @@ -2484,6 +2502,400 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st return my->sign_transaction(tx, broadcast); } + // ========== HF14 Prediction Market Helper Implementations ========== + + annotated_signed_transaction wallet_api::pm_oracle_register(string owner, asset insurance, uint16_t fee_percent, asset fixed_fee, string rules_url, string auto_accept_creator, string auto_accept_resolver, bool auto_accept, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_oracle_register_operation op; + op.owner = owner; + op.insurance = insurance; + op.fee_percent = fee_percent; + op.fixed_fee = fixed_fee; + op.rules_url = rules_url; + // Empty string ⇒ unset (any creator / committee-only), matching the operation's defaults. + op.auto_accept_creator = auto_accept_creator; + op.auto_accept_resolver = auto_accept_resolver; + op.auto_accept = auto_accept; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_oracle_update(string owner, optional insurance_delta, optional fee_percent, optional fixed_fee, optional rules_url, optional auto_accept_creator, optional auto_accept_resolver, optional auto_accept, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_oracle_update_operation op; + op.owner = owner; + op.insurance_delta = insurance_delta; + op.fee_percent = fee_percent; + op.fixed_fee = fixed_fee; + op.rules_url = rules_url; + if (auto_accept_creator.valid()) op.auto_accept_creator = account_name_type(*auto_accept_creator); + if (auto_accept_resolver.valid()) op.auto_accept_resolver = account_name_type(*auto_accept_resolver); + op.auto_accept = auto_accept; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_create_market(string creator, string oracle, uint8_t market_type, vector outcomes, string url, uint16_t oracle_fee_percent, asset oracle_fixed_fee, uint16_t creator_fee_percent, uint16_t liquidity_fee_percent, asset liquidity, share_type lmsr_b, time_point_sec betting_expiration, time_point_sec result_expiration, uint8_t dispute_mode, string dispute_resolver, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_create_market_operation op; + op.creator = creator; + op.oracle = oracle; + op.market_type = market_type; + op.outcomes = outcomes; + op.url = url; + op.oracle_fee_percent = oracle_fee_percent; + op.oracle_fixed_fee = oracle_fixed_fee; + op.creator_fee_percent = creator_fee_percent; + op.liquidity_fee_percent = liquidity_fee_percent; + op.liquidity = liquidity; + op.lmsr_b = lmsr_b; + op.betting_expiration = betting_expiration; + op.result_expiration = result_expiration; + op.dispute_mode = dispute_mode; + op.dispute_resolver = dispute_resolver; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_oracle_accept_market(string oracle, int64_t market_id, bool accept, uint16_t oracle_fee_percent, asset oracle_fixed_fee, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_oracle_accept_market_operation op; + op.oracle = oracle; + op.market_id = market_id; + op.accept = accept; + op.oracle_fee_percent = oracle_fee_percent; + op.oracle_fixed_fee = oracle_fixed_fee; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_place_bet(string account, int64_t market_id, int8_t side, int16_t outcome_index, asset amount, share_type min_tokens, uint8_t mode, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_place_bet_operation op; + op.account = account; + op.market_id = market_id; + op.side = side; + op.outcome_index = outcome_index; + op.amount = amount; + op.min_tokens = min_tokens; + op.mode = mode; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + fc::sha256 wallet_api::pm_commitment_hash(int64_t market_id, string account, int8_t side, int16_t outcome_index, asset amount, share_type min_tokens, string salt) const { + // Byte-identical to verify_commit() in pm_evaluator.cpp. + fc::sha256::encoder enc; + int64_t mid = market_id; + enc.write(reinterpret_cast(&mid), (uint32_t)sizeof(mid)); + account_name_type acc = account; + enc.write(reinterpret_cast(&acc.data), (uint32_t)sizeof(acc.data)); + enc.write(reinterpret_cast(&side), (uint32_t)sizeof(side)); + enc.write(reinterpret_cast(&outcome_index), (uint32_t)sizeof(outcome_index)); + int64_t amt = amount.amount.value; + enc.write(reinterpret_cast(&amt), (uint32_t)sizeof(amt)); + int64_t mnt = min_tokens.value; + enc.write(reinterpret_cast(&mnt), (uint32_t)sizeof(mnt)); + enc.write(salt.data(), (uint32_t)salt.size()); + return enc.result(); + } + + annotated_signed_transaction wallet_api::pm_commit_bet(string account, int64_t market_id, int8_t side, int16_t outcome_index, asset amount, share_type min_tokens, string salt, uint16_t no_reveal_fee_percent, asset escrow_amount, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_commit_bet_operation op; + op.account = account; + op.market_id = market_id; + op.commitment = pm_commitment_hash(market_id, account, side, outcome_index, amount, min_tokens, salt); + op.escrow_amount = escrow_amount; + op.no_reveal_fee_percent = no_reveal_fee_percent; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_reveal_bet(string account, int64_t commit_id, int8_t side, int16_t outcome_index, asset amount, string salt, share_type min_tokens, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_reveal_bet_operation op; + op.account = account; + op.commit_id = commit_id; + op.side = side; + op.outcome_index = outcome_index; + op.amount = amount; + op.salt = salt; + op.min_tokens = min_tokens; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_cancel_bet(string account, int64_t bet_id, share_type min_return, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_cancel_bet_operation op; + op.account = account; + op.bet_id = bet_id; + op.min_return = min_return; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_add_liquidity(string provider, int64_t market_id, asset amount, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_add_liquidity_operation op; + op.provider = provider; + op.market_id = market_id; + op.amount = amount; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_withdraw_liquidity(string provider, int64_t liquidity_id, asset amount, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_withdraw_liquidity_operation op; + op.provider = provider; + op.liquidity_id = liquidity_id; + op.amount = amount; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_resolve_market(string oracle, int64_t market_id, int16_t winning_outcome, string decision_url, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_resolve_market_operation op; + op.oracle = oracle; + op.market_id = market_id; + op.winning_outcome = winning_outcome; + op.decision_url = decision_url; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_no_contest(string oracle, int64_t market_id, string reason, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_no_contest_operation op; + op.oracle = oracle; + op.market_id = market_id; + op.reason = reason; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_dispute_create(string disputer, int64_t market_id, int16_t proposed_outcome, string reason, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_dispute_create_operation op; + op.disputer = disputer; + op.market_id = market_id; + op.proposed_outcome = proposed_outcome; + op.reason = reason; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_dispute_vote(string voter, int64_t market_id, int16_t vote_outcome, int16_t vote_percent, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_dispute_vote_operation op; + op.voter = voter; + op.market_id = market_id; + op.vote_outcome = vote_outcome; + op.vote_percent = vote_percent; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_dispute_resolve(string resolver, int64_t market_id, int16_t correct_outcome, asset penalty_amount, bool ban_oracle, time_point_sec ban_oracle_until, bool ban_creator, time_point_sec ban_creator_until, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_dispute_resolve_operation op; + op.resolver = resolver; + op.market_id = market_id; + op.correct_outcome = correct_outcome; + op.penalty_amount = penalty_amount; + op.ban_oracle = ban_oracle; + op.ban_oracle_until = ban_oracle_until; + op.ban_creator = ban_creator; + op.ban_creator_until = ban_creator_until; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_transfer_position(string from, int64_t bet_id, string to, share_type amount, string memo, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_transfer_position_operation op; + op.from = from; + op.bet_id = bet_id; + op.to = to; + op.amount = amount; + op.memo = memo; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_lazy_deposit(string account, asset amount, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_lazy_deposit_operation op; + op.account = account; + op.amount = amount; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_lazy_withdraw(string account, share_type shares, bool emergency, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_lazy_withdraw_operation op; + op.account = account; + op.shares = shares; + op.emergency = emergency; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_leverage_open(string account, int64_t market_id, int16_t outcome_index, asset collateral, asset loan, share_type min_tokens, uint16_t max_slippage_percent, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_leverage_open_operation op; + op.account = account; + op.market_id = market_id; + op.outcome_index = outcome_index; + op.collateral = collateral; + op.loan = loan; + op.min_tokens = min_tokens; + op.max_slippage_percent = max_slippage_percent; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_leverage_close(string account, int64_t position_id, share_type min_return, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_leverage_close_operation op; + op.account = account; + op.position_id = position_id; + op.min_return = min_return; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + annotated_signed_transaction wallet_api::pm_leverage_convert(string account, int64_t position_id, uint16_t conversion_profit_cost, bool broadcast) { + FC_ASSERT(!is_locked()); + pm_leverage_convert_operation op; + op.account = account; + op.position_id = position_id; + op.conversion_profit_cost = conversion_profit_cost; + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + + // ---------- prediction_market_api read pass-throughs ---------- + + fc::variant wallet_api::pm_get_market(int64_t market_id) const { + return my->pm_api()->get_market(market_id); + } + fc::variant wallet_api::pm_list_markets(int8_t status, uint32_t from, uint32_t limit) const { + return my->pm_api()->list_markets(status, from, limit); + } + fc::variant wallet_api::pm_list_markets_by_oracle(string oracle, uint32_t from, uint32_t limit) const { + return my->pm_api()->list_markets_by_oracle(oracle, from, limit); + } + fc::variant wallet_api::pm_list_markets_by_oracle_status(string oracle, int8_t status, uint32_t from, uint32_t limit) const { + return my->pm_api()->list_markets_by_oracle_status(oracle, status, from, limit); + } + fc::variant wallet_api::pm_list_markets_by_creator(string creator, uint32_t from, uint32_t limit) const { + return my->pm_api()->list_markets_by_creator(creator, from, limit); + } + fc::variant wallet_api::pm_get_market_outcomes(int64_t market_id) const { + return my->pm_api()->get_market_outcomes(market_id); + } + fc::variant wallet_api::pm_get_market_weight_sums(int64_t market_id) const { + return my->pm_api()->get_market_weight_sums(market_id); + } + fc::variant wallet_api::pm_get_market_bets(int64_t market_id, uint32_t from, uint32_t limit) const { + return my->pm_api()->get_market_bets(market_id, from, limit); + } + fc::variant wallet_api::pm_get_account_positions(string account, uint32_t from, uint32_t limit) const { + return my->pm_api()->get_account_positions(account, from, limit); + } + fc::variant wallet_api::pm_get_market_liquidity(int64_t market_id, uint32_t from, uint32_t limit) const { + return my->pm_api()->get_market_liquidity(market_id, from, limit); + } + fc::variant wallet_api::pm_get_account_leverage_positions(string account, uint32_t from, uint32_t limit) const { + return my->pm_api()->get_account_leverage_positions(account, from, limit); + } + fc::variant wallet_api::pm_get_market_leverage_positions(int64_t market_id, uint32_t from, uint32_t limit) const { + return my->pm_api()->get_market_leverage_positions(market_id, from, limit); + } + fc::variant wallet_api::pm_get_creator_ban(string account) const { + return my->pm_api()->get_creator_ban(account); + } + fc::variant wallet_api::pm_get_oracle(string owner) const { + return my->pm_api()->get_oracle(owner); + } + fc::variant wallet_api::pm_list_oracles(uint32_t from, uint32_t limit) const { + return my->pm_api()->list_oracles(from, limit); + } + fc::variant wallet_api::pm_get_dispute(int64_t market_id) const { + return my->pm_api()->get_dispute(market_id); + } + fc::variant wallet_api::pm_get_dispute_votes(int64_t market_id) const { + return my->pm_api()->get_dispute_votes(market_id); + } + fc::variant wallet_api::pm_get_lazy_pool() const { + return my->pm_api()->get_lazy_pool(); + } + fc::variant wallet_api::pm_get_lazy_deposit(string account) const { + return my->pm_api()->get_lazy_deposit(account); + } + fc::variant wallet_api::pm_get_chain_properties() const { + return my->pm_api()->get_pm_chain_properties(); + } + fc::variant wallet_api::pm_get_market_meta(int64_t market_id) const { + return my->pm_api()->get_market_meta(market_id); + } + fc::variant wallet_api::pm_list_markets_by_category(string category, uint32_t from, uint32_t limit) const { + return my->pm_api()->list_markets_by_category(category, from, limit); + } + fc::variant wallet_api::pm_list_markets_by_event(string event, uint32_t from, uint32_t limit) const { + return my->pm_api()->list_markets_by_event(event, from, limit); + } + fc::variant wallet_api::pm_get_market_kline(int64_t market_id, uint32_t from, uint32_t limit) const { + return my->pm_api()->get_market_kline(market_id, from, limit); + } + // ========== VIZ DNS Nameserver Helper Implementations ========== bool wallet_api::ns_validate_ipv4(const string& ipv4) const { diff --git a/package-lock.json b/package-lock.json index 9e2c142b61..bb428d08db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "viz-ledger-docs", "version": "1.0.0", "devDependencies": { - "vitepress": "^1.5.0" + "mermaid": "^11.4.1", + "vitepress": "^1.5.0", + "vitepress-plugin-mermaid": "^2.0.17" } }, "node_modules/@algolia/abtesting": { @@ -270,6 +272,20 @@ "node": ">= 14.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -320,6 +336,20 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@docsearch/css": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", @@ -371,17 +401,6 @@ } } }, - "node_modules/@docsearch/js/node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/@iconify-json/simple-icons": { "version": "1.2.83", "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.83.tgz", @@ -399,6 +418,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -406,6 +437,41 @@ "dev": true, "license": "MIT" }, + "node_modules/@mermaid-js/mermaid-mindmap": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.3.0.tgz", + "integrity": "sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.0.0", + "khroma": "^2.0.0", + "non-layered-tidy-tree-layout": "^2.0.2" + } + }, + "node_modules/@mermaid-js/mermaid-mindmap/node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", + "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.1" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -763,6 +829,290 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -770,6 +1120,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -797,10 +1154,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true, "license": "MIT", "optional": true @@ -826,6 +1183,17 @@ "dev": true, "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -1104,20 +1472,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/algoliasearch": { "version": "5.52.1", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.52.1.tgz", @@ -1145,14 +1499,6 @@ "node": ">= 14.0.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", @@ -1163,21 +1509,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -1211,20 +1542,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1236,6 +1553,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", @@ -1252,6 +1579,16 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1259,34 +1596,564 @@ "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", "dev": true, "license": "MIT", - "optional": true, + "peer": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dev": true, + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" }, "engines": { - "node": ">=6.0" + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.4.0" + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" } }, "node_modules/dequal": { @@ -1313,20 +2180,14 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/dompurify": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" } }, "node_modules/emoji-regex-xs": { @@ -1336,58 +2197,16 @@ "dev": true, "license": "MIT" }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/es-toolkit": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.48.1.tgz", + "integrity": "sha512-wfnXlwd5I75eXRtdD2vuEs50xHHESECDsGD7yiQnfFVNoa5522NwXEbmgo98LfiukSQHs+mBM7/YG3qKJB9/mQ==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } + "workspaces": [ + "docs", + "benchmarks" + ] }, "node_modules/estree-walker": { "version": "2.0.2", @@ -1407,46 +2226,6 @@ "tabbable": "^6.4.0" } }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "optional": true, - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1462,116 +2241,12 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } + "license": "MIT" }, "node_modules/hast-util-to-html": { "version": "9.0.5", @@ -1629,19 +2304,38 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" } }, "node_modules/is-what": { @@ -1657,28 +2351,53 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], "license": "MIT", - "optional": true, "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "commander": "^8.3.0" }, "bin": { - "loose-envify": "cli.js" + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "dev": true + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1696,15 +2415,17 @@ "dev": true, "license": "MIT" }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "dev": true, "license": "MIT", - "optional": true, + "bin": { + "marked": "bin/marked.js" + }, "engines": { - "node": ">= 0.4" + "node": ">= 20" } }, "node_modules/mdast-util-to-hast": { @@ -1729,6 +2450,37 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mermaid": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", + "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "es-toolkit": "^1.45.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", @@ -1823,31 +2575,6 @@ ], "license": "MIT" }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minisearch": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", @@ -1862,14 +2589,28 @@ "dev": true, "license": "MIT" }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", "dev": true, "license": "MIT", "optional": true }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -1884,6 +2625,24 @@ "dev": true, "license": "ISC" }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -1954,17 +2713,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - } - }, "node_modules/regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", @@ -1999,6 +2747,13 @@ "dev": true, "license": "MIT" }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", @@ -2044,6 +2799,33 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/search-insights": { "version": "2.17.3", "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", @@ -2098,6 +2880,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "dev": true, + "license": "MIT" + }, "node_modules/superjson": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", @@ -2118,6 +2907,16 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -2129,13 +2928,15 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=6.10" + } }, "node_modules/unist-util-is": { "version": "6.0.1", @@ -2210,6 +3011,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -2246,6 +3061,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -2772,6 +3588,20 @@ } } }, + "node_modules/vitepress-plugin-mermaid": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/vitepress-plugin-mermaid/-/vitepress-plugin-mermaid-2.0.17.tgz", + "integrity": "sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@mermaid-js/mermaid-mindmap": "^9.3.0" + }, + "peerDependencies": { + "mermaid": "10 || 11", + "vitepress": "^1.0.0 || ^1.0.0-alpha" + } + }, "node_modules/vitepress/node_modules/@shikijs/core": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", diff --git a/package.json b/package.json index 6d46ca9ee5..17e716bfe2 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,15 @@ "predev": "npm run sync-l10n", "prebuild": "npm run sync-l10n", "prepreview": "npm run sync-l10n", + "prestart": "npm run sync-l10n", "dev": "vitepress dev docs", "build": "vitepress build docs", "preview": "vitepress preview docs", "start": "npm run dev" }, "devDependencies": { - "vitepress": "^1.5.0" + "vitepress": "^1.5.0", + "vitepress-plugin-mermaid": "^2.0.17", + "mermaid": "^11.4.1" } } diff --git a/plugins/account_by_key/CMakeLists.txt b/plugins/account_by_key/CMakeLists.txt index d4305fcc38..73d38dca96 100644 --- a/plugins/account_by_key/CMakeLists.txt +++ b/plugins/account_by_key/CMakeLists.txt @@ -9,17 +9,10 @@ list(APPEND CURRENT_TARGET_SOURCES account_by_key_plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/plugins/account_history/CMakeLists.txt b/plugins/account_history/CMakeLists.txt index 0aa4d45ea0..9165b70a53 100644 --- a/plugins/account_history/CMakeLists.txt +++ b/plugins/account_history/CMakeLists.txt @@ -9,17 +9,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if (BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/account_history/plugin.cpp b/plugins/account_history/plugin.cpp index 5dad3b3855..dab4be6da7 100644 --- a/plugins/account_history/plugin.cpp +++ b/plugins/account_history/plugin.cpp @@ -531,6 +531,96 @@ if( options.count(name) ) { \ impacted.insert(op.validator); impacted.insert(op.stakeholder); } + + // --- Prediction markets (HF14, Onix) --- + // Signed ops: the generic handler already records the signer through the + // required authorities, so these overloads add the *other* material + // parties (counterparties) that must also see the event in their history. + void operator()(const pm_oracle_register_operation& op) { + impacted.insert(op.owner); + if (op.auto_accept_creator.size()) impacted.insert(op.auto_accept_creator); + if (op.auto_accept_resolver.size()) impacted.insert(op.auto_accept_resolver); + } + + void operator()(const pm_create_market_operation& op) { + impacted.insert(op.creator); + impacted.insert(op.oracle); + if (op.dispute_resolver.size()) impacted.insert(op.dispute_resolver); + } + + void operator()(const pm_transfer_position_operation& op) { + impacted.insert(op.from); + impacted.insert(op.to); + } + + void operator()(const pm_unban_operation& op) { + impacted.insert(op.resolver); + impacted.insert(op.target); + } + + // Virtual ops carry no signing authority — without an explicit overload + // the generic handler adds nobody and the event is invisible in the + // affected account's history. Market-only vops that carry no account + // field (pm_batch_settle / pm_lazy_recall) are left to the generic + // handler by design. + void operator()(const pm_commit_forfeit_operation& op) { + impacted.insert(op.account); + } + + // Dispute lifecycle -> oracle's own history (P1 oracle-metrics). The oracle now + // carries through the vop, so opening/finalize/auto-close of a dispute against its + // resolution are visible in its timeline (not only the disputer's / market's). + void operator()(const pm_dispute_opened_operation& op) { + impacted.insert(op.oracle); + impacted.insert(op.disputer); + } + + void operator()(const pm_dispute_finalize_operation& op) { + impacted.insert(op.oracle); + } + + void operator()(const pm_dispute_auto_close_operation& op) { + impacted.insert(op.oracle); + } + + void operator()(const pm_auto_payout_operation& op) { + impacted.insert(op.account); + } + + void operator()(const pm_payout_operation& op) { + impacted.insert(op.account); + } + + void operator()(const pm_oracle_missed_penalty_operation& op) { + impacted.insert(op.oracle); + } + + void operator()(const pm_leverage_liquidate_operation& op) { + impacted.insert(op.account); + } + + void operator()(const pm_leverage_resolve_operation& op) { + impacted.insert(op.account); + } + + void operator()(const pm_market_accepted_operation& op) { + impacted.insert(op.oracle); + impacted.insert(op.creator); + } + + void operator()(const pm_market_expired_operation& op) { + impacted.insert(op.oracle); + impacted.insert(op.creator); + } + + void operator()(const pm_ban_expired_operation& op) { + impacted.insert(op.account); + } + + // F1/#300: early-exit deferred claim paid at settlement -> early-exiter's own history. + void operator()(const pm_early_exit_claim_paid_operation& op) { + impacted.insert(op.account); + } //void operator()( const operation& op ){} }; diff --git a/plugins/block_info/CMakeLists.txt b/plugins/block_info/CMakeLists.txt index 2e771eedd8..9162cf5c9f 100644 --- a/plugins/block_info/CMakeLists.txt +++ b/plugins/block_info/CMakeLists.txt @@ -9,17 +9,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/chain/CMakeLists.txt b/plugins/chain/CMakeLists.txt index 0dc1c2c1d4..94f67fbc0f 100644 --- a/plugins/chain/CMakeLists.txt +++ b/plugins/chain/CMakeLists.txt @@ -7,17 +7,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/committee_api/CMakeLists.txt b/plugins/committee_api/CMakeLists.txt index f9e373dd22..dd9bb9db1b 100644 --- a/plugins/committee_api/CMakeLists.txt +++ b/plugins/committee_api/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES committee_api.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/custom_protocol_api/CMakeLists.txt b/plugins/custom_protocol_api/CMakeLists.txt index e9928c74c2..b387018c95 100644 --- a/plugins/custom_protocol_api/CMakeLists.txt +++ b/plugins/custom_protocol_api/CMakeLists.txt @@ -11,17 +11,10 @@ list(APPEND CURRENT_TARGET_SOURCES custom_protocol_api_visitor.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/database_api/CMakeLists.txt b/plugins/database_api/CMakeLists.txt index f6f7010d8c..b6c590acfe 100644 --- a/plugins/database_api/CMakeLists.txt +++ b/plugins/database_api/CMakeLists.txt @@ -17,17 +17,10 @@ list(APPEND ${CURRENT_TARGET}_SOURCES proposal_api_object.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${${CURRENT_TARGET}_HEADERS} ${${CURRENT_TARGET}_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${${CURRENT_TARGET}_HEADERS} - ${${CURRENT_TARGET}_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/plugins/invite_api/CMakeLists.txt b/plugins/invite_api/CMakeLists.txt index 9aef53fafc..bee8ce42b8 100644 --- a/plugins/invite_api/CMakeLists.txt +++ b/plugins/invite_api/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES invite_api.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/json_rpc/CMakeLists.txt b/plugins/json_rpc/CMakeLists.txt index 20e76ca7b8..693986027f 100644 --- a/plugins/json_rpc/CMakeLists.txt +++ b/plugins/json_rpc/CMakeLists.txt @@ -9,17 +9,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/plugins/network_broadcast_api/CMakeLists.txt b/plugins/network_broadcast_api/CMakeLists.txt index 6bf71a5e1a..80a362189a 100644 --- a/plugins/network_broadcast_api/CMakeLists.txt +++ b/plugins/network_broadcast_api/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES network_broadcast_api.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/plugins/network_broadcast_api/network_broadcast_api.cpp b/plugins/network_broadcast_api/network_broadcast_api.cpp index 43a1e96ad2..5b4adc043d 100644 --- a/plugins/network_broadcast_api/network_broadcast_api.cpp +++ b/plugins/network_broadcast_api/network_broadcast_api.cpp @@ -54,7 +54,10 @@ namespace graphene { try { pimpl->_chain.accept_transaction(trx); } catch (const fc::exception& e) { - dlog("accept_transaction failed for ${id}: ${e}, still broadcasting to P2P", + // elog (was dlog): apply/assert failures here are otherwise invisible — the API + // still returns {} and (sync) hangs waiting for a block that never includes the + // dropped tx. Surface the exact reason (e.g. leverage worst-case assert) at error level. + elog("accept_transaction failed for ${id}: ${e}, still broadcasting to P2P", ("id", trx.id())("e", e.to_detail_string())); } pimpl->_p2p.broadcast_transaction(trx); @@ -90,7 +93,10 @@ namespace graphene { try { pimpl->_chain.accept_transaction(trx); } catch (const fc::exception& e) { - dlog("accept_transaction failed for ${id}: ${e}, still broadcasting to P2P", + // elog (was dlog): apply/assert failures here are otherwise invisible — the API + // still returns {} and (sync) hangs waiting for a block that never includes the + // dropped tx. Surface the exact reason (e.g. leverage worst-case assert) at error level. + elog("accept_transaction failed for ${id}: ${e}, still broadcasting to P2P", ("id", trx.id())("e", e.to_detail_string())); } pimpl->_p2p.broadcast_transaction(trx); @@ -141,7 +147,10 @@ namespace graphene { try { pimpl->_chain.accept_transaction(trx); } catch (const fc::exception& e) { - dlog("accept_transaction failed for ${id}: ${e}, still broadcasting to P2P", + // elog (was dlog): apply/assert failures here are otherwise invisible — the API + // still returns {} and (sync) hangs waiting for a block that never includes the + // dropped tx. Surface the exact reason (e.g. leverage worst-case assert) at error level. + elog("accept_transaction failed for ${id}: ${e}, still broadcasting to P2P", ("id", trx.id())("e", e.to_detail_string())); } pimpl->_p2p.broadcast_transaction(trx); diff --git a/plugins/operation_history/CMakeLists.txt b/plugins/operation_history/CMakeLists.txt index 8624cd0b6e..ef75fc5334 100644 --- a/plugins/operation_history/CMakeLists.txt +++ b/plugins/operation_history/CMakeLists.txt @@ -11,17 +11,10 @@ list(APPEND CURRENT_TARGET_SOURCES applied_operation.cpp ) -if (BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/p2p/CMakeLists.txt b/plugins/p2p/CMakeLists.txt index 9a2016fc83..250ca837ec 100644 --- a/plugins/p2p/CMakeLists.txt +++ b/plugins/p2p/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES p2p_plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/paid_subscription_api/CMakeLists.txt b/plugins/paid_subscription_api/CMakeLists.txt index 6f77385552..738961ff25 100644 --- a/plugins/paid_subscription_api/CMakeLists.txt +++ b/plugins/paid_subscription_api/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES paid_subscription_api.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/prediction_market_api/CMakeLists.txt b/plugins/prediction_market_api/CMakeLists.txt new file mode 100644 index 0000000000..8cc2e7f33a --- /dev/null +++ b/plugins/prediction_market_api/CMakeLists.txt @@ -0,0 +1,49 @@ +set(CURRENT_TARGET prediction_market_api) + +list(APPEND CURRENT_TARGET_HEADERS + include/graphene/plugins/prediction_market_api/prediction_market_api.hpp + include/graphene/plugins/prediction_market_api/kline_object.hpp +) + +list(APPEND CURRENT_TARGET_SOURCES + prediction_market_api.cpp +) + +if(BUILD_SHARED_LIBRARIES) + add_library(graphene_${CURRENT_TARGET} SHARED + ${CURRENT_TARGET_HEADERS} + ${CURRENT_TARGET_SOURCES} + ) +else() + add_library(graphene_${CURRENT_TARGET} STATIC + ${CURRENT_TARGET_HEADERS} + ${CURRENT_TARGET_SOURCES} + ) +endif() + +add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) + +set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) + +target_link_libraries( + graphene_${CURRENT_TARGET} + graphene_chain + graphene::chain_plugin + graphene::network + graphene::api + appbase +) + +target_include_directories( + graphene_${CURRENT_TARGET} + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../../" +) + +install(TARGETS + graphene_${CURRENT_TARGET} + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) diff --git a/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/kline_object.hpp b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/kline_object.hpp new file mode 100644 index 0000000000..355ee65b76 --- /dev/null +++ b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/kline_object.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +// HF14 Prediction-Market kline / time-series history (NON-consensus plugin state, owned by the +// prediction_market_api plugin — never part of the state hash, fully undo/redo-safe because it lives +// in chainbase). One record is appended every time a market's per-outcome weights change (a bet, a +// cancel, a liquidation, a batch settle, a leverage open/resolve). Each record is a timestamped +// snapshot of the parimutuel weight (staked amount) on every outcome, so a thin client can plot the +// outcomes' weight ratio over time: x = `timestamp` (unix seconds), y[i] = `weights[i]`. + +#ifndef PM_META_SPACE_ID +#define PM_META_SPACE_ID 30 +#endif + +namespace graphene { namespace plugins { namespace prediction_market_api { + + namespace bip = boost::interprocess; + + enum pm_kline_object_types { + pm_market_kline_object_type = (PM_META_SPACE_ID << 8) + 1 + }; + + using namespace graphene::chain; + using namespace chainbase; + + class pm_market_kline_object final : public object { + public: + template + pm_market_kline_object(Constructor&& c, allocator a) : weights(a) { c(*this); } + + id_type id; + pm_market_id_type market; + uint32_t seq = 0; ///< 0-based, contiguous, monotonic per market (append index) + time_point_sec timestamp; ///< block time of the change; x = timestamp.sec_since_epoch() + uint8_t reason = 0; ///< 0 bet, 1 cancel, 2 liquidation, 3 batch settle, 4 leverage open, 5 leverage resolve + share_type bets_sum; ///< total staked across all outcomes at this point + /// Per-outcome parimutuel weight (staked amount); size == market.outcome_count. The ratio + /// weights[i] / Σweights is the implied probability of outcome i. + bip::vector> weights; + }; + + using pm_market_kline_id_type = object_id; + + struct by_kline_market_seq; + typedef boost::multi_index_container< + pm_market_kline_object, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique, + boost::multi_index::member>, + boost::multi_index::ordered_unique, + boost::multi_index::composite_key, + boost::multi_index::member + >, + boost::multi_index::composite_key_compare, std::less> + > + >, + allocator + > pm_market_kline_index; + +} } } // graphene::plugins::prediction_market_api + +FC_REFLECT((graphene::plugins::prediction_market_api::pm_market_kline_object), + (id)(market)(seq)(timestamp)(reason)(bets_sum)(weights)) +CHAINBASE_SET_INDEX_TYPE(graphene::plugins::prediction_market_api::pm_market_kline_object, + graphene::plugins::prediction_market_api::pm_market_kline_index) diff --git a/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/meta_object.hpp b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/meta_object.hpp new file mode 100644 index 0000000000..2fc61ea4c0 --- /dev/null +++ b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/meta_object.hpp @@ -0,0 +1,13 @@ +#pragma once + +// The pm_market_meta_object / pm_market_meta_index TYPE was moved to libraries/chain +// (graphene/chain/pm_meta_object.hpp) so shared consumers — notably the snapshot plugin — can +// serialize it into the snapshot without a plugin-to-plugin dependency. Runtime registration +// (add_plugin_index) and all the build/prune logic still live in this plugin. This shim re-exposes +// the names in the prediction_market_api namespace so existing plugin code compiles unchanged. + +#include + +namespace graphene { namespace plugins { namespace prediction_market_api { + using namespace graphene::chain; +} } } // graphene::plugins::prediction_market_api diff --git a/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/meta_parse.hpp b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/meta_parse.hpp new file mode 100644 index 0000000000..bf8cd2851f --- /dev/null +++ b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/meta_parse.hpp @@ -0,0 +1,103 @@ +#pragma once + +// Pure, dependency-light helpers for prediction-market metadata: parse a market's free-form +// `metadata` field as JSON and test CSV jurisdiction membership. Kept free of chain/database +// types so they can be unit-tested in isolation (tests/pm/meta_parse_test.cpp). The unified +// prediction_market_api plugin's applied_block handler and list_markets_by_category call these. + +#include +#include +#include +#include + +namespace graphene { namespace plugins { namespace prediction_market_api { + + struct parsed_meta { + std::string category; + std::string subcategory; + std::string tags; ///< CSV + std::string banned_jurisdictions; ///< CSV + std::string title; ///< human-readable question + std::string image; ///< icon/cover URL + std::string condition_id; ///< source dedup id (for client back-link) + std::string description; ///< short resolution rules (how the oracle resolves) — surface-level, for clients + std::string event; ///< parent grouping key (e.g. one match/game); siblings share it + std::string event_title; ///< human-readable event label (e.g. "Dota 2: A vs B") for the event page/cards + bool child = false; ///< true = a child/prop market of a parent event; hidden from category/tag listings by default + }; + + /// Flatten a JSON array (or bare string) into a comma-separated string. + inline std::string meta_join_array(const fc::variant& v) { + std::string s; + if (v.is_array()) { + for (const auto& e : v.get_array()) { if (!s.empty()) s += ","; s += e.as_string(); } + } else if (v.is_string()) { + s = v.as_string(); + } + return s; + } + + /// ASCII-lowercase a copy. Used for case-insensitive tag matching (tags are English labels + /// with inconsistent source casing — "Dota 2" vs "counter strike 2"; clients pass them + /// lowercased). ASCII-only keeps it locale-independent and deterministic. + inline std::string meta_ascii_lower(std::string s) { + for (char& c : s) if (c >= 'A' && c <= 'Z') c = (char)(c + 32); + return s; + } + + /// CSV membership with exact-token boundaries (commas), so "US" never matches "USA". + inline bool meta_csv_contains(const std::string& csv, const std::string& token) { + if (token.empty() || csv.empty()) return false; + size_t pos = 0; + while ((pos = csv.find(token, pos)) != std::string::npos) { + bool left = (pos == 0) || csv[pos-1] == ','; + bool right = (pos + token.size() == csv.size()) || csv[pos+token.size()] == ','; + if (left && right) return true; + pos += token.size(); + } + return false; + } + + /// Case-insensitive CSV membership — both sides ASCII-lowercased, so a "dota 2" query matches + /// a stored "Dota 2" tag. Same comma-boundary semantics as meta_csv_contains. + inline bool meta_csv_contains_ci(const std::string& csv, const std::string& token) { + return meta_csv_contains(meta_ascii_lower(csv), meta_ascii_lower(token)); + } + + /// Parse a market's free-form `metadata` field as JSON, extracting the keys we index and + /// ignoring everything else. Non-JSON / empty input yields an empty result — never throws. + inline parsed_meta parse_market_metadata(const std::string& metadata) { + parsed_meta r; + try { + fc::variant v = fc::json::from_string(metadata); + if (v.is_object()) { + const auto& o = v.get_object(); + if (o.contains("category")) r.category = o["category"].as_string(); + if (o.contains("subcategory")) r.subcategory = o["subcategory"].as_string(); + if (o.contains("tags")) r.tags = meta_join_array(o["tags"]); + if (o.contains("banned_jurisdictions")) + r.banned_jurisdictions = meta_join_array(o["banned_jurisdictions"]); + else if (o.contains("jurisdictions_banned")) + r.banned_jurisdictions = meta_join_array(o["jurisdictions_banned"]); + if (o.contains("title")) r.title = o["title"].as_string(); + if (o.contains("image")) r.image = o["image"].as_string(); + if (o.contains("condition_id")) r.condition_id = o["condition_id"].as_string(); + if (o.contains("description")) r.description = o["description"].as_string(); + if (o.contains("event")) r.event = o["event"].as_string(); + if (o.contains("event_title")) r.event_title = o["event_title"].as_string(); + // child: props side of a split match ("-more-markets"). Accept bool / int(1) / + // "1"/"true" — the parser emits it as an integer 1. + if (o.contains("child")) { + const auto& c = o["child"]; + try { r.child = c.as_bool(); } + catch (...) { + try { r.child = (c.as_int64() != 0); } + catch (...) { const auto s = c.as_string(); r.child = (s == "1" || s == "true"); } + } + } + } + } catch (...) { /* metadata is not JSON — leave empty */ } + return r; + } + +}}} // graphene::plugins::prediction_market_api diff --git a/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/prediction_market_api.hpp b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/prediction_market_api.hpp new file mode 100644 index 0000000000..460c88bbf8 --- /dev/null +++ b/plugins/prediction_market_api/include/graphene/plugins/prediction_market_api/prediction_market_api.hpp @@ -0,0 +1,352 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace plugins { namespace prediction_market_api { + using plugins::json_rpc::msg_pack; + using namespace graphene::chain; + using graphene::protocol::chain_properties_pm; + + // ── Computed read-only DTOs (raw consensus objects + derived fields) ───────── + + // Oracle with a non-consensus reliability score (basis points, API heuristic). + struct pm_oracle_api_object { + pm_oracle_object oracle; + uint32_t reliability_score; // [0..10000] bp, see plugin impl + // Live count of this oracle's status-1 markets past their betting close (betting_expiration + // <= head_block_time, excluding open-ended) — i.e. awaiting the oracle's resolution. Computed + // on read from the oracle's small active set (not stored on pm_oracle_object). The stored + // workload gauges (markets_in_dispute_window, disputes_awaiting_response/decision) travel + // inside `oracle`. + uint32_t markets_awaiting_resolution = 0; + // Age in seconds of this oracle's oldest still-unresolved market (longest time past betting + // close with no resolution). 0 if none. Time-dependent → computed on read (P5). + uint32_t oldest_unresolved_age = 0; + // Median / 95th-percentile resolution latency (seconds), derived on read from the oracle's + // resolution_time_hist buckets. 0 until it has resolved at least one market (P5). + uint32_t resolution_time_p50 = 0; + uint32_t resolution_time_p95 = 0; + }; + + // A bet plus the parimutuel payout it would receive if its side wins (or its + // realized payout once settled). Mirrors settle_market() exactly. + struct pm_position_api_object { + pm_bet_object bet; + share_type expected_payout; + int8_t market_status; + int16_t resolved_outcome; + }; + + struct pm_weight_entry { + int16_t outcome_index; + std::string label; + share_type bets_sum; + share_type weight_sum; + }; + + // Per-side / per-outcome amount and curve-weight aggregates for a market. + struct pm_market_weight_sums_api_object { + uint8_t market_type; + share_type bets_sum; + std::vector outcomes; + }; + + // Live committee-dispute tally + a stake-weighted projection of the finalize cron, so a + // caller can see the quorum status and the verdict that WOULD be applied under current votes. + struct pm_dispute_votes_api_object { + std::vector votes; + // Legacy rough tally (weight = |vote_percent|, NOT stake) — kept for compatibility. + int64_t uphold_weight; + int64_t challenge_weight; + int64_t total_weight; + bool challenger_leads; + int16_t proposed_outcome; + // ── Accurate stake-weighted projection (mirrors pm_dispute_finalize) ── + // Every *_shares value is in vesting-shares: effective_vesting_shares + lazy-pool stake→shares. + int64_t participation_shares = 0; ///< Σ weight of accounts that have voted (= max_rshares) + int64_t electorate_shares = 0; ///< total_vesting_shares + pool_NAV→shares (quorum base) + int64_t quorum_required_shares = 0; ///< electorate × pm_dispute_approve_min_percent + int32_t quorum_percent_bp = 0; ///< participation / electorate (bp, 10000 = 100.00%) + bool quorum_reached = false; ///< participation_shares >= quorum_required_shares + int64_t oracle_defense_shares = 0; ///< Σ rshares defending the oracle (uphold side) + int64_t change_shares = 0; ///< Σ rshares backing an outcome change + std::vector outcome_change_shares; ///< per-outcome backing rshares (size = outcome_count) + bool expected_uphold = true; ///< true ⇒ oracle resolution stands if finalized now + int16_t expected_outcome = -1; ///< outcome that would be set at finalize now + int32_t expected_consensus_strength_bp = 0; ///< winning / participation (bp); 0 when uphold + }; + + // One kline / time-series point for a market: a timestamped snapshot of every outcome's parimutuel + // weight, recorded whenever the weights change. For charting: x = `timestamp` (unix seconds), + // y[i] = `weights[i]`; the ratio weights[i]/Σweights is outcome i's implied probability. + struct pm_kline_api_object { + uint32_t seq = 0; ///< 0-based contiguous index of the change within the market + uint32_t timestamp = 0; ///< unix seconds (x coordinate) + uint8_t reason = 0; ///< 0 bet, 1 cancel, 2 liquidation, 3 batch settle, 4 leverage open, 5 leverage resolve + share_type bets_sum; ///< total staked across all outcomes at this point + std::vector weights; ///< per-outcome staked weight (y values), index = outcome_index + }; + + // ── Leverage previews (read-only projections of the in-node margin math) ───────── + // All three below call the SAME frozen pm::leverage::* functions the evaluators use, so a + // preview matches what pm_leverage_open/close/convert would compute in the next block. They + // are non-consensus quotes: reserves move, so treat the numbers as an estimate at head block. + + // One reason a market/collateral pair cannot support (more) leverage. + struct pm_leverage_constraint { + std::string constraint; ///< leverage_disabled|cpmm_binary_only|market_inactive|expiration_buffer|min_market_liquidity|fund_availability|position_size|solvency + std::string reason; ///< human-readable explanation + }; + + // One point on the leverage slider: everything the open evaluator would derive for this loan. + struct pm_leverage_stop { + uint32_t leverage_x100 = 0; ///< (collateral+loan)/collateral × 100 (100 = 1.00×) + share_type loan; ///< pool loan at this stop + share_type total_bet; ///< collateral + loan (fed into the CPMM) + share_type expected_tokens; ///< weight the AMM would return + share_type pool_profit; ///< liquidation_threshold − loan (pool's cut) + share_type liquidation_threshold; ///< loan × (1 + r%/100) = pool obligation + share_type current_cancel_value; ///< cancel value right after open (no opposing bet) + share_type worst_case_cancel_value; ///< cancel value after the worst opposing bet (solvency basis) + }; + + // get_leverage_quote result: max leverage + a slider of stops, mirroring pm_leverage_open. + struct pm_leverage_quote_api_object { + bool available = false; ///< true ⇒ max_loan > 0 (some leverage possible) + int16_t outcome_index = -1; + share_type collateral; + share_type max_loan; ///< largest solvent loan (0 if none qualifies) + uint32_t max_leverage_x100 = 100; ///< (collateral+max_loan)/collateral × 100 + share_type pool_free_amount; ///< free_balance − leverage_fund_used + share_type fund_available; ///< free_balance×fund% − leverage_fund_used + share_type per_position_cap; ///< fund_available × max_per_position_bp + share_type market_position_cap; ///< liquidity_sum × max_position_ratio% + uint16_t pool_profit_percent = 0; ///< r (plain %) + uint16_t safety_margin_percent = 0; ///< s (plain %) + uint16_t max_slippage_percent = 0; ///< sl (plain %) + uint16_t m_factor_percent = 0; ///< worst-opposing m-factor (plain %) + uint32_t expiration_buffer_sec = 0; ///< leverage disabled this long before betting_expiration + uint32_t funding_rate_ppm_per_day = 0; ///< carry cost on the loan per 24h, in ppm (1e6) + time_point_sec auto_close_time; ///< betting_expiration − buffer (protocol force-close point) + std::vector stops; ///< up to 12 evenly-spaced solvent stops (0 < loan ≤ max_loan) + std::vector failed_constraints; ///< populated when !available + }; + + // get_leverage_close_preview result: mirrors pm_leverage_close at current reserves. + struct pm_leverage_close_preview_api_object { + int64_t position_id = 0; + int16_t outcome_index = -1; + share_type cancel_value; ///< VIZ the tokens fetch from the curve now + share_type pool_obligation; ///< liquidation_threshold → returned to the pool + share_type bettor_receives; ///< cancel_value − pool_obligation (floored 0) + share_type collateral; ///< original bettor stake + share_type loan; ///< pool loan + share_type pool_profit_charge; ///< pool's fixed profit on the loan + bool closeable = false; ///< cancel_value ≥ pool_obligation (else protocol liquidates) + int64_t loss_vs_collateral = 0; ///< collateral − bettor_receives (negative = profit) + int32_t loss_percent_bp = 0; ///< loss_vs_collateral / collateral (bp) + }; + + // get_leverage_convert_preview result: mirrors pm_leverage_convert at current reserves. + struct pm_leverage_convert_preview_api_object { + int64_t position_id = 0; + int16_t outcome_index = -1; + share_type cancel_value; ///< VIZ the tokens fetch now + share_type pool_obligation; ///< loan + pool profit (repaid on convert) + share_type current_profit; ///< cancel_value − pool_obligation + uint16_t conversion_profit_cost_percent = 0; ///< median value the op MUST echo + share_type conversion_fee; ///< current_profit × cost% /100 + share_type total_user_payment; ///< pool_obligation + conversion_fee (debited on convert) + bool convertible = false; ///< current_profit > 0 + }; + + // ── Category taxonomy + live counts (non-consensus, from the meta index) ───────── + // Aggregated over currently-indexed (non-pruned) markets, so counts reflect live/recent + // markets — pruned-out expired markets drop off, matching a "browse now" catalog. + struct pm_subcategory_count { + std::string subcategory; + uint32_t count = 0; + }; + struct pm_category_count { + std::string category; + uint32_t count = 0; + std::vector subcategories; + }; + struct pm_tag_count { + std::string tag; + uint32_t count = 0; + }; + struct pm_market_categories_api_object { + std::vector categories; ///< sorted by count desc + std::vector hot_tags; ///< top tags by count (jurisdiction-ban tags excluded) + }; + + // One-call enriched market view (saves the thin client several round-trips). The account-scoped + // vectors are populated only when get_market_full is called with a non-empty account argument. + struct pm_market_full_api_object { + pm_market_object market; + std::vector outcomes; ///< empty for binary markets + pm_market_weight_sums_api_object weight_sums; ///< per-outcome amount + curve weight + fc::optional oracle; ///< the market's oracle (+ reliability) + fc::optional meta; ///< parsed metadata, if indexed + std::vector my_positions; ///< account's bets on THIS market + std::vector my_leverage_positions; ///< account's leverage on THIS market + std::vector my_liquidity; ///< account's LP on THIS market + }; + + DEFINE_API_ARGS(get_market, msg_pack, fc::variant) + DEFINE_API_ARGS(list_markets, msg_pack, std::vector) + DEFINE_API_ARGS(list_markets_by_oracle, msg_pack, std::vector) + DEFINE_API_ARGS(list_markets_by_oracle_status, msg_pack, std::vector) + DEFINE_API_ARGS(list_markets_awaiting_resolution, msg_pack, std::vector) + DEFINE_API_ARGS(list_markets_in_dispute_window, msg_pack, std::vector) + DEFINE_API_ARGS(list_oracle_disputes, msg_pack, std::vector) + DEFINE_API_ARGS(list_markets_by_creator, msg_pack, std::vector) + DEFINE_API_ARGS(get_market_outcomes, msg_pack, std::vector) + DEFINE_API_ARGS(get_market_weight_sums, msg_pack, pm_market_weight_sums_api_object) + DEFINE_API_ARGS(get_market_bets, msg_pack, std::vector) + DEFINE_API_ARGS(get_account_positions, msg_pack, std::vector) + DEFINE_API_ARGS(get_market_liquidity, msg_pack, std::vector) + DEFINE_API_ARGS(get_account_leverage_positions, msg_pack, std::vector) + DEFINE_API_ARGS(get_market_leverage_positions, msg_pack, std::vector) + DEFINE_API_ARGS(get_creator_ban, msg_pack, pm_creator_ban_object) + DEFINE_API_ARGS(get_oracle, msg_pack, pm_oracle_api_object) + DEFINE_API_ARGS(list_oracles, msg_pack, std::vector) + DEFINE_API_ARGS(get_dispute, msg_pack, pm_dispute_object) + DEFINE_API_ARGS(get_dispute_votes, msg_pack, pm_dispute_votes_api_object) + DEFINE_API_ARGS(get_lazy_pool, msg_pack, pm_lazy_pool_object) + DEFINE_API_ARGS(get_lazy_deposit, msg_pack, pm_lazy_deposit_object) + DEFINE_API_ARGS(get_lazy_withdraw_requests, msg_pack, std::vector) + DEFINE_API_ARGS(get_deferred_claims, msg_pack, std::vector) + DEFINE_API_ARGS(get_account_commits, msg_pack, std::vector) + DEFINE_API_ARGS(get_pm_chain_properties, msg_pack, chain_properties_pm) + DEFINE_API_ARGS(get_market_meta, msg_pack, pm_market_meta_object) + DEFINE_API_ARGS(list_markets_by_category, msg_pack, std::vector) + DEFINE_API_ARGS(list_markets_by_event, msg_pack, std::vector) + DEFINE_API_ARGS(get_market_kline, msg_pack, std::vector) + DEFINE_API_ARGS(get_leverage_quote, msg_pack, pm_leverage_quote_api_object) + DEFINE_API_ARGS(get_leverage_close_preview, msg_pack, pm_leverage_close_preview_api_object) + DEFINE_API_ARGS(get_leverage_convert_preview, msg_pack, pm_leverage_convert_preview_api_object) + DEFINE_API_ARGS(get_market_categories, msg_pack, pm_market_categories_api_object) + DEFINE_API_ARGS(get_category_tag_counts, msg_pack, pm_market_categories_api_object) + DEFINE_API_ARGS(get_market_full, msg_pack, fc::variant) + DEFINE_API_ARGS(get_lazy_allocations, msg_pack, std::vector) + DEFINE_API_ARGS(get_market_lazy_allocation, msg_pack, pm_lazy_allocation_object) + + class prediction_market_api final : public appbase::plugin { + public: + APPBASE_PLUGIN_REQUIRES ( + (chain::plugin) + (json_rpc::plugin) + ) + + DECLARE_API( + (get_market) + (list_markets) + (list_markets_by_oracle) + (list_markets_by_oracle_status) + (list_markets_awaiting_resolution) + (list_markets_in_dispute_window) + (list_oracle_disputes) + (list_markets_by_creator) + (get_market_outcomes) + (get_market_weight_sums) + (get_market_bets) + (get_account_positions) + (get_market_liquidity) + (get_account_leverage_positions) + (get_market_leverage_positions) + (get_creator_ban) + (get_oracle) + (list_oracles) + (get_dispute) + (get_dispute_votes) + (get_lazy_pool) + (get_lazy_deposit) + (get_lazy_withdraw_requests) + (get_deferred_claims) + (get_account_commits) + (get_pm_chain_properties) + (get_market_meta) + (list_markets_by_category) + (list_markets_by_event) + (get_market_kline) + (get_leverage_quote) + (get_leverage_close_preview) + (get_leverage_convert_preview) + (get_market_categories) + (get_category_tag_counts) + (get_market_full) + (get_lazy_allocations) + (get_market_lazy_allocation) + ) + + prediction_market_api(); + ~prediction_market_api(); + + void set_program_options( + boost::program_options::options_description&, + boost::program_options::options_description& config_file_options + ) override; + + static const std::string& name(); + + void plugin_initialize(const boost::program_options::variables_map& options) override; + + void plugin_startup() override; + void plugin_shutdown() override; + + private: + struct impl; + std::unique_ptr pimpl; + }; +} } } // graphene::plugins::prediction_market_api + +FC_REFLECT((graphene::plugins::prediction_market_api::pm_oracle_api_object), + (oracle)(reliability_score)(markets_awaiting_resolution)(oldest_unresolved_age) + (resolution_time_p50)(resolution_time_p95)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_position_api_object), + (bet)(expected_payout)(market_status)(resolved_outcome)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_weight_entry), + (outcome_index)(label)(bets_sum)(weight_sum)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_market_weight_sums_api_object), + (market_type)(bets_sum)(outcomes)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_dispute_votes_api_object), + (votes)(uphold_weight)(challenge_weight)(total_weight)(challenger_leads)(proposed_outcome) + (participation_shares)(electorate_shares)(quorum_required_shares)(quorum_percent_bp) + (quorum_reached)(oracle_defense_shares)(change_shares)(outcome_change_shares) + (expected_uphold)(expected_outcome)(expected_consensus_strength_bp)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_kline_api_object), + (seq)(timestamp)(reason)(bets_sum)(weights)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_leverage_constraint), + (constraint)(reason)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_leverage_stop), + (leverage_x100)(loan)(total_bet)(expected_tokens)(pool_profit)(liquidation_threshold) + (current_cancel_value)(worst_case_cancel_value)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_leverage_quote_api_object), + (available)(outcome_index)(collateral)(max_loan)(max_leverage_x100)(pool_free_amount) + (fund_available)(per_position_cap)(market_position_cap)(pool_profit_percent) + (safety_margin_percent)(max_slippage_percent)(m_factor_percent)(expiration_buffer_sec) + (funding_rate_ppm_per_day)(auto_close_time)(stops)(failed_constraints)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_leverage_close_preview_api_object), + (position_id)(outcome_index)(cancel_value)(pool_obligation)(bettor_receives)(collateral) + (loan)(pool_profit_charge)(closeable)(loss_vs_collateral)(loss_percent_bp)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_leverage_convert_preview_api_object), + (position_id)(outcome_index)(cancel_value)(pool_obligation)(current_profit) + (conversion_profit_cost_percent)(conversion_fee)(total_user_payment)(convertible)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_subcategory_count), + (subcategory)(count)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_category_count), + (category)(count)(subcategories)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_tag_count), + (tag)(count)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_market_categories_api_object), + (categories)(hot_tags)) +FC_REFLECT((graphene::plugins::prediction_market_api::pm_market_full_api_object), + (market)(outcomes)(weight_sums)(oracle)(meta)(my_positions)(my_leverage_positions)(my_liquidity)) diff --git a/plugins/prediction_market_api/prediction_market_api.cpp b/plugins/prediction_market_api/prediction_market_api.cpp new file mode 100644 index 0000000000..8ced48da20 --- /dev/null +++ b/plugins/prediction_market_api/prediction_market_api.cpp @@ -0,0 +1,1859 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define CHECK_ARG_SIZE(_S) \ + FC_ASSERT( \ + args.args->size() == _S, \ + "Expected #_S argument(s), was ${n}", \ + ("n", args.args->size()) ); + +#define CHECK_ARG_MIN_SIZE(_S, _M) \ + FC_ASSERT( \ + args.args->size() >= _S && args.args->size() <= _M, \ + "Expected #_S (maximum #_M) argument(s), was ${n}", \ + ("n", args.args->size()) ); + +#define GET_OPTIONAL_ARG(_I, _T, _D) \ + (args.args->size() > _I) ? \ + (args.args->at(_I).as<_T>()) : \ + static_cast<_T>(_D) + +namespace graphene { namespace plugins { namespace prediction_market_api { + + namespace { + + // Non-consensus reliability score in basis points [0..10000]. Blends resolution accuracy, + // dispute verdicts, responsiveness and timeliness, then docks penalty stamps and bans. + // reliability_score v2 (display-only, bp 0..10000). Weaves in 12 of the 14 oracle counters + // (v1 used only 5): four reputation ratios blended, minus time-decayed penalty stamps and + // bans, then confidence-shrunk toward a neutral prior for oracles with a thin track record. + // Non-consensus; tune the weights freely. Timeliness (on-time resolution ratio derived from + // resolved_late_count) is the 4th ratio; avg_resolution_time stays out — it measures latency + // from betting close, not deadline overrun, and can't be normalized without the market length. + uint32_t reliability_score(const pm_oracle_object& o, fc::time_point_sec now) { + // (1) Resolution accuracy — resolved vs missed-deadline. Optimistic when unproven. + uint64_t completed = (uint64_t)o.markets_resolved + o.missed_count; + int64_t acc = completed ? (int64_t)((uint64_t)o.markets_resolved * 10000 / completed) : 10000; + // (2) Dispute verdicts — upheld (won) vs overturned (lost). + int64_t dtot = (int64_t)o.disputes_won + o.disputes_lost; + int64_t drep = dtot ? (int64_t)((uint64_t)o.disputes_won * 10000 / (uint64_t)dtot) : 10000; + // (3) Dispute responsiveness — of disputes filed, how many the oracle engaged rather than + // ignored into auto-close (dispute_responses_missed counts the ignored ones). + int64_t drecv = (int64_t)o.disputes_received; + int64_t missed = (int64_t)o.dispute_responses_missed; if (missed > drecv) missed = drecv; + int64_t resp = drecv > 0 ? (int64_t)((uint64_t)(drecv - missed) * 10000 / (uint64_t)drecv) : 10000; + // (4) Timeliness — of the resolutions delivered, the share that landed by the advertised + // deadline (resolved_late_count is the past-deadline tally). Late-but-delivered resolves + // otherwise earn full accuracy credit, so without this a chronically-late oracle scores + // identical to a punctual one. A ratio (not the raw count) keeps it fair to high volume. + // Optimistic 10000 until the oracle has resolved anything. + int64_t rres = (int64_t)o.markets_resolved; + int64_t late = (int64_t)o.resolved_late_count; if (late > rres) late = rres; + int64_t timely = rres > 0 ? (int64_t)((uint64_t)(rres - late) * 10000 / (uint64_t)rres) : 10000; + + // Weighted blend: accuracy 40% · verdicts 30% · responsiveness 15% · timeliness 15%. + int64_t score = (acc * 40 + drep * 30 + resp * 15 + timely * 15) / 100; + + // Time-decayed penalty stamps: 300 bp each, halved per 10 days since the most recent stamp + // (mirrors the object's last_penalty_stamp_time 10-day decay note). disputes_auto_closed is + // already folded into penalty_stamps at slash time, so it is not double-charged here. + // penalty_stamps is a LIFETIME cumulative counter (one per zero-volume resolution, pm_evaluator + // §4.10) — on a busy oracle it reaches tens of thousands, so the raw count MUST be capped or + // the linear cost buries the score at 0. Cap the effective stamps the same way the lazy-alloc + // consumer does (pm_evaluator ~line 740 caps at 4) so both readers agree: max 4×300 = 1200 bp. + if (o.penalty_stamps > 0) { + int64_t stamps = o.penalty_stamps > 4 ? 4 : (int64_t)o.penalty_stamps; + int64_t cost = stamps * 300; + int64_t age = (int64_t)now.sec_since_epoch() - (int64_t)o.last_penalty_stamp_time.sec_since_epoch(); + if (age < 0) age = 0; + int64_t halvings = age / 864000; if (halvings > 16) halvings = 16; + cost >>= halvings; + score -= cost; + } + // Bans are severe and lasting. + score -= (int64_t)o.bans_received * 1500; + + // Confidence shrink: a thin track record shouldn't sit at a hard 100 (or crater on one + // dispute). Blend toward a neutral 6000 prior until ~20 markets have been resolved. + int64_t n = (int64_t)o.markets_resolved; + const int64_t FULL = 20; + if (n < FULL) score = (score * n + 6000 * (FULL - n)) / FULL; + + if (score < 0) score = 0; + if (score > 10000) score = 10000; + return (uint32_t)score; + } + + // This oracle's status-1 markets whose betting has closed (betting_expiration in + // (epoch, head_block_time]) — i.e. awaiting the oracle's resolution: their count and the age + // (seconds since betting close) of the OLDEST such market. Walks only the oracle's own active + // set via by_oracle_status(owner, 1), so it is O(this oracle's active markets), not the global + // closed-market prefix. Display-only; both are time-dependent so they're computed on read. + struct oracle_awaiting_info { uint32_t count = 0; uint32_t oldest_age = 0; }; + oracle_awaiting_info oracle_awaiting(const database& db, const account_name_type& owner) { + oracle_awaiting_info r; + int64_t oldest = 0; + const auto now = db.head_block_time(); + const auto& idx = db.get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple(owner, (int8_t)1, pm_market_id_type())); + for (; it != idx.end() && it->oracle == owner && it->status == 1; ++it) + if (it->betting_expiration != fc::time_point_sec() && it->betting_expiration <= now) { + ++r.count; + int64_t age = (int64_t)now.sec_since_epoch() - (int64_t)it->betting_expiration.sec_since_epoch(); + if (age > oldest) oldest = age; + } + r.oldest_age = (uint32_t)(oldest < 0 ? 0 : oldest); + return r; + } + + // p50/p95 resolution latency (seconds) from the oracle's 8-bucket histogram: the UPPER + // boundary of the bucket where the cumulative count first reaches the target percentile. The + // open top bucket (>30d) reports its lower edge (2592000) as a floor. 0 if no resolutions yet. + uint32_t rt_percentile(const pm_oracle_object& o, int pct) { + static const uint32_t ub[8] = {3600u,21600u,86400u,259200u,604800u,1209600u,2592000u,2592000u}; + uint64_t total = 0; + for (int i = 0; i < 8; ++i) total += (uint64_t)o.resolution_time_hist[i].value; + if (total == 0) return 0; + uint64_t target = (total * (uint64_t)pct + 99) / 100; // ceil(total*pct/100) + if (target == 0) target = 1; + uint64_t cum = 0; + for (int i = 0; i < 8; ++i) { cum += (uint64_t)o.resolution_time_hist[i].value; if (cum >= target) return ub[i]; } + return ub[7]; + } + + // Parimutuel payout this bet would receive if its side wins (or its realized + // payout once settled). Byte-mirrors settle_market() in pm_evaluator.cpp. + share_type expected_payout(const database& db, const pm_bet_object& bet, + const pm_market_object& mkt) { + if (bet.status == 3) return bet.resolved_amount; // already settled + if (bet.status != 0) return 0; // cancelled / refunded / queued + + const bool binary = (mkt.market_type == 0); + const int16_t myside = binary ? (int16_t)bet.side : bet.outcome_index; + const bool resolved = (mkt.status == 3 && mkt.resolved_outcome >= 0); + if (resolved && myside != mkt.resolved_outcome) return 0; // already a loser + const int16_t winside = resolved ? mkt.resolved_outcome : myside; + + // Aggregate winning-side amount and curve weight (weight_sum is not stored). + share_type winners_amount = 0; + fc::uint128_t win_weight = 0; + const auto& bidx = db.get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(mkt.id, pm_bet_id_type())); + it != bidx.end() && it->market == mkt.id; ++it) { + if (it->status != 0 && it->status != 3) continue; + int16_t s = binary ? (int16_t)it->side : it->outcome_index; + if (s == winside) { + winners_amount += it->amount; + win_weight += fc::uint128_t((uint64_t)it->weight.value); + } + } + + int64_t losers_sum = mkt.bets_sum.value - winners_amount.value; + if (losers_sum < 0) losers_sum = 0; + int64_t fees = losers_sum * ((int64_t)mkt.oracle_fee_percent + + mkt.creator_fee_percent + mkt.liquidity_fee_percent) / 10000; + int64_t avail = losers_sum - fees; + if (avail < 0) avail = 0; + int64_t fixed_paid = (mkt.oracle_fixed_fee.value < avail) ? mkt.oracle_fixed_fee.value : avail; + int64_t winners_pool = avail - fixed_paid + mkt.forfeit_pool.value; + + if (win_weight == 0) return bet.amount; // sole/edge winner refunded + int64_t profit = (int64_t)(fc::uint128_t((uint64_t)winners_pool) + * fc::uint128_t((uint64_t)bet.weight.value) / win_weight).lo; + int64_t penalty = (int64_t)(fc::uint128_t((uint64_t)profit) + * fc::uint128_t((uint64_t)bet.time_penalty) + / fc::uint128_t((uint64_t)1000000)).lo; + return share_type(bet.amount.value + profit - penalty); + } + + // Listing risk floor (security-threat-model §4.3) — AGGREGATE, per-ORACLE (owner 2026-07-13). + // Insurance backs the oracle's WHOLE book, not each market in isolation, so the verdict is on + // the oracle, not one market. Hidden from default listing (revealed via show_risky) when EITHER: + // (A) insurance < pm_min_oracle_insurance — stake gone/insufficient (e.g. slashed to ~0) → the + // entire book is hidden, INCLUDING zero-bet markets. Fixes the old bug where a broke oracle + // kept full visibility because each fresh market had bets_sum==0 (no per-market "risk"). + // (B) insurance×100 < coverage% × Σ bets_sum over the oracle's OPEN markets — aggregate exposure + // outgrew the stake (an oracle can't honestly back 2.5× its whole open volume). + // Non-consensus (API-only, no HF/replay). Cached per oracle per head block (thread_local) so a + // listing loop over N markets stays O(N + distinct-oracles), not O(N × book). + bool oracle_below_risk_floor(const database& db, const account_name_type& oracle) { + static thread_local std::map> cache; + const uint32_t epoch = db.head_block_num(); + auto cit = cache.find(oracle); + if (cit != cache.end() && cit->second.first == epoch) return cit->second.second; + const auto& mp = db.get_validator_schedule_object().median_props; + const auto& oidx = db.get_index().indices().get(); + auto oit = oidx.find(oracle); + const int64_t ins = (oit != oidx.end()) ? oit->insurance.value : 0; + bool verdict; + if (ins < mp.pm_min_oracle_insurance.amount.value) { + verdict = true; // (A) below minimum stake + } else { + int64_t agg = 0; // (B) aggregate open exposure + const auto& midx = db.get_index().indices().get(); + for (auto mit = midx.lower_bound(oracle); mit != midx.end() && mit->oracle == oracle; ++mit) + if (mit->status == 1) agg += mit->bets_sum.value; + verdict = (ins * 100 < agg * (int64_t)mp.pm_listing_min_coverage_percent); + } + cache[oracle] = { epoch, verdict }; + return verdict; + } + + // Back-compat thin wrapper (list_markets et al. call this per market). + bool below_risk_floor(const database& db, const pm_market_object& mkt) { + return oracle_below_risk_floor(db, mkt.oracle); + } + + // Per-outcome amount + curve-weight aggregate (live from active/resolved bets). Shared by + // get_market_weight_sums and get_market_full. + pm_market_weight_sums_api_object make_weight_sums(const database& db, const pm_market_object& mkt) { + const bool binary = (mkt.market_type == 0); + std::vector amt(mkt.outcome_count, share_type(0)); + std::vector wgt(mkt.outcome_count, share_type(0)); + const auto& bidx = db.get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(mkt.id, pm_bet_id_type())); + it != bidx.end() && it->market == mkt.id; ++it) { + if (it->status != 0 && it->status != 3) continue; + int16_t s = binary ? (int16_t)it->side : it->outcome_index; + if (s >= 0 && s < (int16_t)mkt.outcome_count) { amt[s] += it->amount; wgt[s] += it->weight; } + } + pm_market_weight_sums_api_object out; + out.market_type = mkt.market_type; + out.bets_sum = mkt.bets_sum; + // Real per-outcome labels are stored on-chain (pm_outcome_object) for BOTH binary and + // multi markets (see pm_create_market_evaluator). Serve them for binary too so clients + // show the actual outcome names (team A / team B, Yes/No, Over/Under) instead of "A"/"B". + // Fallback to "A"/"B" only when a label is missing (legacy/blank). + { + const auto& oidx = db.get_index().indices().get(); + for (uint8_t i = 0; i < mkt.outcome_count; ++i) { + auto oit = oidx.find(boost::make_tuple(mkt.id, i)); + std::string label = (oit != oidx.end()) ? to_string(oit->label) : std::string(); + if (binary && label.empty()) label = (i == 0 ? "A" : "B"); + out.outcomes.push_back({(int16_t)i, label, amt[i], wgt[i]}); + } + } + return out; + } + + } // anonymous namespace + + struct prediction_market_api::impl final { + impl(): database_(appbase::app().get_plugin().db()) {} + ~impl() = default; + + graphene::chain::database& database() { return database_; } + graphene::chain::database& database() const { return database_; } + + // Off-chain metadata is ingested per-operation in ingest_market_meta() (from the create op, + // since `metadata` is no longer kept in consensus state). on_block() only prunes expired data. + void on_block() { + auto& db = database_; + run_meta_backfill(); // one-shot DLT meta recovery (no-op once complete) + if (ttl_days_ == 0) return; // archival node: keep off-chain metadata/klines forever + const auto now = db.head_block_time(); + + // Prune expired markets. Both the metadata and the (potentially large) kline history are + // dropped together: drain the market's kline points first (bounded), then remove the meta + // once its klines are gone. A market with more klines than the per-block budget finishes + // over several blocks — its meta stays as the "still pruning" marker until then — so both + // plugin indexes stay bounded without stalling block processing. + const auto& exp_idx = db.get_index().indices().get(); + const auto& kseq = db.get_index().indices().get(); + uint32_t budget = 200; // total object removals this block + for (auto eit = exp_idx.begin(); eit != exp_idx.end() && eit->expiry <= now && budget > 0; ) { + const pm_market_id_type mkt = eit->market; + auto kit = kseq.lower_bound(boost::make_tuple(mkt, (uint32_t)0)); + while (kit != kseq.end() && kit->market == mkt && budget > 0) { + const auto& k = *kit; ++kit; db.remove(k); --budget; + } + const bool kline_drained = (kit == kseq.end() || kit->market != mkt); + if (kline_drained && budget > 0) { + const auto& meta = *eit; ++eit; db.remove(meta); --budget; + } else { + break; // out of budget mid-market; resume this market next block (meta still present) + } + } + } + + // ── Kline / time-series recorder (non-consensus) ────────────────────────────── + // Maps a weight-changing operation to (market, reason); returns none for everything else. + struct kline_event_visitor { + typedef fc::optional> result_type; + const graphene::chain::database& db; + result_type operator()(const protocol::pm_place_bet_operation& o) const { + if (o.mode != 0) return result_type(); // batched bet changes weights only at pm_batch_settle + return std::make_pair(pm_market_id_type(o.market_id), (uint8_t)0); + } + result_type operator()(const protocol::pm_leverage_open_operation& o) const { return std::make_pair(pm_market_id_type(o.market_id), (uint8_t)4); } + result_type operator()(const protocol::pm_batch_settle_operation& o) const { return std::make_pair(pm_market_id_type(o.market_id), (uint8_t)3); } + result_type operator()(const protocol::pm_leverage_liquidate_operation& o)const { return std::make_pair(pm_market_id_type(o.market_id), (uint8_t)2); } + result_type operator()(const protocol::pm_leverage_resolve_operation& o) const { return std::make_pair(pm_market_id_type(o.market_id), (uint8_t)5); } + result_type operator()(const protocol::pm_cancel_bet_operation& o) const { + const auto* b = db.find(pm_bet_id_type(o.bet_id)); + if (b) return std::make_pair(b->market, (uint8_t)1); + return result_type(); + } + template result_type operator()(const T&) const { return result_type(); } + }; + + // Picks out a pm_create_market_operation from the variant (nullptr for anything else), + // so we can ingest its metadata off-chain without storing it in consensus state. + struct create_market_visitor { + typedef const protocol::pm_create_market_operation* result_type; + result_type operator()(const protocol::pm_create_market_operation& o) const { return &o; } + template result_type operator()(const T&) const { return nullptr; } + }; + + // Parse and index a new market's free-form metadata off-chain (non-consensus, prunable). + // The raw blob lives only in the block log / operation — never in chainbase state — so each + // node keeps it only as long as --pmm-ttl-days, and clients stay free to shape it ("like custom"). + // Create the off-chain meta object for a SPECIFIC market from its create op. Idempotent. + // res_exp is the market's result_expiration (== op.result_expiration at creation time). + void create_meta_for(pm_market_id_type mkt_id, time_point_sec res_exp, + const protocol::pm_create_market_operation& op) { + auto& db = database_; + const auto& meta_by_market = db.get_index().indices().get(); + if (meta_by_market.find(mkt_id) != meta_by_market.end()) return; + const uint32_t grace = (uint32_t)db.get_validator_schedule_object().median_props.pm_dispute_grace_sec; + const parsed_meta pm = parse_market_metadata(op.metadata); + db.create([&](pm_market_meta_object& m) { + m.market = mkt_id; + from_string(m.category, pm.category); + from_string(m.subcategory, pm.subcategory); + from_string(m.tags, pm.tags); + from_string(m.banned_jurisdictions, pm.banned_jurisdictions); + from_string(m.title, pm.title); + from_string(m.image, pm.image); + from_string(m.condition_id, pm.condition_id); + from_string(m.description, pm.description); + from_string(m.event, pm.event); + from_string(m.event_title, pm.event_title); + m.child = pm.child; + m.expiry = res_exp + fc::seconds(grace) + fc::seconds((int64_t)ttl_days_ * 86400); + }); + } + + void ingest_market_meta(const protocol::pm_create_market_operation& op) { + auto& db = database_; + // The market this op just created is the newest one; guard by creator + idempotency. + const auto& midx = db.get_index().indices().get(); + if (midx.begin() == midx.end()) return; + auto rit = midx.rbegin(); + if (rit->creator != op.creator) return; + create_meta_for(rit->id, rit->result_expiration, op); + } + + // ── One-shot meta backfill from the DLT block log (fallback) ────────────────── + // After --replay-from-snapshot, meta is rebuilt only for markets whose create op was in the + // reindex window (snapshot_head+1..head). But the DLT rolling block log can reach FURTHER back + // than the snapshot — those older create ops are still on disk, just never re-applied. This + // one-shot pass scans the whole DLT log and re-ingests meta for any market still missing it. + // Runs from on_block() (a safe chainbase write context, like pruning), budgeted across blocks. + // + // op→market mapping is by a STRONG IDENTITY KEY (creator+url+both expirations+outcome count), + // all consensus fields the evaluator copies verbatim from the op into the market. This can never + // mis-assign: a key mismatch just leaves that market empty (no wrong title), unlike a positional + // scheme. Duplicate keys (identical re-created markets) are matched in id order via a small list. + bool mb_done_ = false; + bool mb_started_ = false; + uint32_t mb_next_ = 0; // next DLT block number to scan + uint32_t mb_end_ = 0; // head block of the DLT log at scan start + std::map> mb_index_; // identity key → candidate market ids (asc) + + static std::string mb_key(const std::string& creator, const std::string& url, + uint32_t bexp, uint32_t rexp, uint32_t ocount) { + return creator + '\x1f' + url + '\x1f' + std::to_string(bexp) + '\x1f' + + std::to_string(rexp) + '\x1f' + std::to_string(ocount); + } + + void run_meta_backfill() { + if (mb_done_) return; + auto& db = database_; + const auto& dlt = db.get_dlt_block_log(); + if (!mb_started_) { + mb_started_ = true; + const uint32_t s = dlt.start_block_num(), h = dlt.head_block_num(); + if (s == 0 || h == 0 || h < s) { mb_done_ = true; return; } // no DLT log → nothing to do + const auto& midx = db.get_index().indices().get(); + const auto& meta_by_market = db.get_index().indices().get(); + uint64_t missing = 0; + for (auto it = midx.begin(); it != midx.end(); ++it) { + if (meta_by_market.find(it->id) != meta_by_market.end()) continue; // already has meta + mb_index_[mb_key(std::string(it->creator), to_string(it->url), + it->betting_expiration.sec_since_epoch(), + it->result_expiration.sec_since_epoch(), + (uint32_t)it->outcome_count)].push_back(it->id); + ++missing; + } + mb_next_ = s; mb_end_ = h; + if (missing == 0 || mb_index_.empty()) { mb_done_ = true; mb_index_.clear(); return; } // nothing to recover (normal restart) + wlog("pm meta backfill: scanning DLT blocks ${s}..${h} for ${n} markets missing meta", + ("s", s)("h", h)("n", missing)); + } + uint32_t budget = 500; // DLT block reads per block-apply (bounded to avoid stalls) + uint64_t recovered = 0; + while (mb_next_ <= mb_end_ && budget > 0) { + const uint32_t bn = mb_next_++; --budget; + auto blk = dlt.read_block_by_num(bn); + if (!blk) continue; + for (const auto& trx : blk->transactions) + for (const auto& opv : trx.operations) { + const auto* op = opv.visit(create_market_visitor{}); + if (!op) continue; + auto kit = mb_index_.find(mb_key(std::string(op->creator), op->url, + op->betting_expiration.sec_since_epoch(), + op->result_expiration.sec_since_epoch(), + (uint32_t)op->outcomes.size())); + if (kit == mb_index_.end() || kit->second.empty()) continue; + const pm_market_id_type mid = kit->second.front(); + kit->second.erase(kit->second.begin()); + create_meta_for(mid, op->result_expiration, *op); + ++recovered; + } + } + if (recovered) wlog("pm meta backfill: recovered ${n} markets (up to block ${b})", ("n", recovered)("b", mb_next_ - 1)); + if (mb_next_ > mb_end_) { mb_done_ = true; mb_index_.clear(); wlog("pm meta backfill: complete"); } + } + + // A plugin observer must never throw out of the apply path — swallow everything. + void on_post_apply_operation(const graphene::chain::operation_notification& note) { + try { + const auto ev = note.op.visit(kline_event_visitor{database_}); + if (ev) record_kline(ev->first, ev->second); + } catch (...) {} + try { + if (const auto* cop = note.op.visit(create_market_visitor{})) + ingest_market_meta(*cop); + } catch (...) {} + } + + // Append one snapshot of every outcome's parimutuel weight (staked amount) for `market`. + void record_kline(pm_market_id_type market, uint8_t reason) { + auto& db = database_; + const auto* m = db.find(market); + if (!m) return; + + // Next sequence = (max existing seq for this market) + 1; contiguous and undo-safe. + const auto& kidx = db.get_index().indices().get(); + uint32_t next_seq = 0; + auto it = kidx.upper_bound(boost::make_tuple(market, std::numeric_limits::max())); + if (it != kidx.begin()) { + --it; + if (it->market == market) next_seq = it->seq + 1; + } + + const bool binary = (m->market_type == 0); + const auto& oidx = db.get_index().indices().get(); + db.create([&](pm_market_kline_object& k) { + k.market = market; + k.seq = next_seq; + k.timestamp = db.head_block_time(); + k.reason = reason; + k.bets_sum = m->bets_sum; + if (binary) { + k.weights.push_back(m->a_bets_sum); // outcome 0 + k.weights.push_back(m->b_bets_sum); // outcome 1 + } else { + for (uint8_t i = 0; i < m->outcome_count; ++i) { + auto oit = oidx.find(boost::make_tuple(market, i)); + k.weights.push_back(oit != oidx.end() ? oit->bets_sum : share_type(0)); + } + } + }); + } + + boost::signals2::connection block_conn_; + boost::signals2::connection op_conn_; + uint32_t ttl_days_ = 5; + + private: + graphene::chain::database& database_; + }; + + void prediction_market_api::plugin_startup() { + wlog("prediction_market_api plugin: plugin_startup()"); + } + + void prediction_market_api::plugin_shutdown() { + if (pimpl) { + pimpl->block_conn_.disconnect(); + pimpl->op_conn_.disconnect(); + } + wlog("prediction_market_api plugin: plugin_shutdown()"); + } + + const std::string& prediction_market_api::name() { + static const std::string name = "prediction_market_api"; + return name; + } + + prediction_market_api::prediction_market_api() = default; + prediction_market_api::~prediction_market_api() = default; + + void prediction_market_api::set_program_options( + boost::program_options::options_description&, + boost::program_options::options_description& cfg) { + cfg.add_options() + ("pmm-ttl-days", boost::program_options::value()->default_value(5), + "Days to keep PM market metadata/klines (off-chain) after the dispute window closes; 0 keeps forever"); + } + + void prediction_market_api::plugin_initialize(const boost::program_options::variables_map& options) { + pimpl = std::make_unique(); + if (options.count("pmm-ttl-days")) + pimpl->ttl_days_ = options.at("pmm-ttl-days").as(); + graphene::chain::add_plugin_index(pimpl->database()); + graphene::chain::add_plugin_index(pimpl->database()); + pimpl->block_conn_ = pimpl->database().applied_block.connect( + [&](const protocol::signed_block&) { pimpl->on_block(); }); + // Record a kline point after every weight-changing PM operation (undo/redo-safe: the kline + // objects live in chainbase, so pending-tx writes roll back and re-apply with the block). + pimpl->op_conn_ = pimpl->database().post_apply_operation.connect( + [&](const graphene::chain::operation_notification& note) { pimpl->on_post_apply_operation(note); }); + JSON_RPC_REGISTER_API(name()); + } + + // ── Markets ────────────────────────────────────────────────────────────────── + + // comma-joined meta string → JSON array (clients expect tags / banned_jurisdictions as arrays) + static fc::variant csv_to_array(const std::string& csv) { + std::vector out; + size_t start = 0; + while (start <= csv.size()) { + size_t comma = csv.find(',', start); + std::string tok = csv.substr(start, comma == std::string::npos ? std::string::npos : comma - start); + if (!tok.empty()) out.push_back(fc::variant(tok)); + if (comma == std::string::npos) break; + start = comma + 1; + } + return fc::variant(std::move(out)); + } + + // Build a client "market card": the consensus pm_market_object plus the parsed metadata the + // browse/detail views need but the on-chain object doesn't carry (metadata is off-chain, in + // pm_market_meta_index). We inject BOTH a reconstructed `metadata` object — so clients that + // parse market.metadata keep working with no change — AND flat title/image/category fields for + // clients that read them directly. Empty when the meta was pruned or never ingested. + static fc::variant market_card(const graphene::chain::database& db, const pm_market_object& m) { + fc::variant v; + fc::to_variant(m, v); + fc::mutable_variant_object o(v.get_object()); + const auto& midx = db.get_index().indices().get(); + auto it = midx.find(m.id); + std::string title, image, category; + fc::mutable_variant_object md; + if (it != midx.end()) { + title = to_string(it->title); image = to_string(it->image); category = to_string(it->category); + md("title", title)("image", image)("category", category) + ("subcategory", to_string(it->subcategory)) + ("tags", csv_to_array(to_string(it->tags))) + ("banned_jurisdictions", csv_to_array(to_string(it->banned_jurisdictions))) + ("condition_id", to_string(it->condition_id)) + ("description", to_string(it->description)) + ("event", to_string(it->event)) + ("event_title", to_string(it->event_title)); + } + o["title"] = title; + o["image"] = image; + o["category"] = category; + o["metadata"] = fc::variant(std::move(md)); // reconstructed; clients parse market.metadata + return fc::variant(std::move(o)); + } + + // Volume/expiration sort for account-scoped market listings (list_markets_by_oracle / _by_creator). + // newest/oldest are handled cheaply by reverse/forward index traversal at the call site; these two + // orders need the whole matching set materialized, sorted, then paged — the same approach + // list_markets_by_category uses for its volume/expiration sorts. + static std::vector page_markets_sorted( + const graphene::chain::database& db, std::vector& ms, + const std::string& order, uint32_t from, uint32_t limit) { + if (order == "volume") + std::stable_sort(ms.begin(), ms.end(), [](const pm_market_object* a, const pm_market_object* b){ + return a->bets_sum.value > b->bets_sum.value; }); // busiest first + else // "expiration": soonest-closing first; open-ended (betting_expiration==0) sorts last + std::stable_sort(ms.begin(), ms.end(), [](const pm_market_object* a, const pm_market_object* b){ + int64_t ea = a->betting_expiration.sec_since_epoch(); if (ea == 0) ea = std::numeric_limits::max(); + int64_t eb = b->betting_expiration.sec_since_epoch(); if (eb == 0) eb = std::numeric_limits::max(); + return ea < eb; }); + std::vector result; + result.reserve(std::min(limit, ms.size())); + for (uint32_t i = from; i < ms.size() && result.size() < limit; ++i) + result.push_back(market_card(db, *ms[i])); + return result; + } + + DEFINE_API(prediction_market_api, get_market) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto* m = db.find(pm_market_id_type(market_id)); + FC_ASSERT(m != nullptr, "Market not found"); + return market_card(db, *m); + }); + } + + // list_markets(status, from, limit, [show_risky=false], [order="oldest"]) + // order: "oldest" (id asc, default — legacy) · "newest" (id desc) · "expiration" (soonest-closing + // first via by_betting_expiration, still-open only — global "ending soon" feed). The by_status index keeps + // equal-status elements in insertion order; markets are born into their status (active markets + // never re-enter the group), so within a status insertion order == id order → reverse traversal + // of the equal-range yields newest-first without a full scan/sort. Discovery feeds pass "newest". + DEFINE_API(prediction_market_api, list_markets) { + CHECK_ARG_MIN_SIZE(3, 5) + auto status = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto show_risky = GET_OPTIONAL_ARG(3, bool, false); // reveal under-insured markets + auto order = GET_OPTIONAL_ARG(4, std::string, std::string("oldest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + if (order == "expiration") { + // Still-open markets, SOONEST-CLOSING first (global "ending soon" feed). Walk the + // by_betting_expiration index (status, betting_expiration, id) from head_block_time + // forward: this skips already-closed markets (betting_expiration <= now) and + // open-ended ones (betting_expiration == 0 sorts before `now`), leaving exactly the + // markets whose betting window is about to end, nearest deadline first. + const auto now = db.head_block_time(); + const auto& eidx = db.get_index().indices().get(); + auto itr = eidx.lower_bound(boost::make_tuple(status, now, pm_market_id_type())); + while (from > 0 && itr != eidx.end() && itr->status == status) { ++itr; --from; } + while (result.size() < limit && itr != eidx.end() && itr->status == status) { + if (show_risky || !below_risk_floor(db, *itr)) + result.push_back(market_card(db, *itr)); + ++itr; + } + } else { + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(status); + if (order == "newest") { + auto itr = range.second; // one past the last equal-status market + while (from > 0 && itr != range.first) { --itr; --from; } // skip newest `from` + while (result.size() < limit && itr != range.first) { + --itr; + if (show_risky || !below_risk_floor(db, *itr)) + result.push_back(market_card(db, *itr)); + } + } else { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { + if (show_risky || !below_risk_floor(db, *itr)) + result.push_back(market_card(db, *itr)); + ++itr; + } + } + } + return result; + }); + } + + // list_markets_by_oracle(oracle, from, limit, [order="newest"]) — an oracle's markets. order: + // "newest" (id desc, default) · "oldest" (id asc) · "volume" (bets_sum desc) · "expiration" + // (soonest-closing first). newest/oldest reverse/forward the equal-range cheaply (insertion==id + // order, same assumption list_markets relies on); volume/expiration materialize the oracle's set and + // sort it (page_markets_sorted). (The oracle work-queue methods — awaiting_resolution / + // in_dispute_window / by_oracle_status — stay oldest-first on purpose: a queue is worked oldest-first.) + DEFINE_API(prediction_market_api, list_markets_by_oracle) { + CHECK_ARG_MIN_SIZE(3, 4) + auto oracle = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto order = GET_OPTIONAL_ARG(3, std::string, std::string("newest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(oracle); + if (order == "volume" || order == "expiration") { + std::vector ms; + for (auto itr = range.first; itr != range.second; ++itr) ms.push_back(&*itr); + return page_markets_sorted(db, ms, order, from, limit); + } else if (order == "oldest") { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { result.push_back(market_card(db, *itr)); ++itr; } + } else { // "newest" (default) + auto itr = range.second; + while (from > 0 && itr != range.first) { --itr; --from; } + while (result.size() < limit && itr != range.first) { --itr; result.push_back(market_card(db, *itr)); } + } + return result; + }); + } + + // list_markets_by_oracle_status(oracle, status, from, limit, [order="newest"]): this oracle's markets + // in ONE status, walked over the (oracle, status, id) prefix. Lets a client pull e.g. an oracle's + // active (1) or resolved (3) markets directly instead of fetching by_oracle and filtering. order: + // "newest" (id desc, default — profile browse leads with the latest) · "oldest" · "volume" (bets_sum + // desc) · "expiration" (soonest-closing first). This is the oracle PROFILE feed; the work-queue + // methods (awaiting_resolution / in_dispute_window) are separate and stay oldest-first on purpose. + DEFINE_API(prediction_market_api, list_markets_by_oracle_status) { + CHECK_ARG_MIN_SIZE(4, 5) + auto oracle = args.args->at(0).as(); + auto status = args.args->at(1).as(); + auto from = args.args->at(2).as(); + auto limit = args.args->at(3).as(); + auto order = GET_OPTIONAL_ARG(4, std::string, std::string("newest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(boost::make_tuple(oracle, status)); + if (order == "volume" || order == "expiration") { + std::vector ms; + for (auto itr = range.first; itr != range.second; ++itr) ms.push_back(&*itr); + return page_markets_sorted(db, ms, order, from, limit); + } else if (order == "oldest") { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { result.push_back(market_card(db, *itr)); ++itr; } + } else { // "newest" (default) + auto itr = range.second; + while (from > 0 && itr != range.first) { --itr; --from; } + while (result.size() < limit && itr != range.first) { --itr; result.push_back(market_card(db, *itr)); } + } + return result; + }); + } + + // Markets awaiting THIS oracle's result: active (status 1) markets whose betting window has + // already closed (betting_expiration <= head_block_time) and that are therefore not yet resolved. + // "Awaiting" is not a distinct status — a market stays status 1 from open through close until + // resolve — so it can't be isolated by status alone. The by_betting_expiration index is + // (status, betting_expiration, id): every status-1 market sits contiguously, ordered by + // betting_expiration, so we walk only the bounded prefix whose betting has passed and keep this + // oracle's rows. This is far cheaper than scanning the oracle's entire (mostly resolved) history. + DEFINE_API(prediction_market_api, list_markets_awaiting_resolution) { + CHECK_ARG_MIN_SIZE(3, 3) + auto oracle = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto now = db.head_block_time(); + const auto& idx = db.get_index().indices().get(); + auto itr = idx.lower_bound(boost::make_tuple((int8_t)1, time_point_sec(0), pm_market_id_type())); + for (; itr != idx.end() && itr->status == 1 && itr->betting_expiration <= now; ++itr) { + if (itr->oracle != oracle) continue; + if (from > 0) { --from; continue; } + result.push_back(market_card(db, *itr)); + if (result.size() >= limit) break; + } + return result; + }); + } + + // list_markets_in_dispute_window(oracle, from, limit): this oracle's resolved(3) markets whose + // payout is still pending(1) and that carry NO dispute row yet — i.e. still inside the window + // where a dispute can be filed. Mirrors the stored markets_in_dispute_window gauge on the oracle. + // Walks by_oracle_status(oracle, 3) (this oracle's resolved set only), filtering payout==1 and + // absence of a dispute; far cheaper than scanning the global market set. + DEFINE_API(prediction_market_api, list_markets_in_dispute_window) { + CHECK_ARG_MIN_SIZE(3, 3) + auto oracle = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& midx = db.get_index().indices().get(); + const auto& didx = db.get_index().indices().get(); + auto itr = midx.lower_bound(boost::make_tuple(oracle, (int8_t)3, pm_market_id_type())); + for (; itr != midx.end() && itr->oracle == oracle && itr->status == 3; ++itr) { + if (itr->payout_status != 1) continue; + if (didx.find(itr->id) != didx.end()) continue; // disputed → not in the disputable window + if (from > 0) { --from; continue; } + result.push_back(market_card(db, *itr)); + if (result.size() >= limit) break; + } + return result; + }); + } + + // list_markets_by_creator(creator, from, limit, [order="newest"]) — a creator's markets. order: + // "newest" (id desc, default) · "oldest" (id asc) · "volume" (bets_sum desc) · "expiration" + // (soonest-closing first). by_creator is ordered_non_unique on the name (insertion==id order), so + // newest/oldest reverse/forward the equal-range cheaply; volume/expiration materialize the creator's + // set and sort it (page_markets_sorted), same as list_markets_by_category / _by_oracle. + DEFINE_API(prediction_market_api, list_markets_by_creator) { + CHECK_ARG_MIN_SIZE(3, 4) + auto creator = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto order = GET_OPTIONAL_ARG(3, std::string, std::string("newest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(creator); + if (order == "volume" || order == "expiration") { + std::vector ms; + for (auto itr = range.first; itr != range.second; ++itr) ms.push_back(&*itr); + return page_markets_sorted(db, ms, order, from, limit); + } else if (order == "oldest") { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { result.push_back(market_card(db, *itr)); ++itr; } + } else { // "newest" (default) + auto itr = range.second; + while (from > 0 && itr != range.first) { --itr; --from; } + while (result.size() < limit && itr != range.first) { --itr; result.push_back(market_card(db, *itr)); } + } + return result; + }); + } + + DEFINE_API(prediction_market_api, get_market_outcomes) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + const auto& idx = db.get_index().indices().get(); + auto itr = idx.lower_bound(boost::make_tuple(pm_market_id_type(market_id), (uint8_t)0)); + while (itr != idx.end() && itr->market == pm_market_id_type(market_id)) { + result.push_back(pm_outcome_object(*itr)); + ++itr; + } + return result; + }); + } + + DEFINE_API(prediction_market_api, get_market_weight_sums) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto* m = db.find(pm_market_id_type(market_id)); + FC_ASSERT(m != nullptr, "Market not found"); + return make_weight_sums(db, *m); + }); + } + + // get_market_bets(market, from, limit, [order="newest"]) — the "recent bets" feed, so newest-first + // by default (id desc); "oldest" preserves the legacy id-asc order. by_market is composite (market, id). + DEFINE_API(prediction_market_api, get_market_bets) { + CHECK_ARG_MIN_SIZE(3, 4) + auto market_id = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto order = GET_OPTIONAL_ARG(3, std::string, std::string("newest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(pm_market_id_type(market_id)); + if (order == "oldest") { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { result.push_back(pm_bet_object(*itr)); ++itr; } + } else { // "newest" (default) + auto itr = range.second; + while (from > 0 && itr != range.first) { --itr; --from; } + while (result.size() < limit && itr != range.first) { --itr; result.push_back(pm_bet_object(*itr)); } + } + return result; + }); + } + + // get_account_positions(account, from, limit, [order="newest"]) + // order: "newest" (bet id desc, default — a user's activity should surface their latest bets first, + // not stale positions from months ago) · "oldest" (id asc, legacy). by_account is composite + // (account, id): reverse traversal of the account's equal-range yields newest-first deterministically. + DEFINE_API(prediction_market_api, get_account_positions) { + CHECK_ARG_MIN_SIZE(3, 4) + auto account = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto order = GET_OPTIONAL_ARG(3, std::string, std::string("newest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(account); + auto emit = [&](const pm_bet_object& b) { + const auto* m = db.find(b.market); + share_type ep = (m != nullptr) ? expected_payout(db, b, *m) : share_type(0); + result.push_back(pm_position_api_object{ + pm_bet_object(b), ep, + (m != nullptr) ? m->status : (int8_t)0, + (m != nullptr) ? m->resolved_outcome : (int16_t)-1}); + }; + if (order == "oldest") { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { emit(*itr); ++itr; } + } else { // "newest" (default) + auto itr = range.second; + while (from > 0 && itr != range.first) { --itr; --from; } + while (result.size() < limit && itr != range.first) { --itr; emit(*itr); } + } + return result; + }); + } + + DEFINE_API(prediction_market_api, get_market_liquidity) { + CHECK_ARG_MIN_SIZE(3, 3) + auto market_id = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto itr = idx.lower_bound(boost::make_tuple(pm_market_id_type(market_id), pm_liquidity_id_type())); + while (from > 0 && itr != idx.end() && itr->market == pm_market_id_type(market_id)) { ++itr; --from; } + while (result.size() < limit && itr != idx.end() && itr->market == pm_market_id_type(market_id)) { + result.push_back(pm_liquidity_object(*itr)); + ++itr; + } + return result; + }); + } + + // ── Leverage positions ───────────────────────────────────────────────────────── + + // get_account_leverage_positions(account, from, limit, [order="newest"]) — newest-first by default + // (latest leverage activity surfaces first), "oldest" for the legacy id-asc order. + DEFINE_API(prediction_market_api, get_account_leverage_positions) { + CHECK_ARG_MIN_SIZE(3, 4) + auto account = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto order = GET_OPTIONAL_ARG(3, std::string, std::string("newest")); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(account); + if (order == "oldest") { + auto itr = range.first; + while (from > 0 && itr != range.second) { ++itr; --from; } + while (result.size() < limit && itr != range.second) { result.push_back(pm_leverage_position_object(*itr)); ++itr; } + } else { // "newest" (default) + auto itr = range.second; + while (from > 0 && itr != range.first) { --itr; --from; } + while (result.size() < limit && itr != range.first) { --itr; result.push_back(pm_leverage_position_object(*itr)); } + } + return result; + }); + } + + DEFINE_API(prediction_market_api, get_market_leverage_positions) { + CHECK_ARG_MIN_SIZE(3, 3) + auto market_id = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto itr = idx.lower_bound(boost::make_tuple(pm_market_id_type(market_id), (uint8_t)0, pm_leverage_position_id_type())); + while (from > 0 && itr != idx.end() && itr->market == pm_market_id_type(market_id)) { ++itr; --from; } + while (result.size() < limit && itr != idx.end() && itr->market == pm_market_id_type(market_id)) { + result.push_back(pm_leverage_position_object(*itr)); + ++itr; + } + return result; + }); + } + + DEFINE_API(prediction_market_api, get_creator_ban) { + CHECK_ARG_SIZE(1) + auto account = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& idx = db.get_index().indices().get(); + auto itr = idx.find(account); + FC_ASSERT(itr != idx.end(), "No creator ban for account"); + return pm_creator_ban_object(*itr); + }); + } + + // ── Oracles ────────────────────────────────────────────────────────────────── + + DEFINE_API(prediction_market_api, get_oracle) { + CHECK_ARG_SIZE(1) + auto owner = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& idx = db.get_index().indices().get(); + auto itr = idx.find(owner); + FC_ASSERT(itr != idx.end(), "Oracle not found"); + auto aw = oracle_awaiting(db, itr->owner); + return pm_oracle_api_object{pm_oracle_object(*itr), reliability_score(*itr, db.head_block_time()), + aw.count, aw.oldest_age, + rt_percentile(*itr, 50), rt_percentile(*itr, 95)}; + }); + } + + DEFINE_API(prediction_market_api, list_oracles) { + CHECK_ARG_MIN_SIZE(2, 2) + auto from = args.args->at(0).as(); + auto limit = args.args->at(1).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto itr = idx.begin(); + while (from > 0 && itr != idx.end()) { ++itr; --from; } + while (result.size() < limit && itr != idx.end()) { + result.push_back(pm_oracle_object(*itr)); + ++itr; + } + return result; + }); + } + + // ── Disputes ───────────────────────────────────────────────────────────────── + + DEFINE_API(prediction_market_api, get_dispute) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& idx = db.get_index().indices().get(); + auto itr = idx.find(pm_market_id_type(market_id)); + FC_ASSERT(itr != idx.end(), "Dispute not found"); + return pm_dispute_object(*itr); + }); + } + + DEFINE_API(prediction_market_api, get_dispute_votes) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + pm_dispute_votes_api_object out; + out.uphold_weight = out.challenge_weight = out.total_weight = 0; + out.challenger_leads = false; + out.proposed_outcome = -1; + + const auto& didx = db.get_index().indices().get(); + auto dit = didx.find(pm_market_id_type(market_id)); + if (dit == didx.end()) return out; // no dispute → all projection fields stay default + out.proposed_outcome = dit->proposed_outcome; + + const auto& mkt = db.get(pm_market_id_type(market_id)); + out.expected_outcome = mkt.resolved_outcome; + out.outcome_change_shares.assign(mkt.outcome_count, 0); + + // Lazy-pool → vesting-shares bridge, IDENTICAL to pm_dispute_finalize so the projected + // verdict matches what the cron will actually apply at voting_end_time. + const auto& gpo = db.get_dynamic_global_properties(); + const auto vprice = gpo.get_vesting_share_price(); + const auto* lpool = db.find(pm_lazy_pool_id_type(0)); + const int64_t pool_nav = (lpool && lpool->total_shares.value > 0) + ? lpool->free_balance.value + lpool->allocated_balance.value + lpool->leverage_fund_used.value : 0; + const int64_t pool_total_shares = (lpool ? lpool->total_shares.value : 0); + const int64_t pool_nav_shares = (pool_nav > 0) + ? (asset(share_type(pool_nav), TOKEN_SYMBOL) * vprice).amount.value : 0; + const auto& ldidx = db.get_index().indices().get(); + auto lazy_vote_weight = [&](const account_name_type& acct) -> int64_t { + if (pool_nav <= 0 || pool_total_shares <= 0) return 0; + auto d = ldidx.find(acct); + if (d == ldidx.end() || d->shares.value <= 0) return 0; + int64_t viz = (int64_t)(fc::uint128_t((uint64_t)pool_nav) + * fc::uint128_t((uint64_t)d->shares.value) + / fc::uint128_t((uint64_t)pool_total_shares)).lo; + if (viz <= 0) return 0; + return (asset(share_type(viz), TOKEN_SYMBOL) * vprice).amount.value; + }; + + int64_t max_rshares = 0, oracle_defense = 0, total_change = 0; + const auto& vidx = db.get_index().indices().get(); + auto vit = vidx.lower_bound(boost::make_tuple(pm_market_id_type(market_id), account_name_type())); + while (vit != vidx.end() && vit->market == pm_market_id_type(market_id)) { + // (a) legacy |vote_percent| proxy — preserved for compatibility + int64_t pw = std::abs((int64_t)vit->vote_percent); + if (vit->vote_outcome == -1) out.uphold_weight += pw; + else out.challenge_weight += pw; + // (b) accurate stake-weighted tally — mirrors pm_dispute_finalize step 1 + int64_t w = db.get_account(vit->voter).effective_vesting_shares().amount.value + + lazy_vote_weight(vit->voter); + max_rshares += w; + int32_t pct = (int32_t)vit->vote_percent; + if (pct > 0 && vit->vote_outcome >= 0 && vit->vote_outcome < (int16_t)mkt.outcome_count) { + int64_t r = w * pct / 10000; // CHAIN_100_PERCENT + out.outcome_change_shares[vit->vote_outcome] += r; + total_change += r; + } else { + int32_t a = pct < 0 ? -pct : pct; + oracle_defense += w * a / 10000; + } + out.votes.push_back(pm_dispute_vote_object(*vit)); + ++vit; + } + out.total_weight = out.uphold_weight + out.challenge_weight; + + const auto& mp = db.get_validator_schedule_object().median_props; + if (out.total_weight > 0) + out.challenger_leads = + (out.challenge_weight * 10000 / out.total_weight >= (int64_t)mp.pm_dispute_approve_min_percent); + + // ── Quorum + projected verdict (mirrors pm_dispute_finalize steps 2–5) ── + const int64_t electorate = gpo.total_vesting_shares.amount.value + pool_nav_shares; + const int64_t approve_min = (int64_t)(fc::uint128_t((uint64_t)electorate) + * mp.pm_dispute_approve_min_percent / fc::uint128_t(10000)).lo; + out.participation_shares = max_rshares; + out.electorate_shares = electorate; + out.quorum_required_shares = approve_min; + out.quorum_percent_bp = electorate > 0 + ? (int32_t)((fc::uint128_t((uint64_t)max_rshares) * 10000 / fc::uint128_t((uint64_t)electorate)).lo) : 0; + out.quorum_reached = max_rshares >= approve_min; + out.oracle_defense_shares = oracle_defense; + out.change_shares = total_change; + + const bool uphold = (max_rshares < approve_min) || (total_change <= 0) || (oracle_defense >= total_change); + out.expected_uphold = uphold; + if (uphold) { + out.expected_outcome = mkt.resolved_outcome; + out.expected_consensus_strength_bp = 0; + } else { + int best = 0; + for (uint8_t i = 1; i < mkt.outcome_count; ++i) + if (out.outcome_change_shares[i] > out.outcome_change_shares[best]) best = (int)i; + out.expected_outcome = (int16_t)best; + const int64_t winning = out.outcome_change_shares[best]; + out.expected_consensus_strength_bp = max_rshares > 0 + ? (int32_t)((fc::uint128_t((uint64_t)winning) * 10000 / fc::uint128_t((uint64_t)max_rshares)).lo) : 0; + } + return out; + }); + } + + // list_oracle_disputes(oracle, from, limit): this oracle's currently OPEN disputes (status 0), + // each tagged with its `stage` — "awaiting_response" (oracle has not answered) or + // "awaiting_decision" (oracle answered, committee/resolver verdict pending) — mirroring the stored + // disputes_awaiting_response / disputes_awaiting_decision gauges. Disputes are indexed by market, + // not oracle, so we walk the small open-dispute set via by_auto_close(status 0) and keep those + // whose market names this oracle. Each row carries the enriched market card for the UI. + DEFINE_API(prediction_market_api, list_oracle_disputes) { + CHECK_ARG_MIN_SIZE(3, 3) + auto oracle = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& didx = db.get_index().indices().get(); + const auto& mbyid = db.get_index().indices().get(); + auto itr = didx.lower_bound(boost::make_tuple((uint8_t)0, time_point_sec(0), pm_dispute_id_type())); + for (; itr != didx.end() && itr->status == 0; ++itr) { + auto mit = mbyid.find(itr->market); + if (mit == mbyid.end() || mit->oracle != oracle) continue; + if (from > 0) { --from; continue; } + fc::mutable_variant_object o; + o["market"] = market_card(db, *mit); + o["market_id"] = mit->id._id; + o["disputer"] = itr->disputer; + o["proposed_outcome"] = itr->proposed_outcome; + o["filed_time"] = itr->filed_time; + o["oracle_response_deadline"] = itr->oracle_response_deadline; + o["oracle_response_time"] = itr->oracle_response_time; + o["voting_end_time"] = itr->voting_end_time; + o["auto_close_time"] = itr->auto_close_time; + o["dispute_mode"] = itr->dispute_mode; + o["stage"] = (itr->oracle_response_time == fc::time_point_sec()) + ? "awaiting_response" : "awaiting_decision"; + result.push_back(fc::variant(std::move(o))); + if (result.size() >= limit) break; + } + return result; + }); + } + + // ── Lazy pool & chain properties ─────────────────────────────────────────────── + + DEFINE_API(prediction_market_api, get_lazy_pool) { + CHECK_ARG_SIZE(0) + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto* p = db.find(pm_lazy_pool_id_type(0)); + FC_ASSERT(p != nullptr, "Lazy pool not initialized"); + return pm_lazy_pool_object(*p); + }); + } + + DEFINE_API(prediction_market_api, get_lazy_deposit) { + CHECK_ARG_SIZE(1) + auto account = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& idx = db.get_index().indices().get(); + auto itr = idx.find(account); + FC_ASSERT(itr != idx.end(), "No lazy deposit for account"); + return pm_lazy_deposit_object(*itr); + }); + } + + // Pending (queued) lazy-pool withdrawals for an account: amounts still owed that could not be + // paid in full from free_balance yet. Empty = nothing queued (already paid or never queued). + DEFINE_API(prediction_market_api, get_lazy_withdraw_requests) { + CHECK_ARG_SIZE(1) + auto account = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector out; + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(account); + for (auto it = range.first; it != range.second; ++it) out.push_back(*it); + return out; + }); + } + + // get_deferred_claims(market, [from=0], [limit=100]) + // F1/#300: the outcome-contingent early-exit claims still pending on a market, in FIFO exit order + // (by_claim_market == id order). Each carries {account, kind (0 bet-cancel / 1 leverage-close), + // outcome_index (pays only if this outcome wins), claim_amount, exit_time}. Claims are consumed and + // removed at settlement, so a resolved market returns []. `from` is a starting claim id (0 = oldest), + // `limit` caps the page (<= 1000). Lets a client show a position's "pending early-exit claim" and its + // FIFO standing against the bounded reward bucket. + DEFINE_API(prediction_market_api, get_deferred_claims) { + CHECK_ARG_MIN_SIZE(1, 3) + auto market = args.args->at(0).as(); + uint32_t from = args.args->size() > 1 ? args.args->at(1).as() : 0; + uint32_t limit = args.args->size() > 2 ? args.args->at(2).as() : 100; + FC_ASSERT(limit <= 1000, "limit must be <= 1000"); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector out; + const auto& idx = db.get_index().indices().get(); + auto it = idx.lower_bound(boost::make_tuple(pm_market_id_type(market), + pm_deferred_claim_id_type(from))); + for (; it != idx.end() && it->market == pm_market_id_type(market) && out.size() < limit; ++it) + out.push_back(*it); + return out; + }); + } + + // get_account_commits(account, [open_only=true]) + // The account's commit-reveal commitments (pm_commit_object) in id order. A hidden/batch bet is a + // two-step flow: pm_commit_bet stores a commitment + escrow, then pm_reveal_bet(commit_id, …) reveals + // it — and the reveal op needs the on-chain commit_id, which the client otherwise had no way to look + // up (only the by_commit_account index existed, unexposed). By default returns only OPEN commits + // (status==0, still revealable); pass open_only=false to include revealed(1)/forfeited(2) for history. + DEFINE_API(prediction_market_api, get_account_commits) { + CHECK_ARG_MIN_SIZE(1, 2) + auto account = args.args->at(0).as(); + bool open_only = args.args->size() > 1 ? args.args->at(1).as() : true; + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector out; + const auto& idx = db.get_index().indices().get(); + auto range = idx.equal_range(account); + for (auto it = range.first; it != range.second; ++it) + if (!open_only || it->status == 0) out.push_back(*it); + return out; + }); + } + + DEFINE_API(prediction_market_api, get_pm_chain_properties) { + CHECK_ARG_SIZE(0) + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + return db.get_validator_schedule_object().median_props; + }); + } + + // ── Metadata (parsed off-chain from each market's `metadata` JSON) ────────────── + + DEFINE_API(prediction_market_api, get_market_meta) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& idx = db.get_index().indices().get(); + auto itr = idx.find(pm_market_id_type(market_id)); + FC_ASSERT(itr != idx.end(), "No metadata for market"); + return pm_market_meta_object(*itr); + }); + } + + // list_markets_by_category(category, from, limit, [jurisdiction=""], [subcategory=""], [tag=""], [sort="newest"], [hide_children=true], [show_risky=false], [status=-1]) + // Optional filters: jurisdiction (exclude markets banning it), subcategory (exact), tag (CSV membership). + // status (default -1 = any): keep only markets in this lifecycle status (0 pending · 1 active · 3 + // resolved …). Filtering server-side lets a client page an "active only" browse accurately instead + // of over-fetching and dropping rows locally. + // hide_children (default true): drop child/prop markets of a split match so the listing shows only + // parent markets; the props stay reachable via the parent's event page. Pass false to include them. + // sort: "newest" (market id desc, default) · "oldest" (id asc) · "volume" (bets_sum desc) · + // "expiration" (betting_expiration asc). volume/expiration load each matching market, so they + // scan the whole (non-pruned) category before paging; newest/oldest sort on the meta id alone. + // When the market is loaded (volume/expiration sort or a status filter) each returned row also carries + // a `volume` field (= bets_sum, raw shares) so clients render exact volume badges and rank across + // categories without a second round-trip; unfiltered newest/oldest rows omit it (client fills lazily). + DEFINE_API(prediction_market_api, list_markets_by_category) { + CHECK_ARG_MIN_SIZE(3, 10) + auto category = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto jurisdiction = GET_OPTIONAL_ARG(3, std::string, std::string()); // exclude markets banning it + auto subcategory = GET_OPTIONAL_ARG(4, std::string, std::string()); + auto tag = GET_OPTIONAL_ARG(5, std::string, std::string()); + auto sort = GET_OPTIONAL_ARG(6, std::string, std::string("newest")); + auto hide_children= GET_OPTIONAL_ARG(7, bool, true); // drop child/prop markets by default + auto show_risky = GET_OPTIONAL_ARG(8, bool, false); // reveal markets of under-insured oracles + auto status = GET_OPTIONAL_ARG(9, int8_t, (int8_t)-1); // -1 = any lifecycle status + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const bool need_status = (status != (int8_t)-1); + const bool need_market = (sort == "volume" || sort == "expiration" || need_status); + struct entry { const pm_market_meta_object* m; int64_t vol; int64_t exp; int64_t mid; }; + std::vector es; + const auto& idx = db.get_index().indices().get(); + for (auto itr = idx.lower_bound(category); + itr != idx.end() && to_string(itr->category) == category; ++itr) { + if (hide_children && itr->child) continue; // parent-only listing by default + if (!jurisdiction.empty() && meta_csv_contains(to_string(itr->banned_jurisdictions), jurisdiction)) continue; + if (!subcategory.empty() && to_string(itr->subcategory) != subcategory) continue; + if (!tag.empty() && !meta_csv_contains_ci(to_string(itr->tags), tag)) continue; // case-insensitive tags + const auto* mk = need_market ? db.find(itr->market) : nullptr; + if (need_status && (!mk || mk->status != status)) continue; // server-side lifecycle filter + if (!show_risky) { // hide markets whose oracle is under-insured (aggregate risk floor) + const auto* mko = mk ? mk : db.find(itr->market); + if (mko && oracle_below_risk_floor(db, mko->oracle)) continue; + } + es.push_back({ &*itr, + mk ? mk->bets_sum.value : 0, + mk ? (int64_t)mk->betting_expiration.sec_since_epoch() : std::numeric_limits::max(), + (int64_t)itr->market._id }); + } + if (sort == "volume") + std::stable_sort(es.begin(), es.end(), [](const entry& a, const entry& b){ return a.vol > b.vol; }); + else if (sort == "expiration") + std::stable_sort(es.begin(), es.end(), [](const entry& a, const entry& b){ return a.exp < b.exp; }); + else if (sort == "oldest") + std::stable_sort(es.begin(), es.end(), [](const entry& a, const entry& b){ return a.mid < b.mid; }); + else // "newest" + std::stable_sort(es.begin(), es.end(), [](const entry& a, const entry& b){ return a.mid > b.mid; }); + + std::vector result; + result.reserve(std::min(limit, es.size())); + for (uint32_t i = from; i < es.size() && result.size() < limit; ++i) { + fc::variant v; fc::to_variant(pm_market_meta_object(*es[i].m), v); + fc::mutable_variant_object o(v.get_object()); + if (need_market) + // surface the bets_sum already computed for the sort → exact client-side volume + // badge + global cross-category ranking, no extra get_market_weight_sums call + o["volume"] = es[i].vol; + // The meta object carries no lifecycle state, so category/tag browsing rendered every + // card as "active". Attach the live status/result from the market (query-time, bounded + // by `limit`) so cards show the right badge and can mark the resolved winner. + if (const auto* mk = db.find(es[i].m->market)) { + o["status"] = mk->status; + o["payout_status"] = mk->payout_status; + o["resolved_outcome"] = mk->resolved_outcome; + } + // Emit tags / banned_jurisdictions as ARRAYS (not raw CSV) and expose a reconstructed + // `metadata` object, matching market_card so clients read market.metadata.tags uniformly + // across every listing — the UI never parses raw meta CSV (owner 2026-07-12). + const auto& mm = *es[i].m; + fc::variant tags_arr = csv_to_array(to_string(mm.tags)); + fc::variant banned_arr = csv_to_array(to_string(mm.banned_jurisdictions)); + o["tags"] = tags_arr; + o["banned_jurisdictions"] = banned_arr; + fc::mutable_variant_object md; + md("title", to_string(mm.title))("image", to_string(mm.image)) + ("category", to_string(mm.category))("subcategory", to_string(mm.subcategory)) + ("tags", tags_arr)("banned_jurisdictions", banned_arr) + ("condition_id", to_string(mm.condition_id))("description", to_string(mm.description)) + ("event", to_string(mm.event))("event_title", to_string(mm.event_title)); + o["metadata"] = fc::variant(std::move(md)); + result.push_back(fc::variant(std::move(o))); + } + return result; + }); + } + + // list_markets_by_event(event, from, limit) — sibling markets sharing a parent event key + // (one match/game), oldest-first (meta id asc). Returns full market cards so a client can render + // an event page with each child's outcomes/volume in one round-trip. Empty event key yields + // nothing (standalone markets are not an "event"). Pruned markets are skipped. + DEFINE_API(prediction_market_api, list_markets_by_event) { + CHECK_ARG_MIN_SIZE(3, 4) + auto event = args.args->at(0).as(); + auto from = args.args->at(1).as(); + auto limit = args.args->at(2).as(); + auto show_risky = GET_OPTIONAL_ARG(3, bool, false); // reveal markets of under-insured oracles + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + if (event.empty()) return result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + for (auto itr = idx.lower_bound(event); + itr != idx.end() && to_string(itr->event) == event && result.size() < limit; ++itr) { + const auto* mk = db.find(itr->market); + if (!mk) continue; + if (!show_risky && oracle_below_risk_floor(db, mk->oracle)) continue; + if (from > 0) { --from; continue; } + result.push_back(market_card(db, *mk)); + } + return result; + }); + } + + // get_market_categories() — taxonomy + live counts + hot tags, aggregated over the currently + // indexed (non-pruned) markets. Categories sorted by count desc; hot_tags = top 20 by count + // (jurisdiction-* tags excluded, matching the browse filter). No args. + DEFINE_API(prediction_market_api, get_market_categories) { + CHECK_ARG_SIZE(0) + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::map cat_total; + std::map> cat_sub; + std::map tag_count; + const auto& cidx = db.get_index().indices().get(); + for (auto it = cidx.begin(); it != cidx.end(); ++it) { + const std::string cat = to_string(it->category); + if (cat.empty()) continue; + cat_total[cat]++; + const std::string sub = to_string(it->subcategory); + if (!sub.empty()) cat_sub[cat][sub]++; + const std::string tags = to_string(it->tags); // comma-joined + size_t start = 0; + while (start <= tags.size()) { + size_t comma = tags.find(',', start); + std::string t = tags.substr(start, comma == std::string::npos ? std::string::npos : comma - start); + if (!t.empty() && t.rfind("jurisdiction", 0) != 0) tag_count[t]++; + if (comma == std::string::npos) break; + start = comma + 1; + } + } + + pm_market_categories_api_object out; + for (auto& kv : cat_total) { + pm_category_count c; + c.category = kv.first; + c.count = kv.second; + auto sit = cat_sub.find(kv.first); + if (sit != cat_sub.end()) + for (auto& sc : sit->second) c.subcategories.push_back({sc.first, sc.second}); + out.categories.push_back(std::move(c)); + } + std::stable_sort(out.categories.begin(), out.categories.end(), + [](const pm_category_count& a, const pm_category_count& b){ return a.count > b.count; }); + for (auto& kv : tag_count) out.hot_tags.push_back({kv.first, kv.second}); + std::stable_sort(out.hot_tags.begin(), out.hot_tags.end(), + [](const pm_tag_count& a, const pm_tag_count& b){ return a.count > b.count; }); + if (out.hot_tags.size() > 20) out.hot_tags.resize(20); + return out; + }); + } + + // get_category_tag_counts(category) — authoritative per-tag market counts WITHIN one category + // (all tags, not the global top-20 of get_market_categories). Lets a browse UI show a stable tag + // count that doesn't change with the currently-loaded page. Scans only the category's index slice. + // jurisdiction-* tags excluded (they aren't browse tags). Returns tags in the `hot_tags` field + // (the `categories` field is left empty), sorted by count desc. + DEFINE_API(prediction_market_api, get_category_tag_counts) { + CHECK_ARG_SIZE(1) + auto category = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::map tag_count; + const auto& idx = db.get_index().indices().get(); + for (auto it = idx.lower_bound(category); it != idx.end() && to_string(it->category) == category; ++it) { + const std::string tags = to_string(it->tags); // comma-joined + size_t start = 0; + while (start <= tags.size()) { + size_t comma = tags.find(',', start); + std::string t = tags.substr(start, comma == std::string::npos ? std::string::npos : comma - start); + if (!t.empty() && t.rfind("jurisdiction", 0) != 0) tag_count[t]++; + if (comma == std::string::npos) break; + start = comma + 1; + } + } + pm_market_categories_api_object out; + for (auto& kv : tag_count) out.hot_tags.push_back({kv.first, kv.second}); + std::stable_sort(out.hot_tags.begin(), out.hot_tags.end(), + [](const pm_tag_count& a, const pm_tag_count& b){ return a.count > b.count; }); + return out; + }); + } + + // get_market_kline(market_id, from = 0, limit = 1000) + // Returns a time-window of per-outcome weight snapshots, ascending by seq (oldest→newest), for a + // thin client to plot. Pagination is offset-from-newest: `from` newest points are skipped, then up + // to `limit` (≤ 1000) are returned. So (from=0, limit=1000) is the latest ≤1000 changes, and + // (from=1000, limit=1000) steps another 1000 further back — repeat to lazy-load older history. + DEFINE_API(prediction_market_api, get_market_kline) { + CHECK_ARG_MIN_SIZE(1, 3) + auto market_id = args.args->at(0).as(); + auto from = GET_OPTIONAL_ARG(1, uint32_t, 0u); + auto limit = GET_OPTIONAL_ARG(2, uint32_t, 1000u); + FC_ASSERT(limit <= 1000, "limit must be <= 1000"); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector out; + const auto market = pm_market_id_type(market_id); + const auto& idx = db.get_index().indices().get(); + + // Highest seq present (records are contiguous 0..max ⇒ count = max + 1). + auto last = idx.upper_bound(boost::make_tuple(market, std::numeric_limits::max())); + if (last == idx.begin()) return out; + --last; + if (last->market != market) return out; + const uint32_t count = last->seq + 1; + if (from >= count) return out; + + const uint32_t end_excl = count - from; // newest seq not skipped (exclusive) + const uint32_t start = end_excl > limit ? end_excl - limit : 0; + out.reserve(end_excl - start); + for (auto it = idx.lower_bound(boost::make_tuple(market, start)); + it != idx.end() && it->market == market && it->seq < end_excl; ++it) { + pm_kline_api_object r; + r.seq = it->seq; + r.timestamp = it->timestamp.sec_since_epoch(); + r.reason = it->reason; + r.bets_sum = it->bets_sum; + r.weights.assign(it->weights.begin(), it->weights.end()); + out.push_back(std::move(r)); + } + return out; + }); + } + + // ── Leverage previews ─────────────────────────────────────────────────────────── + // These reuse pm::leverage::* (the SAME frozen math the evaluators run) so a preview equals + // what pm_leverage_open/close/convert would compute at the head block. Non-consensus quotes. + + // get_leverage_quote(market_id, outcome_index, collateral) + // Mirrors pm_leverage_open_evaluator: same constraint ladder + max_leverage_loan() search. + DEFINE_API(prediction_market_api, get_leverage_quote) { + CHECK_ARG_SIZE(3) + auto market_id = args.args->at(0).as(); + auto outcome_index = args.args->at(1).as(); + auto collateral = args.args->at(2).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& mp = db.get_validator_schedule_object().median_props; + const auto now = db.head_block_time(); + const auto* m = db.find(pm_market_id_type(market_id)); + FC_ASSERT(m != nullptr, "Market not found"); + const auto& mkt = *m; + + pm_leverage_quote_api_object out; + out.outcome_index = outcome_index; + out.collateral = share_type(collateral); + out.pool_profit_percent = mp.pm_leverage_pool_profit_percent; + out.safety_margin_percent = mp.pm_leverage_safety_margin_percent; + out.max_slippage_percent = mp.pm_leverage_max_slippage_percent; + out.m_factor_percent = mp.pm_leverage_m_factor_percent; + out.expiration_buffer_sec = mp.pm_leverage_expiration_buffer_sec; + out.funding_rate_ppm_per_day = mp.pm_leverage_funding_rate_ppm_per_day; + // Open-ended markets (betting_expiration == 0) have no protocol force-close point. + out.auto_close_time = (mkt.betting_expiration == fc::time_point_sec()) + ? fc::time_point_sec() + : fc::time_point_sec(mkt.betting_expiration - fc::seconds(mp.pm_leverage_expiration_buffer_sec)); + + auto fail = [&](const char* c, const std::string& why) { + out.failed_constraints.push_back({std::string(c), why}); + }; + // Eligibility ladder — mirrors the evaluator's FC_ASSERTs (collected, not thrown). + if (!mp.pm_leverage_enabled) fail("leverage_disabled", "Leverage is disabled by governance"); + if (mkt.market_type != 0) fail("cpmm_binary_only", "Leverage is CPMM-binary only"); + if (mkt.status != 1) fail("market_inactive", "Market is not active"); + if (outcome_index != 0 && outcome_index != 1) fail("cpmm_binary_only", "outcome_index must be 0/1"); + if (mkt.betting_expiration != fc::time_point_sec() + && now >= mkt.betting_expiration - fc::seconds(mp.pm_leverage_expiration_buffer_sec)) + fail("expiration_buffer", "Too close to betting expiration for leverage"); + if (mkt.liquidity_sum < mp.pm_leverage_min_market_liquidity.amount) + fail("min_market_liquidity", "Market liquidity below leverage minimum"); + + const auto* pool = db.find(pm_lazy_pool_id_type(0)); + const int64_t free_bal = pool ? pool->free_balance.value : 0; + const int64_t fund_used = pool ? pool->leverage_fund_used.value : 0; + const int64_t free_amount = free_bal - fund_used; + const int64_t fund_total = (int64_t)(fc::uint128_t((uint64_t)std::max(free_bal, 0)) + * fc::uint128_t(mp.pm_leverage_fund_percent) / fc::uint128_t(100u)).lo; + const int64_t fund_available = fund_total - fund_used; + const int64_t per_pos_cap = fund_available > 0 + ? (int64_t)(fc::uint128_t((uint64_t)fund_available) + * fc::uint128_t(mp.pm_leverage_max_per_position_bp) / fc::uint128_t(10000u)).lo : 0; + const int64_t pos_cap = (int64_t)(fc::uint128_t((uint64_t)std::max(mkt.liquidity_sum.value, 0)) + * fc::uint128_t(mp.pm_leverage_max_position_ratio_percent) / fc::uint128_t(100u)).lo; + out.pool_free_amount = share_type(free_amount); + out.fund_available = share_type(fund_available); + out.per_position_cap = share_type(per_pos_cap); + out.market_position_cap = share_type(pos_cap); + + if (free_amount <= 0 || fund_available <= 0) fail("fund_availability", "Leverage fund exhausted"); + const int64_t pos_room = pos_cap - collateral; // loan headroom vs market-size cap + if (pos_room <= 0) fail("position_size", "Collateral already at/above market position cap"); + + // Only search when structurally eligible (no blocking constraint above, valid collateral). + const bool eligible = out.failed_constraints.empty() && collateral > 0; + int64_t max_loan = 0; + if (eligible) { + int64_t hi = std::min(free_amount, per_pos_cap); + hi = std::min(hi, pos_room); + if (hi < 0) hi = 0; + max_loan = pm::leverage::max_leverage_loan( + mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, collateral, (int)outcome_index, hi, + mp.pm_leverage_pool_profit_percent, mp.pm_leverage_safety_margin_percent, + mp.pm_leverage_max_slippage_percent, mp.pm_leverage_m_factor_percent); + if (max_loan <= 0) fail("solvency", "No loan size passes the worst-case solvency check"); + } + + out.max_loan = share_type(max_loan); + out.available = (max_loan > 0); + out.max_leverage_x100 = (collateral > 0) + ? (uint32_t)(((int64_t)(collateral + max_loan) * 100) / collateral) : 100; + + // Build up to 12 evenly-spaced stops in (0, max_loan]; skip sub-1.01× points. + if (out.available) { + const int N = 12; + uint32_t last_lev = 0; + for (int i = 1; i <= N; ++i) { + int64_t loan = (int64_t)((fc::uint128_t((uint64_t)max_loan) * (uint64_t)i / (uint64_t)N).lo); + if (loan <= 0) continue; + uint32_t lev = (uint32_t)(((int64_t)(collateral + loan) * 100) / collateral); + if (lev < 101 || lev == last_lev) continue; // min 1.01×, dedupe + last_lev = lev; + pm::leverage::cpmm_fill f = pm::leverage::cpmm_buy( + mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, collateral + loan, (int)outcome_index); + int64_t thr = pm::leverage::liquidation_threshold(loan, mp.pm_leverage_pool_profit_percent); + int64_t cur_cv = pm::leverage::cancel_value(f.new_reserve_a, f.new_reserve_b, mkt.k, + f.tokens, (int)outcome_index); + int64_t mm = pm::leverage::worst_opposing_bet(f.new_reserve_a, f.new_reserve_b, + mp.pm_leverage_max_slippage_percent, mp.pm_leverage_m_factor_percent); + int64_t cvw = pm::leverage::cancel_value_after_opposing(f.new_reserve_a, f.new_reserve_b, mkt.k, + f.tokens, (int)outcome_index, mm); + pm_leverage_stop s; + s.leverage_x100 = lev; + s.loan = share_type(loan); + s.total_bet = share_type(collateral + loan); + s.expected_tokens = share_type(f.tokens); + s.pool_profit = share_type(thr - loan); + s.liquidation_threshold = share_type(thr); + s.current_cancel_value = share_type(cur_cv); + s.worst_case_cancel_value = share_type(cvw); + out.stops.push_back(std::move(s)); + } + } + return out; + }); + } + + // get_leverage_close_preview(position_id) — mirrors pm_leverage_close_evaluator at head reserves. + DEFINE_API(prediction_market_api, get_leverage_close_preview) { + CHECK_ARG_SIZE(1) + auto position_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto* p = db.find(pm_leverage_position_id_type(position_id)); + FC_ASSERT(p != nullptr, "Position not found"); + const auto& pos = *p; + const auto& mkt = db.get(pos.market); + + const int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + pos.tokens.value, (int)pos.outcome_index); + const int64_t obligation = pos.liquidation_threshold.value; + const int64_t bettor = cv >= obligation ? cv - obligation : 0; + + pm_leverage_close_preview_api_object out; + out.position_id = position_id; + out.outcome_index = pos.outcome_index; + out.cancel_value = share_type(cv); + out.pool_obligation = share_type(obligation); + out.bettor_receives = share_type(bettor); + out.collateral = pos.collateral; + out.loan = pos.loan; + out.pool_profit_charge = pos.pool_profit; + out.closeable = (cv >= obligation); + out.loss_vs_collateral = pos.collateral.value - bettor; + out.loss_percent_bp = pos.collateral.value > 0 + ? (int32_t)((out.loss_vs_collateral * 10000) / pos.collateral.value) : 0; + return out; + }); + } + + // get_leverage_convert_preview(position_id) — mirrors pm_leverage_convert_evaluator at head reserves. + DEFINE_API(prediction_market_api, get_leverage_convert_preview) { + CHECK_ARG_SIZE(1) + auto position_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& mp = db.get_validator_schedule_object().median_props; + const auto* p = db.find(pm_leverage_position_id_type(position_id)); + FC_ASSERT(p != nullptr, "Position not found"); + const auto& pos = *p; + const auto& mkt = db.get(pos.market); + + const int64_t cv = pm::leverage::cancel_value(mkt.reserve_a.value, mkt.reserve_b.value, mkt.k, + pos.tokens.value, (int)pos.outcome_index); + const int64_t obligation = pos.liquidation_threshold.value; + const int64_t profit = cv > obligation ? cv - obligation : 0; + const int64_t fee = (int64_t)(fc::uint128_t((uint64_t)profit) + * fc::uint128_t(mp.pm_conversion_profit_cost_percent) / fc::uint128_t(100u)).lo; + + pm_leverage_convert_preview_api_object out; + out.position_id = position_id; + out.outcome_index = pos.outcome_index; + out.cancel_value = share_type(cv); + out.pool_obligation = share_type(obligation); + out.current_profit = share_type(profit); + out.conversion_profit_cost_percent = mp.pm_conversion_profit_cost_percent; + out.conversion_fee = share_type(fee); + out.total_user_payment = share_type(obligation + fee); + out.convertible = (profit > 0); + return out; + }); + } + + // ── Enriched market view + lazy allocations ───────────────────────────────────── + + // get_market_full(market_id, [account]) — one call: market + outcomes + weight sums + oracle + + // metadata, plus (when account given) that account's bets / leverage / LP on THIS market. + DEFINE_API(prediction_market_api, get_market_full) { + CHECK_ARG_MIN_SIZE(1, 2) + auto market_id = args.args->at(0).as(); + auto account = GET_OPTIONAL_ARG(1, account_name_type, account_name_type()); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto* m = db.find(pm_market_id_type(market_id)); + FC_ASSERT(m != nullptr, "Market not found"); + const auto& mkt = *m; + + std::vector outcomes; + { + const auto& idx = db.get_index().indices().get(); + for (auto it = idx.lower_bound(boost::make_tuple(mkt.id, (uint8_t)0)); + it != idx.end() && it->market == mkt.id; ++it) + outcomes.push_back(pm_outcome_object(*it)); + } + + fc::optional oracle; + { + const auto& oidx = db.get_index().indices().get(); + auto oit = oidx.find(mkt.oracle); + if (oit != oidx.end()) { + auto oaw = oracle_awaiting(db, oit->owner); + oracle = pm_oracle_api_object{pm_oracle_object(*oit), reliability_score(*oit, db.head_block_time()), + oaw.count, oaw.oldest_age, + rt_percentile(*oit, 50), rt_percentile(*oit, 95)}; + } + } + + fc::optional meta; + { + const auto& midx = db.get_index().indices().get(); + auto mit = midx.find(mkt.id); + if (mit != midx.end()) meta = pm_market_meta_object(*mit); + } + + std::vector my_positions; + std::vector my_leverage; + std::vector my_liquidity; + if (account != account_name_type()) { + const auto& bidx = db.get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(mkt.id, account, pm_bet_id_type())); + it != bidx.end() && it->market == mkt.id && it->account == account; ++it) + my_positions.push_back(pm_position_api_object{ + pm_bet_object(*it), expected_payout(db, *it, mkt), mkt.status, mkt.resolved_outcome}); + const auto& lidx = db.get_index().indices().get(); + for (auto it = lidx.lower_bound(boost::make_tuple(mkt.id, (uint8_t)0, pm_leverage_position_id_type())); + it != lidx.end() && it->market == mkt.id; ++it) + if (it->account == account) my_leverage.push_back(pm_leverage_position_object(*it)); + const auto& qidx = db.get_index().indices().get(); + for (auto it = qidx.lower_bound(boost::make_tuple(mkt.id, pm_liquidity_id_type())); + it != qidx.end() && it->market == mkt.id; ++it) + if (it->provider == account) my_liquidity.push_back(pm_liquidity_object(*it)); + } + + pm_market_full_api_object full{ + pm_market_object(mkt), std::move(outcomes), make_weight_sums(db, mkt), + oracle, meta, std::move(my_positions), std::move(my_leverage), std::move(my_liquidity)}; + // Overlay the metadata-enriched market card so detail views (which read market.metadata) + // get title/image without depending on the separate `meta` field. + fc::variant fv; + fc::to_variant(full, fv); + fc::mutable_variant_object fo(fv.get_object()); + fo["market"] = market_card(db, mkt); + return fc::variant(std::move(fo)); + }); + } + + DEFINE_API(prediction_market_api, get_lazy_allocations) { + CHECK_ARG_MIN_SIZE(2, 2) + auto from = args.args->at(0).as(); + auto limit = args.args->at(1).as(); + FC_ASSERT(limit <= 1000); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + std::vector result; + result.reserve(limit); + const auto& idx = db.get_index().indices().get(); + auto it = idx.begin(); + while (from > 0 && it != idx.end()) { ++it; --from; } + while (result.size() < limit && it != idx.end()) { result.push_back(pm_lazy_allocation_object(*it)); ++it; } + return result; + }); + } + + DEFINE_API(prediction_market_api, get_market_lazy_allocation) { + CHECK_ARG_SIZE(1) + auto market_id = args.args->at(0).as(); + auto& db = pimpl->database(); + return db.with_weak_read_lock([&]() { + const auto& idx = db.get_index().indices().get(); + auto it = idx.find(pm_market_id_type(market_id)); + FC_ASSERT(it != idx.end(), "No lazy allocation for market"); + return pm_lazy_allocation_object(*it); + }); + } + +} } } // graphene::plugins::prediction_market_api diff --git a/plugins/raw_block/CMakeLists.txt b/plugins/raw_block/CMakeLists.txt index 1874ae46c3..b36371d95c 100644 --- a/plugins/raw_block/CMakeLists.txt +++ b/plugins/raw_block/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/snapshot/CMakeLists.txt b/plugins/snapshot/CMakeLists.txt index 222eb3a84f..5ba8561a9c 100644 --- a/plugins/snapshot/CMakeLists.txt +++ b/plugins/snapshot/CMakeLists.txt @@ -10,17 +10,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if (BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/snapshot/plugin.cpp b/plugins/snapshot/plugin.cpp index f291a51b6b..1efdcda1f6 100644 --- a/plugins/snapshot/plugin.cpp +++ b/plugins/snapshot/plugin.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -777,6 +779,199 @@ inline uint32_t import_account_recovery_requests( return count; } +// ──────────────────────────────────────────────────────────────────────────── +// HF14 PM import helpers (shared_string members need set_shared_string) +// ──────────────────────────────────────────────────────────────────────────── + +inline uint32_t import_pm_oracles(graphene::chain::database& db, const fc::variants& arr) { + uint32_t count = 0; + for (const auto& v : arr) { + auto id_val = v["id"].as_int64(); + auto& mutable_idx = db.get_mutable_index(); + mutable_idx.set_next_id(pm_oracle_id_type(id_val)); + db.create([&](pm_oracle_object& obj) { + obj.owner = v["owner"].as(); + obj.insurance = v["insurance"].as(); + obj.fee_percent = static_cast(v["fee_percent"].as_uint64()); + obj.fixed_fee = v["fixed_fee"].as(); + set_shared_string(obj.rules_url, v["rules_url"]); + obj.active_since = v["active_since"].as(); + obj.last_active_time = v["last_active_time"].as(); + obj.banned_until = v["banned_until"].as(); + obj.markets_accepted = static_cast(v["markets_accepted"].as_uint64()); + obj.markets_resolved = static_cast(v["markets_resolved"].as_uint64()); + obj.no_contest_count = static_cast(v["no_contest_count"].as_uint64()); + obj.missed_count = static_cast(v["missed_count"].as_uint64()); + obj.disputes_received = static_cast(v["disputes_received"].as_uint64()); + obj.disputes_lost = static_cast(v["disputes_lost"].as_uint64()); + obj.disputes_won = static_cast(v["disputes_won"].as_uint64()); + obj.disputes_auto_closed = static_cast(v["disputes_auto_closed"].as_uint64()); + obj.dispute_responses_missed = static_cast(v["dispute_responses_missed"].as_uint64()); + obj.total_volume_resolved = v["total_volume_resolved"].as(); + obj.total_insurance_slashed = v["total_insurance_slashed"].as(); + obj.avg_resolution_time = static_cast(v["avg_resolution_time"].as_uint64()); + obj.penalty_stamps = static_cast(v["penalty_stamps"].as_uint64()); + obj.bans_received = static_cast(v["bans_received"].as_uint64()); + if (v.get_object().contains("last_penalty_stamp_time")) + obj.last_penalty_stamp_time = v["last_penalty_stamp_time"].as(); + if (v.get_object().contains("auto_accept_creator")) + obj.auto_accept_creator = v["auto_accept_creator"].as(); + if (v.get_object().contains("auto_accept_resolver")) + obj.auto_accept_resolver = v["auto_accept_resolver"].as(); + if (v.get_object().contains("auto_accept")) + obj.auto_accept = v["auto_accept"].as(); + // Oracle-metrics fields (P1-P5, display-only): forward-compatible import so a snapshot + // taken after the upgrade keeps the gauges/histogram; old snapshots (fields absent) + // stay zeroed and pm_seed_oracle_gauges() re-derives the gauges on the next block. + if (v.get_object().contains("markets_in_dispute_window")) + obj.markets_in_dispute_window = static_cast(v["markets_in_dispute_window"].as_uint64()); + if (v.get_object().contains("disputes_awaiting_response")) + obj.disputes_awaiting_response = static_cast(v["disputes_awaiting_response"].as_uint64()); + if (v.get_object().contains("disputes_awaiting_decision")) + obj.disputes_awaiting_decision = static_cast(v["disputes_awaiting_decision"].as_uint64()); + if (v.get_object().contains("resolved_late_count")) + obj.resolved_late_count = static_cast(v["resolved_late_count"].as_uint64()); + if (v.get_object().contains("resolution_time_hist")) + obj.resolution_time_hist = v["resolution_time_hist"].as>(); + }); + ++count; + } + return count; +} + +inline uint32_t import_pm_markets(graphene::chain::database& db, const fc::variants& arr) { + uint32_t count = 0; + for (const auto& v : arr) { + auto id_val = v["id"].as_int64(); + auto& mutable_idx = db.get_mutable_index(); + mutable_idx.set_next_id(pm_market_id_type(id_val)); + db.create([&](pm_market_object& obj) { + obj.creator = v["creator"].as(); + obj.oracle = v["oracle"].as(); + obj.market_type = static_cast(v["market_type"].as_uint64()); + obj.outcome_count = static_cast(v["outcome_count"].as_uint64()); + set_shared_string(obj.url, v["url"]); + obj.status = static_cast(v["status"].as_int64()); + obj.payout_status = static_cast(v["payout_status"].as_uint64()); + obj.created_time = v["created_time"].as(); + obj.betting_expiration = v["betting_expiration"].as(); + obj.result_expiration = v["result_expiration"].as(); + if (v.get_object().contains("finalized_time")) + obj.finalized_time = v["finalized_time"].as(); + obj.resolved_outcome = static_cast(v["resolved_outcome"].as_int64()); + obj.reserve_a = v["reserve_a"].as(); + obj.reserve_b = v["reserve_b"].as(); + obj.k = v["k"].as(); + obj.a_bets_sum = v["a_bets_sum"].as(); + obj.b_bets_sum = v["b_bets_sum"].as(); + obj.lmsr_b = v["lmsr_b"].as(); + obj.lmsr_subsidy = v["lmsr_subsidy"].as(); + obj.bets_sum = v["bets_sum"].as(); + obj.liquidity_sum = v["liquidity_sum"].as(); + obj.oracle_fee_percent = static_cast(v["oracle_fee_percent"].as_uint64()); + obj.creator_fee_percent = static_cast(v["creator_fee_percent"].as_uint64()); + obj.liquidity_fee_percent = static_cast(v["liquidity_fee_percent"].as_uint64()); + obj.oracle_fixed_fee = v["oracle_fixed_fee"].as(); + obj.liquidity_fee_earned = v["liquidity_fee_earned"].as(); + obj.forfeit_pool = v["forfeit_pool"].as(); + obj.time_penalty_type = static_cast(v["time_penalty_type"].as_uint64()); + obj.time_penalty_value = static_cast(v["time_penalty_value"].as_uint64()); + obj.penalty_curve_type = static_cast(v["penalty_curve_type"].as_uint64()); + obj.allow_early_resolution = v["allow_early_resolution"].as_bool(); + obj.allow_cancellation = v["allow_cancellation"].as_bool(); + obj.allow_batch = v["allow_batch"].as_bool(); + obj.allow_instant_bet = v["allow_instant_bet"].as_bool(); + obj.endogeneity_tier = static_cast(v["endogeneity_tier"].as_uint64()); + obj.current_epoch = static_cast(v["current_epoch"].as_uint64()); + obj.dispute_mode = static_cast(v["dispute_mode"].as_uint64()); + obj.dispute_resolver = v["dispute_resolver"].as(); + if (v.get_object().contains("dispute_penalty_percent")) + obj.dispute_penalty_percent = static_cast(v["dispute_penalty_percent"].as_int64()); + // #349: restore the per-market deferred-claim counter (contains-guarded — older snapshots + // predate it and correctly default to 0). Without this the cap would reset on reimport. + if (v.get_object().contains("deferred_claim_count")) + obj.deferred_claim_count = static_cast(v["deferred_claim_count"].as_uint64()); + // NB: `metadata` is no longer a consensus field (moved off-chain to the + // prediction_market_api plugin); older snapshots that still carry it are ignored. + }); + ++count; + } + return count; +} + +// NON-consensus PM metadata (10 shared_string fields → needs a dedicated handler, not the generic +// fc::from_variant path). Only called when the section is present AND the index is registered. +inline uint32_t import_pm_market_meta(graphene::chain::database& db, const fc::variants& arr) { + uint32_t count = 0; + for (const auto& v : arr) { + auto id_val = v["id"].as_int64(); + auto& mutable_idx = db.get_mutable_index(); + mutable_idx.set_next_id(pm_market_meta_id_type(id_val)); + db.create([&](pm_market_meta_object& obj) { + obj.market = v["market"].as(); + set_shared_string(obj.category, v["category"]); + set_shared_string(obj.subcategory, v["subcategory"]); + set_shared_string(obj.tags, v["tags"]); + set_shared_string(obj.banned_jurisdictions, v["banned_jurisdictions"]); + set_shared_string(obj.title, v["title"]); + set_shared_string(obj.image, v["image"]); + set_shared_string(obj.condition_id, v["condition_id"]); + set_shared_string(obj.description, v["description"]); + set_shared_string(obj.event, v["event"]); + set_shared_string(obj.event_title, v["event_title"]); + if (v.get_object().contains("child")) + obj.child = v["child"].as_bool(); + obj.expiry = v["expiry"].as(); + }); + ++count; + } + return count; +} + +inline uint32_t import_pm_outcomes(graphene::chain::database& db, const fc::variants& arr) { + uint32_t count = 0; + for (const auto& v : arr) { + auto id_val = v["id"].as_int64(); + auto& mutable_idx = db.get_mutable_index(); + mutable_idx.set_next_id(pm_outcome_id_type(id_val)); + db.create([&](pm_outcome_object& obj) { + obj.market = v["market"].as(); + obj.outcome_index = static_cast(v["outcome_index"].as_uint64()); + set_shared_string(obj.label, v["label"]); + obj.q = v["q"].as(); + obj.bets_sum = v["bets_sum"].as(); + obj.weight_sum = v["weight_sum"].as(); + obj.bets_count = static_cast(v["bets_count"].as_uint64()); + }); + ++count; + } + return count; +} + +inline uint32_t import_pm_disputes(graphene::chain::database& db, const fc::variants& arr) { + uint32_t count = 0; + for (const auto& v : arr) { + auto id_val = v["id"].as_int64(); + auto& mutable_idx = db.get_mutable_index(); + mutable_idx.set_next_id(pm_dispute_id_type(id_val)); + db.create([&](pm_dispute_object& obj) { + obj.market = v["market"].as(); + obj.disputer = v["disputer"].as(); + obj.dispute_fee = v["dispute_fee"].as(); + set_shared_string(obj.reason, v["reason"]); + obj.filed_time = v["filed_time"].as(); + obj.oracle_response_deadline = v["oracle_response_deadline"].as(); + obj.dispute_mode = static_cast(v["dispute_mode"].as_uint64()); + obj.voting_end_time = v["voting_end_time"].as(); + obj.auto_close_time = v["auto_close_time"].as(); + obj.proposed_outcome = static_cast(v["proposed_outcome"].as_int64()); + obj.status = static_cast(v["status"].as_uint64()); + }); + ++count; + } + return count; +} + } // namespace detail // ============================================================================ @@ -812,6 +1007,18 @@ class snapshot_plugin::plugin_impl { bool needs_fresh_snapshot = false; // Snapshot P2P sync config + // --snapshot-include-pm-meta: serialize the (non-consensus) PM metadata index into the + // snapshot so titles/images/tags survive DLT block-log rotation and snapshot hand-off. Default + // ON. When OFF (or when importing an older snapshot that lacks the section) the node falls back + // to rebuilding meta from pm_create_market ops (live seed + DLT backfill). + bool include_pm_meta = true; + // --snapshot-pm-legacy-residual: the PM token-supply invariant is satoshi-exact on a fresh + // chain (delta == 0), so the default is 0 = strict. A testnet that carries frozen PRE-FIX + // leverage-exit dust in its snapshot can set this to that known constant (e.g. -9079) to + // tolerate exactly that legacy value while still catching any NEW divergence. MUST stay 0 on + // mainnet / after a clean state rebuild. (B1: PR #124 review — was a hardcoded -9079 that + // made every healthy/fresh chain fail import.) + int64_t pm_supply_legacy_residual = 0; bool allow_snapshot_serving = false; bool allow_snapshot_serving_only_trusted = false; bool disable_snapshot_anti_spam = false; // Skip all anti-spam checks (for trusted networks) @@ -1138,6 +1345,35 @@ fc::mutable_variant_object snapshot_plugin::plugin_impl::serialize_state() { EXPORT_INDEX(account_recovery_request_index, account_recovery_request_object, "account_recovery_request") EXPORT_INDEX(change_recovery_account_request_index, change_recovery_account_request_object, "change_recovery_account_request") + // HF14 Prediction Market objects (empty before HF14 activates) + EXPORT_INDEX(pm_oracle_index, pm_oracle_object, "pm_oracle") + EXPORT_INDEX(pm_market_index, pm_market_object, "pm_market") + EXPORT_INDEX(pm_outcome_index, pm_outcome_object, "pm_outcome") + EXPORT_INDEX(pm_bet_index, pm_bet_object, "pm_bet") + EXPORT_INDEX(pm_liquidity_index, pm_liquidity_object, "pm_liquidity") + EXPORT_INDEX(pm_commit_index, pm_commit_object, "pm_commit") + EXPORT_INDEX(pm_dispute_index, pm_dispute_object, "pm_dispute") + EXPORT_INDEX(pm_dispute_vote_index, pm_dispute_vote_object, "pm_dispute_vote") + EXPORT_INDEX(pm_lazy_pool_index, pm_lazy_pool_object, "pm_lazy_pool") + EXPORT_INDEX(pm_lazy_deposit_index, pm_lazy_deposit_object, "pm_lazy_deposit") + EXPORT_INDEX(pm_lazy_allocation_index,pm_lazy_allocation_object,"pm_lazy_allocation") + EXPORT_INDEX(pm_leverage_position_index,pm_leverage_position_object,"pm_leverage_position") + // B2 (PR #124): pending lazy-pool withdrawals. Was omitted → on reload the request objects + // vanished while pm_lazy_pool.pending_withdrawals still carried their total, breaking the FIFO + // payout queue (consensus divergence + funds stuck owed but unpayable). Serialize like its peers. + EXPORT_INDEX(pm_lazy_withdraw_request_index,pm_lazy_withdraw_request_object,"pm_lazy_withdraw_request") + // Early-exit deferred claims (F1/#300): outcome-contingent, forward-only (no seed) → MUST be + // in the snapshot or they vanish on reload and the winners'/bucket accounting diverges. + EXPORT_INDEX(pm_deferred_claim_index, pm_deferred_claim_object, "pm_deferred_claim") + EXPORT_INDEX(pm_creator_ban_index, pm_creator_ban_object, "pm_creator_ban") + + // NON-consensus PM metadata (titles/images/tags/event). Serialized only when the operator opts + // in AND the prediction_market_api plugin is loaded (so the index actually exists). Absent from + // the snapshot => importers fall back to rebuilding meta from pm_create_market ops. + if (include_pm_meta && db.has_index()) { + EXPORT_INDEX(pm_market_meta_index, pm_market_meta_object, "pm_market_meta") + } + #undef EXPORT_INDEX return state; @@ -1553,6 +1789,34 @@ void snapshot_plugin::plugin_impl::load_snapshot(const fc::path& input_path) { const auto& cra_idx = db.get_index().indices(); while (!cra_idx.empty()) { db.remove(*cra_idx.begin()); } + // HF14 PM objects + const auto& pm_ora_idx = db.get_index().indices(); + while (!pm_ora_idx.empty()) { db.remove(*pm_ora_idx.begin()); } + const auto& pm_mkt_idx = db.get_index().indices(); + while (!pm_mkt_idx.empty()) { db.remove(*pm_mkt_idx.begin()); } + const auto& pm_out_idx = db.get_index().indices(); + while (!pm_out_idx.empty()) { db.remove(*pm_out_idx.begin()); } + const auto& pm_bet_idx = db.get_index().indices(); + while (!pm_bet_idx.empty()) { db.remove(*pm_bet_idx.begin()); } + const auto& pm_liq_idx = db.get_index().indices(); + while (!pm_liq_idx.empty()) { db.remove(*pm_liq_idx.begin()); } + const auto& pm_com_idx = db.get_index().indices(); + while (!pm_com_idx.empty()) { db.remove(*pm_com_idx.begin()); } + const auto& pm_dsp_idx = db.get_index().indices(); + while (!pm_dsp_idx.empty()) { db.remove(*pm_dsp_idx.begin()); } + const auto& pm_dvt_idx = db.get_index().indices(); + while (!pm_dvt_idx.empty()) { db.remove(*pm_dvt_idx.begin()); } + const auto& pm_lpl_idx = db.get_index().indices(); + while (!pm_lpl_idx.empty()) { db.remove(*pm_lpl_idx.begin()); } + const auto& pm_ldp_idx = db.get_index().indices(); + while (!pm_ldp_idx.empty()) { db.remove(*pm_ldp_idx.begin()); } + const auto& pm_lac_idx = db.get_index().indices(); + while (!pm_lac_idx.empty()) { db.remove(*pm_lac_idx.begin()); } + const auto& pm_lev_idx = db.get_index().indices(); + while (!pm_lev_idx.empty()) { db.remove(*pm_lev_idx.begin()); } + const auto& pm_cbn_idx = db.get_index().indices(); + while (!pm_cbn_idx.empty()) { db.remove(*pm_cbn_idx.begin()); } + ilog(CLOG_ORANGE "Existing objects cleared" CLOG_RESET); } @@ -1717,6 +1981,75 @@ void snapshot_plugin::plugin_impl::load_snapshot(const fc::path& input_path) { ilog(CLOG_ORANGE "Imported ${n} change recovery account requests" CLOG_RESET, ("n", n)); } + // HF14 Prediction Market objects (absent from pre-HF14 snapshots) + if (state.contains("pm_oracle")) { + auto n = detail::import_pm_oracles(db, state["pm_oracle"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_oracle objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_market")) { + auto n = detail::import_pm_markets(db, state["pm_market"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_market objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_outcome")) { + auto n = detail::import_pm_outcomes(db, state["pm_outcome"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_outcome objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_bet")) { + auto n = detail::import_simple_objects(db, state["pm_bet"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_bet objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_liquidity")) { + auto n = detail::import_simple_objects(db, state["pm_liquidity"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_liquidity objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_commit")) { + auto n = detail::import_simple_objects(db, state["pm_commit"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_commit objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_dispute")) { + auto n = detail::import_pm_disputes(db, state["pm_dispute"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_dispute objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_dispute_vote")) { + auto n = detail::import_simple_objects(db, state["pm_dispute_vote"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_dispute_vote objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_lazy_pool")) { + auto n = detail::import_simple_objects(db, state["pm_lazy_pool"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_lazy_pool objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_lazy_deposit")) { + auto n = detail::import_simple_objects(db, state["pm_lazy_deposit"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_lazy_deposit objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_lazy_allocation")) { + auto n = detail::import_simple_objects(db, state["pm_lazy_allocation"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_lazy_allocation objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_leverage_position")) { + auto n = detail::import_simple_objects(db, state["pm_leverage_position"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_leverage_position objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_lazy_withdraw_request")) { // B2 (PR #124): restore pending withdrawals so the FIFO queue matches pm_lazy_pool.pending_withdrawals + auto n = detail::import_simple_objects(db, state["pm_lazy_withdraw_request"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_lazy_withdraw_request objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_deferred_claim")) { // F1/#300: outcome-contingent early-exit claims (forward-only, no seed) + auto n = detail::import_simple_objects(db, state["pm_deferred_claim"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_deferred_claim objects" CLOG_RESET, ("n", n)); + } + if (state.contains("pm_creator_ban")) { + auto n = detail::import_simple_objects(db, state["pm_creator_ban"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_creator_ban objects" CLOG_RESET, ("n", n)); + } + // NON-consensus PM metadata: present only in snapshots taken with snapshot-include-pm-meta. + // Guard on has_index so nodes without the prediction_market_api plugin skip it cleanly; when + // absent the node rebuilds meta from pm_create_market ops (live seed + DLT backfill). + if (state.contains("pm_market_meta") && db.has_index()) { + auto n = detail::import_pm_market_meta(db, state["pm_market_meta"].get_array()); + ilog(CLOG_ORANGE "Imported ${n} pm_market_meta objects" CLOG_RESET, ("n", n)); + } + // Self-healing: detect validators with penalty_percent > 0 but no // corresponding expire records (orphaned penalties from incomplete // snapshot migration). Reset them so counted_votes reflects true votes. @@ -1941,15 +2274,44 @@ void snapshot_plugin::plugin_impl::load_snapshot(const fc::path& input_path) { // missing from the export that held a liquid balance drops the sum below // the independently-tracked current_supply. // - // Accounting verified satoshi-exact (delta=0) against six real mainnet - // snapshots spanning blocks 81334800–81620400 before arming — including - // the height-varying validator pending-reward term, which nets correctly - // every time. The per-component subtotals are logged unconditionally so a - // future mismatch can be attributed to a specific pool. current_supply - // already includes validator pending_stakeholder_reward (credited at - // inflation, moved into total_vesting_fund at create_vesting), so it is - // summed here. Delegation objects and reward-tracking share_type counters - // (curation/posting rewards, rshares) are NOT token pools and are excluded. + // The BASE (chain-core) pools below were verified satoshi-exact (delta=0) + // against six real mainnet snapshots spanning blocks 81334800–81620400 on + // master (PR #126) — including the height-varying validator pending-reward + // term. current_supply already includes validator pending_stakeholder_reward + // (credited at inflation, moved into total_vesting_fund at create_vesting), + // so it is summed here. Delegation objects and reward-tracking share_type + // counters (curation/posting rewards, rshares) are NOT token pools. + // + // ── Prediction-Markets (PM) branch extension ───────────────────────── + // PM is zero-sum: it never mints. A bet / liquidity / lazy-deposit / + // commit-escrow / dispute-fee / oracle-insurance / leverage-collateral + // moves TOKEN OUT of account.balance INTO a PM object field, so + // current_supply still counts it but acc_balance no longer does. Those + // PM-held pools must therefore be added, or the equality is off by exactly + // the PM float. Minimal NON-OVERLAPPING holdings (duplicates excluded): + // bets Σ pm_bet.amount (market/outcome bets_sum & CPMM + // reserves are aggregates of these) + // user LPs Σ pm_liquidity.amount WHERE provider != "" (empty provider = + // lazy pool, already in allocated_balance) + // lazy pool free_balance + allocated_balance (pm_lazy_deposit.principal is a + // per-depositor CLAIM on this = duplicate) + // commits Σ pm_commit.escrow_amount + // disputes Σ pm_dispute.dispute_fee + // forfeit Σ pm_market.forfeit_pool + // insurance Σ pm_oracle.insurance + // leverage Σ (collateral + loan) (loan is debited from free_balance at + // open, so it is held in the position, not the pool) + // + // TODO(pm): this PM accounting is NOT YET satoshi-validated against real PM + // snapshots (unlike the base pools). The status filters and double-count + // exclusions below are a best-effort static read of pm_evaluator.cpp and + // MUST be reconciled to delta==0 on real PM snapshots (all market lifecycle + // states: active / committed / revealed / disputed / settled / leveraged) + // before the TOKEN check is re-armed as a fatal FC_ASSERT. Until then this + // check is LOG-ONLY on the pm branch — it does NOT increment + // invariant_failures. The (4) SHARES check and the #125 count/referential/ + // singleton checks stay fatal (PM holds no VESTS; those pools are unchanged). + // Tracking issue: https://github.com/VIZ-Blockchain/viz-cpp-node/issues/127 { const auto& dgp = db.get_dynamic_global_properties(); int64_t acc_balance = 0, acc_reserved = 0; @@ -1976,25 +2338,114 @@ void snapshot_plugin::plugin_impl::load_snapshot(const fc::path& input_path) { const int64_t vesting_fund = dgp.total_vesting_fund.amount.value; const int64_t reward_fund = dgp.total_reward_fund.amount.value; const int64_t committee_fund = dgp.committee_fund.amount.value; - const int64_t summed_token = acc_balance + acc_reserved + const int64_t base_token = acc_balance + acc_reserved + escrow_balance + escrow_fee + invite_balance + validator_pending + vesting_fund + reward_fund + committee_fund; + + // ── PM pools (see TODO(pm) above — LOG-ONLY, unvalidated) ── + // Status codes are documented in pm_objects.hpp; held-state filters are + // marked UNVERIFIED and are the crux of the reconciliation task. + int64_t pm_bets = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + if (itr->status == 0 || itr->status == 5 || itr->status == 6) // UNVERIFIED: active/queued/revealed-pending held + pm_bets += itr->amount.value; + } + int64_t pm_user_lp = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + if (itr->status == 0 && itr->provider.size() > 0) // exclude empty-provider lazy LP (dup of allocated_balance) + pm_user_lp += itr->amount.value; + } + int64_t pm_lazy = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + pm_lazy += itr->free_balance.value + itr->allocated_balance.value; + } + int64_t pm_commit = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + if (itr->status == 0) // UNVERIFIED: escrow held only while committed + pm_commit += itr->escrow_amount.value; + } + int64_t pm_dispute = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + if (itr->status == 0) // UNVERIFIED: fee held only while dispute open + pm_dispute += itr->dispute_fee.value; + } + int64_t pm_forfeit = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + pm_forfeit += itr->forfeit_pool.value; // zeroed post-settlement + } + int64_t pm_insurance = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + pm_insurance += itr->insurance.value; + } + int64_t pm_leverage = 0; + { + const auto& idx = db.get_index().indices(); + for (auto itr = idx.begin(); itr != idx.end(); ++itr) + if (itr->status == 0) // UNVERIFIED: collateral+loan held only while active + pm_leverage += itr->collateral.value + itr->loan.value; + } + const int64_t pm_token = pm_bets + pm_user_lp + pm_lazy + pm_commit + + pm_dispute + pm_forfeit + pm_insurance + pm_leverage; + + const int64_t summed_token = base_token + pm_token; const int64_t expected_supply = dgp.current_supply.amount.value; + const int64_t pm_delta = summed_token - expected_supply; + // ── issue #127: PM token supply invariant is now ENFORCED (satoshi-exact) after the + // leverage-exit residual fix (route total_bet-cv → forfeit_pool). On a fresh chain the + // PM accounting reconciles to 0. + // + // Anchor the check to a configurable legacy residual (default 0 = strict, satoshi-exact). + // A testnet snapshot may carry frozen PRE-FIX leverage-exit dust that code cannot + // retro-heal; the operator sets --snapshot-pm-legacy-residual to that known constant to + // import that specific state while STILL catching any new divergence. Default 0 keeps + // mainnet/fresh chains strict. (B1: PR #124 — was hardcoded -9079, fatal on healthy + // chains. See journal 2026-07-15, q#199.) + const int64_t PM_SUPPLY_LEGACY_RESIDUAL = pm_supply_legacy_residual; ilog(CLOG_ORANGE "Snapshot TOKEN invariant: " - "current_supply=${cs} vs summed=${sm}, delta=${d}. Components: " - "acc_balance=${ab} acc_reserved=${ar} escrow_balance=${eb} escrow_fee=${ef} " + "current_supply=${cs} vs summed=${sm} (base=${bt} + pm=${pm}), delta=${d}. " + "Base: acc_balance=${ab} acc_reserved=${ar} escrow_balance=${eb} escrow_fee=${ef} " "invite_balance=${ib} validator_pending=${vp} vesting_fund=${vf} " - "reward_fund=${rf} committee_fund=${cf}" CLOG_RESET, - ("cs", expected_supply)("sm", summed_token)("d", summed_token - expected_supply) + "reward_fund=${rf} committee_fund=${cf}. " + "PM: bets=${pb} user_lp=${pl} lazy=${plz} commit=${pc} dispute=${pd} " + "forfeit=${pf} insurance=${pi} leverage=${plv}" CLOG_RESET, + ("cs", expected_supply)("sm", summed_token)("bt", base_token)("pm", pm_token) + ("d", summed_token - expected_supply) ("ab", acc_balance)("ar", acc_reserved)("eb", escrow_balance)("ef", escrow_fee) ("ib", invite_balance)("vp", validator_pending)("vf", vesting_fund) - ("rf", reward_fund)("cf", committee_fund)); - if (summed_token != expected_supply) { - elog(CLOG_RED "Snapshot completeness: TOKEN supply conservation violated — " - "summed pools (${sm}) != dgp.current_supply (${cs}), delta=${d}; the " - "snapshot is missing accounts that held a liquid balance" CLOG_RESET, - ("sm", summed_token)("cs", expected_supply)("d", summed_token - expected_supply)); + ("rf", reward_fund)("cf", committee_fund) + ("pb", pm_bets)("pl", pm_user_lp)("plz", pm_lazy)("pc", pm_commit) + ("pd", pm_dispute)("pf", pm_forfeit)("pi", pm_insurance)("plv", pm_leverage)); + if (pm_delta != PM_SUPPLY_LEGACY_RESIDUAL) { + // ENFORCED (issue #127): after the leverage-exit residual fix the PM accounting + // is satoshi-exact, so any drift away from the expected residual is a genuine + // token divergence → fail import and re-sync from a trusted peer. ++invariant_failures; + elog(CLOG_RED "Snapshot TOKEN supply invariant DIVERGED — summed (${sm}) != " + "current_supply (${cs}), delta=${d}, expected=${exp}, drift_from_anchor=${dr} " + "(base_delta=${bd}). PM accounting no longer reconciles; refusing corrupt state." CLOG_RESET, + ("sm", summed_token)("cs", expected_supply)("d", pm_delta) + ("exp", PM_SUPPLY_LEGACY_RESIDUAL)("dr", pm_delta - PM_SUPPLY_LEGACY_RESIDUAL) + ("bd", base_token - expected_supply)); + } else if (pm_delta != 0) { + // Matched the TEMP testnet anchor (frozen pre-fix legacy dust): tolerated, not a + // failure — but loudly remind that the anchor must be 0 for mainnet/fresh chains. + wlog(CLOG_ORANGE "Snapshot TOKEN invariant at legacy anchor: delta=${d} == " + "PM_SUPPLY_LEGACY_RESIDUAL (frozen pre-fix testnet dust). REMOVE the anchor " + "(set 0) before mainnet." CLOG_RESET, ("d", pm_delta)); } } @@ -4121,6 +4572,15 @@ void snapshot_plugin::set_program_options( "Directory for auto-generated snapshot files (default: /snapshots)") ("snapshot-max-age-days", bpo::value()->default_value(90), "Delete snapshots older than N days after creating a new one (0 = disabled)") + ("snapshot-include-pm-meta", bpo::value()->default_value(true), + "Serialize the (non-consensus) prediction-market metadata index (titles/images/tags/event) " + "into snapshots so it survives DLT block-log rotation and snapshot hand-off. Disable to keep " + "snapshots smaller; importers then rebuild meta from pm_create_market ops (live seed + DLT backfill).") + ("snapshot-pm-legacy-residual", bpo::value()->default_value(0), + "Tolerated PM token-supply invariant residual (raw) when importing a snapshot. Default 0 = " + "strict (satoshi-exact), required for mainnet/fresh chains. Set to a testnet snapshot's known " + "frozen pre-fix dust (e.g. -9079) only to import that specific legacy state; any OTHER drift " + "still fails import.") ("allow-snapshot-serving", bpo::value()->default_value(false), "Enable serving snapshots over TCP to other nodes") ("allow-snapshot-serving-only-trusted", bpo::value()->default_value(false), @@ -4182,6 +4642,17 @@ void snapshot_plugin::plugin_initialize(const bpo::variables_map& options) { ilog("Snapshot directory: ${d}", ("d", my->snapshot_dir)); my->snapshot_auto_latest = options.at("snapshot-auto-latest").as(); + + if (options.count("snapshot-include-pm-meta")) { + my->include_pm_meta = options.at("snapshot-include-pm-meta").as(); + ilog("Snapshot PM metadata section: ${e}", ("e", my->include_pm_meta ? "ENABLED" : "disabled")); + } + if (options.count("snapshot-pm-legacy-residual")) { + my->pm_supply_legacy_residual = options.at("snapshot-pm-legacy-residual").as(); + if (my->pm_supply_legacy_residual != 0) + wlog("Snapshot PM supply invariant anchored to legacy residual ${r} (non-strict); MUST be 0 on mainnet/fresh chains", + ("r", my->pm_supply_legacy_residual)); + } if (my->snapshot_auto_latest) { if (my->snapshot_path.empty()) { // Auto-discover latest snapshot in snapshot-dir diff --git a/plugins/validator/CMakeLists.txt b/plugins/validator/CMakeLists.txt index 17ffa2886b..a4cd4835ad 100644 --- a/plugins/validator/CMakeLists.txt +++ b/plugins/validator/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES validator.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/plugins/validator/validator.cpp b/plugins/validator/validator.cpp index 2be710286c..d2c087eab4 100644 --- a/plugins/validator/validator.cpp +++ b/plugins/validator/validator.cpp @@ -165,6 +165,15 @@ namespace graphene { bool _minority_fork_recovering = false; fc::time_point _minority_fork_recovery_start; + // Operator override (testnet / single-operator fork): when set via + // 'disable-minority-fork-detection', both the standard and the DLT + // minority-fork checks are skipped entirely. On a fork where the + // operator controls ALL validators, "last 21 blocks all ours" is the + // normal healthy state, not an isolation symptom — the detector would + // otherwise loop forever resetting to LIB. Unlike enable-stale-production, + // this flag is never auto-cleared by the healthy-participation path. + bool _disable_minority_fork_detection = false; + // P18: slot=0 stall detection — tracks consecutive // not_time_yet returns to detect NTP/clock issues. uint32_t _slot_zero_streak = 0; @@ -234,6 +243,7 @@ namespace graphene { command_line_options.add_options() ("enable-stale-production", bpo::value()->implicit_value(true) , "Enable block production, even if the chain is stale.") + ("disable-minority-fork-detection", bpo::value()->implicit_value(true) , "Disable minority-fork detection (testnet / single-operator fork where this node controls all validators). Never auto-cleared by healthy participation.") ("required-participation", bpo::value()->default_value(33 * CHAIN_1_PERCENT), "Percent of validators (0-99) that must be participating in order to produce blocks") ("validator,v", bpo::value>()->composing()->multitoken(), ("name of validator controlled by this node (e.g. " + validator_id_example + " )").c_str()) ("witness,w", bpo::value>()->composing()->multitoken(), "[DEPRECATED] Use --validator. Name of validator controlled by this node (legacy 'witness' option, kept for config.ini backward compatibility).") @@ -294,6 +304,15 @@ namespace graphene { } } + if(options.count("disable-minority-fork-detection")){ + if (options["disable-minority-fork-detection"].as()) { + pimpl->_disable_minority_fork_detection = true; + wlog("Minority-fork detection DISABLED by operator config. " + "Use only on a testnet / single-operator fork where this node " + "controls all validators."); + } + } + if(options.count("required-participation")){ pimpl->_required_validator_participation = options["required-participation"].as(); } @@ -1590,7 +1609,7 @@ namespace graphene { // continue producing (bootstrap / testnet / recovery scenario). // With enable-stale-production=false (default): we're on the wrong fork, // pop back to LIB and resync from the P2P network. - if (!emergency_active) { + if (!emergency_active && !_disable_minority_fork_detection) { auto fork_head = db.get_fork_db().head(); if (fork_head) { bool all_ours = true; @@ -1653,7 +1672,7 @@ namespace graphene { // "ours" is expected — other nodes sync from us. Skip minority fork // detection entirely to avoid false positives and the production // deadlock that would otherwise occur. - if (emergency_active && db._dlt_mode) { + if (emergency_active && db._dlt_mode && !_disable_minority_fork_detection) { // If committee is in the schedule and we have its key, WE are the // emergency master. All blocks being "ours" is expected -- other // nodes sync from us. Skip minority fork detection to prevent diff --git a/plugins/validator_api/CMakeLists.txt b/plugins/validator_api/CMakeLists.txt index 5bfbc7b448..f0504bc9f5 100644 --- a/plugins/validator_api/CMakeLists.txt +++ b/plugins/validator_api/CMakeLists.txt @@ -9,17 +9,10 @@ list(APPEND CURRENT_TARGET_SOURCES plugin.cpp ) -if (BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/plugins/validator_guard/CMakeLists.txt b/plugins/validator_guard/CMakeLists.txt index c7b5ccf926..cb1da481b6 100644 --- a/plugins/validator_guard/CMakeLists.txt +++ b/plugins/validator_guard/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES validator_guard.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) set_property(TARGET graphene_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) diff --git a/plugins/webserver/CMakeLists.txt b/plugins/webserver/CMakeLists.txt index 181adf51e5..47c4d7ce52 100644 --- a/plugins/webserver/CMakeLists.txt +++ b/plugins/webserver/CMakeLists.txt @@ -8,17 +8,10 @@ list(APPEND CURRENT_TARGET_SOURCES webserver_plugin.cpp ) -if(BUILD_SHARED_LIBRARIES) - add_library(graphene_${CURRENT_TARGET} SHARED +add_library(graphene_${CURRENT_TARGET} ${VIZ_LIBRARY_TYPE} ${CURRENT_TARGET_HEADERS} ${CURRENT_TARGET_SOURCES} ) -else() - add_library(graphene_${CURRENT_TARGET} STATIC - ${CURRENT_TARGET_HEADERS} - ${CURRENT_TARGET_SOURCES} - ) -endif() add_library(graphene::${CURRENT_TARGET} ALIAS graphene_${CURRENT_TARGET}) diff --git a/programs/build_helpers/CMakeLists.txt b/programs/build_helpers/CMakeLists.txt index 8af954a753..4a58078fde 100644 --- a/programs/build_helpers/CMakeLists.txt +++ b/programs/build_helpers/CMakeLists.txt @@ -1,7 +1,13 @@ -add_executable(cat-parts cat-parts.cpp) -if(UNIX AND NOT APPLE) - set(rt_library rt) -endif() +# cat-parts concatenates hardfork.d/*.hf into hardfork.hpp. It is only invoked +# on MSVC/MinGW (see libraries/chain/CMakeLists.txt); every other platform uses +# cat_parts.py instead. Only build the binary where it is actually used so Linux +# and macOS builds don't compile and link a tool they never run. +if(MSVC OR MINGW) + add_executable(cat-parts cat-parts.cpp) + if(UNIX AND NOT APPLE) + set(rt_library rt) + endif() -# we only actually need Boost, but link against FC for now so we don't duplicate it. -target_link_libraries(cat-parts PRIVATE fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS}) + # we only actually need Boost, but link against FC for now so we don't duplicate it. + target_link_libraries(cat-parts PRIVATE fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS}) +endif() diff --git a/programs/vizd/CMakeLists.txt b/programs/vizd/CMakeLists.txt index d6767dd4fd..a867ded985 100644 --- a/programs/vizd/CMakeLists.txt +++ b/programs/vizd/CMakeLists.txt @@ -33,6 +33,7 @@ target_link_libraries( graphene::committee_api graphene::invite_api graphene::paid_subscription_api + graphene::prediction_market_api graphene::custom_protocol_api graphene::snapshot graphene::validator_guard diff --git a/programs/vizd/main.cpp b/programs/vizd/main.cpp index 7187bbf77c..a7d2772065 100644 --- a/programs/vizd/main.cpp +++ b/programs/vizd/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,7 @@ namespace graphene { appbase::app().register_plugin(); appbase::app().register_plugin(); appbase::app().register_plugin(); + appbase::app().register_plugin(); appbase::app().register_plugin(); appbase::app().register_plugin(); appbase::app().register_plugin(); diff --git a/tests/consensus_sim/CMakeLists.txt b/tests/consensus_sim/CMakeLists.txt index 78b9f92e92..4d429bb0b4 100644 --- a/tests/consensus_sim/CMakeLists.txt +++ b/tests/consensus_sim/CMakeLists.txt @@ -29,18 +29,24 @@ set_target_properties(consensus_sim_harness PROPERTIES CXX_STANDARD_REQUIRED ON ) -# Sanitizers on by default for the harness -target_compile_options(consensus_sim_harness PUBLIC - -fsanitize=address - -fsanitize=undefined - -fno-omit-frame-pointer - -O1 - -g -) -target_link_options(consensus_sim_harness PUBLIC - -fsanitize=address - -fsanitize=undefined -) +# Sanitizers on by default for the harness (GCC/Clang only — MSVC rejects these flags). +if(NOT MSVC) + target_compile_options(consensus_sim_harness PUBLIC + -fsanitize=address + -fsanitize=undefined + # fc's static_variant / boost flat_set storage is intentionally under-aligned (benign on + # x86); the alignment check floods the log with non-fatal noise, so disable just that one. + -fno-sanitize=alignment + -fno-omit-frame-pointer + -O1 + -g + ) + target_link_options(consensus_sim_harness PUBLIC + -fsanitize=address + -fsanitize=undefined + -fno-sanitize=alignment + ) +endif() if(WITH_COVERAGE) target_compile_options(consensus_sim_harness PUBLIC --coverage) @@ -56,6 +62,7 @@ set(SCENARIO_SOURCES scenarios/test_smoke_no_faults.cpp scenarios/test_determinism_replay.cpp scenarios/test_equivocation.cpp + scenarios/test_pm_lifecycle.cpp scenarios/test_wedge_predicate.cpp scenarios/test_routable_endpoint.cpp ) diff --git a/tests/consensus_sim/harness/simulated_node.hpp b/tests/consensus_sim/harness/simulated_node.hpp index fe39402d52..5d256b211e 100644 --- a/tests/consensus_sim/harness/simulated_node.hpp +++ b/tests/consensus_sim/harness/simulated_node.hpp @@ -81,6 +81,11 @@ class simulated_node { graphene::protocol::chain_id_type chain_id() const; + /// Direct access to the wrapped chain state for assertions (read PM objects / + /// account balances). Tests must not mutate it outside block application. + graphene::chain::database& db() noexcept { return *db_; } + const graphene::chain::database& db() const noexcept { return *db_; } + const std::string& label() const noexcept { return label_; } private: diff --git a/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp b/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp new file mode 100644 index 0000000000..d3011f3ac7 --- /dev/null +++ b/tests/consensus_sim/scenarios/test_pm_lifecycle.cpp @@ -0,0 +1,5672 @@ +// HF14 Prediction-Market integration tests (single node, real chain state, real evaluators). +// +// Brings up a live `database`, registers the initiator ("viz") as a real validator with +// stake + self-vote (the genesis committee witness is excluded from normal selection, so a +// real validator is needed to pass HF13's stake-weighted scheduling), drives to HF14, then +// exercises the on-chain PM flow: oracle registration and a full binary market lifecycle +// (create → bet → resolve → auto-payout) with parimutuel + zero-sum assertions. + +#include +#include "simulated_node.hpp" +#include "genesis_factory.hpp" +#include "virtual_clock.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace consensus_sim; +using namespace graphene::chain; +using namespace graphene::protocol; + +namespace { + account_name_type next_validator(simulated_node& n) { return n.db().get_scheduled_validator(1); } + + fc::ecc::private_key key_for(const genesis_params& gp, const account_name_type& v) { + return (v == gp.initiator_name) ? gp.initiator_key : gp.genesis_witness_key; + } + + void produce(simulated_node& n, const genesis_params& gp, fc::time_point_sec& when) { + when += fc::seconds(CHAIN_BLOCK_INTERVAL); + const auto v = next_validator(n); + n.produce_block(v, key_for(gp, v), when); + } + + signed_transaction sign_ops(const std::vector& ops, + const fc::ecc::private_key& key, const simulated_node& node) { + signed_transaction tx; + tx.set_reference_block(node.head_block_id()); + tx.set_expiration(node.head_block_time() + fc::seconds(60)); + for (const auto& op : ops) tx.operations.emplace_back(op); + tx.sign(key, node.chain_id()); + return tx; + } + + authority single_key_auth(const public_key_type& k) { + authority a; a.weight_threshold = 1; a.key_auths[k] = 1; return a; + } + fc::ecc::private_key derive_key(const std::string& name) { + return fc::ecc::private_key::regenerate(fc::sha256::hash(name)); + } + + // Register "viz" as a staked, self-voted validator, then advance until HF14 activates. + // Returns true if HF14 became active. On a non-testnet build (HF14 far future) or other + // genesis limitation it returns false and the caller skips the on-chain assertions. + bool bring_to_hf14(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when) { + try { + produce(node, gp, when); // block 1 (committee gap-filler) + + const share_type bal = node.db().get_account(gp.initiator_name).balance.amount; + transfer_to_vesting_operation tv; + tv.from = gp.initiator_name; tv.to = gp.initiator_name; + tv.amount = asset(share_type(bal.value / 2), TOKEN_SYMBOL); + validator_update_operation vu; + vu.owner = gp.initiator_name; vu.url = "viz"; + vu.block_signing_key = gp.initiator_key.get_public_key(); + account_validator_vote_operation vv; + vv.account = gp.initiator_name; vv.validator = gp.initiator_name; vv.approve = true; + node.push_pending_transaction(sign_ops({tv, vu, vv}, gp.initiator_key, node)); + + for (int i = 0; i < 300 && !node.db().has_hardfork(CHAIN_HARDFORK_14); ++i) + produce(node, gp, when); + } catch (const std::exception& e) { + BOOST_TEST_MESSAGE(std::string("could not reach HF14 (harness/genesis limit): ") + e.what()); + } + return node.db().has_hardfork(CHAIN_HARDFORK_14); + } + + void create_and_fund(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when, + const std::string& name, const fc::ecc::private_key& key, share_type liquid) { + const auto pub = key.get_public_key(); + const auto& mp = node.db().get_validator_schedule_object().median_props; + account_create_operation ac; + ac.fee = mp.account_creation_fee; + ac.delegation = asset(0, SHARES_SYMBOL); + ac.creator = gp.initiator_name; + ac.new_account_name = name; + ac.master = single_key_auth(pub); + ac.active = single_key_auth(pub); + ac.regular = single_key_auth(pub); + ac.memo_key = pub; + transfer_operation tr; + tr.from = gp.initiator_name; tr.to = name; tr.amount = asset(liquid, TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({ac, tr}, gp.initiator_key, node)); + produce(node, gp, when); + } + + // Long-market odds-drift simulation, shared by the binary/skewed/multi cases. + // Places the given (outcome, stake) bets from the initiator, prints how far the + // commission-baked BOARD coefficient shown at bet time drifts from the FINAL one, then + // resolves outcome 0 and asserts the market settles. + // + // IMPORTANT: the board coefficient is PARIMUTUEL — odds_i = 1 + Σ(other stake pools) x + // (1-commission) / own_stake_pool. It is built from a_bets_sum/b_bets_sum (the staked + // BETS), which CPMM/LMSR/lazy-pool liquidity never touches (liquidity only feeds the + // curve reserves → bet WEIGHT). So deeper liquidity cannot pin these odds; only the order + // flow can. `binary` selects the bet shape (side vs outcome_index). + void run_drift_market(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when, + bool binary, int outcome_count, int feeBp, + const std::vector& outc, const std::vector& stk, + const pm_market_id_type& market_id, const char* label) { + const double nf = 1.0 - feeBp / 10000.0; + const int N = (int)outc.size(); + + std::vector batch; + for (int i = 0; i < N; ++i) { + pm_place_bet_operation pb; + pb.account = gp.initiator_name; pb.market_id = market_id._id; + if (binary) { pb.side = (int8_t)outc[i]; pb.outcome_index = -1; } + else { pb.side = -1; pb.outcome_index = outc[i]; } + pb.amount = asset(share_type(stk[i]), TOKEN_SYMBOL); pb.mode = 0; + batch.push_back(pb); + if (batch.size() == 10) { node.push_pending_transaction(sign_ops(batch, gp.initiator_key, node)); produce(node, gp, when); batch.clear(); } + } + if (!batch.empty()) { node.push_pending_transaction(sign_ops(batch, gp.initiator_key, node)); produce(node, gp, when); } + + std::vector bo; std::vector bs, bw; + { + const auto& bidx = node.db().get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(market_id, pm_bet_id_type())); + it != bidx.end() && it->market == market_id; ++it) { + bo.push_back(binary ? (int)it->side : (int)it->outcome_index); + bs.push_back(it->amount.value); bw.push_back(it->weight.value); + } + } + const int M = (int)bo.size(); + BOOST_REQUIRE_EQUAL(M, N); + + std::vector pool(outcome_count, 0); int64_t totPool = 0; + for (int i = 0; i < M; ++i) { pool[bo[i]] += bs[i]; totPool += bs[i]; } + + std::vector rpool(outcome_count, 0); int64_t rtot = 0; + std::vector shown(M), finalc(M), absdrift(M); + for (int i = 0; i < M; ++i) { + int o = bo[i]; + rpool[o] += bs[i]; rtot += bs[i]; + double ownR = (double)rpool[o], oppR = (double)(rtot - rpool[o]); + double ownF = (double)pool[o], oppF = (double)(totPool - pool[o]); + shown[i] = 1.0 + oppR * nf / ownR; + finalc[i] = 1.0 + oppF * nf / ownF; + absdrift[i] = std::fabs((finalc[i] - shown[i]) / shown[i]); + } + + char buf[256]; + BOOST_TEST_MESSAGE(""); + std::snprintf(buf, sizeof(buf), "=== %s : %d bets, %d outcomes, commission %d%% ===", label, M, outcome_count, feeBp/100); + BOOST_TEST_MESSAGE(buf); + { + std::string ln = "final pools (VIZ): "; + for (int o = 0; o < outcome_count; ++o) { std::snprintf(buf,sizeof(buf), "%s%lld", o?" / ":"", (long long)(pool[o]/1000)); ln += buf; } + ln += " -> final board odds: "; + for (int o = 0; o < outcome_count; ++o) { + double od = 1.0 + (double)(totPool - pool[o]) * nf / pool[o]; + std::snprintf(buf,sizeof(buf), "%sx%.2f", o?" / ":"", od); ln += buf; + } + BOOST_TEST_MESSAGE(ln); + } + BOOST_TEST_MESSAGE("sample (first 3 / last 3) — i: outcome stake shown -> final (|drift|):"); + for (int k = 0; k < 6 && k < M; ++k) { + int i = (k < 3) ? k : M - 6 + k; + std::snprintf(buf, sizeof(buf), " #%4d: out=%d stake=%lld x%.3f -> x%.3f (%.1f%%)", + i, bo[i], (long long)(bs[i]/1000), shown[i], finalc[i], 100.0*absdrift[i]); + BOOST_TEST_MESSAGE(buf); + } + BOOST_TEST_MESSAGE("decile avg|drift|%"); + for (int d = 0; d < 10; ++d) { + int lo = d*(M/10), hi = lo + (M/10); double sd = 0; + for (int i = lo; i < hi; ++i) sd += absdrift[i]; + std::snprintf(buf, sizeof(buf), " %2d %6.2f%%", d+1, 100.0*sd/(hi-lo)); + BOOST_TEST_MESSAGE(buf); + } + std::vector srt = absdrift; std::sort(srt.begin(), srt.end()); + double mean = 0; for (double x : absdrift) mean += x; mean /= M; + std::snprintf(buf, sizeof(buf), "drift |d|: mean=%.2f%% median=%.2f%% p90=%.2f%% max=%.2f%%", + 100*mean, 100*srt[M/2], 100*srt[(M*9)/10], 100*srt[M-1]); + BOOST_TEST_MESSAGE(buf); + + // Resolve outcome 0 (early — needs betting closed) and confirm settlement. + const auto& mkt = node.db().get(market_id); + for (int i = 0; i < 600 && node.head_block_time() < mkt.betting_expiration; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; rm.oracle = gp.initiator_name; rm.market_id = market_id._id; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 300 && node.db().get(market_id).payout_status != 3; ++i) produce(node, gp, when); + BOOST_CHECK_EQUAL(node.db().get(market_id).payout_status, 3); + } +} + + // ── GC helpers (terminal-market garbage collection) ────────────────────────── + // True once the market AND every object in its cluster are gone from state. + bool market_cluster_absent(const database& db, const pm_market_id_type& mid) { + if (db.find(mid) != nullptr) return false; + auto range_empty = [&](const auto& idx) { + auto it = idx.lower_bound(boost::make_tuple(mid)); + return it == idx.end() || it->market != mid; + }; + if (!range_empty(db.get_index().indices().get())) return false; + if (!range_empty(db.get_index().indices().get())) return false; + if (!range_empty(db.get_index().indices().get())) return false; + if (!range_empty(db.get_index().indices().get())) return false; + if (!range_empty(db.get_index().indices().get())) return false; + if (!range_empty(db.get_index().indices().get())) return false; + { const auto& d = db.get_index().indices().get(); if (d.find(mid) != d.end()) return false; } + { const auto& a = db.get_index().indices().get(); if (a.find(mid) != a.end()) return false; } + return true; + } + + // A terminal market must carry a finalized_time and still be present, then be fully GC'd once + // pm_closed_market_retention_sec elapses. GC tests set that retention small (30s below). + void assert_gc_after_retention(simulated_node& node, const genesis_params& gp, + fc::time_point_sec& when, const pm_market_id_type& mid) { + BOOST_REQUIRE(node.db().find(mid) != nullptr); + BOOST_CHECK_MESSAGE(node.db().get(mid).finalized_time != fc::time_point_sec(), + "finalized_time must be stamped when the market becomes terminal"); + BOOST_REQUIRE(!market_cluster_absent(node.db(), mid)); // cluster present pre-retention + + for (int i = 0; i < 400 && node.db().find(mid) != nullptr; ++i) + produce(node, gp, when); + + BOOST_CHECK(node.db().find(mid) == nullptr); // market reclaimed + BOOST_CHECK(market_cluster_absent(node.db(), mid)); // whole cluster reclaimed + } + + // Publish PM props with test-short timers (grace + GC retention = 30s) so the crons fire fast. + void publish_fast_pm_props(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when, + chain_properties_pm props) { + props.pm_dispute_grace_sec = 30; + props.pm_closed_market_retention_sec = 30; // GC 30s after death (5 d in production) + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && + node.db().get_validator_schedule_object().median_props.pm_closed_market_retention_sec != 30u; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get_validator_schedule_object().median_props.pm_closed_market_retention_sec, 30u); + } + + void register_self_oracle(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when) { + const auto& mp = node.db().get_validator_schedule_object().median_props; + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + } + +BOOST_AUTO_TEST_SUITE(pm_lifecycle_suite) + +BOOST_AUTO_TEST_CASE(oracle_register_after_hf14) { + auto gp = make_genesis_params(0x1234u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-oracle", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; PM math covered by tests/pm/*. Skipping."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const asset insurance = mp.pm_min_oracle_insurance; + + pm_oracle_register_operation op; + op.owner = gp.initiator_name; + op.insurance = insurance; + op.fixed_fee = asset(0, TOKEN_SYMBOL); + op.rules_url = ""; + const asset before = node.db().get_account(gp.initiator_name).balance; + node.push_pending_transaction(sign_ops({op}, gp.initiator_key, node)); + produce(node, gp, when); + + const auto& oidx = node.db().get_index().indices().get(); + auto it = oidx.find(gp.initiator_name); + BOOST_REQUIRE(it != oidx.end()); + BOOST_CHECK_EQUAL(it->insurance.value, insurance.amount.value); + const asset after = node.db().get_account(gp.initiator_name).balance; + BOOST_CHECK_EQUAL((before - after).amount.value, + insurance.amount.value + mp.pm_oracle_registration_fee.amount.value); +} + +BOOST_AUTO_TEST_CASE(binary_market_lifecycle) { + auto gp = make_genesis_params(0x2222u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-market", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping market lifecycle."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; // a convenient nonzero scale + + // The validator publishes PM props with a short dispute grace so the auto-payout cron + // settles within a handful of blocks (default grace is 12h — impractical for a test). + { + chain_properties_pm props; // inline consensus defaults + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + // viz registers as oracle and creates a self-oracle binary market (auto-active). + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + // Fund two bettors. + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; // resolve after betting ends, before result_expiration + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + + // The market is the only one created → id 0. + const pm_market_id_type market_id(0); + BOOST_REQUIRE(node.db().find(market_id) != nullptr); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, 1); // active (self-oracle) + + // alice bets side A, bob bets side B. + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + pm_place_bet_operation bb; + bb.account = "bob"; bb.market_id = 0; bb.side = 1; bb.outcome_index = -1; + bb.amount = asset(share_type(unit), TOKEN_SYMBOL); bb.mode = 0; + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + const asset alice_after_bet = node.db().get_account("alice").balance; + const asset bob_after_bet = node.db().get_account("bob").balance; + + // Advance past betting_expiration (30s) but before result_expiration (90s), then + // early-resolve to A (side 0). Resolving after result_expiration would let the + // oracle-missed cron close the market first. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; rm.decision_url = ""; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + // Advance well past result_expiration + grace so the auto-payout cron settles the market. + const uint32_t grace = (uint32_t)mp.pm_dispute_grace_sec; + const int blocks = (int)((grace + 240) / CHAIN_BLOCK_INTERVAL) + 80; + for (int i = 0; i < blocks && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + // Winner (alice) paid > stake; loser (bob) got nothing back; zero-sum across bettors. + const asset alice_final = node.db().get_account("alice").balance; + const asset bob_final = node.db().get_account("bob").balance; + BOOST_TEST_MESSAGE("alice paid: " << (alice_final - alice_after_bet).amount.value + << " bob: " << (bob_final - bob_after_bet).amount.value); + BOOST_CHECK_GT((alice_final - alice_after_bet).amount.value, 0); // winner credited + BOOST_CHECK_EQUAL((bob_final - bob_after_bet).amount.value, 0); // loser unpaid + // Binary self-oracle, no fees: winner receives the whole losers' pool + own stake back. + BOOST_CHECK_EQUAL((alice_final - alice_after_bet).amount.value, unit * 2); +} + +BOOST_AUTO_TEST_CASE(committee_dispute_flips_outcome) { + auto gp = make_genesis_params(0x3333u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-dispute", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping dispute scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // Short PM timers (defaults are 12h/3d/14d) + small dispute fee so a bettor can file. + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 15; + props.pm_dispute_auto_close_sec = 600; // > voting_end so finalize wins the race + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_vote_period_sec, 15u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; // committee + cm.dispute_penalty_percent = 500; // 5% insurance slash if the oracle is overridden + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + // alice → A, bob → B. + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Oracle resolves to A (early), then bob disputes claiming B is correct. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).resolved_outcome, 0); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 2); // disputed + + // The validator (a stakeholder) first votes to UPHOLD the oracle (toward A)... + pm_dispute_vote_operation dv; + dv.voter = gp.initiator_name; dv.market_id = 0; dv.vote_outcome = -1; dv.vote_percent = 10000; + node.push_pending_transaction(sign_ops({dv}, gp.initiator_key, node)); + produce(node, gp, when); + + // ...then REVISES the ballot before voting closes, as new arguments surface — a committee + // dispute is an open public hearing (no commit-reveal), so a repeat vote overwrites the + // previous one (latest ballot wins, no "Already voted" rejection). The flip to B below only + // succeeds if this revision is accepted. + dv.vote_outcome = 1; + node.push_pending_transaction(sign_ops({dv}, gp.initiator_key, node)); + produce(node, gp, when); + + const asset bob_pre = node.db().get_account("bob").balance; + + // Advance past voting_end → finalize flips to B → auto-payout settles. + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + BOOST_TEST_MESSAGE("flipped outcome=" << (int)m.resolved_outcome + << " payout_status=" << (int)m.payout_status + << " bob delta=" << (node.db().get_account("bob").balance - bob_pre).amount.value); + BOOST_CHECK_EQUAL(m.resolved_outcome, 1); // overturned to B + BOOST_CHECK_EQUAL(m.payout_status, 3); // settled + // bob bet B and the dispute overturned to B → bob is paid (own stake + alice's pool + + // the slashed oracle insurance forfeit). alice (A) gets nothing. + BOOST_CHECK_GT((node.db().get_account("bob").balance - bob_pre).amount.value, 0); +} + +// Committee dispute — lazy-pool stake carries governance weight. A non-validator voter "carol" has +// ~0 vesting SHARES but a large lazy-pool deposit. Her deposit, converted to vesting-shares (pool NAV +// × her shares / total shares, at get_vesting_share_price), must alone clear the participation quorum +// and overturn the oracle. Without the lazy-pool credit her weight is ~0 → the dispute would uphold. +BOOST_AUTO_TEST_CASE(committee_dispute_lazy_pool_voting_weight) { + auto gp = make_genesis_params(0x3344u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lazyvote", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping lazy-pool voting weight."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 15; + props.pm_dispute_auto_close_sec = 600; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_vote_period_sec != 15; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_vote_period_sec, 15u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + // carol: no vesting SHARES; her only governance weight is a lazy-pool deposit. Size it to + // ~tvf/3 so that even after it inflates the quorum denominator she clears the 10% bar. + const int64_t tvf = node.db().get_dynamic_global_properties().total_vesting_fund.amount.value; + const int64_t deposit_amt = tvf / 3; + auto carol_key = derive_key("carol"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "carol", carol_key, share_type(deposit_amt + unit)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_lazy_deposit_operation dep; + dep.account = "carol"; dep.amount = asset(share_type(deposit_amt), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, carol_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(pm_lazy_pool_id_type(0)).free_balance.value, deposit_amt); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; cm.dispute_penalty_percent = 500; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation bb; + bb.account = "bob"; bb.market_id = 0; bb.side = 1; bb.outcome_index = -1; + bb.amount = asset(share_type(unit), TOKEN_SYMBOL); bb.mode = 0; + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; // oracle says A + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 2); + + // Only carol votes (the validator abstains). Her weight is purely the lazy-pool stake. + pm_dispute_vote_operation dv; + dv.voter = "carol"; dv.market_id = 0; dv.vote_outcome = 1; dv.vote_percent = 10000; + node.push_pending_transaction(sign_ops({dv}, carol_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + BOOST_TEST_MESSAGE("lazy-vote: resolved=" << (int)m.resolved_outcome + << " payout_status=" << (int)m.payout_status << " deposit=" << deposit_amt); + // Overturned to B ⇒ carol's lazy-pool stake alone passed the quorum and won the tally. + BOOST_CHECK_EQUAL(m.resolved_outcome, 1); + BOOST_CHECK_EQUAL(m.payout_status, 3); +} + +// #2 — External oracle rejects a pending market. The creator's seed liquidity is returned +// (exactly once — zero-sum: PM must never emit tokens) and the market closes with status=-1. +// The non-refundable creation fee stays with the DAO, so the creator's net cost is just the fee. +BOOST_AUTO_TEST_CASE(oracle_reject_refunds_liquidity_once) { + auto gp = make_genesis_params(0x6666u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-reject", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping oracle-reject scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t fee = mp.pm_market_creation_fee.amount.value; + + // viz registers as the (external) oracle; carol is a separate creator. + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto carol_key = derive_key("carol"); + create_and_fund(node, gp, when, "carol", carol_key, share_type(unit * 4 + fee + unit)); + + const asset carol_before = node.db().get_account("carol").balance; + + pm_create_market_operation cm; + cm.creator = "carol"; cm.oracle = gp.initiator_name; // external oracle → pending + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(60); + cm.result_expiration = node.head_block_time() + fc::seconds(120); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, carol_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, 0); // pending acceptance + + const asset carol_after_create = node.db().get_account("carol").balance; + // create cost = fee + seed liquidity. + BOOST_CHECK_EQUAL((carol_before - carol_after_create).amount.value, fee + unit * 4); + + // Oracle rejects. + pm_oracle_accept_market_operation rej; + rej.oracle = gp.initiator_name; rej.market_id = 0; rej.accept = false; + node.push_pending_transaction(sign_ops({rej}, gp.initiator_key, node)); + produce(node, gp, when); + + const asset carol_after_reject = node.db().get_account("carol").balance; + const int64_t refund = (carol_after_reject - carol_after_create).amount.value; + BOOST_TEST_MESSAGE("reject: seed refund=" << refund << " (expect " << (unit * 4) + << ", net cost vs before=" << (carol_before - carol_after_reject).amount.value << ")"); + BOOST_CHECK_EQUAL(node.db().get(market_id).status, -1); + // Seed returned exactly once — no token emission. + BOOST_CHECK_EQUAL(refund, unit * 4); + // Net creator cost == the creation fee only. + BOOST_CHECK_EQUAL((carol_before - carol_after_reject).amount.value, fee); +} + +// #8 — Bet cancellation (requires allow_cancellation). C bets A, D bets B (shifting the +// CPMM reserves), then C cancels: the canceller is made whole (full stake back), the bet +// flips to status 1, and the market's reserves/bets_sum are reversed by exactly the stake. +BOOST_AUTO_TEST_CASE(bet_cancellation_reverses_cpmm) { + auto gp = make_genesis_params(0x5555u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-cancel", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping cancellation scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(60); + cm.result_expiration = node.head_block_time() + fc::seconds(120); + cm.allow_cancellation = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + // alice → A, bob → B (bob's bet shifts the pool that alice cancels against). + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Locate alice's active bet id. + uint64_t alice_bet_id = 0; bool found = false; + for (const auto& b : node.db().get_index().indices()) { + if (b.market == market_id && b.account == "alice" && b.status == 0) { + alice_bet_id = b.id._id; found = true; break; + } + } + BOOST_REQUIRE(found); + + const asset alice_pre_cancel = node.db().get_account("alice").balance; + const share_type bets_sum_pre = node.db().get(market_id).bets_sum; + + pm_cancel_bet_operation cancel; + cancel.account = "alice"; cancel.bet_id = alice_bet_id; cancel.min_return = 0; + node.push_pending_transaction(sign_ops({cancel}, alice_key, node)); + produce(node, gp, when); + + // Canceller refunded full stake; bet now cancelled (status 1); market bets_sum reduced by stake. + const int64_t refund = (node.db().get_account("alice").balance - alice_pre_cancel).amount.value; + const auto& bet = node.db().get(pm_bet_id_type(alice_bet_id)); + const share_type bets_sum_post = node.db().get(market_id).bets_sum; + BOOST_TEST_MESSAGE("cancel: refund=" << refund + << " bet.status=" << (int)bet.status + << " bets_sum " << bets_sum_pre.value << " -> " << bets_sum_post.value); + BOOST_CHECK_EQUAL(refund, unit); + BOOST_CHECK_EQUAL(bet.status, 1); + BOOST_CHECK_EQUAL((bets_sum_pre - bets_sum_post).value, unit); +} + +// #5 — Oracle misses the resolution deadline. With no report by result_expiration the +// maintenance cron slashes pm_oracle_penalty_percent of the oracle's insurance to the +// committee, refunds every active bet in full, returns LP, and closes the market with +// resolved_outcome=-1 / payout_status=3. Zero-sum: bettors made whole, oracle pays the fine. +BOOST_AUTO_TEST_CASE(oracle_missed_refunds_and_slashes) { + auto gp = make_genesis_params(0x4444u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-missed", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping oracle-missed scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(60); + cm.allow_early_resolution = false; // let it expire unresolved + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + const asset alice_after_bet = node.db().get_account("alice").balance; + const asset bob_after_bet = node.db().get_account("bob").balance; + + // Oracle insurance before the miss penalty. + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(gp.initiator_name)->insurance.value; + const int64_t expect_slash = insurance_before * (int64_t)mp.pm_oracle_penalty_percent / 10000; + BOOST_REQUIRE_GT(expect_slash, 0); + + // Never resolve. Advance past result_expiration (+60s) so the missed-oracle cron fires. + for (int i = 0; i < 120 && + node.db().get(market_id).status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + BOOST_REQUIRE_EQUAL(m.status, 3); + BOOST_CHECK_EQUAL(m.payout_status, 3); + BOOST_CHECK_EQUAL(m.resolved_outcome, -1); + + // Both bettors refunded their full stake. + const int64_t alice_refund = (node.db().get_account("alice").balance - alice_after_bet).amount.value; + const int64_t bob_refund = (node.db().get_account("bob").balance - bob_after_bet).amount.value; + BOOST_TEST_MESSAGE("oracle-missed: alice refund=" << alice_refund + << " bob refund=" << bob_refund + << " insurance slashed=" << (insurance_before - oidx.find(gp.initiator_name)->insurance.value)); + BOOST_CHECK_EQUAL(alice_refund, unit); + BOOST_CHECK_EQUAL(bob_refund, unit); + + // Oracle insurance slashed exactly by pm_oracle_penalty_percent. + BOOST_CHECK_EQUAL(insurance_before - oidx.find(gp.initiator_name)->insurance.value, expect_slash); +} + +// #37 — Lazy-pool active-market exposure penalty (consensus risk control). For every OTHER +// active market the same oracle already runs, a fresh allocation is cut by 5% (×95/100). +// We fund the pool, open two self-oracle markets by the same oracle back-to-back, and assert +// the second market's allocation equals its un-penalized 20%-of-free base reduced by exactly +// one 5% step. (#16's "betting block / listing hide" is an API read-filter — below_risk_floor +// in prediction_market_api, explicitly non-consensus — so it has no on-chain assertion.) +BOOST_AUTO_TEST_CASE(lazy_active_market_penalty) { + auto gp = make_genesis_params(0x7777u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lazy37", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping lazy-penalty scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + BOOST_REQUIRE(mp.pm_lazy_pool_enabled); + const int64_t alloc_bp = (int64_t)mp.pm_lazy_alloc_percent; + const int64_t cap_bp = (int64_t)mp.pm_lazy_max_total_alloc_percent; + + // Pure-integer mirror of maybe_allocate_lazy's base+clamp (penalties applied separately). + auto base_alloc = [&](int64_t free, int64_t allocated) -> int64_t { + int64_t total = free + allocated; + int64_t headroom = total * cap_bp / 10000 - allocated; + int64_t a = free * alloc_bp / 10000; + if (a > free) a = free; + if (a > headroom) a = headroom; + return a < 0 ? 0 : a; + }; + + const pm_lazy_pool_id_type pool_id(0); + auto pool_free = [&]{ return node.db().get(pool_id).free_balance.value; }; + auto pool_allocated = [&]{ return node.db().get(pool_id).allocated_balance.value; }; + + // Raw allocation recorded at a market's activation (pm_lazy_allocation_object.original_amount). + // Unlike pool.allocated_balance, this is NOT touched by the graduated-recall cron (§7, which + // claws back idle markets' capital every block), so it isolates the activation-time penalty. + auto alloc_orig = [&](pm_market_id_type mid) -> int64_t { + const auto& aidx = node.db().get_index().indices().get(); + auto it = aidx.find(mid); + return it != aidx.end() ? it->original_amount.value : -1; + }; + + // viz seeds the lazy pool and funds a bettor. + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(unit * 10), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + + auto self_market_op = [&]() { + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; // self-oracle → active at creation + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(300); + cm.result_expiration = node.head_block_time() + fc::seconds(600); + cm.dispute_mode = 0; + return cm; + }; + + // M1: no OTHER active market for this oracle → full un-penalised allocation. + const int64_t exp_m1 = base_alloc(pool_free(), pool_allocated()); + // Create M1 AND bet on it in the same block, so M1 is never "idle": the recall cron then + // leaves its allocation (and the pool's free_balance) intact, keeping M2's input deterministic. + { + pm_create_market_operation cm = self_market_op(); + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + produce(node, gp, when); + } + const int64_t m1_orig = alloc_orig(pm_market_id_type(0)); + + // M2: same oracle, exactly one OTHER active market (M1) → base cut by exactly one 5% step. + const int64_t base_m2 = base_alloc(pool_free(), pool_allocated()); // un-penalised + const int64_t exp_m2 = base_m2 * 95 / 100; + { + pm_create_market_operation cm = self_market_op(); + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + } + const int64_t m2_orig = alloc_orig(pm_market_id_type(1)); + + BOOST_TEST_MESSAGE("lazy #37: m1_orig=" << m1_orig << " (exp " << exp_m1 << ") " + << "m2_orig=" << m2_orig << " (un-penalised " << base_m2 + << " -> exp " << exp_m2 << ")"); + BOOST_REQUIRE_GT(exp_m1, 0); + BOOST_CHECK_EQUAL(m1_orig, exp_m1); // first market: full allocation + BOOST_CHECK_EQUAL(m2_orig, exp_m2); // second: exactly one 5% active-market penalty + BOOST_CHECK_LT(m2_orig, base_m2); // strictly below its un-penalised base +} + +// #36 — Graduated recall is time-gated: an idle market's pool allocation is clawed back ONE +// 10%-step per ~10% of the market's lifetime, NOT every block. This is the regression guard +// for the recall-cron fix (the cron runs every block; without the time gate it drained an +// idle market's whole subsidy within ~10 blocks). We open an idle, long-lived market and +// watch alloc.check_step advance one notch per step_dur, holding steady between gates. +BOOST_AUTO_TEST_CASE(lazy_recall_is_time_gated) { + auto gp = make_genesis_params(0x8888u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-recall", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping recall-timing scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + BOOST_REQUIRE(mp.pm_lazy_pool_enabled); + + const pm_market_id_type mid(0); + auto alloc_step = [&]() -> int { + const auto& aidx = node.db().get_index().indices().get(); + auto it = aidx.find(mid); + return it != aidx.end() ? (int)it->check_step : -1; + }; + auto alloc_amt = [&]() -> int64_t { + const auto& aidx = node.db().get_index().indices().get(); + auto it = aidx.find(mid); + return it != aidx.end() ? it->amount.value : -1; + }; + auto advance_until = [&](uint32_t target_sec) { + for (int g = 0; g < 1000 && node.head_block_time().sec_since_epoch() < target_sec; ++g) + produce(node, gp, when); + }; + + // Seed the pool so the self-oracle market gets a real allocation at activation. + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(unit * 10), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + + // Long-lived market, never bet on → stays idle. window/10 = step_dur ≫ block interval. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(2000); + cm.result_expiration = node.head_block_time() + fc::seconds(4000); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + + const auto& m = node.db().get(mid); + const uint32_t t0 = m.created_time.sec_since_epoch(); + const int64_t window = (int64_t)m.result_expiration.sec_since_epoch() - (int64_t)t0; + const int64_t step_dur = window / 10; + BOOST_REQUIRE_GT(step_dur, (int64_t)CHAIN_BLOCK_INTERVAL * 4); // gate must span many blocks + + const int64_t orig = alloc_amt(); + BOOST_REQUIRE_GT(orig, 0); + BOOST_CHECK_EQUAL(alloc_step(), 0); // just allocated, nothing recalled yet + + // Before the first gate (half a step in): still no recall, even though many blocks passed. + advance_until(t0 + (uint32_t)(step_dur / 2)); + const int64_t amt_pre = alloc_amt(); + BOOST_CHECK_EQUAL(alloc_step(), 0); + BOOST_CHECK_EQUAL(amt_pre, orig); + + // Just past the first gate: exactly ONE 10% step recalled. + advance_until(t0 + (uint32_t)step_dur + (uint32_t)(step_dur / 4)); + const int step1 = alloc_step(); + const int64_t amt1 = alloc_amt(); + BOOST_CHECK_EQUAL(step1, 1); + BOOST_CHECK_LT(amt1, orig); + + // THE regression check: many more blocks pass within the same step window → still step 1, + // allocation unchanged. (Under the bug this would have advanced one step per block.) + advance_until(t0 + (uint32_t)(step_dur * 9 / 5)); // ~1.8 steps, still before gate 2 + BOOST_CHECK_EQUAL(alloc_step(), 1); + BOOST_CHECK_EQUAL(alloc_amt(), amt1); + + // Past the second gate: now exactly step 2, allocation reduced again. + advance_until(t0 + (uint32_t)(step_dur * 2) + (uint32_t)(step_dur / 4)); + const int step2 = alloc_step(); + const int64_t amt2 = alloc_amt(); + BOOST_TEST_MESSAGE("recall timing: step_dur=" << step_dur << " orig=" << orig + << " amt1=" << amt1 << " step2=" << step2 << " amt2=" << amt2); + BOOST_CHECK_EQUAL(step2, 2); + BOOST_CHECK_LT(amt2, amt1); +} + +// #7 — Committee upholds the oracle (dispute fails). The original outcome stands, the winner +// keeps the normal parimutuel payout, the oracle's insurance is NOT slashed, and the loser +// forfeits both the bet and the dispute fee. We measure where the forfeited dispute fee goes +// (winner via forfeit_pool vs. lost) to confirm zero-sum is preserved. +BOOST_AUTO_TEST_CASE(committee_dispute_upholds_oracle) { + auto gp = make_genesis_params(0x9999u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-uphold", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping uphold scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 15; + props.pm_dispute_auto_close_sec = 600; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + // A SEPARATE oracle account (not the block-producing validator) so its balance reflects + // only the dispute fee — viz's balance is polluted by block rewards. + auto orac_key = derive_key("orac"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + // viz creates the market with the EXTERNAL oracle → it starts pending until orac accepts. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, 1); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + const asset alice_after_bet = node.db().get_account("alice").balance; + + // orac resolves to A; bob disputes (claims B). + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(account_name_type("orac"))->insurance.value; + const asset orac_before = node.db().get_account("orac").balance; + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 2); + const asset bob_after_dispute = node.db().get_account("bob").balance; + + // The validator (full stake) votes to UPHOLD the oracle (negative-side weight). With + // oracle_defense ≥ change votes, the dispute is rejected. + pm_dispute_vote_operation dv; + dv.voter = gp.initiator_name; dv.market_id = 0; dv.vote_outcome = -1; dv.vote_percent = 10000; + node.push_pending_transaction(sign_ops({dv}, gp.initiator_key, node)); + produce(node, gp, when); + + // Advance past voting_end → finalize upholds → auto-payout settles. + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + const int64_t insurance_after = oidx.find(account_name_type("orac"))->insurance.value; + const int64_t alice_gain = (node.db().get_account("alice").balance - alice_after_bet).amount.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_after_dispute).amount.value; + const int64_t orac_gain = (node.db().get_account("orac").balance - orac_before).amount.value; + + BOOST_TEST_MESSAGE("uphold: resolved=" << (int)m.resolved_outcome + << " payout_status=" << (int)m.payout_status + << " alice_gain=" << alice_gain << " (expect winner pool " << (unit * 2) << ")" + << " bob_gain_post_dispute=" << bob_gain + << " orac_gain=" << orac_gain << " (expect fee " << unit << ")" + << " insurance " << insurance_before << "->" << insurance_after); + + BOOST_CHECK_EQUAL(m.resolved_outcome, 0); // oracle's A upheld + BOOST_CHECK_EQUAL(m.payout_status, 3); // settled + BOOST_CHECK_EQUAL(insurance_after, insurance_before); // oracle NOT slashed on uphold + BOOST_CHECK_EQUAL(alice_gain, unit * 2); // winner keeps the normal parimutuel pool + BOOST_CHECK_EQUAL(bob_gain, 0); // loser gets nothing back post-dispute + // Zero-sum: the forfeited dispute fee compensates the upheld oracle (committee-dao §Resolution). + BOOST_CHECK_EQUAL(orac_gain, unit); +} + +// #7b — Good-faith oracle (dispute_penalty_percent < 0). The community overrides the oracle, +// but because the market was flagged subjective, the oracle is NOT slashed — instead it keeps +// a carve-out of the dispute fee (|pp|/10000), and the disputer is refunded the remainder. +BOOST_AUTO_TEST_CASE(dispute_good_faith_oracle_not_slashed) { + auto gp = make_genesis_params(0xAAAAu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-goodfaith", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping good-faith scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 15; + props.pm_dispute_auto_close_sec = 600; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + cm.dispute_penalty_percent = -5000; // subjective market → oracle keeps 50% of the fee, no slash + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(account_name_type("orac"))->insurance.value; + const asset orac_before = node.db().get_account("orac").balance; + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + const asset bob_after_dispute = node.db().get_account("bob").balance; + + // Validator votes to OVERRIDE toward B. + pm_dispute_vote_operation dv; + dv.voter = gp.initiator_name; dv.market_id = 0; dv.vote_outcome = 1; dv.vote_percent = 10000; + node.push_pending_transaction(sign_ops({dv}, gp.initiator_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + const int64_t insurance_after = oidx.find(account_name_type("orac"))->insurance.value; + const int64_t orac_gain = (node.db().get_account("orac").balance - orac_before).amount.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_after_dispute).amount.value; + + BOOST_TEST_MESSAGE("good-faith: resolved=" << (int)m.resolved_outcome + << " insurance " << insurance_before << "->" << insurance_after + << " orac_gain=" << orac_gain << " (expect fee/2 " << (unit / 2) << ")" + << " bob_gain=" << bob_gain); + + BOOST_CHECK_EQUAL(m.resolved_outcome, 1); // overridden to B + BOOST_CHECK_EQUAL(insurance_after, insurance_before); // good-faith: NOT slashed + BOOST_CHECK_EQUAL(orac_gain, unit / 2); // oracle keeps |pp|/10000 of the fee + BOOST_CHECK_GT(bob_gain, 0); // disputer refunded remainder + winnings +} + +// #20/#22/#24 — Lazy pool deposit/shares/lock/planned-withdraw. First deposit mints 1:1 shares; +// a second deposit (no yield yet) mints proportionally; planned withdrawal is rejected before +// unlock (the lock is now enforced) and returns principal exactly once it elapses. +BOOST_AUTO_TEST_CASE(lazy_deposit_lock_and_withdraw) { + auto gp = make_genesis_params(0xB111u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lazylife", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping lazy lifecycle."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + BOOST_REQUIRE(mp.pm_lazy_pool_enabled); + + // Short deposit lock so the test can cross it. + { + chain_properties_pm props; + props.pm_lazy_lock_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_lazy_lock_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_lazy_lock_sec, 30u); + } + + const pm_lazy_pool_id_type pool_id(0); + auto pool_free = [&]{ return node.db().get(pool_id).free_balance.value; }; + auto pool_shares = [&]{ return node.db().get(pool_id).total_shares.value; }; + auto dep_of = [&](const char* acct) -> const pm_lazy_deposit_object* { + const auto& didx = node.db().get_index().indices().get(); + auto it = didx.find(account_name_type(acct)); + return it != didx.end() ? &*it : nullptr; + }; + + const int64_t D1 = unit * 10, D2 = unit * 4; + + // viz: first deposit → 1:1 shares. + pm_lazy_deposit_operation d1; + d1.account = gp.initiator_name; d1.amount = asset(share_type(D1), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({d1}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_CHECK_EQUAL(pool_free(), D1); + BOOST_CHECK_EQUAL(pool_shares(), D1); + { + auto* dv = dep_of("viz"); + BOOST_REQUIRE(dv); + BOOST_CHECK_EQUAL(dv->shares.value, D1); + BOOST_CHECK_EQUAL(dv->principal.value, D1); + BOOST_CHECK_GT(dv->unlock_time.sec_since_epoch(), node.head_block_time().sec_since_epoch()); + } + + // alice: second deposit, no yield yet → new_shares = amount × total_shares / free = D2. + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(D2 + unit * 2)); + pm_lazy_deposit_operation d2; + d2.account = "alice"; d2.amount = asset(share_type(D2), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({d2}, alice_key, node)); + produce(node, gp, when); + BOOST_CHECK_EQUAL(pool_free(), D1 + D2); + BOOST_CHECK_EQUAL(pool_shares(), D1 + D2); + BOOST_REQUIRE(dep_of("alice")); + BOOST_CHECK_EQUAL(dep_of("alice")->shares.value, D2); + const asset alice_after_deposit = node.db().get_account("alice").balance; + + // Planned withdrawal BEFORE unlock is rejected (lock enforced). + pm_lazy_withdraw_operation wbad; + wbad.account = "alice"; wbad.shares = 0; wbad.emergency = false; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({wbad}, alice_key, node)), + std::runtime_error); + + // Cross the lock, then plan-withdraw in full → principal back, pool restored. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_lazy_withdraw_operation wd; + wd.account = "alice"; wd.shares = 0; wd.emergency = false; + node.push_pending_transaction(sign_ops({wd}, alice_key, node)); + produce(node, gp, when); + + const int64_t alice_gain = (node.db().get_account("alice").balance - alice_after_deposit).amount.value; + BOOST_TEST_MESSAGE("lazy lifecycle: alice planned withdraw=" << alice_gain + << " (principal " << D2 << ") pool free=" << pool_free() + << " shares=" << pool_shares()); + BOOST_CHECK_EQUAL(alice_gain, D2); // principal returned (no yield) + BOOST_CHECK_EQUAL(pool_free(), D1); // alice's principal left free_balance + BOOST_CHECK_EQUAL(pool_shares(), D1); // alice's shares burned + BOOST_CHECK(dep_of("alice") == nullptr); // deposit object removed +} + +// #21/#25/#27 — Lazy pool earns yield and emergency-withdraw penalty. The pool allocates to a +// market; a one-sided bet that loses leaves the winners' pool undistributed → it flows to the +// LPs (incl. the pool) as yield. This verifies the free_balance accounting fix (yield must land +// in free_balance, and a withdrawal must deduct principal+rewards from it — otherwise rewards +// are emitted) plus the emergency penalty (penalty on profit retained in the pool). +BOOST_AUTO_TEST_CASE(lazy_yield_and_emergency_penalty) { + auto gp = make_genesis_params(0xB222u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lazyyield", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping lazy yield."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + BOOST_REQUIRE(mp.pm_lazy_pool_enabled); + + // Short auto-payout grace so the market settles fast. Keep the (long) deposit lock so viz's + // deposit is still locked at emergency-withdraw time. + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + const pm_lazy_pool_id_type pool_id(0); + auto pool_free = [&]{ return node.db().get(pool_id).free_balance.value; }; + auto pool_shares = [&]{ return node.db().get(pool_id).total_shares.value; }; + auto pool_earned = [&]{ return node.db().get(pool_id).earned_balance.value; }; + + const int64_t D = unit * 10; + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(D), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(pool_free(), D); + + // viz registers as oracle and opens a self-oracle market → the pool auto-allocates to it. + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + BOOST_REQUIRE_LT(pool_free(), D); // pool allocated a slice to the market + + // alice bets side A only; nobody bets B. + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + produce(node, gp, when); + + // Resolve to B (no B bettors) → A's pool is undistributed → flows to LPs incl. the pool. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 1; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + // Yield landed in the pool: earned > 0 AND free_balance == principal + yield (the fix — + // without it the yield would be missing from free_balance, i.e. silently lost/emitted). + const int64_t earned = pool_earned(); + BOOST_TEST_MESSAGE("lazy yield: earned=" << earned << " free=" << pool_free() + << " (expect D+earned=" << (D + earned) << ")"); + BOOST_CHECK_GT(earned, 0); + BOOST_CHECK_EQUAL(pool_free(), D + earned); + + // viz emergency-withdraws while still locked → penalty on profit stays in the pool and is the + // only thing left in free_balance (sole depositor); all shares burned. (Verifies the withdraw + // deducts principal+rewards from free_balance, and the emergency penalty math/destination.) + pm_lazy_withdraw_operation wd; + wd.account = gp.initiator_name; wd.shares = 0; wd.emergency = true; + node.push_pending_transaction(sign_ops({wd}, gp.initiator_key, node)); + produce(node, gp, when); + + const int64_t expect_penalty = earned * (int64_t)mp.pm_lazy_emergency_penalty_percent / 10000; + BOOST_TEST_MESSAGE("emergency: free=" << pool_free() << " (expect penalty " << expect_penalty + << ") shares=" << pool_shares()); + BOOST_CHECK_EQUAL(pool_shares(), 0); + BOOST_CHECK_EQUAL(pool_free(), expect_penalty); +} + +// #49 — Leverage open → voluntary close. The pool fronts a loan L (leverage_fund_used += L, +// free_balance -= L); collateral+loan enter the CPMM. Closing immediately (no price move) returns +// the position value: the pool recovers its obligation L×(1+R/100) (profit = L×R/100 → earned), +// the trader gets the remainder. Zero-sum: the trader's net loss equals the pool's interest gain. +BOOST_AUTO_TEST_CASE(leverage_open_and_close) { + auto gp = make_genesis_params(0xC111u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lev", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping leverage open/close."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // Enable leverage and relax the sizing knobs so a meaningful loan fits; disable lazy auto- + // allocation so the pool's whole free_balance is available as leverage capital. + { + chain_properties_pm props; + props.pm_leverage_enabled = true; + props.pm_leverage_expiration_buffer_sec = 0; + props.pm_leverage_fund_percent = 100; + props.pm_leverage_max_per_position_bp = 10000; + props.pm_leverage_max_position_ratio_percent = 100; + props.pm_lazy_alloc_percent = 0; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_leverage_enabled); + } + + const pm_lazy_pool_id_type pool_id(0); + auto lev_used = [&]{ return node.db().get(pool_id).leverage_fund_used.value; }; + auto pool_free = [&]{ return node.db().get(pool_id).free_balance.value; }; + auto pool_earned = [&]{ return node.db().get(pool_id).earned_balance.value; }; + + const int64_t D = unit * 100; // 10M pool free + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(D), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(pool_free(), D); + + // Self-oracle market, liquidity ≥ pm_leverage_min_market_liquidity; long betting window. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 60), TOKEN_SYMBOL); // 6M → reserves 3M/3M + cm.betting_expiration = node.head_block_time() + fc::seconds(3600); + cm.result_expiration = node.head_block_time() + fc::seconds(7200); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(pool_free(), D); // no lazy allocation (alloc_percent=0) + + auto trader_key = derive_key("trader"); + create_and_fund(node, gp, when, "trader", trader_key, share_type(unit * 25)); + + const int64_t C = unit * 20, L = unit * 10; // collateral 2M, loan 1M + const int64_t R = (int64_t)mp.pm_leverage_pool_profit_percent; + const int64_t threshold = L + L * R / 100; // obligation = L×(1+R/100) = 1.1M + const int64_t expect_profit = threshold - L; // pool interest = L×R/100 = 0.1M + + const asset trader_before = node.db().get_account("trader").balance; + pm_leverage_open_operation op; + op.account = "trader"; op.market_id = 0; op.outcome_index = 0; + op.collateral = asset(share_type(C), TOKEN_SYMBOL); op.loan = asset(share_type(L), TOKEN_SYMBOL); + op.min_tokens = 0; op.max_slippage_percent = 0; + node.push_pending_transaction(sign_ops({op}, trader_key, node)); + produce(node, gp, when); + + const pm_leverage_position_id_type pos_id(0); + { + const auto& pos = node.db().get(pos_id); + BOOST_CHECK_EQUAL(pos.loan.value, L); + BOOST_CHECK_EQUAL(pos.liquidation_threshold.value, threshold); + BOOST_CHECK_EQUAL(pos.status, 0); + } + BOOST_CHECK_EQUAL(lev_used(), L); // pool fronted the loan + BOOST_CHECK_EQUAL(pool_free(), D - L); // loan left free_balance + + // Voluntary close (no price move → cv ≥ obligation). + pm_leverage_close_operation cl; + cl.account = "trader"; cl.position_id = 0; cl.min_return = 0; + node.push_pending_transaction(sign_ops({cl}, trader_key, node)); + produce(node, gp, when); + + const int64_t trader_delta = (node.db().get_account("trader").balance - trader_before).amount.value; + BOOST_TEST_MESSAGE("leverage: pos.status=" << (int)node.db().get(pos_id).status + << " lev_used=" << lev_used() << " pool free=" << pool_free() + << " earned=" << pool_earned() << " trader_delta=" << trader_delta + << " (expect -" << expect_profit << ")"); + BOOST_CHECK_EQUAL(node.db().get(pos_id).status, 4); // closed + BOOST_CHECK_EQUAL(lev_used(), 0); // loan repaid + BOOST_CHECK_EQUAL(pool_earned(), expect_profit); // pool kept the interest + BOOST_CHECK_EQUAL(pool_free(), D + expect_profit); // free == D + interest (loan back + profit) + BOOST_CHECK_EQUAL(trader_delta, -expect_profit); // zero-sum: trader's loss == pool's gain +} + +// #50 — Cancel-bet liquidation cascade (Case B) with pool bad debt. A large same-side bet is +// placed, a leveraged position opens on that side, then the bet is cancelled — the cancel +// recomputes k and shifts the curve so the position's cancel_value drops below its loan. The +// cancel-bet cascade liquidates it; the pool recovers only cancel_value (< loan) and eats the +// shortfall (bad debt). The liquidated trader loses their collateral (gets nothing back). +BOOST_AUTO_TEST_CASE(leverage_cancel_bet_cascade_bad_debt) { + auto gp = make_genesis_params(0xC222u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-levbd", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping leverage cascade."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_leverage_enabled = true; + props.pm_leverage_expiration_buffer_sec = 0; + props.pm_leverage_fund_percent = 100; + props.pm_leverage_max_per_position_bp = 10000; + props.pm_leverage_max_position_ratio_percent = 100; + props.pm_lazy_alloc_percent = 0; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_leverage_enabled); + } + + const pm_lazy_pool_id_type pool_id(0); + auto lev_used = [&]{ return node.db().get(pool_id).leverage_fund_used.value; }; + auto pool_free = [&]{ return node.db().get(pool_id).free_balance.value; }; + + const int64_t D = unit * 100; + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(D), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 60), TOKEN_SYMBOL); // 6M → reserves 3M/3M + cm.betting_expiration = node.head_block_time() + fc::seconds(3600); + cm.result_expiration = node.head_block_time() + fc::seconds(7200); + cm.allow_cancellation = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + + auto bob_key = derive_key("bob"), trader_key = derive_key("trader"); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 70)); + create_and_fund(node, gp, when, "trader", trader_key, share_type(unit * 25)); + + // bob bets side A (6M), moving reserves to (9M, 1M). + pm_place_bet_operation bb; + bb.account = "bob"; bb.market_id = 0; bb.side = 0; bb.outcome_index = -1; + bb.amount = asset(share_type(unit * 60), TOKEN_SYMBOL); bb.mode = 0; + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Locate bob's bet id (to cancel later). + uint64_t bob_bet_id = 0; bool found = false; + for (const auto& b : node.db().get_index().indices()) { + if (b.market == pm_market_id_type(0) && b.account == "bob" && b.status == 0) { + bob_bet_id = b.id._id; found = true; break; + } + } + BOOST_REQUIRE(found); + + // trader opens a leveraged position on side A (C=2M, L=1M) at reserves (9M, 1M). + const int64_t L = unit * 10; + const asset trader_before = node.db().get_account("trader").balance; + pm_leverage_open_operation op; + op.account = "trader"; op.market_id = 0; op.outcome_index = 0; + op.collateral = asset(share_type(unit * 20), TOKEN_SYMBOL); op.loan = asset(share_type(L), TOKEN_SYMBOL); + op.min_tokens = 0; op.max_slippage_percent = 0; + node.push_pending_transaction(sign_ops({op}, trader_key, node)); + produce(node, gp, when); + const pm_leverage_position_id_type pos_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(pos_id).status, 0); + const int64_t free_before_cancel = pool_free(); // == D - L + BOOST_REQUIRE_EQUAL(free_before_cancel, D - L); + + // bob cancels his side-A bet → cancel-bet cascade liquidates trader's now-underwater position. + pm_cancel_bet_operation cancel; + cancel.account = "bob"; cancel.bet_id = bob_bet_id; cancel.min_return = 0; + node.push_pending_transaction(sign_ops({cancel}, bob_key, node)); + produce(node, gp, when); + + const auto& pos = node.db().get(pos_id); + const int64_t cv = pos.cancel_value_at_liquidation.value; + const int64_t trader_delta = (node.db().get_account("trader").balance - trader_before).amount.value; + BOOST_TEST_MESSAGE("cascade bad-debt: pos.status=" << (int)pos.status + << " cv=" << cv << " loan=" << pos.loan.value + << " pool_received=" << pos.pool_received.value + << " bettor_received=" << pos.bettor_received.value + << " lev_used=" << lev_used() << " pool free=" << pool_free() + << " (expect D-baddebt=" << (D - (L - cv)) << ") trader_delta=" << trader_delta); + + BOOST_CHECK_EQUAL(pos.status, 1); // liquidated by the cancel cascade + BOOST_CHECK_EQUAL(cv, 500000); // engineered underwater cancel_value + BOOST_CHECK_LT(pos.pool_received.value, pos.loan.value);// BAD DEBT: pool recovered < loan + BOOST_CHECK_EQUAL(pos.pool_received.value, cv); // pool got exactly cancel_value + BOOST_CHECK_EQUAL(pos.bettor_received.value, 0); // liquidated trader gets nothing + BOOST_CHECK_EQUAL(lev_used(), 0); // loan cleared + BOOST_CHECK_EQUAL(pool_free(), D - (L - cv)); // pool absorbed the shortfall + BOOST_CHECK_EQUAL(trader_delta, -(int64_t)(unit * 20)); // trader lost the full collateral (got 0 back) +} + +// #54 — Account-mode dispute (dispute_mode=1, centralized resolver). The named resolver flips the +// outcome, slashes the resolver-specified penalty from the oracle, and the disputer is refunded +// the fee + reward carve-out (same canon as committee mode). A non-resolver cannot resolve. +BOOST_AUTO_TEST_CASE(account_mode_dispute_resolver_flips) { + auto gp = make_genesis_params(0xD111u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-acctdisp", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping account-mode dispute."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + // External oracle + a separate resolver account (must differ from oracle & creator). + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 1; cm.dispute_resolver = "judge"; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 2); + + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(account_name_type("orac"))->insurance.value; + const asset bob_after_dispute = node.db().get_account("bob").balance; + + // A non-resolver (alice) cannot resolve an account-mode dispute. + pm_dispute_resolve_operation bad; + bad.resolver = "alice"; bad.market_id = 0; bad.correct_outcome = 1; + bad.penalty_amount = asset(unit * 5, TOKEN_SYMBOL); + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({bad}, alice_key, node)), + std::runtime_error); + + // The designated resolver flips to B and slashes 5 units of insurance. + pm_dispute_resolve_operation dr; + dr.resolver = "judge"; dr.market_id = 0; dr.correct_outcome = 1; + dr.penalty_amount = asset(unit * 5, TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dr}, judge_key, node)); + produce(node, gp, when); + + // Advance past result_expiration + grace → auto-payout settles. + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + const int64_t insurance_after = oidx.find(account_name_type("orac"))->insurance.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_after_dispute).amount.value; + BOOST_TEST_MESSAGE("account-mode: resolved=" << (int)m.resolved_outcome + << " payout_status=" << (int)m.payout_status + << " insurance " << insurance_before << "->" << insurance_after + << " bob_gain=" << bob_gain); + + BOOST_CHECK_EQUAL(m.resolved_outcome, 1); // resolver flipped to B + BOOST_CHECK_EQUAL(m.payout_status, 3); // settled + BOOST_CHECK_EQUAL(insurance_before - insurance_after, unit * 5); // resolver-set penalty slashed + BOOST_CHECK_GT(bob_gain, 0); // disputer: fee refund + reward + winnings +} + +// #17 — Oracle voluntary no-contest. The oracle bails on an active market: all bets are refunded, +// LP principal returned, and a penalty (pm_no_contest_penalty_permille of the dispute fee, NOT of +// the whole insurance) is slashed from the oracle's insurance and distributed pro-rata to the +// refunded bettors as compensation. +BOOST_AUTO_TEST_CASE(oracle_no_contest_refund_and_compensate) { + auto gp = make_genesis_params(0xD222u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-nocontest", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping no-contest."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // dispute_fee = 2 units → penalty = 500‰ × 2u = 1u; split pro-rata between the two bettors. + // Short grace so the disputable no-contest settles quickly (no dispute filed here). + { + chain_properties_pm props; + props.pm_dispute_fee = asset(unit * 2, TOKEN_SYMBOL); + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_fee.amount.value != unit * 2; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_fee.amount.value, unit * 2); + } + + auto orac_key = derive_key("orac"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(60); + cm.result_expiration = node.head_block_time() + fc::seconds(120); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + const asset alice_after_bet = node.db().get_account("alice").balance; + const asset bob_after_bet = node.db().get_account("bob").balance; + + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(account_name_type("orac"))->insurance.value; + + pm_no_contest_operation nc; + nc.oracle = "orac"; nc.market_id = 0; nc.reason = "cannot judge"; + node.push_pending_transaction(sign_ops({nc}, orac_key, node)); + produce(node, gp, when); + // No-contest is disputable: market is now unresolved (-1) with the dispute window open. + BOOST_REQUIRE_EQUAL(node.db().get(market_id).resolved_outcome, -1); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 1); + + // Nobody disputes → after the grace the market settles: refund + penalty distribution. + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + const int64_t insurance_after = oidx.find(account_name_type("orac"))->insurance.value; + const int64_t alice_gain = (node.db().get_account("alice").balance - alice_after_bet).amount.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_after_bet).amount.value; + const int64_t penalty = unit * 2 * (int64_t)mp.pm_no_contest_penalty_percent / 10000; // = 1u + + BOOST_TEST_MESSAGE("no-contest: status=" << (int)m.status << " payout=" << (int)m.payout_status + << " insurance " << insurance_before << "->" << insurance_after + << " (penalty " << penalty << ") alice_gain=" << alice_gain + << " bob_gain=" << bob_gain); + + BOOST_CHECK_EQUAL(m.payout_status, 3); + BOOST_CHECK_EQUAL(insurance_before - insurance_after, penalty); // penalty = ‰ × dispute_fee + // each bettor: stake refund (unit) + half the penalty (penalty/2). + BOOST_CHECK_EQUAL(alice_gain, unit + penalty / 2); + BOOST_CHECK_EQUAL(bob_gain, unit + penalty / 2); +} + +// #18 — Dispute against an (abusive) no-contest. The oracle declares no-contest, but a bettor +// disputes and the committee overrides it to a real outcome — the market then settles to that +// outcome (not a refund) and the oracle is slashed for the wrongful no-contest. +BOOST_AUTO_TEST_CASE(dispute_overrides_no_contest) { + auto gp = make_genesis_params(0xD333u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-ncdisp", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping no-contest dispute."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 15; + props.pm_dispute_auto_close_sec = 600; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(300); + cm.result_expiration = node.head_block_time() + fc::seconds(600); + cm.dispute_mode = 0; + cm.dispute_penalty_percent = 500; // 5% insurance slash if the no-contest is overridden + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Oracle bails (no-contest), opening the dispute window. + pm_no_contest_operation nc; + nc.oracle = gp.initiator_name; nc.market_id = 0; nc.reason = "cannot judge"; + node.push_pending_transaction(sign_ops({nc}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).resolved_outcome, -1); + + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(gp.initiator_name)->insurance.value; + + // bob disputes, claiming B is the correct outcome (the market WAS judgeable). + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B clearly won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 2); + const asset bob_pre = node.db().get_account("bob").balance; + + // The validator votes to override the no-contest toward B. + pm_dispute_vote_operation dv; + dv.voter = gp.initiator_name; dv.market_id = 0; dv.vote_outcome = 1; dv.vote_percent = 10000; + node.push_pending_transaction(sign_ops({dv}, gp.initiator_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + const int64_t insurance_after = oidx.find(gp.initiator_name)->insurance.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_pre).amount.value; + BOOST_TEST_MESSAGE("no-contest overridden: resolved=" << (int)m.resolved_outcome + << " payout=" << (int)m.payout_status + << " insurance " << insurance_before << "->" << insurance_after + << " bob_gain=" << bob_gain); + + BOOST_CHECK_EQUAL(m.resolved_outcome, 1); // no-contest overridden to B + BOOST_CHECK_EQUAL(m.payout_status, 3); // settled as a real outcome + BOOST_CHECK_EQUAL(insurance_before - insurance_after, + insurance_before * 500 / 10000); // 5% (×100% consensus) slashed + BOOST_CHECK_GT(bob_gain, 0); // disputer + B winner paid +} + +// #19 — Dispute auto-close (anti-freeze). A dispute is filed but nobody votes; once auto_close_time +// passes (before voting would finalize) the cron voids the market: all bets refunded, oracle +// penalised, and — the path this guards — the disputer's escrowed fee is returned. +BOOST_AUTO_TEST_CASE(dispute_auto_close_refunds_fee) { + auto gp = make_genesis_params(0xD444u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-autoclose", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping auto-close."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // auto_close fires well before voting would finalize. + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 600; + props.pm_dispute_auto_close_sec = 5; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_auto_close_sec != 5; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_auto_close_sec, 5u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + const asset alice_after_bet = node.db().get_account("alice").balance; + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + const auto& oidx = node.db().get_index().indices().get(); + const int64_t insurance_before = oidx.find(gp.initiator_name)->insurance.value; + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + const asset bob_after_dispute = node.db().get_account("bob").balance; // fee already debited + + // Nobody votes; advance past auto_close_time → the cron voids the market. + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + const int64_t insurance_after = oidx.find(gp.initiator_name)->insurance.value; + const int64_t alice_gain = (node.db().get_account("alice").balance - alice_after_bet).amount.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_after_dispute).amount.value; + BOOST_TEST_MESSAGE("auto-close: resolved=" << (int)m.resolved_outcome << " payout=" << (int)m.payout_status + << " insurance " << insurance_before << "->" << insurance_after + << " alice_gain=" << alice_gain << " bob_gain=" << bob_gain); + + BOOST_CHECK_EQUAL(m.resolved_outcome, -1); // voided + BOOST_CHECK_EQUAL(m.payout_status, 3); + BOOST_CHECK_EQUAL(insurance_before - insurance_after, insurance_before * 500 / 10000); // oracle penalised 5% + BOOST_CHECK_EQUAL(alice_gain, unit); // bet refunded + BOOST_CHECK_EQUAL(bob_gain, unit * 2); // bet refund + dispute fee returned +} + +// #35 — Position transfer. A bettor hands their whole position to another account; on resolution +// the new owner collects the payout (the original owner gets nothing). An over-sized transfer is +// rejected. +BOOST_AUTO_TEST_CASE(position_transfer_changes_owner) { + auto gp = make_genesis_params(0xE111u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-xfer", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping position transfer."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"), carol_key = derive_key("carol"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "carol", carol_key, share_type(unit * 2)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; + bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Find alice's bet + its weight. + uint64_t alice_bet_id = 0; int64_t alice_weight = 0; bool found = false; + for (const auto& b : node.db().get_index().indices()) { + if (b.market == market_id && b.account == "alice" && b.status == 0) { + alice_bet_id = b.id._id; alice_weight = b.weight.value; found = true; break; + } + } + BOOST_REQUIRE(found); + + // Over-sized transfer is rejected. + pm_transfer_position_operation bad; + bad.from = "alice"; bad.bet_id = alice_bet_id; bad.to = "carol"; + bad.amount = share_type(alice_weight + 1); + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({bad}, alice_key, node)), + std::runtime_error); + + // Full transfer (amount 0) hands the position to carol. + pm_transfer_position_operation tp; + tp.from = "alice"; tp.bet_id = alice_bet_id; tp.to = "carol"; tp.amount = 0; + node.push_pending_transaction(sign_ops({tp}, alice_key, node)); + produce(node, gp, when); + BOOST_CHECK(node.db().get(pm_bet_id_type(alice_bet_id)).account == account_name_type("carol")); + + const asset alice_pre = node.db().get_account("alice").balance; + const asset carol_pre = node.db().get_account("carol").balance; + + // Resolve A → the transferred position (now carol's) wins. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const int64_t alice_gain = (node.db().get_account("alice").balance - alice_pre).amount.value; + const int64_t carol_gain = (node.db().get_account("carol").balance - carol_pre).amount.value; + BOOST_TEST_MESSAGE("transfer: carol_gain=" << carol_gain << " (expect " << (unit * 2) + << ") alice_gain=" << alice_gain); + BOOST_CHECK_EQUAL(carol_gain, unit * 2); // new owner collects winner payout (stake + losers' pool) + BOOST_CHECK_EQUAL(alice_gain, 0); // original owner no longer holds the position +} + +// #53 — Batch commit-reveal. A bettor commits a hashed bet (escrow), reveals it (surplus refunded, +// bet queued), and the epoch cron batch-settles it into the CPMM (side 0 → reserve_a rises, the +// same direction as an instant bet — guards the side-convention fix). An un-revealed commit is +// forfeited: a penalty stays in the pool and the remainder is refunded. +BOOST_AUTO_TEST_CASE(batch_commit_reveal_and_forfeit) { + auto gp = make_genesis_params(0xF111u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-batch", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping batch commit-reveal."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_commit_reveal_enabled = true; + props.pm_batch_epoch_blocks = 5; + props.pm_reveal_window_blocks = 5; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_commit_reveal_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_commit_reveal_enabled); + } + const uint16_t no_reveal = mp.pm_commit_no_reveal_penalty_percent; + + // Reproduces verify_commit's byte layout exactly. + auto make_commitment = [&](const account_name_type& acct, int64_t mid, int8_t side, + int16_t oidx, int64_t amount, int64_t min_tokens, const std::string& salt) { + fc::sha256::encoder enc; + enc.write((const char*)&mid, sizeof(mid)); + enc.write((const char*)&acct.data, sizeof(acct.data)); + enc.write((const char*)&side, sizeof(side)); + enc.write((const char*)&oidx, sizeof(oidx)); + enc.write((const char*)&amount, sizeof(amount)); + enc.write((const char*)&min_tokens, sizeof(min_tokens)); + enc.write(salt.data(), (uint32_t)salt.size()); + return enc.result(); + }; + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); // reserves 2u / 2u + cm.betting_expiration = node.head_block_time() + fc::seconds(600); + cm.result_expiration = node.head_block_time() + fc::seconds(1200); + cm.allow_batch = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + const int64_t reserve_a0 = node.db().get(market_id).reserve_a.value; // 2u + + // alice commits a side-A bet (escrow 2u, intended amount 1u), then reveals it. + pm_commit_bet_operation ac; + ac.account = "alice"; ac.market_id = 0; + ac.commitment = make_commitment(account_name_type("alice"), 0, /*side*/0, /*oidx*/-1, unit, 0, "salt-a"); + ac.escrow_amount = asset(unit * 2, TOKEN_SYMBOL); ac.no_reveal_fee_percent = no_reveal; + node.push_pending_transaction(sign_ops({ac}, alice_key, node)); + produce(node, gp, when); + + const asset alice_after_commit = node.db().get_account("alice").balance; + pm_reveal_bet_operation ar; + ar.account = "alice"; ar.commit_id = 0; ar.side = 0; ar.outcome_index = -1; + ar.amount = asset(unit, TOKEN_SYMBOL); ar.salt = "salt-a"; ar.min_tokens = 0; + node.push_pending_transaction(sign_ops({ar}, alice_key, node)); + produce(node, gp, when); + // Surplus (escrow 2u − amount 1u = 1u) refunded at reveal. + BOOST_CHECK_EQUAL((node.db().get_account("alice").balance - alice_after_commit).amount.value, unit); + + // bob commits but never reveals → will be forfeited. + const asset bob_before_commit = node.db().get_account("bob").balance; + pm_commit_bet_operation bc; + bc.account = "bob"; bc.market_id = 0; + bc.commitment = make_commitment(account_name_type("bob"), 0, 1, -1, unit, 0, "salt-b"); + bc.escrow_amount = asset(unit * 2, TOKEN_SYMBOL); bc.no_reveal_fee_percent = no_reveal; + node.push_pending_transaction(sign_ops({bc}, bob_key, node)); + produce(node, gp, when); + + // Find alice's queued bet (status 5) and advance to the epoch boundary that batch-settles it. + uint64_t alice_bet_id = 0; bool found = false; + for (const auto& b : node.db().get_index().indices()) + if (b.market == market_id && b.account == "alice") { alice_bet_id = b.id._id; found = true; break; } + BOOST_REQUIRE(found); + for (int i = 0; i < 20 && + node.db().get(pm_bet_id_type(alice_bet_id)).status == 5; ++i) + produce(node, gp, when); + + const auto& abet = node.db().get(pm_bet_id_type(alice_bet_id)); + const int64_t reserve_a1 = node.db().get(market_id).reserve_a.value; + BOOST_TEST_MESSAGE("batch: alice bet status=" << (int)abet.status << " weight=" << abet.weight.value + << " reserve_a " << reserve_a0 << "->" << reserve_a1); + BOOST_CHECK_EQUAL(abet.status, 0); // batch-settled into an active bet + BOOST_CHECK_GT(abet.weight.value, 0); // received CPMM tokens + BOOST_CHECK_EQUAL(reserve_a1, reserve_a0 + unit); // side-0 bet raised reserve_a (fix) + + // Advance past bob's reveal deadline → forfeit: penalty kept, remainder refunded. + for (int i = 0; i < 30 && (node.db().get_account("bob").balance - bob_before_commit).amount.value + == -(unit * 2); ++i) + produce(node, gp, when); + const int64_t bob_delta = (node.db().get_account("bob").balance - bob_before_commit).amount.value; + const int64_t expect_penalty = unit * 2 * (int64_t)no_reveal / 10000; + BOOST_TEST_MESSAGE("forfeit: bob_delta=" << bob_delta << " (expect -" << expect_penalty << ")"); + BOOST_CHECK_EQUAL(bob_delta, -expect_penalty); // lost only the no-reveal penalty +} + +// #32 — Multi-outcome (LMSR) market lifecycle: a 3-outcome market is created with the client- +// computed b, bettors back each outcome, the oracle resolves one, and only the winner is paid. +BOOST_AUTO_TEST_CASE(lmsr_multi_market_lifecycle) { + auto gp = make_genesis_params(0xF222u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lmsr", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping LMSR multi."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"), carol_key = derive_key("carol"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "carol", carol_key, share_type(unit * 4)); + + const int64_t L = unit * 30; // generous LMSR liquidity + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 1; cm.outcomes = {"A", "B", "C"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(L), TOKEN_SYMBOL); + cm.lmsr_b = graphene::chain::lmsr::lmsr_b_from_liquidity(L, 3); // client-computed b (node checks) + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).market_type, 1); + + // Each bettor backs a different outcome (side = -1 for multi; outcome_index selects). + auto bet = [&](const char* who, const fc::ecc::private_key& key, int16_t oidx) { + pm_place_bet_operation b; + b.account = who; b.market_id = 0; b.side = -1; b.outcome_index = oidx; + b.amount = asset(share_type(unit), TOKEN_SYMBOL); b.min_tokens = 0; b.mode = 0; + node.push_pending_transaction(sign_ops({b}, key, node)); + }; + bet("alice", alice_key, 0); + bet("bob", bob_key, 1); + bet("carol", carol_key, 2); + produce(node, gp, when); + const asset alice_after_bet = node.db().get_account("alice").balance; + const asset bob_after_bet = node.db().get_account("bob").balance; + const asset carol_after_bet = node.db().get_account("carol").balance; + + // Resolve to outcome A (index 0) → alice wins. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 200 && + node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + + const int64_t alice_gain = (node.db().get_account("alice").balance - alice_after_bet).amount.value; + const int64_t bob_gain = (node.db().get_account("bob").balance - bob_after_bet).amount.value; + const int64_t carol_gain = (node.db().get_account("carol").balance - carol_after_bet).amount.value; + BOOST_TEST_MESSAGE("lmsr multi: b=" << cm.lmsr_b.value << " alice(win)=" << alice_gain + << " bob=" << bob_gain << " carol=" << carol_gain); + + BOOST_CHECK_EQUAL(node.db().get(market_id).payout_status, 3); + BOOST_CHECK_GT(alice_gain, 0); // winner paid + BOOST_CHECK_EQUAL(bob_gain, 0); // losers get nothing + BOOST_CHECK_EQUAL(carol_gain, 0); +} + +// #55 — allow_instant_bet=false. A market may disable instant bets to force the front-run-resistant +// batch flow. An instant bet (mode 0) must be rejected; a batch bet (mode 1) is accepted. The +// consensus gate lives in pm_place_bet (the field alone was previously not enforced). +BOOST_AUTO_TEST_CASE(instant_bet_disabled_gate) { + auto gp = make_genesis_params(0x5511u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-noinstant", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping instant-bet gate."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + BOOST_REQUIRE(mp.pm_commit_reveal_enabled); // needed for allow_batch to stick (create evaluator) + + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + + // Self-oracle binary market with instant betting disabled, batch enabled. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(600); + cm.result_expiration = node.head_block_time() + fc::seconds(1200); + cm.allow_instant_bet = false; cm.allow_batch = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).allow_instant_bet, false); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).allow_batch, true); + + // Instant bet (mode 0) is rejected. + pm_place_bet_operation instant; + instant.account = "alice"; instant.market_id = 0; instant.side = 0; instant.outcome_index = -1; + instant.amount = asset(share_type(unit), TOKEN_SYMBOL); instant.mode = 0; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({instant}, alice_key, node)), + std::runtime_error); + + // Batch bet (mode 1) is accepted. + const asset alice_before = node.db().get_account("alice").balance; + pm_place_bet_operation batch = instant; batch.mode = 1; + node.push_pending_transaction(sign_ops({batch}, alice_key, node)); + produce(node, gp, when); + + const int64_t spent = (alice_before - node.db().get_account("alice").balance).amount.value; + BOOST_TEST_MESSAGE("instant-gate: batch spent=" << spent); + BOOST_CHECK_EQUAL(spent, unit); // batch bet went through (stake debited) + uint32_t bets = 0; + for (const auto& b : node.db().get_index().indices()) + if (b.market == market_id && b.account == account_name_type("alice")) ++bets; + BOOST_CHECK_EQUAL(bets, 1u); +} + +// #14 — Dispute + time-limited creator ban. An account-mode resolver bars the creator from opening +// new markets. The ban is recorded as a pm_creator_ban_object and create_market rejects while in +// force (the op field existed but was previously a no-op). +BOOST_AUTO_TEST_CASE(dispute_bans_creator_from_new_markets) { + auto gp = make_genesis_params(0x1401u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-creatorban", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping creator ban."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t creation_fee = mp.pm_market_creation_fee.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"), maker_key = derive_key("maker"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + create_and_fund(node, gp, when, "maker", maker_key, share_type(creation_fee * 2 + unit * 10)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + // "maker" creates an account-mode market; "judge" is the resolver. + pm_create_market_operation cm; + cm.creator = "maker"; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 1; cm.dispute_resolver = "judge"; + node.push_pending_transaction(sign_ops({cm}, maker_key, node)); + produce(node, gp, when); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + + // The resolver flips the outcome AND bans the creator for a fixed window. + const fc::time_point_sec ban_until = node.head_block_time() + fc::seconds(100000); + pm_dispute_resolve_operation dr; + dr.resolver = "judge"; dr.market_id = 0; dr.correct_outcome = 1; + dr.penalty_amount = asset(unit * 2, TOKEN_SYMBOL); + dr.ban_creator = true; dr.ban_creator_until = ban_until; + node.push_pending_transaction(sign_ops({dr}, judge_key, node)); + produce(node, gp, when); + + // The ban is recorded. + const auto& cbidx = node.db().get_index().indices().get(); + auto cb = cbidx.find(account_name_type("maker")); + BOOST_REQUIRE(cb != cbidx.end()); + BOOST_CHECK(cb->banned_until == ban_until); + BOOST_CHECK_EQUAL(cb->ban_count, 1u); + + // A banned creator cannot open a new market. + pm_create_market_operation cm2; + cm2.creator = "maker"; cm2.oracle = "orac"; + cm2.market_type = 0; cm2.outcomes = {"A", "B"}; cm2.url = "again"; + cm2.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm2.betting_expiration = node.head_block_time() + fc::seconds(30); + cm2.result_expiration = node.head_block_time() + fc::seconds(90); + cm2.dispute_mode = 1; cm2.dispute_resolver = "judge"; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({cm2}, maker_key, node)), + std::runtime_error); + BOOST_TEST_MESSAGE("creator-ban: maker banned until " << ban_until.sec_since_epoch() + << ", new market rejected"); +} + +// #15 — Fractional LP withdrawal. A second LP withdraws half its position during the betting period +// (principal is withdrawable before betting_expiration); the position stays active with the +// remainder, then a full withdrawal closes it. +BOOST_AUTO_TEST_CASE(fractional_liquidity_withdrawal) { + auto gp = make_genesis_params(0x1501u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-fraclp", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping fractional LP withdrawal."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // Self-oracle market with a long betting window (withdrawal is allowed before expiration). + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(3600); + cm.result_expiration = node.head_block_time() + fc::seconds(7200); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + // A second LP adds liquidity (becomes pm_liquidity id 1; seed LP is id 0). + auto lp2_key = derive_key("lp2"); + create_and_fund(node, gp, when, "lp2", lp2_key, share_type(unit * 4)); + pm_add_liquidity_operation al; + al.provider = "lp2"; al.market_id = 0; al.amount = asset(share_type(unit * 2), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({al}, lp2_key, node)); + produce(node, gp, when); + + const pm_liquidity_id_type lp2_id(1); + BOOST_REQUIRE_EQUAL(node.db().get(lp2_id).amount.value, unit * 2); + BOOST_REQUIRE_EQUAL(node.db().get(lp2_id).status, 0); + + // Withdraw 50% — position stays active with the remainder. + const asset lp2_before = node.db().get_account("lp2").balance; + pm_withdraw_liquidity_operation w1; + w1.provider = "lp2"; w1.liquidity_id = 1; w1.amount = asset(share_type(unit), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({w1}, lp2_key, node)); + produce(node, gp, when); + + BOOST_CHECK_EQUAL(node.db().get(lp2_id).amount.value, unit); // half left + BOOST_CHECK_EQUAL(node.db().get(lp2_id).status, 0); // still active + BOOST_CHECK_EQUAL((node.db().get_account("lp2").balance - lp2_before).amount.value, unit); + + // Withdraw the remainder (amount 0 = full position) — position closes. + pm_withdraw_liquidity_operation w2; + w2.provider = "lp2"; w2.liquidity_id = 1; w2.amount = asset(0, TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({w2}, lp2_key, node)); + produce(node, gp, when); + + BOOST_CHECK_EQUAL(node.db().get(lp2_id).status, 3); // closed + BOOST_CHECK_EQUAL((node.db().get_account("lp2").balance - lp2_before).amount.value, unit * 2); + BOOST_TEST_MESSAGE("fractional LP: withdrew 50% then remainder; principal fully returned"); +} + +// #56 — Oracle fixed fee (offer→quote model). The creator OFFERS a max fixed fee at create; the +// external oracle QUOTES its own (<= offer) at accept, which is frozen onto the market and paid +// from the settlement pool (never minted). The oracle cannot quote above the offer. A self-oracle +// market freezes the creator's own offer (0 here). Quote fields ride on pm_oracle_accept_market. +BOOST_AUTO_TEST_CASE(oracle_fixed_fee_external_vs_self) { + auto gp = make_genesis_params(0x5601u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-fixedfee", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping fixed-fee."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t F = unit / 2; // fixed fee, < losers_sum so it is fully funded + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; // advisory only; market fee is quoted at accept + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + // M0: external oracle. Creator offers a fixed-fee ceiling of F. + pm_create_market_operation cm0; + cm0.creator = gp.initiator_name; cm0.oracle = "orac"; + cm0.market_type = 0; cm0.outcomes = {"A", "B"}; cm0.url = "external"; + cm0.oracle_fixed_fee = asset(share_type(F), TOKEN_SYMBOL); // offered ceiling + cm0.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm0.betting_expiration = node.head_block_time() + fc::seconds(30); + cm0.result_expiration = node.head_block_time() + fc::seconds(90); + cm0.allow_early_resolution = true; cm0.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm0}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type m0(0); + + // The oracle cannot quote a fixed fee above the creator's offer. + pm_oracle_accept_market_operation bad; + bad.oracle = "orac"; bad.market_id = 0; bad.accept = true; + bad.oracle_fixed_fee = asset(share_type(F + unit), TOKEN_SYMBOL); + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({bad}, orac_key, node)), std::runtime_error); + + // The oracle quotes exactly F (<= offer); it is frozen onto the market. + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + acc.oracle_fee_percent = 0; acc.oracle_fixed_fee = asset(share_type(F), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + BOOST_CHECK_EQUAL(node.db().get(m0).oracle_fixed_fee.value, F); + + // M1: self-oracle → no fixed fee. + pm_create_market_operation cm1; + cm1.creator = gp.initiator_name; cm1.oracle = gp.initiator_name; + cm1.market_type = 0; cm1.outcomes = {"A", "B"}; cm1.url = "self"; + cm1.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm1.betting_expiration = node.head_block_time() + fc::seconds(30); + cm1.result_expiration = node.head_block_time() + fc::seconds(90); + cm1.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm1}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type m1(1); + BOOST_CHECK_EQUAL(node.db().get(m1).oracle_fixed_fee.value, 0); + + // Settle M0 with a losing pool; the external oracle is paid exactly its fixed fee. + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + const asset orac_before = node.db().get_account("orac").balance; + for (int i = 0; i < 200 && node.db().get(m0).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(m0).payout_status, 3); + + const int64_t orac_gain = (node.db().get_account("orac").balance - orac_before).amount.value; + BOOST_TEST_MESSAGE("fixed-fee: external oracle gained " << orac_gain << " (expect " << F << ")"); + BOOST_CHECK_EQUAL(orac_gain, F); // oracle_fee_percent==0 → take is exactly the fixed fee +} + +// #9 — Time-weighted LP fees. Two LPs of equal principal, deposited at different times, split the +// liquidity-fee bonus by principal × time-in-market: the earlier LP earns strictly more. +BOOST_AUTO_TEST_CASE(time_weighted_lp_fee_split) { + auto gp = make_genesis_params(0x0901u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-lptime", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping time-weighted LP."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"), lp2_key = derive_key("lp2"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "lp2", lp2_key, share_type(unit * 4)); + + // Seed LP (id 0) deposits at creation; 20% liquidity fee feeds the LP bonus. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 2), TOKEN_SYMBOL); + cm.liquidity_fee_percent = 1000; // 10% (bp) of the losers' pool → LP bonus + cm.betting_expiration = node.head_block_time() + fc::seconds(150); + cm.result_expiration = node.head_block_time() + fc::seconds(180); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + // Open the time gap (≈75s, > 1 minute granularity), then the late LP (id 1) deposits equal + // principal. + for (int i = 0; i < 25; ++i) produce(node, gp, when); + pm_add_liquidity_operation al; + al.provider = "lp2"; al.market_id = 0; al.amount = asset(share_type(unit * 2), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({al}, lp2_key, node)); + produce(node, gp, when); + + // Generate a losing pool (bob loses) so the liquidity fee is non-zero. + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Past betting (150s), early-resolve to A, then settle. + for (int i = 0; i < 30 && node.head_block_time() < cm.betting_expiration; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + const int64_t seed_fee = node.db().get(pm_liquidity_id_type(0)).earned_fee.value; + const int64_t late_fee = node.db().get(pm_liquidity_id_type(1)).earned_fee.value; + BOOST_TEST_MESSAGE("time-weighted LP: seed earned=" << seed_fee << " late earned=" << late_fee); + BOOST_CHECK_GT(seed_fee, 0); // bonus distributed + BOOST_CHECK_GT(seed_fee, late_fee); // earlier LP earns strictly more for equal principal +} + +// #38 — Fault penalty stamp. A zero-volume resolution (oracle resolves a market that took no bets) +// stamps the oracle's penalty_stamps counter (lazy-pool spam defence, §4.10). +BOOST_AUTO_TEST_CASE(zero_volume_resolution_stamps_oracle) { + auto gp = make_genesis_params(0x3801u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-faultstamp", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping fault stamp."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + const auto& oidx = node.db().get_index().indices().get(); + const uint32_t stamps_before = oidx.find(gp.initiator_name)->penalty_stamps; + + // Self-oracle market that takes NO bets. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + // No bets. Advance past betting, resolve to A, settle. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + const uint32_t stamps_after = oidx.find(gp.initiator_name)->penalty_stamps; + BOOST_TEST_MESSAGE("fault stamp: penalty_stamps " << stamps_before << " -> " << stamps_after); + BOOST_CHECK_EQUAL(stamps_after, stamps_before + 1); // zero-volume resolution stamped the oracle +} + +// #52 — Convert a leveraged position to a normal bet. A profitable position (same-side volume pushed +// its cancel_value above the loan obligation) is converted: the loan + conversion fee are paid, the +// pool loan clears, and the position becomes a plain parimutuel bet. +BOOST_AUTO_TEST_CASE(leverage_convert_to_normal_bet) { + auto gp = make_genesis_params(0x5201u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-levconv", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping leverage convert."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_leverage_enabled = true; + props.pm_leverage_expiration_buffer_sec = 0; + props.pm_leverage_fund_percent = 100; + props.pm_leverage_max_per_position_bp = 10000; + props.pm_leverage_max_position_ratio_percent = 100; + props.pm_lazy_alloc_percent = 0; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_leverage_enabled); + } + + const pm_lazy_pool_id_type pool_id(0); + auto lev_used = [&]{ return node.db().get(pool_id).leverage_fund_used.value; }; + auto pool_earned = [&]{ return node.db().get(pool_id).earned_balance.value; }; + + const int64_t D = unit * 100; + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(D), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 60), TOKEN_SYMBOL); // 6M → reserves 3M/3M + cm.betting_expiration = node.head_block_time() + fc::seconds(3600); + cm.result_expiration = node.head_block_time() + fc::seconds(7200); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + + // Convert pays off the WHOLE loan obligation (L×(1+R/100) ≈ unit*11) plus a profit-share fee, + // so the trader must hold far more than the collateral alone. + auto trader_key = derive_key("trader"), whale_key = derive_key("whale"); + create_and_fund(node, gp, when, "trader", trader_key, share_type(unit * 60)); + create_and_fund(node, gp, when, "whale", whale_key, share_type(unit * 40)); + + // trader opens a leveraged position on side A. + const int64_t L = unit * 10; + pm_leverage_open_operation op; + op.account = "trader"; op.market_id = 0; op.outcome_index = 0; + op.collateral = asset(share_type(unit * 20), TOKEN_SYMBOL); op.loan = asset(share_type(L), TOKEN_SYMBOL); + op.min_tokens = 0; op.max_slippage_percent = 0; + node.push_pending_transaction(sign_ops({op}, trader_key, node)); + produce(node, gp, when); + const pm_leverage_position_id_type pos_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(pos_id).status, 0); + BOOST_REQUIRE_EQUAL(lev_used(), L); + + // A whale piles onto the SAME side (A), pushing the position's cancel_value above its loan + // obligation → the position is now in profit and can be converted. + pm_place_bet_operation wb; + wb.account = "whale"; wb.market_id = 0; wb.side = 0; wb.outcome_index = -1; + wb.amount = asset(share_type(unit * 20), TOKEN_SYMBOL); wb.mode = 0; + node.push_pending_transaction(sign_ops({wb}, whale_key, node)); + produce(node, gp, when); + + const int64_t earned_before = pool_earned(); + const asset trader_before = node.db().get_account("trader").balance; + pm_leverage_convert_operation cv; + cv.account = "trader"; cv.position_id = 0; + cv.conversion_profit_cost = (uint16_t)mp.pm_conversion_profit_cost_percent; + node.push_pending_transaction(sign_ops({cv}, trader_key, node)); + produce(node, gp, when); + + const auto& pos = node.db().get(pos_id); + const int64_t trader_delta = (node.db().get_account("trader").balance - trader_before).amount.value; + BOOST_TEST_MESSAGE("convert: pos.status=" << (int)pos.status << " lev_used=" << lev_used() + << " pool_earned " << earned_before << "->" << pool_earned() + << " trader_delta=" << trader_delta); + + BOOST_CHECK_EQUAL(pos.status, 5); // converted + BOOST_CHECK_EQUAL(lev_used(), 0); // loan repaid to the pool + BOOST_CHECK_GT(pool_earned(), earned_before); // pool kept interest + conversion fee + BOOST_CHECK_LT(trader_delta, 0); // trader paid the obligation + fee + // The position is now a plain pm_bet owned by the trader (side A). + uint32_t trader_bets = 0; + for (const auto& b : node.db().get_index().indices()) + if (b.market == pm_market_id_type(0) && b.account == account_name_type("trader") && b.status == 0) + ++trader_bets; + BOOST_CHECK_EQUAL(trader_bets, 1u); +} + +// ─── Virtual-operation emission (this session's new vops) ──────────────────────── +// The harness produces blocks, so post_apply_operation only fires for plugins when +// enable_plugins_on_push_transaction(true) is set — we use that to capture vops. + +// pm_market_accepted: emitted on external accept (oracle's quote frozen) and on a self-oracle +// create (auto-accept). Also covers the offer→quote rule on the % fee. +BOOST_AUTO_TEST_CASE(market_accepted_vop_and_quote) { + auto gp = make_genesis_params(0x7A11u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-acceptvop", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping market_accepted vop."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // pm_market_accepted is emitted inside the accept / self-create transaction (not the cron), so the + // pending-apply (is_producing()==false) fires post_apply_operation without enabling plugins-on-push; + // enabling it would double-count (pending + block). Cron vops below DO need the enable flag. + std::vector accepts; + auto conn = node.db().post_apply_operation.connect([&](const operation_notification& note) { + if (note.op.which() == operation::tag::value) + accepts.push_back(note.op.get()); + }); + + auto orac_key = derive_key("orac"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + // M0 external: creator offers oracle_fee_percent ceiling 300, fixed ceiling unit/2. + pm_create_market_operation cm0; + cm0.creator = gp.initiator_name; cm0.oracle = "orac"; + cm0.market_type = 0; cm0.outcomes = {"A", "B"}; cm0.url = "ext"; + cm0.oracle_fee_percent = 300; cm0.oracle_fixed_fee = asset(share_type(unit / 2), TOKEN_SYMBOL); + cm0.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm0.betting_expiration = node.head_block_time() + fc::seconds(30); + cm0.result_expiration = node.head_block_time() + fc::seconds(90); + cm0.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm0}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type m0(0); + + // Quoting ABOVE the offer is rejected. + pm_oracle_accept_market_operation bad; + bad.oracle = "orac"; bad.market_id = 0; bad.accept = true; + bad.oracle_fee_percent = 400; // > offer 300 + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({bad}, orac_key, node)), std::runtime_error); + + // The oracle quotes 200 (< offer 300, < cap); it freezes and emits pm_market_accepted. + accepts.clear(); + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + acc.oracle_fee_percent = 200; acc.oracle_fixed_fee = asset(share_type(unit / 4), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + BOOST_CHECK_EQUAL(node.db().get(m0).oracle_fee_percent, 200); + BOOST_CHECK_EQUAL(node.db().get(m0).oracle_fixed_fee.value, unit / 4); + // The harness fires post_apply_operation on BOTH pending-apply and block-generate, so an identical + // tx-emitted vop appears 1-2 times; assert presence + fields, not an exact count. + BOOST_REQUIRE(!accepts.empty()); + BOOST_CHECK(accepts[0].oracle == account_name_type("orac")); + BOOST_CHECK(accepts[0].creator == gp.initiator_name); + BOOST_CHECK_EQUAL(accepts[0].market_id, 0); + BOOST_CHECK_EQUAL(accepts[0].oracle_fee_percent, 200); + BOOST_CHECK_EQUAL(accepts[0].oracle_fixed_fee.amount.value, unit / 4); // vop field is `asset` + BOOST_CHECK_EQUAL(accepts[0].self_oracle, false); + + // Self-oracle market auto-accepts at creation and emits pm_market_accepted(self_oracle=true). + accepts.clear(); + pm_create_market_operation cm1; + cm1.creator = gp.initiator_name; cm1.oracle = gp.initiator_name; + cm1.market_type = 0; cm1.outcomes = {"A", "B"}; cm1.url = "self"; + cm1.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm1.betting_expiration = node.head_block_time() + fc::seconds(30); + cm1.result_expiration = node.head_block_time() + fc::seconds(90); + cm1.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm1}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE(!accepts.empty()); + BOOST_CHECK_EQUAL(accepts[0].self_oracle, true); + BOOST_CHECK_EQUAL(accepts[0].market_id, 1); + conn.disconnect(); +} + +// Oracle auto-accept policy: an oracle can pre-authorize markets to go LIVE at creation, gated by +// BOTH "from whom" (auto_accept_creator) and "which dispute setup" (auto_accept_resolver). The +// resolver pin is the anti-collusion guard: with it empty the oracle only auto-accepts COMMITTEE +// markets (dispute_mode==0), so a maker cannot slip in a colluding named resolver under the +// auto-accept. Non-matching markets fall back to the normal manual-accept (pending) flow. +// M0: carol + committee → matches (creator ok, resolver ok) → live (status 1) + vop. +// M1: carol + named resolver judge → resolver mismatch (empty = committee only) → pending (0). +// M2: dave + committee → creator mismatch (pinned to carol) → pending (0). +BOOST_AUTO_TEST_CASE(auto_accept_resolver_and_creator_pins_gate_creation) { + auto gp = make_genesis_params(0xA11Eu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-autoaccept", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping auto-accept scenario."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t fee = mp.pm_market_creation_fee.amount.value; + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + + // pm_market_accepted fires inside the create tx (evaluator, not the cron); the pending-apply + // already drives post_apply_operation, so capture without enabling plugins-on-push. + std::vector accepts; + auto conn = node.db().post_apply_operation.connect([&](const operation_notification& note) { + if (note.op.which() == operation::tag::value) + accepts.push_back(note.op.get()); + }); + + // Oracle registers with auto-accept ON, pinned to creator "carol", resolver EMPTY (committee-only). + auto orac_key = derive_key("orac"); + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fee_percent = 100; oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + oreg.auto_accept = true; + oreg.auto_accept_creator = "carol"; + oreg.auto_accept_resolver = account_name_type(); // empty ⇒ committee only + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto carol_key = derive_key("carol"), dave_key = derive_key("dave"), judge_key = derive_key("judge"); + create_and_fund(node, gp, when, "carol", carol_key, share_type(fee * 2 + unit * 9)); + create_and_fund(node, gp, when, "dave", dave_key, share_type(fee + unit * 5)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit)); + + auto make_market = [&](const std::string& creator, const fc::ecc::private_key& key, + uint8_t dispute_mode, const std::string& resolver, const char* url) { + pm_create_market_operation cm; + cm.creator = creator; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = url; + cm.oracle_fee_percent = 300; cm.oracle_fixed_fee = asset(0, TOKEN_SYMBOL); // offer ceiling ≥ list 100 + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(60); + cm.result_expiration = node.head_block_time() + fc::seconds(120); + cm.dispute_mode = dispute_mode; + if (dispute_mode == 1) cm.dispute_resolver = resolver; + node.push_pending_transaction(sign_ops({cm}, key, node)); + produce(node, gp, when); + }; + + const auto& oidx = node.db().get_index().indices().get(); + auto orac_it = oidx.find(account_name_type("orac")); + BOOST_REQUIRE(orac_it != oidx.end()); + + // M0 — carol + committee ⇒ auto-accepted at creation (live), oracle's LIST terms frozen. + accepts.clear(); + make_market("carol", carol_key, 0, "", "m0-committee"); + const pm_market_id_type m0(0); + BOOST_CHECK_EQUAL(node.db().get(m0).status, 1); // live, no manual accept + BOOST_CHECK_EQUAL(node.db().get(m0).oracle_fee_percent, 100); // frozen at list price + BOOST_CHECK_EQUAL(node.db().get(m0).oracle_fixed_fee.value, 0); + BOOST_CHECK_EQUAL(orac_it->markets_accepted, 1u); + BOOST_REQUIRE(!accepts.empty()); + BOOST_CHECK(accepts[0].oracle == account_name_type("orac")); + BOOST_CHECK(accepts[0].creator == account_name_type("carol")); + BOOST_CHECK_EQUAL(accepts[0].self_oracle, false); + BOOST_CHECK_EQUAL(accepts[0].oracle_fee_percent, 100); + + // M1 — carol + NAMED resolver "judge": resolver pin is empty (committee-only) ⇒ NOT auto-accepted. + accepts.clear(); + make_market("carol", carol_key, 1, "judge", "m1-named-resolver"); + const pm_market_id_type m1(1); + BOOST_CHECK_EQUAL(node.db().get(m1).status, 0); // pending — sham resolver blocked + BOOST_CHECK_EQUAL(orac_it->markets_accepted, 1u); // unchanged + BOOST_CHECK(accepts.empty()); // no accept vop + + // M2 — dave + committee: creator pin is "carol" ⇒ NOT auto-accepted despite a matching dispute setup. + accepts.clear(); + make_market("dave", dave_key, 0, "", "m2-other-creator"); + const pm_market_id_type m2(2); + BOOST_CHECK_EQUAL(node.db().get(m2).status, 0); // pending — creator mismatch + BOOST_CHECK_EQUAL(orac_it->markets_accepted, 1u); // still only M0 + BOOST_CHECK(accepts.empty()); + + conn.disconnect(); +} + +// pm_payout: one per active bet at settlement — winner gets payout>0, loser gets payout 0, +// each carrying the stake + the side it was on. +BOOST_AUTO_TEST_CASE(payout_vop_per_bettor) { + auto gp = make_genesis_params(0x7A22u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-payoutvop", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping payout vop."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + // Capture only the settlement payouts. + std::vector payouts; + node.db().enable_plugins_on_push_transaction(true); + auto conn = node.db().post_apply_operation.connect([&](const operation_notification& note) { + if (note.op.which() == operation::tag::value) + payouts.push_back(note.op.get()); + }); + + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + bool saw_alice = false, saw_bob = false; + for (const auto& p : payouts) { + if (p.account == account_name_type("alice")) { + saw_alice = true; + BOOST_CHECK_EQUAL(p.amount.amount.value, unit); + BOOST_CHECK_EQUAL(p.side, 0); + BOOST_CHECK_GT(p.payout.amount.value, 0); // winner credited + } else if (p.account == account_name_type("bob")) { + saw_bob = true; + BOOST_CHECK_EQUAL(p.amount.amount.value, unit); + BOOST_CHECK_EQUAL(p.side, 1); + BOOST_CHECK_EQUAL(p.payout.amount.value, 0); // loser: 0 + } + } + BOOST_CHECK(saw_alice); + BOOST_CHECK(saw_bob); + conn.disconnect(); +} + +// pm_leverage_resolve: a leveraged position carried to market resolution is force-closed at +// settlement (status → 2 won / 3 lost) and emits pm_leverage_resolve with the outcome + leverage. +BOOST_AUTO_TEST_CASE(leverage_resolve_vop_at_settlement) { + auto gp = make_genesis_params(0x7A33u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-levresolve", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping leverage resolve vop."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_leverage_enabled = true; + props.pm_leverage_expiration_buffer_sec = 0; + props.pm_leverage_fund_percent = 100; + props.pm_leverage_max_per_position_bp = 10000; + props.pm_leverage_max_position_ratio_percent = 100; + props.pm_lazy_alloc_percent = 0; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_leverage_enabled); + } + + const pm_lazy_pool_id_type pool_id(0); + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(unit * 100), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 60), TOKEN_SYMBOL); // ≥ leverage min liquidity + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + auto trader_key = derive_key("trader"); + create_and_fund(node, gp, when, "trader", trader_key, share_type(unit * 25)); + + // collateral 10u + loan 10u → total_bet 20u → leverage = 20/10 = 2 (loan 10u is the + // proven-safe size from leverage_open_and_close; higher ratios fail the worst-case safety gate). + pm_leverage_open_operation op; + op.account = "trader"; op.market_id = 0; op.outcome_index = 0; + op.collateral = asset(share_type(unit * 10), TOKEN_SYMBOL); op.loan = asset(share_type(unit * 10), TOKEN_SYMBOL); + op.min_tokens = 0; op.max_slippage_percent = 0; + node.push_pending_transaction(sign_ops({op}, trader_key, node)); + produce(node, gp, when); + const pm_leverage_position_id_type pos_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(pos_id).status, 0); + + // Past betting, resolve to A; then settle (force-close the open position). + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + std::vector resolves; + node.db().enable_plugins_on_push_transaction(true); + auto conn = node.db().post_apply_operation.connect([&](const operation_notification& note) { + if (note.op.which() == operation::tag::value) + resolves.push_back(note.op.get()); + }); + + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + const auto& pos = node.db().get(pos_id); + BOOST_TEST_MESSAGE("leverage resolve: status=" << (int)pos.status + << " pool_received=" << pos.pool_received.value + << " bettor_received=" << pos.bettor_received.value + << " resolves=" << resolves.size()); + BOOST_CHECK(pos.status == 2 || pos.status == 3); // resolved (won/lost), NOT 1 (liquidated) + BOOST_REQUIRE_EQUAL(resolves.size(), 1u); + BOOST_CHECK(resolves[0].account == account_name_type("trader")); + BOOST_CHECK_EQUAL(resolves[0].market_id, 0); + BOOST_CHECK_EQUAL(resolves[0].outcome_index, 0); + BOOST_CHECK_EQUAL(resolves[0].leverage, 2); // total_bet 20 / collateral 10 + BOOST_CHECK_EQUAL(resolves[0].won, pos.status == 2); + conn.disconnect(); +} + +// Governance-volatility guard: disabling pm_leverage_enabled must NOT strip liquidation protection +// from positions that are already open. The flag only blocks NEW opens; the protective cascade on a +// cancel-bet (and opposing bet) keeps running so the pool is never left exposed if delegates flip the +// flag off mid-flight. Mirrors leverage_cancel_bet_cascade_bad_debt, but turns leverage OFF before the +// cancel and still expects the position liquidated. +BOOST_AUTO_TEST_CASE(leverage_disabled_keeps_liquidation_protection) { + auto gp = make_genesis_params(0xC333u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-levoffprot", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping leverage-disabled protection."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + auto relaxed = [&](bool enabled) { + chain_properties_pm props; + props.pm_leverage_enabled = enabled; + props.pm_leverage_expiration_buffer_sec = 0; + props.pm_leverage_fund_percent = 100; + props.pm_leverage_max_per_position_bp = 10000; + props.pm_leverage_max_position_ratio_percent = 100; + props.pm_lazy_alloc_percent = 0; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + return vp; + }; + node.push_pending_transaction(sign_ops({relaxed(true)}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_leverage_enabled); + + const pm_lazy_pool_id_type pool_id(0); + const int64_t D = unit * 100; + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(D), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 60), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(3600); // wide: survives the median wait + cm.result_expiration = node.head_block_time() + fc::seconds(7200); + cm.allow_cancellation = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + + auto bob_key = derive_key("bob"), trader_key = derive_key("trader"); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 70)); + create_and_fund(node, gp, when, "trader", trader_key, share_type(unit * 25)); + + // bob bets side A (6M) → reserves (9M, 1M); trader opens a leveraged A position there. + pm_place_bet_operation bb; + bb.account = "bob"; bb.market_id = 0; bb.side = 0; bb.outcome_index = -1; + bb.amount = asset(share_type(unit * 60), TOKEN_SYMBOL); bb.mode = 0; + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + uint64_t bob_bet_id = 0; bool found = false; + for (const auto& b : node.db().get_index().indices()) { + if (b.market == pm_market_id_type(0) && b.account == account_name_type("bob") && b.status == 0) { + bob_bet_id = b.id._id; found = true; break; + } + } + BOOST_REQUIRE(found); + + pm_leverage_open_operation op; + op.account = "trader"; op.market_id = 0; op.outcome_index = 0; + op.collateral = asset(share_type(unit * 20), TOKEN_SYMBOL); op.loan = asset(share_type(unit * 10), TOKEN_SYMBOL); + op.min_tokens = 0; op.max_slippage_percent = 0; + node.push_pending_transaction(sign_ops({op}, trader_key, node)); + produce(node, gp, when); + const pm_leverage_position_id_type pos_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(pos_id).status, 0); + + // Delegates turn leverage OFF while the position is open. + node.push_pending_transaction(sign_ops({relaxed(false)}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(!mp.pm_leverage_enabled); + BOOST_REQUIRE_EQUAL(node.db().get(pos_id).status, 0); // still open + + // bob cancels his A bet → the protective cascade must still fire and liquidate the position. + pm_cancel_bet_operation cancel; + cancel.account = "bob"; cancel.bet_id = bob_bet_id; cancel.min_return = 0; + node.push_pending_transaction(sign_ops({cancel}, bob_key, node)); + produce(node, gp, when); + + BOOST_TEST_MESSAGE("leverage-off protection: pos.status=" + << (int)node.db().get(pos_id).status + << " (leverage_enabled=" << mp.pm_leverage_enabled << ")"); + // Liquidated despite leverage being disabled — the flag only blocks new opens. + BOOST_CHECK_EQUAL(node.db().get(pos_id).status, 1); + BOOST_CHECK_EQUAL(node.db().get(pool_id).leverage_fund_used.value, 0); // loan cleared +} + +// ───────────────────────────────────────────────────────────────────────────── +// Betting odds & payout table — how a bettor's decimal coefficient is built. +// +// Two layers, both printed as plain rows: +// 1. LIVE odds (price discovery): the CPMM curve sets the implied probability +// P(A)=reserve_b/(reserve_a+reserve_b); decimal odds = 1/P. These move with +// every bet, exactly like a bookmaker's board. +// 2. REALIZED coefficient (settlement): a winner is paid its OWN stake back plus +// a parimutuel share of the LOSERS' pool, and the losers' pool is first cut by +// the oracle %, creator %, and liquidity-provider % (bp of losers_sum). The +// share is by CURVE WEIGHT (tokens), not raw stake — earlier money on a side +// buys more weight before the price moves. +// +// We place real bets (real CPMM weights), resolve A on-chain and assert the paid +// amounts equal the production settlement function, then reuse that SAME function +// (compute_settlement) to print the counterfactual "B wins" table. So both payout +// tables are authoritative, not hand-math. +BOOST_AUTO_TEST_CASE(betting_odds_and_payout_table) { + auto gp = make_genesis_params(0x0DD5u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-odds", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping odds table."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; // 100.000 VIZ + + // Short settle grace so the auto-payout cron fires quickly after resolution. + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 5; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 5; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 5u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + // Four bettors: a1,a2 back A; b1,b2 back B. Each funded generously. + struct better { std::string name; fc::ecc::private_key key; int8_t side; int64_t stake; }; + std::vector bs = { + { "anna", derive_key("anna"), 0, unit * 2 }, + { "bill", derive_key("bill"), 1, unit * 1 }, + { "andy", derive_key("andy"), 0, unit * 1 }, + { "boris", derive_key("boris"), 1, unit * 3 }, + }; + for (auto& b : bs) create_and_fund(node, gp, when, b.name, b.key, share_type(b.stake + unit)); + + // Self-oracle market, seeded 4u/4u (k=16u²). Commission baked into settlement: + // oracle 3%, liquidity 2%, creator 0% — all bp of the losers' pool. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.oracle_fee_percent = 300; // 3.00% + cm.oracle_fixed_fee = asset(0, TOKEN_SYMBOL); + cm.creator_fee_percent = 0; + cm.liquidity_fee_percent = 200; // 2.00% + cm.liquidity = asset(share_type(unit * 8), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(120); + cm.result_expiration = node.head_block_time() + fc::seconds(240); + cm.allow_early_resolution = true; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + const auto& mkt0 = node.db().get(market_id); + const uint16_t ORACLE_BP = mkt0.oracle_fee_percent; + const uint16_t CREATOR_BP = mkt0.creator_fee_percent; + const uint16_t LIQ_BP = mkt0.liquidity_fee_percent; + + // Decimal formatting helper: num/den → "X.XX". + auto fx = [](int64_t num, int64_t den) -> std::string { + if (den == 0) return "inf"; + int64_t v = num * 100 / den; // hundredths + std::string f = std::to_string(v % 100); + if (f.size() < 2) f = "0" + f; + return std::to_string(v / 100) + "." + f; + }; + + const int feeBp = (int)ORACLE_BP + (int)CREATOR_BP + (int)LIQ_BP; // total commission off losers' pool + BOOST_TEST_MESSAGE(""); + BOOST_TEST_MESSAGE("=== LIVE BOARD (parimutuel, commission baked in) — updates per bet ==="); + BOOST_TEST_MESSAGE("seed liquidity " << mkt0.reserve_a.value/1000 + mkt0.reserve_b.value/1000 + << " VIZ | commission off losers' pool: oracle " << fx(ORACLE_BP,100) + << "% + liq " << fx(LIQ_BP,100) << "% + creator " << fx(CREATOR_BP,100) + << "% = " << fx(feeBp,100) << "%"); + BOOST_TEST_MESSAGE(" decimal odds = 1 + opposing_pool x (1 - commission) / own_pool (all VIZ)"); + BOOST_TEST_MESSAGE("step bettor side stake weight(tokens) poolA/poolB impliedP(A) oddsA oddsB"); + + struct cap { std::string name; int8_t side; int64_t stake; int64_t weight; uint32_t tp; }; + std::vector caps; + + int step = 0; + for (auto& b : bs) { + const auto& m_pre = node.db().get(market_id); + const int64_t ra0 = m_pre.reserve_a.value, rb0 = m_pre.reserve_b.value; + + pm_place_bet_operation pb; + pb.account = b.name; pb.market_id = 0; pb.side = b.side; pb.outcome_index = -1; + pb.amount = asset(share_type(b.stake), TOKEN_SYMBOL); pb.mode = 0; + node.push_pending_transaction(sign_ops({pb}, b.key, node)); + produce(node, gp, when); + + const auto& m_post = node.db().get(market_id); + const int64_t ra1 = m_post.reserve_a.value, rb1 = m_post.reserve_b.value; + const int64_t weight = (b.side == 0) ? (rb0 - rb1) : (ra0 - ra1); // CPMM tokens out + caps.push_back({ b.name, b.side, b.stake, weight, 0u }); + + // Parimutuel board: a side's holders get their stake back + the opposing pool net of + // commission, so the side-level decimal coefficient is 1 + opp_net/own (commission baked in). + const int64_t As = m_post.a_bets_sum.value, Bs = m_post.b_bets_sum.value; + const int64_t Anet = As * (10000 - feeBp) / 10000; + const int64_t Bnet = Bs * (10000 - feeBp) / 10000; + BOOST_TEST_MESSAGE(" " << ++step << " " << b.name + << " " << (b.side == 0 ? "A" : "B") + << " " << b.stake/1000 + << " " << weight/1000 + << " " << As/1000 << "/" << Bs/1000 + << " " << fx(As, As + Bs) // tote-implied P(A) = poolA/(poolA+poolB) + << " x" << fx(As + Bnet, As) // oddsA = 1 + Bnet/As + << " x" << fx(Bs + Anet, Bs)); // oddsB = 1 + Anet/Bs + } + + // Pull the real curve weight/time_penalty the chain recorded (authoritative). + { + const auto& bidx = node.db().get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(market_id, pm_bet_id_type())); + it != bidx.end() && it->market == market_id; ++it) { + for (auto& c : caps) + if (c.name == std::string(it->account)) { c.weight = it->weight.value; c.tp = it->time_penalty; } + } + } + + // Analytic settlement via the production function — for BOTH outcomes. + struct proj { pm::settle_result res; int64_t total_weight; std::vector wi; int64_t losers; }; + auto project = [&](int8_t win_side) -> proj { + pm::settle_params sp; + sp.oracle_fee_percent = ORACLE_BP; sp.creator_fee_percent = CREATOR_BP; + sp.liquidity_fee_percent = LIQ_BP; sp.forfeit_pool = mkt0.forfeit_pool.value; + sp.oracle_fixed_fee = mkt0.oracle_fixed_fee.value; + std::vector w; proj P; P.total_weight = 0; P.losers = 0; + for (size_t i = 0; i < caps.size(); ++i) { + if (caps[i].side == win_side) { + w.push_back(pm::winner_in{caps[i].stake, caps[i].weight, caps[i].tp}); + P.wi.push_back(i); P.total_weight += caps[i].weight; + } else P.losers += caps[i].stake; + } + sp.losers_sum = P.losers; + P.res = pm::compute_settlement(sp, w); + return P; + }; + + auto print_table = [&](int8_t win_side, const char* label) -> pm::settle_result { + proj P = project(win_side); + const pm::settle_result& res = P.res; + const std::vector& wi = P.wi; + const int64_t total_weight = P.total_weight; + const int64_t losers = P.losers; + BOOST_TEST_MESSAGE(""); + BOOST_TEST_MESSAGE(std::string("=== SETTLEMENT — ") + label + " (losers' pool = " + + std::to_string(losers/1000) + " VIZ) ==="); + BOOST_TEST_MESSAGE(" commission off losers' pool: oracle=" << res.oracle_take/1000 + << " creator=" << res.creator_take/1000 << " liq(LP)=" << res.lp_bonus/1000 + << " VIZ → winners' pool=" << (losers - res.oracle_take - res.creator_take - res.lp_bonus)/1000); + BOOST_TEST_MESSAGE(" bettor stake weight wt-share profit payout coefficient(payout/stake)"); + for (size_t k = 0; k < wi.size(); ++k) { + const auto& c = caps[wi[k]]; + int64_t payout = res.winner_payout[k]; + int64_t profit = payout - c.stake; + BOOST_TEST_MESSAGE(" " << c.name << " " << c.stake/1000 + << " " << c.weight/1000 + << " " << fx(c.weight, total_weight) // weight share + << " " << profit/1000 + << " " << payout/1000 + << " x" << fx(payout, c.stake)); // realized decimal coefficient + } + // Losers: stake forfeited, coefficient 0. + for (size_t i = 0; i < caps.size(); ++i) + if (caps[i].side != win_side) + BOOST_TEST_MESSAGE(" " << caps[i].name << " " << caps[i].stake/1000 + << " " << caps[i].weight/1000 << " (lost) -" + << caps[i].stake/1000 << " 0 x0.00"); + return res; + }; + + const auto resA = print_table(0, "OUTCOME A WINS"); + const auto resB = print_table(1, "OUTCOME B WINS"); + + // ── Verify the analytic A-table equals the REAL on-chain settlement ── + std::vector bal_before(caps.size()); + for (size_t i = 0; i < caps.size(); ++i) + bal_before[i] = node.db().get_account(caps[i].name).balance.amount.value; + + // Early resolution needs the betting window closed (now >= betting_expiration). + for (int i = 0; i < 200 && + node.head_block_time() < node.db().get(market_id).betting_expiration; ++i) + produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; // A wins + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + // On-chain winner payout == compute_settlement payout (proves the printed math is real). + { + size_t k = 0; + for (size_t i = 0; i < caps.size(); ++i) { + if (caps[i].side != 0) continue; // A winners, in bet order + int64_t got = node.db().get_account(caps[i].name).balance.amount.value - bal_before[i]; + BOOST_CHECK_EQUAL(got, resA.winner_payout[k]); + ++k; + } + // Losers received nothing back. + for (size_t i = 0; i < caps.size(); ++i) + if (caps[i].side != 0) + BOOST_CHECK_EQUAL(node.db().get_account(caps[i].name).balance.amount.value, bal_before[i]); + } + + // ── Zero-sum invariant for both projections (no token emission) ── + auto check_zero_sum = [&](const pm::settle_result& res, int8_t win_side) { + int64_t out = res.oracle_take + res.creator_take + res.lp_bonus; + for (auto p : res.winner_payout) out += p; + int64_t in = 0; + for (auto& c : caps) in += c.stake; // winners' stakes + losers' stakes (forfeit_pool=0 here) + BOOST_CHECK_EQUAL(out, in); + (void)win_side; + }; + check_zero_sum(resA, 0); + check_zero_sum(resB, 1); + BOOST_TEST_MESSAGE(""); + BOOST_TEST_MESSAGE("zero-sum OK: Σpayout + oracle + creator + LP == Σ all stakes (no emission), both outcomes."); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Same odds & payout demonstration for a MULTI (LMSR) market, 3 outcomes A/B/C. +// The settlement math is identical (parimutuel by curve weight, fees off the +// losers' pool); only the weight comes from the LMSR cost function instead of CPMM. +// The live board generalises: outcome i's decimal coefficient = +// 1 + (Σ_{j≠i} pool_j) x (1 - commission) / pool_i. +BOOST_AUTO_TEST_CASE(betting_odds_and_payout_table_multi) { + auto gp = make_genesis_params(0x0DD6u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-odds-multi", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping multi odds table."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 5; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 5; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 5u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + struct better { std::string name; fc::ecc::private_key key; int16_t oidx; int64_t stake; }; + std::vector bs = { + { "anna", derive_key("anna"), 0, unit * 2 }, // A + { "bill", derive_key("bill"), 1, unit * 3 }, // B + { "cleo", derive_key("cleo"), 2, unit * 1 }, // C + { "andy", derive_key("andy"), 0, unit * 1 }, // A again + }; + for (auto& b : bs) create_and_fund(node, gp, when, b.name, b.key, share_type(b.stake + unit)); + + const int64_t L = unit * 30; + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 1; cm.outcomes = {"A", "B", "C"}; cm.url = "criteria"; + cm.oracle_fee_percent = 300; // 3.00% + cm.oracle_fixed_fee = asset(0, TOKEN_SYMBOL); + cm.creator_fee_percent = 0; + cm.liquidity_fee_percent = 200; // 2.00% + cm.liquidity = asset(share_type(L), TOKEN_SYMBOL); + cm.lmsr_b = graphene::chain::lmsr::lmsr_b_from_liquidity(L, 3); + cm.betting_expiration = node.head_block_time() + fc::seconds(120); + cm.result_expiration = node.head_block_time() + fc::seconds(240); + cm.allow_early_resolution = true; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + const auto& mkt0 = node.db().get(market_id); + BOOST_REQUIRE_EQUAL(mkt0.market_type, 1); + const uint16_t ORACLE_BP = mkt0.oracle_fee_percent; + const uint16_t CREATOR_BP = mkt0.creator_fee_percent; + const uint16_t LIQ_BP = mkt0.liquidity_fee_percent; + const int feeBp = (int)ORACLE_BP + (int)CREATOR_BP + (int)LIQ_BP; + const char* OUT[3] = { "A", "B", "C" }; + + auto fx = [](int64_t num, int64_t den) -> std::string { + if (den == 0) return "inf"; + int64_t v = num * 100 / den; + std::string f = std::to_string(v % 100); + if (f.size() < 2) f = "0" + f; + return std::to_string(v / 100) + "." + f; + }; + auto pool_of = [&](int oi) -> int64_t { + const auto& oo = node.db().get_index().indices().get(); + auto it = oo.lower_bound(boost::make_tuple(market_id, (uint8_t)oi)); + return (it != oo.end() && it->market == market_id && it->outcome_index == oi) ? it->bets_sum.value : 0; + }; + + BOOST_TEST_MESSAGE(""); + BOOST_TEST_MESSAGE("=== LIVE BOARD (multi/LMSR, commission baked in) — updates per bet ==="); + BOOST_TEST_MESSAGE("LMSR b=" << mkt0.lmsr_b.value/1000 << " VIZ | commission off losers' pool = " + << fx(feeBp,100) << "% (odds_i = 1 + Σ_other x (1-comm) / pool_i)"); + BOOST_TEST_MESSAGE("step bettor out stake weight(tokens) poolA/poolB/poolC oddsA / oddsB / oddsC"); + + struct cap { std::string name; int16_t oidx; int64_t stake; int64_t weight; uint32_t tp; }; + std::vector caps; + + int step = 0; + for (auto& b : bs) { + pm_place_bet_operation pb; + pb.account = b.name; pb.market_id = 0; pb.side = -1; pb.outcome_index = b.oidx; + pb.amount = asset(share_type(b.stake), TOKEN_SYMBOL); pb.mode = 0; + node.push_pending_transaction(sign_ops({pb}, b.key, node)); + produce(node, gp, when); + + // Read back this bet's LMSR curve weight (tokens) from the chain. + int64_t myw = 0; uint32_t mytp = 0; + { + const auto& bidx = node.db().get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(market_id, pm_bet_id_type())); + it != bidx.end() && it->market == market_id; ++it) + if (std::string(it->account) == b.name) { myw = it->weight.value; mytp = it->time_penalty; } + } + caps.push_back({ b.name, b.oidx, b.stake, myw, mytp }); + + int64_t pool[3] = { pool_of(0), pool_of(1), pool_of(2) }; + const int64_t tot = pool[0] + pool[1] + pool[2]; + std::string odds; + for (int i = 0; i < 3; ++i) { + int64_t net = (tot - pool[i]) * (10000 - feeBp) / 10000; + odds += (i ? " / " : "") + std::string("x") + fx(pool[i] + net, pool[i]); + } + BOOST_TEST_MESSAGE(" " << ++step << " " << b.name << " " << OUT[b.oidx] + << " " << b.stake/1000 + << " " << myw/1000 + << " " << pool[0]/1000 << "/" << pool[1]/1000 << "/" << pool[2]/1000 + << " " << odds); + } + + struct proj { pm::settle_result res; int64_t total_weight; std::vector wi; int64_t losers; }; + auto project = [&](int16_t win) -> proj { + pm::settle_params sp; + sp.oracle_fee_percent = ORACLE_BP; sp.creator_fee_percent = CREATOR_BP; + sp.liquidity_fee_percent = LIQ_BP; sp.forfeit_pool = mkt0.forfeit_pool.value; + sp.oracle_fixed_fee = mkt0.oracle_fixed_fee.value; + std::vector w; proj P; P.total_weight = 0; P.losers = 0; + for (size_t i = 0; i < caps.size(); ++i) { + if (caps[i].oidx == win) { + w.push_back(pm::winner_in{caps[i].stake, caps[i].weight, caps[i].tp}); + P.wi.push_back(i); P.total_weight += caps[i].weight; + } else P.losers += caps[i].stake; + } + sp.losers_sum = P.losers; + P.res = pm::compute_settlement(sp, w); + return P; + }; + + auto print_table = [&](int16_t win) -> pm::settle_result { + proj P = project(win); + const pm::settle_result& res = P.res; + BOOST_TEST_MESSAGE(""); + BOOST_TEST_MESSAGE(std::string("=== SETTLEMENT — OUTCOME ") + OUT[win] + " WINS (losers' pool = " + + std::to_string(P.losers/1000) + " VIZ) ==="); + BOOST_TEST_MESSAGE(" commission: oracle=" << res.oracle_take/1000 << " creator=" << res.creator_take/1000 + << " liq(LP)=" << res.lp_bonus/1000 << " VIZ → winners' pool=" + << (P.losers - res.oracle_take - res.creator_take - res.lp_bonus)/1000); + BOOST_TEST_MESSAGE(" bettor out stake weight wt-share profit payout coefficient"); + for (size_t k = 0; k < P.wi.size(); ++k) { + const auto& c = caps[P.wi[k]]; + int64_t payout = res.winner_payout[k]; + BOOST_TEST_MESSAGE(" " << c.name << " " << OUT[c.oidx] << " " << c.stake/1000 + << " " << c.weight/1000 << " " << fx(c.weight, P.total_weight) + << " " << (payout - c.stake)/1000 << " " << payout/1000 + << " x" << fx(payout, c.stake)); + } + for (size_t i = 0; i < caps.size(); ++i) + if (caps[i].oidx != win) + BOOST_TEST_MESSAGE(" " << caps[i].name << " " << OUT[caps[i].oidx] << " " + << caps[i].stake/1000 << " " << caps[i].weight/1000 << " (lost) -" + << caps[i].stake/1000 << " 0 x0.00"); + return res; + }; + + const auto resA = print_table(0); + print_table(1); + print_table(2); + + // Verify the analytic A-table equals the REAL on-chain settlement. + std::vector bal_before(caps.size()); + for (size_t i = 0; i < caps.size(); ++i) + bal_before[i] = node.db().get_account(caps[i].name).balance.amount.value; + + // Early resolution needs the betting window closed (now >= betting_expiration). + for (int i = 0; i < 200 && + node.head_block_time() < node.db().get(market_id).betting_expiration; ++i) + produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; // A + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + { + size_t k = 0; + for (size_t i = 0; i < caps.size(); ++i) { + if (caps[i].oidx != 0) continue; + int64_t got = node.db().get_account(caps[i].name).balance.amount.value - bal_before[i]; + BOOST_CHECK_EQUAL(got, resA.winner_payout[k]); + ++k; + } + for (size_t i = 0; i < caps.size(); ++i) + if (caps[i].oidx != 0) + BOOST_CHECK_EQUAL(node.db().get_account(caps[i].name).balance.amount.value, bal_before[i]); + } + + BOOST_TEST_MESSAGE(""); + BOOST_TEST_MESSAGE("multi LMSR: on-chain winner payouts match compute_settlement; zero-sum preserved."); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Long market: 1000 mixed bets (random side & size), measuring how far the +// commission-baked decimal coefficient SHOWN to a bettor at bet time drifts from +// the FINAL coefficient at close. In a parimutuel board the displayed odds are not +// locked: odds_side = 1 + opposing_pool x (1 - commission) / own_pool, and both +// pools keep growing. Early bettors (thin pool) see the biggest drift; late bettors +// see almost none. The board number depends only on the per-side staked pools, so a +// deep liquidity seed (kept here only to keep the CPMM curve well-behaved) does not +// affect it. All 1000 bets come from the high-stake initiator (one account may bet +// both sides; nothing restricts it) so bandwidth/funding are never the bottleneck. +BOOST_AUTO_TEST_CASE(odds_drift_long_market_1000_bets) { + auto gp = make_genesis_params(0x0DD7u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-drift", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping drift simulation."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 5; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 5; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 5u); + } + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + // Deep seed so 1000 bets never drain a CPMM reserve to zero (board math is + // independent of this). oracle 3% + liquidity 2% = 5% commission. + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.oracle_fee_percent = 300; + cm.oracle_fixed_fee = asset(0, TOKEN_SYMBOL); + cm.creator_fee_percent = 0; + cm.liquidity_fee_percent = 200; + cm.liquidity = asset(share_type(unit * 400), TOKEN_SYMBOL); + // Betting window must outlast the ~100 placement blocks (~300s) but still close so the + // oracle can resolve early (can_resolve_early requires now >= betting_expiration). + cm.betting_expiration = node.head_block_time() + fc::seconds(600); + cm.result_expiration = node.head_block_time() + fc::seconds(1200); + cm.allow_early_resolution = true; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + const auto& mkt0 = node.db().get(market_id); + const int feeBp = (int)mkt0.oracle_fee_percent + (int)mkt0.creator_fee_percent + (int)mkt0.liquidity_fee_percent; + const double nf = 1.0 - feeBp / 10000.0; // net-of-commission factor + + // Place 1000 bets, side & size from a deterministic xorshift PRNG (reproducible). + // Bundle 10 ops/tx → 100 blocks. Stakes 5..40 VIZ. + const int N = 1000; + const int64_t base = unit / 20; // 5 VIZ + uint64_t rng = 0x9E3779B97F4A7C15ull; + auto nr = [&]() -> uint64_t { rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; return rng; }; + + std::vector batch; + for (int i = 0; i < N; ++i) { + pm_place_bet_operation pb; + pb.account = gp.initiator_name; pb.market_id = 0; + pb.side = (int8_t)(nr() & 1ull); pb.outcome_index = -1; + pb.amount = asset(share_type(base * (int64_t)(1 + nr() % 8)), TOKEN_SYMBOL); + pb.mode = 0; + batch.push_back(pb); + if (batch.size() == 10) { + node.push_pending_transaction(sign_ops(batch, gp.initiator_key, node)); + produce(node, gp, when); + batch.clear(); + } + } + if (!batch.empty()) { node.push_pending_transaction(sign_ops(batch, gp.initiator_key, node)); produce(node, gp, when); } + + // Read all bets in creation order (= bet-id order) → authoritative (side, stake, weight). + std::vector side; std::vector stake, weight; + { + const auto& bidx = node.db().get_index().indices().get(); + for (auto it = bidx.lower_bound(boost::make_tuple(market_id, pm_bet_id_type())); + it != bidx.end() && it->market == market_id; ++it) { + side.push_back(it->side); stake.push_back(it->amount.value); weight.push_back(it->weight.value); + } + } + BOOST_REQUIRE_EQUAL((int)side.size(), N); + + // Final per-side pools (stake) and curve-weight totals. + int64_t PA = 0, PB = 0; double WA = 0, WB = 0; + for (int i = 0; i < N; ++i) { + if (side[i] == 0) { PA += stake[i]; WA += (double)weight[i]; } + else { PB += stake[i]; WB += (double)weight[i]; } + } + + // Replay the running board; compute SHOWN (at bet time, post-own-bet) vs FINAL + // (at close) for two views: (1) board side-level coefficient = what the UI shows; + // (2) the bettor's personal weight-based coefficient = what they actually realise. + int64_t rPA = 0, rPB = 0; double rWA = 0, rWB = 0; + std::vector shown(N), finalc(N), absdrift(N), absdriftP(N); + for (int i = 0; i < N; ++i) { + double w = (double)weight[i], s = (double)stake[i]; + double ownP, oppP, ownW, fOwnP, fOppP, fOwnW; + if (side[i] == 0) { + rPA += stake[i]; rWA += w; + ownP = (double)rPA; oppP = (double)rPB; ownW = rWA; + fOwnP = (double)PA; fOppP = (double)PB; fOwnW = WA; + } else { + rPB += stake[i]; rWB += w; + ownP = (double)rPB; oppP = (double)rPA; ownW = rWB; + fOwnP = (double)PB; fOppP = (double)PA; fOwnW = WB; + } + shown[i] = 1.0 + oppP * nf / ownP; // board, at bet time + finalc[i] = 1.0 + fOppP * nf / fOwnP; // board, at close + absdrift[i] = std::fabs((finalc[i] - shown[i]) / shown[i]); + + double shownP = 1.0 + oppP * nf * w / (ownW * s); // personal, at bet time + double finalP = 1.0 + fOppP * nf * w / (fOwnW * s); // personal, at close + absdriftP[i] = std::fabs((finalP - shownP) / shownP); + } + + // ── Report ── + char buf[220]; + BOOST_TEST_MESSAGE(""); + std::snprintf(buf, sizeof(buf), "=== ODDS DRIFT over %d bets (board = 1 + opp_pool x %.2f / own_pool; commission %d%%) ===", + N, nf, feeBp/100); + BOOST_TEST_MESSAGE(buf); + std::snprintf(buf, sizeof(buf), "final pools: A=%lld VIZ B=%lld VIZ -> final board oddsA=x%.3f oddsB=x%.3f", + (long long)(PA/1000), (long long)(PB/1000), 1.0 + (double)PB*nf/PA, 1.0 + (double)PA*nf/PB); + BOOST_TEST_MESSAGE(buf); + BOOST_TEST_MESSAGE("decile avg-shown avg-final avg|drift|% (board coefficient)"); + for (int d = 0; d < 10; ++d) { + double ss = 0, sfv = 0, sd = 0; int lo = d * (N/10), hi = lo + (N/10); + for (int i = lo; i < hi; ++i) { ss += shown[i]; sfv += finalc[i]; sd += absdrift[i]; } + int cnt = hi - lo; + std::snprintf(buf, sizeof(buf), " %2d x%.3f x%.3f %6.2f%%", + d + 1, ss/cnt, sfv/cnt, 100.0 * sd/cnt); + BOOST_TEST_MESSAGE(buf); + } + + std::vector sorted = absdrift; std::sort(sorted.begin(), sorted.end()); + double mean = 0; for (double x : absdrift) mean += x; mean /= N; + double meanP = 0; for (double x : absdriftP) meanP += x; meanP /= N; + int within5 = 0, within20 = 0; + for (double x : absdrift) { if (x <= 0.05) ++within5; if (x <= 0.20) ++within20; } + std::snprintf(buf, sizeof(buf), "board drift |d|: mean=%.2f%% median=%.2f%% p90=%.2f%% max=%.2f%%", + 100*mean, 100*sorted[N/2], 100*sorted[(N*9)/10], 100*sorted[N-1]); + BOOST_TEST_MESSAGE(buf); + std::snprintf(buf, sizeof(buf), "within +/-5%%: %d/%d bets within +/-20%%: %d/%d | personal(weight) drift mean=%.2f%%", + within5, N, within20, N, 100*meanP); + BOOST_TEST_MESSAGE(buf); + BOOST_TEST_MESSAGE("read: early bets are quoted on a near-empty pool so their shown odds drift hard;" + " by the last decile the board has converged."); + + // Sanity: the 1000-bet market settles cleanly on-chain (the PM zero-sum invariant + // itself is asserted in the dedicated payout-table tests). + // Early resolution needs the betting window closed (now >= betting_expiration). + for (int i = 0; i < 400 && node.head_block_time() < mkt0.betting_expiration; ++i) + produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 300 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_CHECK_EQUAL(node.db().get(market_id).payout_status, 3); +} + +// Binary, but order flow is heavily skewed (~80% on outcome A) → a lopsided board: +// favourite near x1.2, underdog ~x4-5. Shows the drift on a non-even market. +BOOST_AUTO_TEST_CASE(odds_drift_binary_skewed) { + auto gp = make_genesis_params(0x0DD8u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-drift-skew", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + { chain_properties_pm props; props.pm_dispute_grace_sec = 5; + versioned_chain_properties_update_operation vp; vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 5; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 5u); } + pm_oracle_register_operation oreg; oreg.owner = gp.initiator_name; + oreg.insurance = mp.pm_min_oracle_insurance; oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.oracle_fee_percent = 300; cm.oracle_fixed_fee = asset(0, TOKEN_SYMBOL); + cm.creator_fee_percent = 0; cm.liquidity_fee_percent = 200; + cm.liquidity = asset(share_type(unit * 400), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(600); + cm.result_expiration = node.head_block_time() + fc::seconds(1200); + cm.allow_early_resolution = true; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + const auto& mkt0 = node.db().get(market_id); + const int feeBp = (int)mkt0.oracle_fee_percent + (int)mkt0.creator_fee_percent + (int)mkt0.liquidity_fee_percent; + + const int N = 1000; const int64_t base = unit / 20; + uint64_t rng = 0xBADC0FFEE0DDF00Dull; + auto nr = [&]() -> uint64_t { rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; return rng; }; + std::vector outc(N); std::vector stk(N); + for (int i = 0; i < N; ++i) { + outc[i] = (nr() % 100 < 80) ? 0 : 1; // ~80% of bets on A + stk[i] = base * (int64_t)(1 + nr() % 8); + } + run_drift_market(node, gp, when, /*binary=*/true, /*outcomes=*/2, feeBp, outc, stk, market_id, + "BINARY skewed (~80% on A)"); +} + +// Multi (LMSR, 3 outcomes) with 1000 bets split 50/30/20 across A/B/C — the parimutuel +// board generalises (odds_i = 1 + Σ_other x (1-comm) / pool_i) and drifts the same way. +BOOST_AUTO_TEST_CASE(odds_drift_multi_1000) { + auto gp = make_genesis_params(0x0DD9u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-drift-multi", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + { chain_properties_pm props; props.pm_dispute_grace_sec = 5; + versioned_chain_properties_update_operation vp; vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 5; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 5u); } + pm_oracle_register_operation oreg; oreg.owner = gp.initiator_name; + oreg.insurance = mp.pm_min_oracle_insurance; oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + const int64_t L = unit * 400; + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 1; cm.outcomes = {"A", "B", "C"}; cm.url = "criteria"; + cm.oracle_fee_percent = 300; cm.oracle_fixed_fee = asset(0, TOKEN_SYMBOL); + cm.creator_fee_percent = 0; cm.liquidity_fee_percent = 200; + cm.liquidity = asset(share_type(L), TOKEN_SYMBOL); + cm.lmsr_b = graphene::chain::lmsr::lmsr_b_from_liquidity(L, 3); + cm.betting_expiration = node.head_block_time() + fc::seconds(600); + cm.result_expiration = node.head_block_time() + fc::seconds(1200); + cm.allow_early_resolution = true; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + const auto& mkt0 = node.db().get(market_id); + BOOST_REQUIRE_EQUAL(mkt0.market_type, 1); + const int feeBp = (int)mkt0.oracle_fee_percent + (int)mkt0.creator_fee_percent + (int)mkt0.liquidity_fee_percent; + + const int N = 1000; const int64_t base = unit / 20; + uint64_t rng = 0xFEEDFACECAFEB00Dull; + auto nr = [&]() -> uint64_t { rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; return rng; }; + std::vector outc(N); std::vector stk(N); + for (int i = 0; i < N; ++i) { + uint64_t r = nr() % 100; + outc[i] = (r < 50) ? 0 : (r < 80 ? 1 : 2); // 50 / 30 / 20 + stk[i] = base * (int64_t)(1 + nr() % 8); + } + run_drift_market(node, gp, when, /*binary=*/false, /*outcomes=*/3, feeBp, outc, stk, market_id, + "MULTI/LMSR (50/30/20)"); +} + +// #58 — Oracle rebuttal on a dispute (pm_dispute_oracle_respond) + resolution justification stored +// on the market (pm_resolve_market.decision_reason / decision_url). Account-mode market so a named +// dispute exists to rebut. +BOOST_AUTO_TEST_CASE(oracle_rebuttal_and_decision_reason) { + auto gp = make_genesis_params(0x58EEu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-rebuttal", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping rebuttal."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 1; cm.dispute_resolver = "judge"; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + rm.decision_url = "https://evidence.example/x"; rm.decision_reason = "A clearly won per source"; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + // J: the oracle's justification is stored on the market and readable directly. + { + const auto& m = node.db().get(market_id); + BOOST_CHECK_EQUAL(to_string(m.decision_url), std::string("https://evidence.example/x")); + BOOST_CHECK_EQUAL(to_string(m.decision_reason), std::string("A clearly won per source")); + } + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 2); + + // D: a non-oracle cannot rebut. + pm_dispute_oracle_respond_operation badresp; + badresp.oracle = "alice"; badresp.market_id = 0; badresp.response = "not mine"; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({badresp}, alice_key, node)), + std::runtime_error); + + // D: the market oracle rebuts; the response is stored on the dispute for all viewers. + pm_dispute_oracle_respond_operation resp; + resp.oracle = "orac"; resp.market_id = 0; resp.response = "Source confirms A; dispute is wrong"; + node.push_pending_transaction(sign_ops({resp}, orac_key, node)); + produce(node, gp, when); + + const auto& didx = node.db().get_index().indices().get(); + auto dit = didx.find(market_id); + BOOST_REQUIRE(dit != didx.end()); + BOOST_CHECK_EQUAL(to_string(dit->oracle_response), std::string("Source confirms A; dispute is wrong")); + BOOST_CHECK(dit->oracle_response_time != fc::time_point_sec()); + BOOST_TEST_MESSAGE("rebuttal stored; decision_reason persisted on market"); +} + +// #59 — Manual unban (pm_unban): the account-mode resolver that imposed an oracle+creator ban lifts +// it early; a non-issuer is rejected; the creator can then open a market again. +BOOST_AUTO_TEST_CASE(resolver_unban_lifts_ban) { + auto gp = make_genesis_params(0x59BAu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-unban", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping unban."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t creation_fee = mp.pm_market_creation_fee.amount.value; + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"), maker_key = derive_key("maker"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + create_and_fund(node, gp, when, "maker", maker_key, share_type(creation_fee * 4 + unit * 20)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = "maker"; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 1; cm.dispute_resolver = "judge"; + node.push_pending_transaction(sign_ops({cm}, maker_key, node)); + produce(node, gp, when); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + + // Resolver flips + bans BOTH the oracle and the creator far into the future. + const fc::time_point_sec ban_until = node.head_block_time() + fc::seconds(1000000); + pm_dispute_resolve_operation dr; + dr.resolver = "judge"; dr.market_id = 0; dr.correct_outcome = 1; + dr.penalty_amount = asset(unit, TOKEN_SYMBOL); + dr.ban_oracle = true; dr.ban_oracle_until = ban_until; + dr.ban_creator = true; dr.ban_creator_until = ban_until; + node.push_pending_transaction(sign_ops({dr}, judge_key, node)); + produce(node, gp, when); + + const auto& oidx = node.db().get_index().indices().get(); + const auto& cbidx = node.db().get_index().indices().get(); + BOOST_REQUIRE(oidx.find(account_name_type("orac"))->banned_until == ban_until); + BOOST_REQUIRE(oidx.find(account_name_type("orac"))->banned_by == account_name_type("judge")); + BOOST_REQUIRE(cbidx.find(account_name_type("maker")) != cbidx.end()); + BOOST_REQUIRE(cbidx.find(account_name_type("maker"))->banned_until == ban_until); + + // A non-issuer cannot lift the ban. + pm_unban_operation badu; + badu.resolver = "alice"; badu.target = "maker"; badu.unban_creator = true; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({badu}, alice_key, node)), + std::runtime_error); + + // The issuing resolver lifts both bans. + pm_unban_operation u; + u.resolver = "judge"; u.target = "orac"; u.unban_oracle = true; + node.push_pending_transaction(sign_ops({u}, judge_key, node)); + pm_unban_operation u2; + u2.resolver = "judge"; u2.target = "maker"; u2.unban_creator = true; + node.push_pending_transaction(sign_ops({u2}, judge_key, node)); + produce(node, gp, when); + + BOOST_CHECK(oidx.find(account_name_type("orac"))->banned_until == fc::time_point_sec(0)); + BOOST_CHECK(cbidx.find(account_name_type("maker"))->banned_until == fc::time_point_sec(0)); + + // The un-banned creator can open a new market again. + pm_create_market_operation cm2; + cm2.creator = "maker"; cm2.oracle = "orac"; + cm2.market_type = 0; cm2.outcomes = {"A", "B"}; cm2.url = "again"; + cm2.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm2.betting_expiration = node.head_block_time() + fc::seconds(30); + cm2.result_expiration = node.head_block_time() + fc::seconds(90); + cm2.dispute_mode = 1; cm2.dispute_resolver = "judge"; + BOOST_CHECK_NO_THROW(node.push_pending_transaction(sign_ops({cm2}, maker_key, node))); + produce(node, gp, when); + BOOST_TEST_MESSAGE("unban: judge lifted oracle+creator bans; maker re-created a market"); +} + +// #60 — Automatic ban expiry: a short creator ban lapses at banned_until and the per-block cron +// clears it (banned_until → 0) and emits pm_ban_expired, after which the creator can act again. +BOOST_AUTO_TEST_CASE(creator_ban_auto_expires) { + auto gp = make_genesis_params(0x60AEu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-banexpiry", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping ban expiry."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t creation_fee = mp.pm_market_creation_fee.amount.value; + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"), maker_key = derive_key("maker"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + create_and_fund(node, gp, when, "maker", maker_key, share_type(creation_fee * 4 + unit * 20)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = "maker"; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 1; cm.dispute_resolver = "judge"; + node.push_pending_transaction(sign_ops({cm}, maker_key, node)); + produce(node, gp, when); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + + // Ban the creator for a SHORT window (~9s ≈ 3 blocks). + const fc::time_point_sec ban_until = node.head_block_time() + fc::seconds(9); + pm_dispute_resolve_operation dr; + dr.resolver = "judge"; dr.market_id = 0; dr.correct_outcome = 1; + dr.penalty_amount = asset(unit, TOKEN_SYMBOL); + dr.ban_creator = true; dr.ban_creator_until = ban_until; + node.push_pending_transaction(sign_ops({dr}, judge_key, node)); + produce(node, gp, when); + + const auto& cbidx = node.db().get_index().indices().get(); + BOOST_REQUIRE(cbidx.find(account_name_type("maker"))->banned_until == ban_until); + + // Advance past banned_until; the cron sweep clears it (→ 0) and emits pm_ban_expired. + for (int i = 0; i < 20 && + cbidx.find(account_name_type("maker"))->banned_until != fc::time_point_sec(0); ++i) + produce(node, gp, when); + BOOST_CHECK(cbidx.find(account_name_type("maker"))->banned_until == fc::time_point_sec(0)); + + // Creator can act again after automatic expiry. + pm_create_market_operation cm2; + cm2.creator = "maker"; cm2.oracle = "orac"; + cm2.market_type = 0; cm2.outcomes = {"A", "B"}; cm2.url = "afterexpiry"; + cm2.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm2.betting_expiration = node.head_block_time() + fc::seconds(30); + cm2.result_expiration = node.head_block_time() + fc::seconds(90); + cm2.dispute_mode = 1; cm2.dispute_resolver = "judge"; + BOOST_CHECK_NO_THROW(node.push_pending_transaction(sign_ops({cm2}, maker_key, node))); + produce(node, gp, when); + BOOST_TEST_MESSAGE("ban auto-expired via cron; maker re-created a market"); +} + +// #61 — Ban-expiry virtual op. A short oracle+creator ban lapses; the cron emits pm_ban_expired +// (one per lifted ban, carrying the account and which ban(s) lifted). +BOOST_AUTO_TEST_CASE(ban_expired_vop_emitted) { + auto gp = make_genesis_params(0x61EEu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-banvop", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping ban-expiry vop."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t creation_fee = mp.pm_market_creation_fee.amount.value; + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"), maker_key = derive_key("maker"); + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + create_and_fund(node, gp, when, "maker", maker_key, share_type(creation_fee * 4 + unit * 20)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = "maker"; cm.oracle = "orac"; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 1; cm.dispute_resolver = "judge"; + node.push_pending_transaction(sign_ops({cm}, maker_key, node)); + produce(node, gp, when); + + pm_oracle_accept_market_operation acc; + acc.oracle = "orac"; acc.market_id = 0; acc.accept = true; + node.push_pending_transaction(sign_ops({acc}, orac_key, node)); + produce(node, gp, when); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = "orac"; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, orac_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + + const fc::time_point_sec ban_until = node.head_block_time() + fc::seconds(9); + pm_dispute_resolve_operation dr; + dr.resolver = "judge"; dr.market_id = 0; dr.correct_outcome = 1; + dr.penalty_amount = asset(unit, TOKEN_SYMBOL); + dr.ban_oracle = true; dr.ban_oracle_until = ban_until; + dr.ban_creator = true; dr.ban_creator_until = ban_until; + node.push_pending_transaction(sign_ops({dr}, judge_key, node)); + produce(node, gp, when); + + // Capture pm_ban_expired vops emitted by the cron once banned_until lapses. + std::vector lifts; + node.db().enable_plugins_on_push_transaction(true); + auto conn = node.db().post_apply_operation.connect([&](const operation_notification& note) { + if (note.op.which() == operation::tag::value) + lifts.push_back(note.op.get()); + }); + + for (int i = 0; i < 20 && lifts.size() < 2; ++i) produce(node, gp, when); + conn.disconnect(); + + bool oracle_lift = false, creator_lift = false; + for (const auto& l : lifts) { + if (l.account == account_name_type("orac") && l.oracle) oracle_lift = true; + if (l.account == account_name_type("maker") && l.creator) creator_lift = true; + } + BOOST_CHECK(oracle_lift); + BOOST_CHECK(creator_lift); + const auto& oidx = node.db().get_index().indices().get(); + const auto& cbidx = node.db().get_index().indices().get(); + BOOST_CHECK(oidx.find(account_name_type("orac"))->banned_until == fc::time_point_sec(0)); + BOOST_CHECK(cbidx.find(account_name_type("maker"))->banned_until == fc::time_point_sec(0)); + BOOST_TEST_MESSAGE("pm_ban_expired emitted for oracle+creator on auto-expiry"); +} + +// #62 — No-contest stores its rationale on the market as decision_reason (readable via get_market). +BOOST_AUTO_TEST_CASE(no_contest_records_decision_reason) { + auto gp = make_genesis_params(0x62AAu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-nocontest-reason", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping no-contest reason."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + produce(node, gp, when); + + pm_no_contest_operation nc; + nc.oracle = gp.initiator_name; nc.market_id = 0; + nc.reason = "source unavailable, cannot verify"; + node.push_pending_transaction(sign_ops({nc}, gp.initiator_key, node)); + produce(node, gp, when); + + const auto& m = node.db().get(market_id); + BOOST_CHECK_EQUAL(m.resolved_outcome, -1); + BOOST_CHECK_EQUAL(to_string(m.decision_reason), std::string("source unavailable, cannot verify")); + BOOST_TEST_MESSAGE("no-contest reason stored on market.decision_reason"); +} + +// #63 — pm_unban guards: lifting a ban that does not exist / is not active is rejected. +BOOST_AUTO_TEST_CASE(unban_rejected_when_not_banned) { + auto gp = make_genesis_params(0x63BBu, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-unban-guard", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping unban guard."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t reg_fee = mp.pm_oracle_registration_fee.amount.value; + + auto orac_key = derive_key("orac"), judge_key = derive_key("judge"); + create_and_fund(node, gp, when, "orac", orac_key, + share_type(mp.pm_min_oracle_insurance.amount.value + reg_fee + unit * 2)); + create_and_fund(node, gp, when, "judge", judge_key, share_type(unit * 2)); + pm_oracle_register_operation oreg; + oreg.owner = "orac"; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, orac_key, node)); + produce(node, gp, when); + + // Oracle exists but is NOT banned → unban_oracle is rejected. + pm_unban_operation u1; + u1.resolver = "judge"; u1.target = "orac"; u1.unban_oracle = true; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({u1}, judge_key, node)), + std::runtime_error); + + // No creator-ban row for the target → unban_creator is rejected. + pm_unban_operation u2; + u2.resolver = "judge"; u2.target = "orac"; u2.unban_creator = true; + BOOST_CHECK_THROW(node.push_pending_transaction(sign_ops({u2}, judge_key, node)), + std::runtime_error); + BOOST_TEST_MESSAGE("unban rejected when there is no active ban"); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Terminal-market garbage collection — all five ways a market can die must, after +// pm_closed_market_retention_sec, have their whole object cluster reclaimed from state. +// Each case first drives the market to its terminal state (asserting finalized_time is +// stamped), then advances past the (test-short) retention and asserts the market and its +// bets/outcomes/liquidity/… are gone. Deterministic cron ⇒ identical on every node. +// ───────────────────────────────────────────────────────────────────────────── + +// Death #5 — resolved + auto-paid. +BOOST_AUTO_TEST_CASE(gc_resolved_market_after_retention) { + auto gp = make_genesis_params(0x6C01u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-gc-resolved", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + publish_fast_pm_props(node, gp, when, chain_properties_pm{}); + register_self_oracle(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); // past betting_expiration + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + for (int i = 0; i < 120 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + assert_gc_after_retention(node, gp, when, market_id); + BOOST_TEST_MESSAGE("resolved market GC'd after retention"); +} + +// Death #1 — oracle rejects the market's terms. +BOOST_AUTO_TEST_CASE(gc_oracle_reject_after_retention) { + auto gp = make_genesis_params(0x6C02u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-gc-reject", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t fee = mp.pm_market_creation_fee.amount.value; + publish_fast_pm_props(node, gp, when, chain_properties_pm{}); + register_self_oracle(node, gp, when); // viz is the (external) oracle + + auto carol_key = derive_key("carol"); + create_and_fund(node, gp, when, "carol", carol_key, share_type(unit * 4 + fee + unit)); + + pm_create_market_operation cm; + cm.creator = "carol"; cm.oracle = gp.initiator_name; // external oracle → pending + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(60); + cm.result_expiration = node.head_block_time() + fc::seconds(120); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, carol_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, 0); // pending + + pm_oracle_accept_market_operation rej; + rej.oracle = gp.initiator_name; rej.market_id = 0; rej.accept = false; + node.push_pending_transaction(sign_ops({rej}, gp.initiator_key, node)); + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, -1); // rejected + + assert_gc_after_retention(node, gp, when, market_id); + BOOST_TEST_MESSAGE("oracle-rejected market GC'd after retention"); +} + +// Death #2 — oracle never accepts; the accept window expires and the cron voids the market. +BOOST_AUTO_TEST_CASE(gc_accept_window_expired_after_retention) { + auto gp = make_genesis_params(0x6C03u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-gc-expired", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + const int64_t fee = mp.pm_market_creation_fee.amount.value; + chain_properties_pm props; props.pm_oracle_accept_window_sec = 15; // short accept window + publish_fast_pm_props(node, gp, when, props); + register_self_oracle(node, gp, when); // viz is the (external) oracle + + auto carol_key = derive_key("carol"); + create_and_fund(node, gp, when, "carol", carol_key, share_type(unit * 4 + fee + unit)); + + pm_create_market_operation cm; + cm.creator = "carol"; cm.oracle = gp.initiator_name; // external oracle → pending + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(120); + cm.result_expiration = node.head_block_time() + fc::seconds(240); + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, carol_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + // Never accept — advance past the accept window; the cron voids the market (status -1). + for (int i = 0; i < 120 && node.db().get(market_id).status != -1; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, -1); // expired-unaccepted + + assert_gc_after_retention(node, gp, when, market_id); + BOOST_TEST_MESSAGE("accept-window-expired market GC'd after retention"); +} + +// Death #3 — oracle misses the resolution deadline; the cron refunds and closes the market. +BOOST_AUTO_TEST_CASE(gc_oracle_missed_after_retention) { + auto gp = make_genesis_params(0x6C04u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-gc-missed", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + publish_fast_pm_props(node, gp, when, chain_properties_pm{}); + register_self_oracle(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(60); + cm.allow_early_resolution = false; cm.dispute_mode = 0; // let it expire unresolved + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Never resolve — advance past result_expiration; the missed-oracle cron closes it. + for (int i = 0; i < 120 && node.db().get(market_id).status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, 3); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + assert_gc_after_retention(node, gp, when, market_id); + BOOST_TEST_MESSAGE("oracle-missed market GC'd after retention"); +} + +// Death #4 — a dispute is filed but nobody votes; the anti-freeze auto-close voids the market. +BOOST_AUTO_TEST_CASE(gc_dispute_auto_close_after_retention) { + auto gp = make_genesis_params(0x6C05u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-gc-autoclose", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + if (!bring_to_hf14(node, gp, when)) { BOOST_TEST_MESSAGE("HF14 not reachable; skipping."); return; } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + chain_properties_pm props; + props.pm_oracle_dispute_response_sec = 5; + props.pm_dispute_vote_period_sec = 600; + props.pm_dispute_auto_close_sec = 5; + props.pm_dispute_fee = asset(unit, TOKEN_SYMBOL); + publish_fast_pm_props(node, gp, when, props); + register_self_oracle(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 4)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 4)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + pm_place_bet_operation ba; + ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb = ba; bb.account = "bob"; bb.side = 1; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + for (int i = 0; i < 15; ++i) produce(node, gp, when); // past betting_expiration + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_dispute_create_operation dc; + dc.disputer = "bob"; dc.market_id = 0; dc.proposed_outcome = 1; dc.reason = "B won"; + node.push_pending_transaction(sign_ops({dc}, bob_key, node)); + produce(node, gp, when); + + // Nobody votes — advance past auto_close_time; the cron voids the market. + for (int i = 0; i < 200 && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).resolved_outcome, -1); // voided + + assert_gc_after_retention(node, gp, when, market_id); + BOOST_TEST_MESSAGE("dispute-auto-closed market GC'd after retention"); +} + +// ── Batch-epoch-settle scheduling (PR #139 + cursor follow-up) ─────────────── +// The per-block cap must budget WORK, not markets: idle allow_batch markets must +// not consume it (or starve everyone behind them), and the scan must round-robin +// so always-busy low-id markets can't permanently starve newer ones. +namespace { + + // Reproduces verify_commit's byte layout exactly (same as batch_commit_reveal_and_forfeit). + fc::sha256 batch_commitment(const account_name_type& acct, int64_t mid, int8_t side, + int16_t oidx, int64_t amount, int64_t min_tokens, + const std::string& salt) { + fc::sha256::encoder enc; + enc.write((const char*)&mid, sizeof(mid)); + enc.write((const char*)&acct.data, sizeof(acct.data)); + enc.write((const char*)&side, sizeof(side)); + enc.write((const char*)&oidx, sizeof(oidx)); + enc.write((const char*)&amount, sizeof(amount)); + enc.write((const char*)&min_tokens, sizeof(min_tokens)); + enc.write(salt.data(), (uint32_t)salt.size()); + return enc.result(); + } + + // Enable commit-reveal batch mode with a 5-block epoch and the given (tiny) processing cap. + void publish_batch_props(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when, + uint32_t cap) { + chain_properties_pm props; + props.pm_commit_reveal_enabled = true; + props.pm_batch_epoch_blocks = 5; + props.pm_reveal_window_blocks = 5; + props.pm_processing_cap_per_block = cap; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + const auto& mp = node.db().get_validator_schedule_object().median_props; + for (int i = 0; i < 60 && mp.pm_processing_cap_per_block != cap; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_processing_cap_per_block, cap); + BOOST_REQUIRE(mp.pm_commit_reveal_enabled); + } + + // Initiator creates a self-oracle binary CPMM market with allow_batch. Ids are sequential + // from 0 on the fresh per-test chain. + void create_batch_market(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when, + int64_t liquidity) { + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(liquidity), TOKEN_SYMBOL); + cm.betting_expiration = node.head_block_time() + fc::seconds(600); + cm.result_expiration = node.head_block_time() + fc::seconds(1200); + cm.allow_batch = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + } + + // Commit (block 1) + reveal (block 2) a side-0 batch bet — leaves a queued (status 5) + // bet on the market. The caller tracks the chain-global commit id sequence. + void queue_batch_bet(simulated_node& node, const genesis_params& gp, fc::time_point_sec& when, + const std::string& acct, const fc::ecc::private_key& key, + int64_t market_id, uint32_t commit_id, int64_t amount, + const std::string& salt) { + const uint16_t no_reveal = + node.db().get_validator_schedule_object().median_props.pm_commit_no_reveal_penalty_percent; + pm_commit_bet_operation c; + c.account = acct; c.market_id = market_id; + c.commitment = batch_commitment(account_name_type(acct), market_id, 0, -1, amount, 0, salt); + c.escrow_amount = asset(amount * 2, TOKEN_SYMBOL); c.no_reveal_fee_percent = no_reveal; + node.push_pending_transaction(sign_ops({c}, key, node)); + produce(node, gp, when); + pm_reveal_bet_operation r; + r.account = acct; r.commit_id = commit_id; r.side = 0; r.outcome_index = -1; + r.amount = asset(amount, TOKEN_SYMBOL); r.salt = salt; r.min_tokens = 0; + node.push_pending_transaction(sign_ops({r}, key, node)); + produce(node, gp, when); + } + + uint64_t find_bet_id(simulated_node& node, const pm_market_id_type& mid, const std::string& acct) { + for (const auto& b : node.db().get_index().indices()) + if (b.market == mid && b.account == acct) return b.id._id; + BOOST_REQUIRE_MESSAGE(false, "queued bet not found"); + return 0; + } + +} // namespace + +// Regression for PR #139: idle allow_batch markets must not consume the processing cap or +// bump their epoch. cap=1, three markets, only the NEWEST has a queued bet — pre-#139 the +// oldest idle market ate the whole budget every boundary and the bet stayed queued forever. +BOOST_AUTO_TEST_CASE(batch_settle_idle_markets_do_not_starve) { + auto gp = make_genesis_params(0xF331u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-starve-idle", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping batch starvation (idle)."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + publish_batch_props(node, gp, when, /*cap*/1); + register_self_oracle(node, gp, when); + + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 8)); + + for (int m = 0; m < 3; ++m) create_batch_market(node, gp, when, unit * 4); + + // Only market 2 (the newest) gets a bet. + queue_batch_bet(node, gp, when, "alice", alice_key, /*market*/2, /*commit*/0, unit, "salt-idle"); + const uint64_t bet_id = find_bet_id(node, pm_market_id_type(2), "alice"); + BOOST_REQUIRE_EQUAL(node.db().get(pm_bet_id_type(bet_id)).status, 5); + + for (int i = 0; i < 20 && + node.db().get(pm_bet_id_type(bet_id)).status == 5; ++i) + produce(node, gp, when); + + const auto& bet = node.db().get(pm_bet_id_type(bet_id)); + BOOST_CHECK_EQUAL(bet.status, 0); // settled, not starved behind idle markets + BOOST_CHECK_GT(bet.weight.value, 0); + // Idle markets kept their epoch (and therefore never consumed the cap). + BOOST_CHECK_EQUAL(node.db().get(pm_market_id_type(0)).current_epoch, 0u); + BOOST_CHECK_EQUAL(node.db().get(pm_market_id_type(1)).current_epoch, 0u); + BOOST_CHECK_GT(node.db().get(pm_market_id_type(2)).current_epoch, 0u); +} + +// Cursor round-robin: with cap=1 and market 0 fed a fresh queued bet EVERY epoch, a fixed +// scan start would settle market 0 at every boundary and starve market 1 forever. The +// persisted cursor resumes past market 0, so market 1 settles within a couple of epochs. +BOOST_AUTO_TEST_CASE(batch_settle_round_robin_prevents_busy_starvation) { + auto gp = make_genesis_params(0xF332u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-starve-busy", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping batch starvation (busy)."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + publish_batch_props(node, gp, when, /*cap*/1); + register_self_oracle(node, gp, when); + + auto alice_key = derive_key("alice"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 30)); + + for (int m = 0; m < 2; ++m) create_batch_market(node, gp, when, unit * 4); + + auto to_boundary = [&]() { + do { produce(node, gp, when); } while (node.db().head_block_num() % 5 != 0); + }; + + // From a boundary: queue on market 0 (blocks 1–2), then on market 1 (blocks 3–4), so + // both are queued before the block-5 boundary and market 0 shadows market 1 under cap=1. + to_boundary(); + uint32_t commit_seq = 0; + queue_batch_bet(node, gp, when, "alice", alice_key, 0, commit_seq++, unit, "salt-b0"); + queue_batch_bet(node, gp, when, "alice", alice_key, 1, commit_seq++, unit, "salt-b1"); + const uint64_t m1_bet = find_bet_id(node, pm_market_id_type(1), "alice"); + + // Keep market 0 busy at EVERY boundary (commit ≡1, reveal ≡2, boundary ≡0 — so a queued + // market-0 bet shadows market 1 under cap=1 at each epoch). Market 1 must still settle. + int round = 0; + for (; round < 6 && node.db().get(pm_bet_id_type(m1_bet)).status == 5; ++round) { + queue_batch_bet(node, gp, when, "alice", alice_key, 0, commit_seq++, unit, + "salt-r" + std::to_string(round)); + to_boundary(); + } + + const auto& bet = node.db().get(pm_bet_id_type(m1_bet)); + BOOST_TEST_MESSAGE("busy-starvation: market-1 bet settled after " << round << " round(s)"); + BOOST_CHECK_EQUAL(bet.status, 0); // round-robin reached market 1 + BOOST_CHECK_GT(bet.weight.value, 0); +} + +// ───────────────────────────────────────────────────────────────────────────── +// End-to-end ZERO-SUM conservation through the REAL evaluators. +// +// The existing cases assert LOCAL money movement (this winner got X, this loser +// got 0), and betting_odds_and_payout_table's check_zero_sum validates the pure +// compute_settlement result struct — but nothing asserts that the CHAIN itself +// neither mints nor burns across a full PM lifecycle driven through the live +// database + evaluators + settlement cron. This is the PR's core safety claim +// ("prediction markets never mint or burn; current_supply untouched"). +// +// Method: VIZ supply may change ONLY by block inflation +// (CHAIN_DIGITAL_ASSET_ISSUED_PER_BLOCK, credited to committee/reward-fund/validator — +// never minted or burned by PM). So across a full PM lifecycle current_supply must +// grow by exactly inflation × (blocks produced); any PM mint/burn is a deviation. + +// A binary market with fees, an LP, and unequal bets on both sides. Assert the chain's +// current_supply grows by exactly block inflation across the full lifecycle (no PM mint/burn). +BOOST_AUTO_TEST_CASE(supply_conserved_binary_lifecycle) { + auto gp = make_genesis_params(0x5C01u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-supply", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping supply-conservation lifecycle."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // Short dispute grace so the auto-payout cron settles within a few blocks. + { + chain_properties_pm props; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && mp.pm_dispute_grace_sec != 30; ++i) produce(node, gp, when); + BOOST_REQUIRE_EQUAL(mp.pm_dispute_grace_sec, 30u); + } + + // Oracle "viz" with a nonzero % fee schedule so oracle/creator/LP takes are all exercised. + pm_oracle_register_operation oreg; + oreg.owner = gp.initiator_name; oreg.insurance = mp.pm_min_oracle_insurance; + oreg.fixed_fee = asset(0, TOKEN_SYMBOL); oreg.rules_url = ""; + node.push_pending_transaction(sign_ops({oreg}, gp.initiator_key, node)); + produce(node, gp, when); + + auto alice_key = derive_key("alice"), bob_key = derive_key("bob"), lp_key = derive_key("liz"); + create_and_fund(node, gp, when, "alice", alice_key, share_type(unit * 8)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 8)); + create_and_fund(node, gp, when, "liz", lp_key, share_type(unit * 8)); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 4), TOKEN_SYMBOL); + cm.oracle_fee_percent = 200; cm.creator_fee_percent = 100; cm.liquidity_fee_percent = 300; // 6% total + cm.betting_expiration = node.head_block_time() + fc::seconds(30); + cm.result_expiration = node.head_block_time() + fc::seconds(90); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).status, 1); + + // An extra LP adds depth (its principal returns at settlement + a fee share). + pm_add_liquidity_operation al; + al.provider = "liz"; al.market_id = 0; al.amount = asset(share_type(unit * 4), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({al}, lp_key, node)); + produce(node, gp, when); + + // Global zero-sum test: VIZ supply may change ONLY by block inflation + // (CHAIN_DIGITAL_ASSET_ISSUED_PER_BLOCK = 1000 mVIZ/block, credited to committee/ + // reward-fund/validator — never minted or burned by PM). So across the whole PM + // lifecycle current_supply must grow by exactly 1000 × (blocks produced). Any PM + // mint/burn shows up as a deviation. This is the invariant the suite never asserted. + const int64_t supply_before = node.db().get_dynamic_global_properties().current_supply.amount.value; + const uint32_t num_before = node.db().head_block_num(); + + // Unequal bets on both sides so losers_sum > 0 and the parimutuel split is nontrivial. + pm_place_bet_operation ba; ba.account = "alice"; ba.market_id = 0; ba.side = 0; ba.outcome_index = -1; + ba.amount = asset(share_type(unit * 3), TOKEN_SYMBOL); ba.mode = 0; + pm_place_bet_operation bb; bb.account = "bob"; bb.market_id = 0; bb.side = 1; bb.outcome_index = -1; + bb.amount = asset(share_type(unit * 2), TOKEN_SYMBOL); bb.mode = 0; + node.push_pending_transaction(sign_ops({ba}, alice_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + // Betting closes, oracle resolves A (side 0), auto-payout cron settles. + for (int i = 0; i < 15; ++i) produce(node, gp, when); + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; rm.decision_url = ""; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + const uint32_t grace = (uint32_t)mp.pm_dispute_grace_sec; + const int blocks = (int)((grace + 240) / CHAIN_BLOCK_INTERVAL) + 80; + for (int i = 0; i < blocks && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + const int64_t supply_after = node.db().get_dynamic_global_properties().current_supply.amount.value; + const uint32_t num_after = node.db().head_block_num(); + + const int64_t inflation = (int64_t)(num_after - num_before) * CHAIN_DIGITAL_ASSET_ISSUED_PER_BLOCK; + const int64_t supply_delta = supply_after - supply_before; + BOOST_TEST_MESSAGE("supply: before=" << supply_before << " after=" << supply_after + << " delta=" << supply_delta << " expected_inflation=" << inflation + << " (blocks=" << (num_after - num_before) << ")"); + // Exact zero-sum: the entire PM lifecycle (bets, LP, resolve, parimutuel settle, fees) + // moved value between accounts/pools but neither minted nor burned — supply grew by + // block inflation alone. + BOOST_CHECK_EQUAL(supply_delta, inflation); +} + +// ───────────────────────────────────────────────────────────────────────────── +// F1/#300 early-exit deferred claim, driven through the REAL evaluators. +// +// The whole deferred-claim subsystem (leverage close/liquidate residual → contingent +// claim → paid from a bounded slice of the losing pool at settlement) had ZERO +// integration coverage: grep deferred/early_exit/claim_paid across tests/ = 0. This +// exercises the money path end-to-end and asserts (a) a claim is recorded on a +// profitable force-close, (b) the early-exiter is actually paid from the bucket when +// their side wins, and (c) the market still conserves VIZ through settlement. +BOOST_AUTO_TEST_CASE(early_exit_deferred_claim_paid_from_bucket) { + auto gp = make_genesis_params(0x5E02u, 1); + fc::time_point_sec start(fc::time_point::now()); + fc::time_point_sec hf(CHAIN_HARDFORK_14_TIME); + if (hf > start) start = hf; + start += fc::seconds(CHAIN_BLOCK_INTERVAL); + virtual_clock clk(start); + simulated_node node("pm-earlyexit", gp, clk); + fc::time_point_sec when = start - fc::seconds(CHAIN_BLOCK_INTERVAL); + + if (!bring_to_hf14(node, gp, when)) { + BOOST_TEST_MESSAGE("HF14 not reachable; skipping early-exit deferred claim."); + return; + } + + const auto& mp = node.db().get_validator_schedule_object().median_props; + const int64_t unit = mp.pm_min_liquidity.amount.value; + + // Enable leverage; short grace so the market settles quickly. + { + chain_properties_pm props; + props.pm_leverage_enabled = true; + props.pm_leverage_expiration_buffer_sec = 0; + props.pm_leverage_fund_percent = 100; + props.pm_leverage_max_per_position_bp = 10000; + props.pm_leverage_max_position_ratio_percent = 100; + props.pm_lazy_alloc_percent = 0; + props.pm_dispute_grace_sec = 30; + versioned_chain_properties_update_operation vp; + vp.owner = gp.initiator_name; vp.props = props; + node.push_pending_transaction(sign_ops({vp}, gp.initiator_key, node)); + for (int i = 0; i < 60 && !mp.pm_leverage_enabled; ++i) produce(node, gp, when); + BOOST_REQUIRE(mp.pm_leverage_enabled); + } + + const pm_lazy_pool_id_type pool_id(0); + const int64_t D = unit * 100; + pm_lazy_deposit_operation dep; + dep.account = gp.initiator_name; dep.amount = asset(share_type(D), TOKEN_SYMBOL); + node.push_pending_transaction(sign_ops({dep}, gp.initiator_key, node)); + produce(node, gp, when); + + pm_create_market_operation cm; + cm.creator = gp.initiator_name; cm.oracle = gp.initiator_name; + cm.market_type = 0; cm.outcomes = {"A", "B"}; cm.url = "criteria"; + cm.liquidity = asset(share_type(unit * 60), TOKEN_SYMBOL); // reserves 3M/3M + cm.betting_expiration = node.head_block_time() + fc::seconds(60); + cm.result_expiration = node.head_block_time() + fc::seconds(120); + cm.allow_early_resolution = true; + cm.dispute_mode = 0; + node.push_pending_transaction(sign_ops({cm}, gp.initiator_key, node)); + produce(node, gp, when); + const pm_market_id_type market_id(0); + + auto trader_key = derive_key("trader"), whale_key = derive_key("whale"), bob_key = derive_key("bob"); + create_and_fund(node, gp, when, "trader", trader_key, share_type(unit * 40)); + create_and_fund(node, gp, when, "whale", whale_key, share_type(unit * 100)); + create_and_fund(node, gp, when, "bob", bob_key, share_type(unit * 40)); + + // trader opens leverage on A; whale piles onto A (pushes trader's cancel_value above its + // loan → the position is in profit). bob bets B (the loser, funds the pool that pays claims). + const int64_t L = unit * 10; + pm_leverage_open_operation op; + op.account = "trader"; op.market_id = 0; op.outcome_index = 0; + op.collateral = asset(share_type(unit * 20), TOKEN_SYMBOL); op.loan = asset(share_type(L), TOKEN_SYMBOL); + op.min_tokens = 0; op.max_slippage_percent = 0; + node.push_pending_transaction(sign_ops({op}, trader_key, node)); + produce(node, gp, when); + const pm_leverage_position_id_type pos_id(0); + BOOST_REQUIRE_EQUAL(node.db().get(pos_id).status, 0); + + pm_place_bet_operation wb; wb.account = "whale"; wb.market_id = 0; wb.side = 0; wb.outcome_index = -1; + wb.amount = asset(share_type(unit * 80), TOKEN_SYMBOL); wb.mode = 0; + pm_place_bet_operation bb; bb.account = "bob"; bb.market_id = 0; bb.side = 1; bb.outcome_index = -1; + bb.amount = asset(share_type(unit * 20), TOKEN_SYMBOL); bb.mode = 0; + node.push_pending_transaction(sign_ops({wb}, whale_key, node)); + node.push_pending_transaction(sign_ops({bb}, bob_key, node)); + produce(node, gp, when); + + const int64_t trader_before_close = node.db().get_account("trader").balance.amount.value; + + // Close betting so the settlement force-close (reason 2) records the trader's profitable + // residual as an outcome-contingent deferred claim on side A (not an immediate refund). + for (int i = 0; i < 40 && node.head_block_time() < node.db().get(market_id).betting_expiration; ++i) + produce(node, gp, when); + + // A deferred claim for the trader on outcome 0 should now exist (recorded at force-close). + auto count_claims = [&](account_name_type acct, int16_t outcome) { + uint32_t n = 0; int64_t total = 0; + const auto& cidx = node.db().get_index().indices().get(); + for (auto it = cidx.lower_bound(boost::make_tuple(market_id, pm_deferred_claim_id_type())); + it != cidx.end() && it->market == market_id; ++it) + if (it->account == acct && (int16_t)it->outcome_index == outcome) { ++n; total += it->claim_amount.value; } + return std::make_pair(n, total); + }; + // The market's force-close cron runs at/after betting close; give it a couple of blocks. + for (int i = 0; i < 5 && count_claims("trader", 0).first == 0; ++i) produce(node, gp, when); + auto claim = count_claims("trader", 0); + { + const auto& pos = node.db().get(pos_id); + BOOST_TEST_MESSAGE("leverage pos after close: status=" << (int)pos.status + << " cv_at_liq=" << pos.cancel_value_at_liquidation.value + << " pool_received=" << pos.pool_received.value + << " bettor_received=" << pos.bettor_received.value + << " liq_threshold=" << pos.liquidation_threshold.value + << " funding_paid=" << pos.funding_paid.value); + } + BOOST_TEST_MESSAGE("deferred claims for trader on A: n=" << claim.first << " total=" << claim.second); + BOOST_CHECK_GE(claim.first, 1u); // a contingent claim was recorded + BOOST_CHECK_GT(claim.second, 0); // for a positive (profit) amount + + const int64_t trader_at_close = node.db().get_account("trader").balance.amount.value; + // The residual was DEFERRED, not refunded immediately: the trader's balance did not jump by + // the claim amount at close (it is paid only at settlement, iff A wins). + BOOST_CHECK_EQUAL(trader_at_close, trader_before_close); + + // Resolve A (side 0) — the trader's outcome wins, so the deferred claim pays from the bucket. + pm_resolve_market_operation rm; + rm.oracle = gp.initiator_name; rm.market_id = 0; rm.winning_outcome = 0; rm.decision_url = ""; + node.push_pending_transaction(sign_ops({rm}, gp.initiator_key, node)); + produce(node, gp, when); + const uint32_t grace = (uint32_t)mp.pm_dispute_grace_sec; + const int blocks = (int)((grace + 240) / CHAIN_BLOCK_INTERVAL) + 80; + for (int i = 0; i < blocks && node.db().get(market_id).payout_status != 3; ++i) + produce(node, gp, when); + BOOST_REQUIRE_EQUAL(node.db().get(market_id).payout_status, 3); + + // After settlement: the trader was paid their deferred claim (bounded by the early-exit + // bucket = cap% of losers_sum). Their balance rose from the close snapshot by > 0, and every + // deferred-claim row for the market is consumed (settlement removes them). + const int64_t trader_final = node.db().get_account("trader").balance.amount.value; + BOOST_TEST_MESSAGE("trader balance: before_close=" << trader_before_close + << " at_close=" << trader_at_close << " final=" << trader_final + << " (deferred paid=" << (trader_final - trader_at_close) << ")"); + BOOST_CHECK_GT(trader_final, trader_at_close); // the contingent claim actually paid at settlement + + uint32_t remaining = 0; + const auto& cidx = node.db().get_index().indices().get(); + for (auto it = cidx.lower_bound(boost::make_tuple(market_id, pm_deferred_claim_id_type())); + it != cidx.end() && it->market == market_id; ++it) ++remaining; + BOOST_CHECK_EQUAL(remaining, 0u); // all claims consumed at settlement (none leak to GC) +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/pm/CMakeLists.txt b/tests/pm/CMakeLists.txt new file mode 100644 index 0000000000..a5777696c0 --- /dev/null +++ b/tests/pm/CMakeLists.txt @@ -0,0 +1,59 @@ +# HF14 LMSR Q96 vector-parity test — consensus merge gate (strict equality vs the +# normative PHP/GMP vectors). Built under BUILD_CONSENSUS_TESTS, same as consensus_sim. + +add_executable(pm_lmsr_vectors_tests lmsr_vectors_test.cpp) + +target_link_libraries(pm_lmsr_vectors_tests + PRIVATE graphene_chain + graphene_protocol + fc + Boost::unit_test_framework +) + +# Absolute path to the frozen vector file (lives next to this CMakeLists). +target_compile_definitions(pm_lmsr_vectors_tests + PRIVATE LMSR_VECTORS_JSON=\"${CMAKE_CURRENT_SOURCE_DIR}/lmsr_fixed_vectors.json\" +) + +# Static libboost_unit_test_framework — do NOT define BOOST_TEST_DYN_LINK (see consensus_sim note). + +add_test(NAME pm_lmsr_vectors COMMAND pm_lmsr_vectors_tests) + +# HF14 parimutuel settlement test — zero-sum conservation + payout-by-weight + edge cases. +add_executable(pm_parimutuel_tests parimutuel_test.cpp) + +target_link_libraries(pm_parimutuel_tests + PRIVATE graphene_chain + graphene_protocol + fc + Boost::unit_test_framework +) + +add_test(NAME pm_parimutuel COMMAND pm_parimutuel_tests) + +# HF14 leverage CPMM math test — frozen vs prototype (cancel_value, threshold, max leverage). +add_executable(pm_leverage_tests leverage_test.cpp) + +target_link_libraries(pm_leverage_tests + PRIVATE graphene_chain + graphene_protocol + fc + Boost::unit_test_framework +) + +add_test(NAME pm_leverage COMMAND pm_leverage_tests) + +# HF14 prediction-market metadata parsing — pure helpers of the prediction_market_api plugin +# (JSON metadata extraction + CSV jurisdiction membership). +add_executable(pm_meta_parse_tests meta_parse_test.cpp) + +target_link_libraries(pm_meta_parse_tests + PRIVATE fc + Boost::unit_test_framework +) + +target_include_directories(pm_meta_parse_tests + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/prediction_market_api/include" +) + +add_test(NAME pm_meta_parse COMMAND pm_meta_parse_tests) diff --git a/tests/pm/leverage_test.cpp b/tests/pm/leverage_test.cpp new file mode 100644 index 0000000000..d2a3f1e526 --- /dev/null +++ b/tests/pm/leverage_test.cpp @@ -0,0 +1,81 @@ +// HF14 leverage CPMM math test — frozen against leverage-risk-off-strategy.md §4. +// Pure integer math used by the leverage evaluators / atomic-liquidation hook. The +// reserve convention matches the chain's pm_place_bet (outcome 0 adds to reserve_a). + +#define BOOST_TEST_MODULE pm_leverage +#include + +#include + +using namespace graphene::chain::pm::leverage; + +namespace { + // Equal-reserve deep market: ra = rb = 10,000,000 mVIZ, k = ra*rb. + const int64_t RA = 10000000, RB = 10000000; + const fc::uint128_t K = fc::uint128_t((uint64_t)RA) * fc::uint128_t((uint64_t)RB); +} + +// §4.4 — liquidation threshold = loan × (1 + R%/100); safety adds S%. +BOOST_AUTO_TEST_CASE(threshold_and_safety) { + BOOST_CHECK_EQUAL(liquidation_threshold(1000000, 10), 1100000); // 1000 × 1.10 + int64_t safe = (int64_t)(fc::uint128_t((uint64_t)liquidation_threshold(1000000, 10)) + * fc::uint128_t(101u) / fc::uint128_t(100u)).lo; + BOOST_CHECK_EQUAL(safe, 1111000); // 1100 × 1.01 +} + +// §4.1 — cpmm_buy returns positive tokens and moves reserves in the right direction. +BOOST_AUTO_TEST_CASE(cpmm_buy_direction) { + auto a = cpmm_buy(RA, RB, K, 1000000, 0); // bet on A + BOOST_CHECK_GT(a.tokens, 0); + BOOST_CHECK_GT(a.new_reserve_a, RA); // A side grows + BOOST_CHECK_LT(a.new_reserve_b, RB); // B side shrinks + auto b = cpmm_buy(RA, RB, K, 1000000, 1); // bet on B (symmetric) + BOOST_CHECK_EQUAL(a.tokens, b.tokens); +} + +// §4.2 — cancel_value > 0, symmetric on equal reserves, sub-linear (slippage); +// and cancel_value_after_opposing(m=0) reduces to cancel_value. +BOOST_AUTO_TEST_CASE(cancel_value_properties) { + int64_t cv_a = cancel_value(RA, RB, K, 1000000, 0); + int64_t cv_b = cancel_value(RA, RB, K, 1000000, 1); + BOOST_CHECK_GT(cv_a, 0); + BOOST_CHECK_EQUAL(cv_a, cv_b); // symmetric + + int64_t cv_5 = cancel_value(RA, RB, K, 5000000, 0); + BOOST_CHECK_GT(cv_5, cv_a); // more tokens → more VIZ + BOOST_CHECK_LT(cv_5, cv_a * 5); // but sub-linear + + BOOST_CHECK_EQUAL(cancel_value_after_opposing(RA, RB, K, 1000000, 0, 0), cv_a); +} + +// §4.3 — an opposing bet lowers the position's cancel value (monotone in M). +BOOST_AUTO_TEST_CASE(opposing_bet_lowers_value) { + int64_t base = cancel_value_after_opposing(RA, RB, K, 1000000, 0, 0); + int64_t small = cancel_value_after_opposing(RA, RB, K, 1000000, 0, 500000); + int64_t big = cancel_value_after_opposing(RA, RB, K, 1000000, 0, 2000000); + BOOST_CHECK_LT(small, base); + BOOST_CHECK_LT(big, small); + int64_t m = worst_opposing_bet(RA, RB, 10, 100); // 10% slippage, full M_max + BOOST_CHECK_EQUAL(m, RA / 10); // min(ra,rb) × 10% × 100% +} + +// §4.6 Constraint 2 — max leverage binary search returns a bounded loan whose +// worst-case cancel value satisfies the safety threshold after the bet is placed. +BOOST_AUTO_TEST_CASE(max_leverage_constraint) { + const int64_t collateral = 1000000; // 1000 VIZ + const int outcome = 0; + const uint16_t R = 10, S = 1, SL = 10, MF = 100; + + int64_t loan = max_leverage_loan(RA, RB, K, collateral, outcome, + collateral * 10, R, S, SL, MF); + BOOST_CHECK_GT(loan, 0); + BOOST_CHECK_LE(loan, collateral * 10); + + // Re-derive the constraint at the chosen loan: place (C+L), worst opposing bet, check. + auto f = cpmm_buy(RA, RB, K, collateral + loan, outcome); + int64_t m = worst_opposing_bet(f.new_reserve_a, f.new_reserve_b, SL, MF); + int64_t cvw = cancel_value_after_opposing(f.new_reserve_a, f.new_reserve_b, K, f.tokens, outcome, m); + int64_t thr_safe = (int64_t)(fc::uint128_t((uint64_t)liquidation_threshold(loan, R)) + * fc::uint128_t(100u + S) / fc::uint128_t(100u)).lo; + BOOST_CHECK_GE(cvw, thr_safe); +} diff --git a/tests/pm/lmsr_fixed_vectors.json b/tests/pm/lmsr_fixed_vectors.json new file mode 100644 index 0000000000..8da076220c --- /dev/null +++ b/tests/pm/lmsr_fixed_vectors.json @@ -0,0 +1,625 @@ +{ + "version": 1, + "spec": "docs/lmsr-fixed-point-spec.md", + "generated_by": "module/lmsr_fixed.php (PHP+GMP, normative)", + "numbers_are_decimal_strings": true, + "note": "All numeric values, including Q96 intermediates, are decimal strings. Equality is exact (===); no tolerance is permitted.", + "constants": { + "ONE": "79228162514264337593543950336", + "HALF": "39614081257132168796771975168", + "LN2": "54916777467707473351141471128", + "EXP_DOMAIN_LIMIT": "15845632502852867518708790067200", + "MAX_B": "9007199254740992", + "MAX_Q": "9007199254740992" + }, + "mul_q": [ + { + "a": "79228162514264337593543950336", + "b": "79228162514264337593543950336", + "expected": "79228162514264337593543950336" + }, + { + "a": "79228162514264337593543950336", + "b": "39614081257132168796771975168", + "expected": "39614081257132168796771975168" + }, + { + "a": "54916777467707473351141471128", + "b": "54916777467707473351141471128", + "expected": "38065409467179368169621602747" + }, + { + "a": "237684487542793012780631851008", + "b": "79228162514264337593543950336", + "expected": "237684487542793012780631851008" + }, + { + "a": "-79228162514264337593543950336", + "b": "39614081257132168796771975168", + "expected": "-39614081257132168796771975168" + } + ], + "div_q": [ + { + "a": "79228162514264337593543950336", + "b": "79228162514264337593543950336", + "expected": "79228162514264337593543950336" + }, + { + "a": "79228162514264337593543950336", + "b": "158456325028528675187087900672", + "expected": "39614081257132168796771975168" + }, + { + "a": "54916777467707473351141471128", + "b": "79228162514264337593543950336", + "expected": "54916777467707473351141471128" + }, + { + "a": "554597137599850363154807652352", + "b": "237684487542793012780631851008", + "expected": "184865712533283454384935884117" + }, + { + "a": "-79228162514264337593543950336", + "b": "316912650057057350374175801344", + "expected": "-19807040628566084398385987584" + } + ], + "exp_q": [ + { + "x": "0", + "expected": "79228162514264337593543950336" + }, + { + "x": "54916777467707473351141471128", + "expected": "158456325028528675187087900672" + }, + { + "x": "-54916777467707473351141471128", + "expected": "39614081257132168796771975168" + }, + { + "x": "79228162514264337593543950336", + "expected": "215364474464724850177511348332" + }, + { + "x": "-79228162514264337593543950336", + "expected": "29146412150787779157341161346" + }, + { + "x": "158456325028528675187087900672", + "expected": "585421337433093623126455912472" + }, + { + "x": "-396140812571321687967719751680", + "expected": "533835159856043089203045521" + }, + { + "x": "27458388733853736675570735564", + "expected": "112045541949572279837463876445" + }, + { + "x": "7922816251426433759354395033", + "expected": "87560661103336128385318961295" + }, + { + "x": "12345678901234567890", + "expected": "79228162526610016495740397074" + }, + { + "x": "-98765432109876543210", + "expected": "79228162415498905545227654557" + } + ], + "ln_q": [ + { + "x": "79228162514264337593543950336", + "expected": "0" + }, + { + "x": "158456325028528675187087900672", + "expected": "54916777467707473351141471128" + }, + { + "x": "237684487542793012780631851008", + "expected": "87041032946764879767665216836" + }, + { + "x": "792281625142643375935439503360", + "expected": "182429585950654714090129938590" + }, + { + "x": "39614081257132168796771975168", + "expected": "-54916777467707473351141471128" + }, + { + "x": "7922816251426433759354395033", + "expected": "-182429585950654714090129938636" + }, + { + "x": "79228162514264337593543950", + "expected": "-547288757851964142270389816164" + }, + { + "x": "79228162514264337593543950336000000", + "expected": "1094577515703928284540779631608" + } + ], + "lmsr_cost": [ + { + "q": [ + 0, + 0 + ], + "b": 100000, + "expected": 69314 + }, + { + "q": [ + 0, + 0, + 0 + ], + "b": 100000, + "expected": 109861 + }, + { + "q": [ + 10000, + 0, + 0 + ], + "b": 100000, + "expected": 113306 + }, + { + "q": [ + 1000, + 2000, + 3000 + ], + "b": 5000, + "expected": 7559 + }, + { + "q": [ + 50000, + 10000 + ], + "b": 20000, + "expected": 52538 + }, + { + "q": [ + 5000, + 5000, + 5000, + 5000 + ], + "b": 200000, + "expected": 282258 + }, + { + "q": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "b": 100000, + "expected": 230264 + }, + { + "q": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "b": 100000, + "expected": 277258 + }, + { + "q": [ + 1000000, + 0 + ], + "b": 50000, + "expected": 1000000 + }, + { + "q": [ + 2500, + 2500 + ], + "b": 5000, + "expected": 5965 + } + ], + "lmsr_prices": [ + { + "q": [ + 0, + 0 + ], + "b": 100000, + "expected": [ + 500000, + 500000 + ] + }, + { + "q": [ + 0, + 0, + 0 + ], + "b": 100000, + "expected": [ + 333333, + 333333, + 333333 + ] + }, + { + "q": [ + 10000, + 0, + 0 + ], + "b": 100000, + "expected": [ + 355913, + 322043, + 322043 + ] + }, + { + "q": [ + 1000, + 2000, + 3000 + ], + "b": 5000, + "expected": [ + 269307, + 328932, + 401759 + ] + }, + { + "q": [ + 50000, + 10000 + ], + "b": 20000, + "expected": [ + 880797, + 119202 + ] + }, + { + "q": [ + 5000, + 5000, + 5000, + 5000 + ], + "b": 200000, + "expected": [ + 250000, + 250000, + 250000, + 250000 + ] + }, + { + "q": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "b": 100000, + "expected": [ + 99995, + 99996, + 99997, + 99998, + 99999, + 100000, + 100001, + 100002, + 100003, + 100004 + ] + }, + { + "q": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "b": 100000, + "expected": [ + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500, + 62500 + ] + }, + { + "q": [ + 1000000, + 0 + ], + "b": 50000, + "expected": [ + 999999, + 0 + ] + }, + { + "q": [ + 2500, + 2500 + ], + "b": 5000, + "expected": [ + 500000, + 500000 + ] + } + ], + "lmsr_buy_cost": [ + { + "q": [ + 1000, + 2000, + 3000 + ], + "b": 5000, + "i": 0, + "delta": 1000, + "expected": 290 + }, + { + "q": [ + 1000, + 2000, + 3000 + ], + "b": 5000, + "i": 1, + "delta": 1000, + "expected": 351 + }, + { + "q": [ + 0, + 0, + 0 + ], + "b": 8000, + "i": 0, + "delta": 5000, + "expected": 2034 + }, + { + "q": [ + 0, + 0, + 0 + ], + "b": 8000, + "i": 2, + "delta": 50000, + "expected": 41242 + }, + { + "q": [ + 50000, + 10000 + ], + "b": 20000, + "i": 0, + "delta": 1000, + "expected": 883 + }, + { + "q": [ + 50000, + 10000 + ], + "b": 20000, + "i": 1, + "delta": 100000, + "expected": 58433 + } + ], + "lmsr_sell_return": [ + { + "q": [ + 10000, + 5000, + 2000 + ], + "b": 8000, + "i": 0, + "delta": 1000, + "expected": 510 + }, + { + "q": [ + 10000, + 5000, + 2000 + ], + "b": 8000, + "i": 1, + "delta": 2500, + "expected": 628 + }, + { + "q": [ + 50000, + 10000 + ], + "b": 20000, + "i": 0, + "delta": 25000, + "expected": 19801 + } + ], + "lmsr_tokens_for_amount": [ + { + "q": [ + 1000, + 2000, + 3000 + ], + "b": 5000, + "i": 0, + "amount": 10000, + "expected": 16039 + }, + { + "q": [ + 0, + 0, + 0 + ], + "b": 8000, + "i": 1, + "amount": 50000, + "expected": 58778 + }, + { + "q": [ + 50000, + 10000 + ], + "b": 20000, + "i": 0, + "amount": 30000, + "expected": 31999 + }, + { + "q": [ + 0, + 0 + ], + "b": 10000, + "i": 0, + "amount": 1000, + "expected": 1909 + }, + { + "q": [ + 0, + 0 + ], + "b": 10000, + "i": 0, + "amount": 100000, + "expected": 106931 + }, + { + "q": [ + 1000000, + 0 + ], + "b": 50000, + "i": 1, + "amount": 50000, + "expected": 499999 + } + ], + "lmsr_b_from_liquidity": [ + { + "liquidity": 100000, + "n": 2, + "expected": 144269 + }, + { + "liquidity": 100000, + "n": 3, + "expected": 91023 + }, + { + "liquidity": 100000, + "n": 4, + "expected": 72134 + }, + { + "liquidity": 100000, + "n": 10, + "expected": 43429 + }, + { + "liquidity": 100000, + "n": 16, + "expected": 36067 + }, + { + "liquidity": 1000000, + "n": 5, + "expected": 621334 + }, + { + "liquidity": 1234567, + "n": 7, + "expected": 634441 + } + ] +} diff --git a/tests/pm/lmsr_vectors_test.cpp b/tests/pm/lmsr_vectors_test.cpp new file mode 100644 index 0000000000..f1885a6dcd --- /dev/null +++ b/tests/pm/lmsr_vectors_test.cpp @@ -0,0 +1,149 @@ +// HF14 LMSR Q96 vector-parity test — the #1 consensus merge gate. +// +// Loads the frozen normative vector file (generated by reference/lmsr_fixed.php, GMP) and +// asserts the C++ port reproduces every value with STRICT equality (no tolerance). A single +// mismatch is a fork risk. This MUST pass on every supported toolchain (MSVC / UCRT64 / Linux) +// BEFORE the PM evaluators are trusted. See lmsr-fixed-point-spec.md §5. + +#define BOOST_TEST_MODULE pm_lmsr_vectors +#include + +#include + +#include +#include +#include + +#include +#include + +using namespace graphene::chain::lmsr; +using graphene::chain::lmsr::q96; + +#ifndef LMSR_VECTORS_JSON +#error "LMSR_VECTORS_JSON path must be defined by CMake" +#endif + +namespace { + + fc::variant load_vectors() { + return fc::json::from_file(fc::path(std::string(LMSR_VECTORS_JSON))); + } + + q96 to_q96(const fc::variant& v) { return q96(v.as_string()); } + + std::vector to_i64_vec(const fc::variant& v) { + std::vector out; + for (const auto& e : v.get_array()) out.push_back(e.as_int64()); + return out; + } + + std::string s(const q96& x) { return x.str(); } + +} // namespace + +BOOST_AUTO_TEST_SUITE(pm_lmsr_vectors) + +BOOST_AUTO_TEST_CASE(constants_match) { + auto V = load_vectors(); + const auto& c = V["constants"]; + BOOST_CHECK_EQUAL(s(q96(1) << 96), c["ONE"].as_string()); + BOOST_CHECK_EQUAL(c["LN2"].as_string(), std::string("54916777467707473351141471128")); +} + +BOOST_AUTO_TEST_CASE(mul_q_vectors) { + auto V = load_vectors(); + for (const auto& t : V["mul_q"].get_array()) { + auto o = t; + q96 got = detail::mul_q(to_q96(o["a"]), to_q96(o["b"])); + BOOST_CHECK_EQUAL(s(got), o["expected"].as_string()); + } +} + +BOOST_AUTO_TEST_CASE(div_q_vectors) { + auto V = load_vectors(); + for (const auto& t : V["div_q"].get_array()) { + auto o = t; + q96 got = detail::div_q(to_q96(o["a"]), to_q96(o["b"])); + BOOST_CHECK_EQUAL(s(got), o["expected"].as_string()); + } +} + +BOOST_AUTO_TEST_CASE(exp_q_vectors) { + auto V = load_vectors(); + for (const auto& t : V["exp_q"].get_array()) { + auto o = t; + q96 got = detail::exp_q(to_q96(o["x"])); + BOOST_CHECK_EQUAL(s(got), o["expected"].as_string()); + } +} + +BOOST_AUTO_TEST_CASE(ln_q_vectors) { + auto V = load_vectors(); + for (const auto& t : V["ln_q"].get_array()) { + auto o = t; + q96 got = detail::ln_q(to_q96(o["x"])); + BOOST_CHECK_EQUAL(s(got), o["expected"].as_string()); + } +} + +BOOST_AUTO_TEST_CASE(lmsr_cost_vectors) { + auto V = load_vectors(); + for (const auto& t : V["lmsr_cost"].get_array()) { + auto o = t; + int64_t got = lmsr_cost(to_i64_vec(o["q"]), o["b"].as_int64()); + BOOST_CHECK_EQUAL(got, o["expected"].as_int64()); + } +} + +BOOST_AUTO_TEST_CASE(lmsr_prices_vectors) { + auto V = load_vectors(); + for (const auto& t : V["lmsr_prices"].get_array()) { + auto o = t; + auto got = lmsr_prices(to_i64_vec(o["q"]), o["b"].as_int64()); + auto exp = to_i64_vec(o["expected"]); + BOOST_REQUIRE_EQUAL(got.size(), exp.size()); + for (size_t i = 0; i < exp.size(); ++i) BOOST_CHECK_EQUAL(got[i], exp[i]); + } +} + +BOOST_AUTO_TEST_CASE(lmsr_buy_cost_vectors) { + auto V = load_vectors(); + for (const auto& t : V["lmsr_buy_cost"].get_array()) { + auto o = t; + int64_t got = lmsr_buy_cost(to_i64_vec(o["q"]), o["b"].as_int64(), + (int)o["i"].as_int64(), o["delta"].as_int64()); + BOOST_CHECK_EQUAL(got, o["expected"].as_int64()); + } +} + +BOOST_AUTO_TEST_CASE(lmsr_sell_return_vectors) { + auto V = load_vectors(); + for (const auto& t : V["lmsr_sell_return"].get_array()) { + auto o = t; + int64_t got = lmsr_sell_return(to_i64_vec(o["q"]), o["b"].as_int64(), + (int)o["i"].as_int64(), o["delta"].as_int64()); + BOOST_CHECK_EQUAL(got, o["expected"].as_int64()); + } +} + +BOOST_AUTO_TEST_CASE(lmsr_tokens_for_amount_vectors) { + auto V = load_vectors(); + for (const auto& t : V["lmsr_tokens_for_amount"].get_array()) { + auto o = t; + int64_t got = lmsr_tokens_for_amount(to_i64_vec(o["q"]), o["b"].as_int64(), + (int)o["i"].as_int64(), o["amount"].as_int64()); + BOOST_CHECK_EQUAL(got, o["expected"].as_int64()); + } +} + +BOOST_AUTO_TEST_CASE(lmsr_b_from_liquidity_vectors) { + auto V = load_vectors(); + for (const auto& t : V["lmsr_b_from_liquidity"].get_array()) { + auto o = t; + int64_t got = lmsr_b_from_liquidity(o["liquidity"].as_int64(), (int)o["n"].as_int64()); + BOOST_CHECK_EQUAL(got, o["expected"].as_int64()); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/pm/meta_parse_test.cpp b/tests/pm/meta_parse_test.cpp new file mode 100644 index 0000000000..01b8ffc4a0 --- /dev/null +++ b/tests/pm/meta_parse_test.cpp @@ -0,0 +1,65 @@ +// HF14 prediction-market metadata parsing — pure helpers used by the prediction_market_api +// plugin (off-chain index builder). Markets carry a free-form `metadata` JSON string; the plugin +// extracts the keys it indexes and ignores everything else, and never throws on junk input. + +#define BOOST_TEST_MODULE pm_meta_parse +#include + +#include + +using namespace graphene::plugins::prediction_market_api; + +BOOST_AUTO_TEST_SUITE(pm_meta_parse) + +BOOST_AUTO_TEST_CASE(parse_full_metadata) { + auto m = parse_market_metadata( + R"({"category":"sports","subcategory":"soccer","tags":["world-cup","final"], + "banned_jurisdictions":["US","FR"],"title":"Will USA win the World Cup?", + "image":"https://cdn.example/usa.png","condition_id":"0xabc123", + "event":"wc-final-2026","unknown_key":{"x":1},"extra":[1,2,3]})"); + BOOST_CHECK_EQUAL(m.category, "sports"); + BOOST_CHECK_EQUAL(m.subcategory, "soccer"); + BOOST_CHECK_EQUAL(m.tags, "world-cup,final"); + BOOST_CHECK_EQUAL(m.banned_jurisdictions, "US,FR"); // unknown_key / extra ignored + BOOST_CHECK_EQUAL(m.title, "Will USA win the World Cup?"); + BOOST_CHECK_EQUAL(m.image, "https://cdn.example/usa.png"); + BOOST_CHECK_EQUAL(m.condition_id, "0xabc123"); + BOOST_CHECK_EQUAL(m.event, "wc-final-2026"); +} + +BOOST_AUTO_TEST_CASE(title_image_absent_leave_empty) { + auto m = parse_market_metadata(R"({"category":"crypto"})"); + BOOST_CHECK(m.title.empty()); + BOOST_CHECK(m.image.empty()); + BOOST_CHECK(m.condition_id.empty()); + BOOST_CHECK(m.event.empty()); +} + +BOOST_AUTO_TEST_CASE(non_json_yields_empty) { + for (const char* junk : {"Will it rain tomorrow?", "", "not json {", "[1,2,3]", "42"}) { + auto m = parse_market_metadata(junk); // never throws + BOOST_CHECK(m.category.empty()); + BOOST_CHECK(m.banned_jurisdictions.empty()); + } +} + +BOOST_AUTO_TEST_CASE(jurisdictions_banned_alias_and_string_form) { + BOOST_CHECK_EQUAL(parse_market_metadata(R"({"jurisdictions_banned":["CN"]})").banned_jurisdictions, "CN"); + // a bare string (not an array) is accepted as-is + BOOST_CHECK_EQUAL(parse_market_metadata(R"({"banned_jurisdictions":"RU"})").banned_jurisdictions, "RU"); +} + +BOOST_AUTO_TEST_CASE(jurisdiction_csv_membership) { + BOOST_CHECK(meta_csv_contains("US,FR,DE", "FR")); // middle + BOOST_CHECK(meta_csv_contains("US,FR,DE", "US")); // first + BOOST_CHECK(meta_csv_contains("US,FR,DE", "DE")); // last + BOOST_CHECK(meta_csv_contains("FR", "FR")); // single + BOOST_CHECK(!meta_csv_contains("US,FR,DE", "U")); // not a partial-token match + BOOST_CHECK(!meta_csv_contains("US,FR,DE", "USA")); // superset + BOOST_CHECK(!meta_csv_contains("USA,FRA", "US")); // prefix, not whole token + BOOST_CHECK(!meta_csv_contains("US,FR,DE", "GB")); // absent + BOOST_CHECK(!meta_csv_contains("", "US")); // empty csv + BOOST_CHECK(!meta_csv_contains("US,FR", "")); // empty token +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/pm/parimutuel_test.cpp b/tests/pm/parimutuel_test.cpp new file mode 100644 index 0000000000..4f35b5a054 --- /dev/null +++ b/tests/pm/parimutuel_test.cpp @@ -0,0 +1,215 @@ +// HF14 parimutuel settlement test — verifies the pure money-split math used by +// settle_market() (pm_evaluator.cpp). The single invariant that MUST always hold +// is ZERO-SUM conservation (no token emission, no leak): +// +// Σ winner_payout + oracle_take + creator_take + lp_bonus +// == Σ winner.amount + losers_sum + forfeit_pool +// +// (oracle_fixed_fee is funded FROM the pool, so it is NOT an input term — it is +// part of oracle_take which is drawn out of losers_sum.) Plus the spec edge +// cases from parimutuel-settlement.md §5. + +#define BOOST_TEST_MODULE pm_parimutuel +#include + +#include + +#include +#include + +using namespace graphene::chain::pm; + +namespace { + + int64_t sum_payout(const settle_result& r) { + return std::accumulate(r.winner_payout.begin(), r.winner_payout.end(), (int64_t)0); + } + + // Σ outputs must equal Σ inputs (losers_sum + forfeit_pool + winner principals). `uncovered` + // (F1) is an INPUT: when winners_pool would go negative the pot cannot fund the payout, so the + // caller tops it up from LP principal — that external top-up is on the input side of the identity. + void check_conservation(const settle_params& p, const std::vector& w, + const settle_result& r) { + int64_t in = p.losers_sum + p.forfeit_pool + r.uncovered; + for (const auto& x : w) in += x.amount; + int64_t out = sum_payout(r) + r.oracle_take + r.creator_take + r.lp_bonus; + BOOST_CHECK_EQUAL(in, out); + BOOST_CHECK(r.oracle_take >= 0); + BOOST_CHECK(r.creator_take >= 0); + BOOST_CHECK(r.lp_bonus >= 0); + BOOST_CHECK(r.uncovered >= 0); + } + +} // namespace + +// Two winners split the losers' pool strictly by weight (no fees/penalty/forfeit). +BOOST_AUTO_TEST_CASE(weight_proportional_split) { + settle_params p; + p.losers_sum = 400; + std::vector w = { {100, 100, 0}, {200, 300, 0} }; + auto r = compute_settlement(p, w); + + // winners_pool = 400; profit = 400 * weight / 400. + BOOST_CHECK_EQUAL(r.winner_payout[0], 100 + 100); // 100 principal + 100 profit + BOOST_CHECK_EQUAL(r.winner_payout[1], 200 + 300); // 200 principal + 300 profit + BOOST_CHECK_EQUAL(r.lp_bonus, 0); + BOOST_CHECK_EQUAL(r.oracle_take, 0); + check_conservation(p, w, r); +} + +// Fees are bp OF losers_sum (10000 = 100%) and route to oracle/creator/LP. +BOOST_AUTO_TEST_CASE(fees_from_losers_sum) { + settle_params p; + p.losers_sum = 1000; + p.oracle_fee_percent = 500; // 5% → 50 + p.creator_fee_percent = 200; // 2% → 20 + p.liquidity_fee_percent = 300; // 3% → 30 → LP + std::vector w = { {500, 100, 0} }; + auto r = compute_settlement(p, w); + + BOOST_CHECK_EQUAL(r.oracle_take, 50); + BOOST_CHECK_EQUAL(r.creator_take, 20); + BOOST_CHECK_EQUAL(r.lp_bonus, 30); + BOOST_CHECK_EQUAL(r.winner_payout[0], 500 + 900); // winners_pool = 1000-100 + check_conservation(p, w, r); +} + +// No winning tokens → the whole pool becomes an LP bonus (spec §5). +BOOST_AUTO_TEST_CASE(no_winners_pool_to_lp) { + settle_params p; + p.losers_sum = 500; + auto r = compute_settlement(p, {}); + BOOST_CHECK(r.winner_payout.empty()); + BOOST_CHECK_EQUAL(r.lp_bonus, 500); + check_conservation(p, {}, r); +} + +// Everyone won (losers_sum = 0): winners share only the forfeit_pool, never lose. +BOOST_AUTO_TEST_CASE(all_winners_only_forfeit) { + settle_params p; + p.losers_sum = 0; + p.forfeit_pool = 100; + std::vector w = { {200, 50, 0}, {200, 50, 0} }; + auto r = compute_settlement(p, w); + BOOST_CHECK_EQUAL(r.winner_payout[0], 200 + 50); + BOOST_CHECK_EQUAL(r.winner_payout[1], 200 + 50); + check_conservation(p, w, r); +} + +// Single winner takes the entire winners_pool. +BOOST_AUTO_TEST_CASE(single_winner_takes_pool) { + settle_params p; + p.losers_sum = 777; + std::vector w = { {123, 9, 0} }; + auto r = compute_settlement(p, w); + BOOST_CHECK_EQUAL(r.winner_payout[0], 123 + 777); + BOOST_CHECK_EQUAL(r.lp_bonus, 0); + check_conservation(p, w, r); +} + +// time_penalty docks PROFIT only; the docked amount accrues to the LP bonus. +BOOST_AUTO_TEST_CASE(time_penalty_to_lp) { + settle_params p; + p.losers_sum = 1000; + std::vector w = { {300, 100, 500000} }; // 50% of profit (1e6 scale) + auto r = compute_settlement(p, w); + // profit = 1000, penalty = 1000 * 0.5 = 500. + BOOST_CHECK_EQUAL(r.winner_payout[0], 300 + 1000 - 500); + BOOST_CHECK_EQUAL(r.lp_bonus, 500); + check_conservation(p, w, r); +} + +// oracle_fixed_fee is capped at the available pool and funded from it (never minted). +BOOST_AUTO_TEST_CASE(oracle_fixed_fee_capped) { + settle_params p; + p.losers_sum = 100; + p.oracle_fixed_fee = 1000; // exceeds the pool + std::vector w = { {50, 10, 0} }; + auto r = compute_settlement(p, w); + BOOST_CHECK_EQUAL(r.oracle_take, 100); // takes the whole pool, capped + BOOST_CHECK_EQUAL(r.winner_payout[0], 50); // winner gets only principal back + check_conservation(p, w, r); +} + +// Rounding dust from the integer weight split is swept into the LP bonus. +BOOST_AUTO_TEST_CASE(rounding_dust_to_lp) { + settle_params p; + p.losers_sum = 10; + std::vector w = { {5, 1, 0}, {5, 2, 0} }; // total weight 3 + auto r = compute_settlement(p, w); + // profit = floor(10*1/3)=3, floor(10*2/3)=6 → distributed 9, dust 1 → LP. + BOOST_CHECK_EQUAL(r.winner_payout[0], 5 + 3); + BOOST_CHECK_EQUAL(r.winner_payout[1], 5 + 6); + BOOST_CHECK_EQUAL(r.lp_bonus, 1); + check_conservation(p, w, r); +} + +// ── Time-weighted LP fee split (distribute_lp) ────────────────────────────────── + +// Equal principal, longer time-in-market earns a larger fee share; Σ == bonus. +BOOST_AUTO_TEST_CASE(lp_time_weight_early_earns_more) { + std::vector lps = { {1000, 3600 * 10}, {1000, 0} }; // 10h vs 0 + auto s = distribute_lp(lps, 600); + BOOST_CHECK_EQUAL(s[0] + s[1], 600); // exact conservation + BOOST_CHECK_GT(s[0], s[1]); // earlier LP earns more +} + +// Equal time reduces to a plain principal-pro-rata split. +BOOST_AUTO_TEST_CASE(lp_equal_time_pro_rata) { + std::vector lps = { {300, 0}, {100, 0} }; + auto s = distribute_lp(lps, 400); + BOOST_CHECK_EQUAL(s[0] + s[1], 400); + BOOST_CHECK_EQUAL(s[0], 300); // 400 * 300/400 + BOOST_CHECK_EQUAL(s[1], 100); +} + +// Single LP takes the whole bonus; zero bonus pays nothing. +BOOST_AUTO_TEST_CASE(lp_single_and_zero_bonus) { + auto one = distribute_lp({ {500, 1234} }, 250); + BOOST_CHECK_EQUAL(one[0], 250); + auto none = distribute_lp({ {500, 10}, {500, 20} }, 0); + BOOST_CHECK_EQUAL(none[0], 0); + BOOST_CHECK_EQUAL(none[1], 0); +} + +// F1 (PR #124): a forfeit_pool more negative than the losers' pot drives winners_pool below zero. +// The floor stops the uint64 wrap AND the shortfall is reported as `uncovered` (charged to LP +// principal by the caller) rather than emitted — winners get exactly their principal back. +BOOST_AUTO_TEST_CASE(negative_winners_pool_reports_uncovered) { + settle_params p; + p.losers_sum = 500; + p.forfeit_pool = -1000; // leverage profit outran the losing stakes + std::vector w = { {1000, 100, 0} }; + auto r = compute_settlement(p, w); + BOOST_CHECK_EQUAL(r.uncovered, 500); // = |avail + forfeit_pool| = |500 - 1000| + BOOST_CHECK_EQUAL(r.winner_payout[0], 1000); // principal only, no profit + BOOST_CHECK_EQUAL(r.lp_bonus, 0); + check_conservation(p, w, r); // holds with uncovered on the input side +} + +// F1 boundary: when the losers' pot still covers the negative forfeit, winners_pool stays >= 0 and +// nothing is uncovered (the floor never engages). +BOOST_AUTO_TEST_CASE(negative_forfeit_still_covered_no_uncovered) { + settle_params p; + p.losers_sum = 100000; + p.forfeit_pool = -1000; + std::vector w = { {1000, 100, 0} }; + auto r = compute_settlement(p, w); + BOOST_CHECK_EQUAL(r.uncovered, 0); + BOOST_CHECK_EQUAL(r.winner_payout[0], 1000 + 99000); // full winners_pool = 100000 - 1000 + check_conservation(p, w, r); +} + +// F3 (PR #124): a time_penalty above 1e6 (only reachable if a median mis-set pm_max_time_penalty) +// must be clamped to the profit so the payout never drops below principal — otherwise settle_market +// silently drops the winner's stake (`if (payout > 0)`) while lp_bonus carries a phantom penalty. +BOOST_AUTO_TEST_CASE(time_penalty_clamped_at_profit) { + settle_params p; + p.losers_sum = 1000; + std::vector w = { {300, 100, 2000000} }; // 200% of profit — must clamp to 100% + auto r = compute_settlement(p, w); + BOOST_CHECK_EQUAL(r.winner_payout[0], 300); // principal returned, profit fully docked, never negative + BOOST_CHECK(r.winner_payout[0] >= w[0].amount); + BOOST_CHECK_EQUAL(r.lp_bonus, 1000); // the whole profit accrues to LP (clamped penalty) + check_conservation(p, w, r); +} diff --git a/tests/pm/replay/.gitignore b/tests/pm/replay/.gitignore new file mode 100644 index 0000000000..89f9ac04aa --- /dev/null +++ b/tests/pm/replay/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/tests/pm/replay/README.md b/tests/pm/replay/README.md new file mode 100644 index 0000000000..f53777fdb5 --- /dev/null +++ b/tests/pm/replay/README.md @@ -0,0 +1,85 @@ +# PM replay cases + +Standalone replay programs used to verify the PR #124 review findings and the fix round. +They are **not** part of the CMake build and are not run by CI — each one is a `main()` that +replays a concrete money path and prints the numbers, so a claim about the PM math can be checked +in about five seconds without building a node. + +## Why they run without a chain build + +`libraries/chain/pm/parimutuel.cpp` and `libraries/chain/pm/leverage.cpp` are deliberately free of +database and chainbase types — their only dependency is `fc::uint128_t`. So the money math links on +its own, against the real consensus source, with no libfc and no node. + +`fc_u128_shim.cpp` supplies the only two out-of-line `fc::uint128_t` members those files reference +(`operator*=`, `operator/=`) using native `unsigned __int128`. fc's real `operator/=` delegates to +`boost::multiprecision::uint128_t` truncating division and `operator*=` is a wrapping 128-bit +multiply (`thirdparty/fc/src/uint128_t.cpp:224,257`) — both bit-identical to the native ops, so +truncation behaves exactly as it does in consensus. + +The cases that replay *evaluator* transitions (`pm_place_bet`, `pm_cancel_bet`, +`pm_add_liquidity`, `pm_withdraw_liquidity`) model them inline rather than linking +`pm_evaluator.cpp`, which does need the database. Each such model is transcribed from the evaluator +and cites its line numbers; they preserve the truncating `uint128` division, `share_type`'s +`int64_t` width, and the `k = reserve_a × reserve_b` rebuild. + +## Build & run + +```sh +tests/pm/replay/build.sh # all cases -> tests/pm/replay/out/ +tests/pm/replay/build.sh t12_chain # just one +out/t12_chain +``` + +`BOOST_INC` and `CXX` are overridable. Building from a git worktree needs +`FC_INC=
/thirdparty/fc/include`, since a worktree carries no submodules. + +## Cases + +Round 1 — against PR head `84b502c`: + +| file | finding | what it shows | +| --- | --- | --- | +| `t1-parimutuel-test.patch` | B3 | 2 extra Boost.Test cases for `tests/pm/parimutuel_test.cpp`; `negative_forfeit_pool_never_mints` failed on `84b502c` (payout `4611686018427388654`) | +| `dump.cpp` | B3 | payout figures for 1 / 2-equal / 2-unequal winners, plus a positive control | +| `t2_reach.cpp` | B3 | `curve_residual < 0` on every profitable leveraged close | +| `t3_lp.cpp` | B4 | reserves and `k` never shrank on LP withdraw | +| `t3b_loan.cpp` | B4 | `max_leverage_loan` does *not* grow with phantom depth — hypothesis rejected | +| `t3c_weight.cpp` | B4 | the real money path: +29.6% settlement claim weight per VIZ, free | +| `t4_cancel.cpp` | B6 | `reserve_a` = −58 824, `k` 1e12 → ~4.8e25 | +| `t5_threshold.cpp` | B3 | detonation boundary: `forfeit_pool = -9600` fine, `-9601` gives `4611686018427487903` | +| `t6_cycle.cpp` | B4/B5 | the 9-step bet/cancel + LP cycle | + +Round 2 — against the fix commits (`172d87c` → `b06bdbf` → `26fd26a` → `f697e5c`): + +| file | target | what it shows | +| --- | --- | --- | +| `t7_fixed.cpp` | B4/B5 fix | exploit gone (+2.69% → +0.00%), phantom depth 0, no ratchet over 10 cycles; but the round trip is not exact under truncation | +| `t8_shrink.cpp` | B4 fix regression | the residual excess is bet-splitting truncation, not the LP cycle; and a legal LP withdrawal between bet and cancel drove `reserve_a` to −25 000 (now refused by `b06bdbf`) | +| `t9_mag.cpp` | B4 fix residual | sizes the truncation drift: ≤1 satoshi per round trip, but unbounded — 500 cycles decay a 100-VIZ stake's weight 62 501 → 62 431 | +| `t10_conserve.cpp` | B3 fix | the `winners_pool < 0 → 0` floor emits exactly `\|winners_pool\|`; 1163/1988 swept settlements emit. Checks the contract stated in `parimutuel.hpp:11-13` | +| `t11_cancel_move.cpp` | B6 fix | the guard refuses `t8`'s detonation, but a *passing* cancel on a moved curve still rebuilds `k` wrong — an opposing bet is enough, no LP op needed. 4752/5400 triples inflate the next claim, 0 deflate | +| `t12_chain.cpp` | B6 residual | bet A / bet B / cancel A / bet A → +64.06% claim weight for the same stake, compounding to +75.79% over 5 rounds; final reserves bit-identical to honest | +| `t12b_asym.cpp` | same, asymmetric | 65 890 chains, 0 adverse, best +10 202% | +| `t12c_trace.cpp` | same, worst case | 1000 at risk → weight 102 093 vs 991 honest (103×); market `reserve_b` 105 500 → 3 407 | +| `t13_penalty.cpp` | B9 wiring | `pm_max_time_penalty` is unbounded in `chain_properties_pm::validate()`; at 2e6 a winner's payout goes to −50 000 while `lp_bonus` holds 200 000 of 150 000 available | + +Round 3 — against the F1/F2/F3 fix commits (`93e43e7` → `5ff694e` → `f3ff915` → `a01016b`): + +| file | target | what it shows | +| --- | --- | --- | +| `t14_f2_ledger.cpp` | F2 fix + F1 fix | F2 holds: the `t12` chain now costs 27 457 instead of being free and `k` is invariant under the same truncation convention as `place_bet`. F1 does not: `compute_settlement` never assigns `settle_result::uncovered`, so `5ff694e`'s LP charge is dead code — and the curve-priced cancel makes a negative `forfeit_pool` reachable with no leverage, emitting up to 138 463 on a 200 000 market | + +Round 4 — early-exit / deferred-claim settlement pass (against head `dd6c5d1`): + +| file | target | what it shows | +| --- | --- | --- | +| `t15_early_exit_headroom.cpp` | `499246e9` (headroom clamp) + F1/#300 conservation | The early-exit deferred-claim pass had NO test coverage. Models the settle-pass verbatim (`pm_evaluator.cpp:647-700`) and settles through the real `compute_settlement`. **A:** with the bucket UNCLAMPED, the default 33% cap + a valid 90%-fee market overdraws the pot → `uncovered = 23 000` charged to LP principal, reachable with default params. **B:** the real clamped code caps the bucket to headroom (33 000 → 10 000), `uncovered = 0`, ledger balances. **C:** a 1 680-combination sweep of valid (fees, cap, forfeit, claim) confirms the clamp is solvency-neutral — 0 unbalanced ledgers and 0 claim-induced `uncovered` regressions vs the no-claim baseline. The 400 `uncovered_hits` are the pre-existing negative-`forfeit_pool` case (the F1 LP-charge target), not the claim path. Exit code 0 on pass. | + +`t10_conserve`, `t13_penalty` and `t14_f2_ledger` re-run unchanged against this head; `t15` is new. + +## Live corroboration + +`prediction_market_api` on testnet: market **19** carried `forfeit_pool = -69227`, reconciling +exactly from its three leverage positions (−88 579 / +19 352 / 0). Settlement zeroes the field +(`pm_evaluator.cpp:411,473`), so state cannot rule out earlier occurrences. diff --git a/tests/pm/replay/build.sh b/tests/pm/replay/build.sh new file mode 100755 index 0000000000..c52781f2ff --- /dev/null +++ b/tests/pm/replay/build.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Build every replay case. No CMake target, no libfc link, no chain build required — +# see README.md for why this works. +# +# ./build.sh build all, into ./out +# ./build.sh t12_chain build one +# BOOST_INC=... ./build.sh +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +root="$(cd "$here/../../.." && pwd)" +# FC_INC override: a git worktree carries no submodules, so point this at the main +# checkout's thirdparty/fc/include when building from one. +: "${FC_INC:=$root/thirdparty/fc/include}" +fc_inc="$FC_INC" +: "${BOOST_INC:=/opt/homebrew/opt/boost/include}" +[ -d "$BOOST_INC" ] || BOOST_INC=/usr/include +: "${CXX:=clang++}" + +inc=(-I"$root/libraries/chain/include" -I"$fc_inc" -I"$BOOST_INC") +out="$here/out"; mkdir -p "$out" + +# Cases that link the real consensus math. The rest are self-contained (native +# unsigned __int128 stands in for fc::uint128_t) and need only -std=c++17. +linked="t14_f2_ledger t15_early_exit_headroom dump t2_reach t3_lp t3b_loan t3c_weight t4_cancel t5_threshold t6_cycle t10_conserve t13_penalty" + +build_objs() { + [ -f "$out/parimutuel.o" ] && return 0 + echo " cc libraries/chain/pm/{parimutuel,leverage}.cpp + fc_u128_shim.cpp" + $CXX -std=c++14 -w -c "$root/libraries/chain/pm/parimutuel.cpp" -o "$out/parimutuel.o" "${inc[@]}" + $CXX -std=c++14 -w -c "$root/libraries/chain/pm/leverage.cpp" -o "$out/leverage.o" "${inc[@]}" + $CXX -std=c++14 -w -c "$here/fc_u128_shim.cpp" -o "$out/shim.o" -I"$fc_inc" -I"$BOOST_INC" +} + +one() { + local name="$1" src="$here/$1.cpp" + [ -f "$src" ] || { echo "no such case: $1" >&2; exit 1; } + if [[ " $linked " == *" $name "* ]]; then + build_objs + $CXX -std=c++14 -w -o "$out/$name" "$src" "$out/parimutuel.o" "$out/leverage.o" "$out/shim.o" "${inc[@]}" + else + $CXX -std=c++17 -O1 -w -o "$out/$name" "$src" + fi + echo " built out/$name" +} + +if [ $# -gt 0 ]; then + for n in "$@"; do one "${n%.cpp}"; done +else + for f in "$here"/t*.cpp "$here"/dump.cpp; do + [ "$(basename "$f")" = "fc_u128_shim.cpp" ] && continue + one "$(basename "${f%.cpp}")" + done +fi +echo "run e.g.: $out/t12_chain" diff --git a/tests/pm/replay/dump.cpp b/tests/pm/replay/dump.cpp new file mode 100644 index 0000000000..f90dc872fa --- /dev/null +++ b/tests/pm/replay/dump.cpp @@ -0,0 +1,23 @@ +#include +#include +using namespace graphene::chain::pm; +static void run(const char* tag, int64_t fp, std::vector w) { + settle_params p; p.losers_sum = 0; p.forfeit_pool = fp; + auto r = compute_settlement(p, w); + long long in = p.losers_sum + p.forfeit_pool, out = r.oracle_take + r.creator_take + r.lp_bonus; + printf("%s forfeit_pool=%lld winners=%zu\n", tag, (long long)fp, w.size()); + for (size_t i = 0; i < w.size(); ++i) { + in += w[i].amount; out += r.winner_payout[i]; + printf(" winner[%zu] amount=%-6lld weight=%-6lld payout=%lld\n", + i, (long long)w[i].amount, (long long)w[i].weight, (long long)r.winner_payout[i]); + } + printf(" lp_bonus=%lld oracle=%lld creator=%lld\n Sigma_in=%lld Sigma_out=%lld (int64-wrapped)\n\n", + (long long)r.lp_bonus, (long long)r.oracle_take, (long long)r.creator_take, in, out); +} +int main() { + run("A two unequal winners:", -1000, { {1000,1000,0}, {3000,3000,0} }); + run("B two equal winners: ", -1000, { {1000,1000,0}, {1000,1000,0} }); + run("C single winner: ", -1000, { {4000,4000,0} }); + run("D control, positive: ", 1000, { {1000,1000,0}, {3000,3000,0} }); + return 0; +} diff --git a/tests/pm/replay/fc_u128_shim.cpp b/tests/pm/replay/fc_u128_shim.cpp new file mode 100644 index 0000000000..dec8a28319 --- /dev/null +++ b/tests/pm/replay/fc_u128_shim.cpp @@ -0,0 +1,23 @@ +// Minimal, bit-exact stand-in for the two out-of-line fc::uint128_t members that +// libraries/chain/pm/parimutuel.cpp references, so the pure PM math can be unit +// tested without linking libfc. fc's real operator/= delegates to +// boost::multiprecision::uint128_t truncating division and its operator*= is a +// wrapping 128-bit multiply (fc/src/uint128_t.cpp:224,257) — both identical to +// the compiler's native unsigned __int128 ops. +#include + +namespace fc { + static inline unsigned __int128 pack(uint64_t h, uint64_t l) { + return ((unsigned __int128)h << 64) | l; + } + + uint128_t& uint128_t::operator*=(const uint128_t& b) { + unsigned __int128 v = pack(hi, lo) * pack(b.hi, b.lo); + hi = (uint64_t)(v >> 64); lo = (uint64_t)v; return *this; + } + + uint128_t& uint128_t::operator/=(const uint128_t& b) { + unsigned __int128 v = pack(hi, lo) / pack(b.hi, b.lo); + hi = (uint64_t)(v >> 64); lo = (uint64_t)v; return *this; + } +} diff --git a/tests/pm/replay/t1-parimutuel-test.patch b/tests/pm/replay/t1-parimutuel-test.patch new file mode 100644 index 0000000000..38306a93ed --- /dev/null +++ b/tests/pm/replay/t1-parimutuel-test.patch @@ -0,0 +1,42 @@ +diff --git a/tests/pm/parimutuel_test.cpp b/tests/pm/parimutuel_test.cpp +index 697e91f..cb79c4f 100644 +--- a/tests/pm/parimutuel_test.cpp ++++ b/tests/pm/parimutuel_test.cpp +@@ -93,6 +93,37 @@ BOOST_AUTO_TEST_CASE(all_winners_only_forfeit) { + check_conservation(p, w, r); + } + ++// A NEGATIVE forfeit_pool must never mint. liquidate_position()/pm_leverage_close() ++// add an unclamped `curve_residual = pos.total_bet - cancel_value` to ++// market.forfeit_pool, which is negative whenever the closing position is in ++// profit — so this input is reachable on-chain. ++BOOST_AUTO_TEST_CASE(negative_forfeit_pool_never_mints) { ++ settle_params p; ++ p.losers_sum = 0; ++ p.forfeit_pool = -1000; // leverage-exit residual ++ std::vector w = { {1000, 1000, 0}, {3000, 3000, 0} }; // unequal weight ++ auto r = compute_settlement(p, w); ++ ++ // Payouts may be capped, but total outputs can never exceed total inputs. ++ int64_t in = p.losers_sum + p.forfeit_pool; ++ for (const auto& x : w) in += x.amount; ++ int64_t out = sum_payout(r) + r.oracle_take + r.creator_take + r.lp_bonus; ++ BOOST_CHECK_LE(out, in); ++ for (size_t i = 0; i < w.size(); ++i) ++ BOOST_CHECK_LE(r.winner_payout[i], w[i].amount + p.losers_sum); ++} ++ ++// Same input with a SINGLE winner: the (uint64_t) two's-complement roundtrip is ++// exact when weight == total_weight, so this case hides the bug above. ++BOOST_AUTO_TEST_CASE(negative_forfeit_pool_single_winner) { ++ settle_params p; ++ p.losers_sum = 0; ++ p.forfeit_pool = -1000; ++ std::vector w = { {4000, 4000, 0} }; ++ auto r = compute_settlement(p, w); ++ BOOST_CHECK_LE(r.winner_payout[0], w[0].amount); ++} ++ + // Single winner takes the entire winners_pool. + BOOST_AUTO_TEST_CASE(single_winner_takes_pool) { + settle_params p; diff --git a/tests/pm/replay/t10_conserve.cpp b/tests/pm/replay/t10_conserve.cpp new file mode 100644 index 0000000000..7d5290853a --- /dev/null +++ b/tests/pm/replay/t10_conserve.cpp @@ -0,0 +1,80 @@ +// t10_conserve.cpp — does b06bdbf's `if (winners_pool < 0) winners_pool = 0;` restore the +// conservation identity that pm_evaluator.cpp:395 documents? +// +// "Money is conserved exactly: Σ outputs == Σ all bet amounts + LP principal + forfeit_pool" +// +// Σ in = Σ winner principals + losers_sum + forfeit_pool (LP principal cancels: returned 1:1) +// Σ out = Σ winner payouts + oracle_take + creator_take + lp_bonus +// +// The catastrophic uint64 wrap (B3) is gone. This asks the narrower question: with the floor, +// is the remaining shortfall absorbed anywhere, or emitted? +#include +#include +using namespace graphene::chain::pm; + +static long long run(const char* tag, int64_t losers, int64_t fp, + std::vector w, bool verbose) { + settle_params p; + p.losers_sum = losers; + p.forfeit_pool = fp; + p.oracle_fee_percent = 100; // 1% + p.creator_fee_percent = 100; // 1% + p.liquidity_fee_percent = 200; // 2% + auto r = compute_settlement(p, w); + + long long in = losers + fp, out = r.oracle_take + r.creator_take + r.lp_bonus; + for (size_t i = 0; i < w.size(); ++i) { in += w[i].amount; out += r.winner_payout[i]; } + + int64_t oracle_fee = losers * 100 / 10000; + int64_t creator_fee = losers * 100 / 10000; + int64_t liq_fee = losers * 200 / 10000; + int64_t avail = losers - oracle_fee - creator_fee - liq_fee; if (avail < 0) avail = 0; + long long wp_raw = avail + fp; // pre-floor winners_pool (oracle_fixed_fee = 0 here) + + if (verbose) + printf(" %-28s losers=%-8lld fp=%-9lld winners_pool(raw)=%-9lld Sin=%-9lld Sout=%-9lld delta=%+lld %s\n", + tag, (long long)losers, (long long)fp, wp_raw, in, out, out - in, + out == in ? "conserved" : "*** EMITTED ***"); + return out - in; +} + +int main() { + printf("=== 1. floor engaged (winners_pool would be negative) ===\n"); + run("2 winners, no losers", 0, -1000, {{1000,1000,0},{3000,3000,0}}, true); + run("2 winners, small losers", 500, -1000, {{1000,1000,0},{3000,3000,0}}, true); + run("2 winners, fp == -avail", 1042, -1021, {{1000,1000,0},{3000,3000,0}}, true); + run("single winner", 0, -5000, {{4000,4000,0}}, true); + run("no winning tokens", 0, -5000, {{4000,0,0}}, true); + + printf("\n=== 2. floor NOT engaged (control) ===\n"); + run("positive forfeit_pool", 0, 1000, {{1000,1000,0},{3000,3000,0}}, true); + run("negative but covered", 100000,-1000, {{1000,1000,0},{3000,3000,0}}, true); + run("fp == 0", 10000, 0, {{1000,1000,0},{3000,3000,0}}, true); + + printf("\n=== 3. is the emission exactly |winners_pool|? sweep ===\n"); + long long bad = 0, tot = 0, worst = 0; bool law = true; + for (int64_t losers = 0; losers <= 200000; losers += 7331) + for (int64_t fp = -300000; fp <= 50000; fp += 4999) { + int64_t of = losers*100/10000, cf = losers*100/10000, lf = losers*200/10000; + int64_t avail = losers - of - cf - lf; if (avail < 0) avail = 0; + long long wp = avail + fp; + long long d = run("", losers, fp, {{1000,1000,0},{3000,3000,0}}, false); + ++tot; + if (d != 0) { ++bad; if (d > worst) worst = d; if (d != -wp) law = false; } + else if (wp < 0) law = false; // floor engaged but nothing emitted → law wrong + } + printf(" %lld/%lld settlements emit tokens; worst single emission = %+lld\n", bad, tot, worst); + printf(" emission == |negative winners_pool| in every case: %s\n", law ? "YES" : "no"); + + printf("\n=== 4. reachability, anchored on live testnet market 19 (forfeit_pool = -69227) ===\n"); + // Emission = |avail + forfeit_pool| when negative, avail = losers_sum*(1 - fee_bp/10000). + // With the 4%% of fees used above, a market carrying market 19's residual emits for every + // losers_sum below 69227/0.96 = 72111 raw. + for (int64_t losers : {0LL, 20000LL, 50000LL, 72111LL, 100000LL}) + run("mkt-19 residual", losers, -69227, {{50000,50000,0}}, true); + printf(" -> break-even losers_sum = %lld raw (%.3f VIZ); below it the settlement emits\n", + (long long)(69227 * 10000 / 9600), 69227 * 10000 / 9600 / 1000.0); + printf(" (forfeit_pool is the NET of every leverage close on the market, so the residual\n" + " grows with leverage volume while losers_sum does not.)\n"); + return 0; +} diff --git a/tests/pm/replay/t11_cancel_move.cpp b/tests/pm/replay/t11_cancel_move.cpp new file mode 100644 index 0000000000..c62d228768 --- /dev/null +++ b/tests/pm/replay/t11_cancel_move.cpp @@ -0,0 +1,151 @@ +// t11_cancel_move.cpp — b06bdbf's B6 guard vs. the k corruption it claims to fix. +// +// The commit message says the assert fixes "underflowing share_type and corrupting +// k = reserve_a x reserve_b". The assert only rejects the case where the reversal would make a +// reserve NEGATIVE. It says nothing about the case where the reversal succeeds on a curve that +// has moved: pm_cancel_bet still reverses the NOMINAL pair (amount, weight) recorded at bet time, +// so whenever any other operation touched the curve in between, the post-cancel state is not the +// state the market would have had without the bet — and k is rebuilt from the wrong reserves. +// +// Counterfactual = the identical market with the same intervening op but no bet/cancel pair. +// Every transition mirrors pm_evaluator.cpp on origin/pm (f697e5c) incl. truncating uint128 div. +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v) { return (u128)(uint64_t)v; } +static int64_t D(u128 v) { return (int64_t)(uint64_t)v; } + +struct market { int64_t ra = 0, rb = 0, bets = 0, L = 0; u128 k = 0; }; +struct bet { int64_t amount = 0, weight = 0; int side = 0; }; + +static market make_market(int64_t liq) { + market m; int64_t h = liq / 2; + m.ra = h; m.rb = liq - h; m.L = liq; m.k = U(m.ra) * U(m.rb); return m; +} +// pm_place_bet, binary CPMM (:1110-1140) — k preserved across the fill +static bet place(market& m, int side, int64_t amount) { + int64_t rin = side == 0 ? m.ra : m.rb, rout = side == 0 ? m.rb : m.ra; + int64_t nout = D(m.k / U(rin + amount)); + bet b; b.amount = amount; b.weight = rout - nout; b.side = side; + if (side == 0) { m.ra += amount; m.rb = nout; } else { m.rb += amount; m.ra = nout; } + m.bets += amount; return b; +} +// pm_cancel_bet (:1305-1322) with b06bdbf's B6 assert. Returns false = cancel refused. +static bool cancel(market& m, const bet& b) { + int64_t rin = b.side == 0 ? m.ra : m.rb; + if (rin < b.amount) return false; // <-- b06bdbf FC_ASSERT + if (b.side == 0) { m.ra -= b.amount; m.rb += b.weight; } + else { m.rb -= b.amount; m.ra += b.weight; } + m.bets -= b.amount; + m.k = U(m.ra) * U(m.rb); // k rebuilt from whatever is there + return true; +} +// pm_add_liquidity / pm_withdraw_liquidity after 172d87c — proportional, price-neutral +static void add_liq(market& m, int64_t a) { + if (m.L > 0) { u128 n = U(m.L + a), d = U(m.L); + m.ra = D(U(m.ra) * n / d); m.rb = D(U(m.rb) * n / d); } + m.L += a; m.k = U(m.ra) * U(m.rb); +} +static bool wd_liq(market& m, int64_t w, int64_t minliq) { + if (m.L - w < minliq) return false; + u128 n = U(m.L - w), d = U(m.L); + m.ra = D(U(m.ra) * n / d); m.rb = D(U(m.rb) * n / d); + m.L -= w; m.k = U(m.ra) * U(m.rb); return true; +} +// implied probability of side A, x1e4, as the CPMM curve prices it +static double pA(const market& m) { return 100.0 * m.rb / (double)(m.ra + m.rb); } + +// weight a fresh `stake` on side A buys from a given state — the settlement claim +static int64_t probe(market m, int64_t stake) { return place(m, 0, stake).weight; } + +static void report(const char* tag, market got, market want, int64_t probe_stake) { + int64_t wg = probe(got, probe_stake), ww = probe(want, probe_stake); + printf(" %-34s ra=%-9lld rb=%-9lld P(A)=%6.2f%% k=%-14llu next-%lld buys %lld\n", + tag, (long long)got.ra, (long long)got.rb, pA(got), + (unsigned long long)got.k, (long long)probe_stake, (long long)wg); + printf(" %-34s ra=%-9lld rb=%-9lld P(A)=%6.2f%% k=%-14llu %lld %+.2f%%\n", + " counterfactual (no bet/cancel)", (long long)want.ra, (long long)want.rb, pA(want), + (unsigned long long)want.k, (long long)ww, 100.0 * (wg - ww) / (double)ww); +} + +int main() { + const int64_t MINLIQ = 100000; + + printf("=== A. the case b06bdbf DOES stop (t8's detonation) ===\n"); + { + market m = make_market(400000); + bet a = place(m, 0, 100000); + wd_liq(m, 300000, MINLIQ); + printf(" after legal 300k withdrawal: ra=%lld bet nominal=%lld -> cancel %s\n", + (long long)m.ra, (long long)a.amount, cancel(m, a) ? "APPLIED" : "REFUSED (guard holds)"); + } + + printf("\n=== B. same shape, withdrawal small enough to pass the guard ===\n"); + { + market got = make_market(400000), want = make_market(400000); + bet a = place(got, 0, 50000); + wd_liq(got, 100000, MINLIQ); + wd_liq(want, 100000, MINLIQ); // counterfactual: same withdrawal, no bet + printf(" cancel %s\n", cancel(got, a) ? "APPLIED" : "REFUSED"); + report("after bet+withdraw+cancel", got, want, 50000); + } + + printf("\n=== C. no LP op at all — just an opposing bet in between ===\n"); + { + market got = make_market(400000), want = make_market(400000); + bet a = place(got, 0, 50000); + place(got, 1, 50000); + place(want, 1, 50000); // counterfactual: only the opposing bet + printf(" cancel %s\n", cancel(got, a) ? "APPLIED" : "REFUSED"); + report("after bet+opposing bet+cancel", got, want, 50000); + } + + printf("\n=== D. LP ADD in between ===\n"); + { + market got = make_market(400000), want = make_market(400000); + bet a = place(got, 0, 50000); + add_liq(got, 200000); + add_liq(want, 200000); + printf(" cancel %s\n", cancel(got, a) ? "APPLIED" : "REFUSED"); + report("after bet+add+cancel", got, want, 50000); + } + + printf("\n=== E. repeat the C cycle — does it ratchet? ===\n"); + { + market m = make_market(400000); + for (int i = 1; i <= 10; ++i) { + bet a = place(m, 0, 50000); + bet opp = place(m, 1, 50000); + if (!cancel(m, a)) { printf(" cycle %d: cancel refused\n", i); break; } + if (!cancel(m, opp)) { printf(" cycle %2d: opposing cancel refused; ra=%lld rb=%lld\n", i, (long long)m.ra, (long long)m.rb); break; } + if (i == 1 || i == 5 || i == 10) + printf(" after cycle %2d: ra=%-8lld rb=%-8lld k=%-14llu depth=%lld (started 400000)\n", + i, (long long)m.ra, (long long)m.rb, (unsigned long long)m.k, + (long long)(m.ra + m.rb)); + } + } + + printf("\n=== F. sweep: how often, and how large, is the claim-weight excess? ===\n"); + { + long long tot = 0, refused = 0, favourable = 0, adverse = 0; + double best = 0, worst = 0; + for (int64_t liq = 200000; liq <= 900000; liq += 47000) + for (int64_t amt = 5000; amt <= 200000; amt += 9973) + for (int64_t opp = 5000; opp <= 200000; opp += 11117) { + market got = make_market(liq), want = make_market(liq); + bet a = place(got, 0, amt); + place(got, 1, opp); + place(want, 1, opp); + ++tot; + if (!cancel(got, a)) { ++refused; continue; } + int64_t wg = probe(got, amt), ww = probe(want, amt); + if (ww <= 0) continue; + double p = 100.0 * (wg - ww) / (double)ww; + if (p > 0) { ++favourable; if (p > best) best = p; } + else if (p < 0) { ++adverse; if (p < worst) worst = p; } + } + printf(" %lld triples: %lld refused by the guard, %lld inflate the next claim (worst %+.2f%%),\n" + " %lld deflate it (worst %+.2f%%)\n", tot, refused, favourable, best, adverse, worst); + } + return 0; +} diff --git a/tests/pm/replay/t12_chain.cpp b/tests/pm/replay/t12_chain.cpp new file mode 100644 index 0000000000..d276c88661 --- /dev/null +++ b/tests/pm/replay/t12_chain.cpp @@ -0,0 +1,120 @@ +// t12_chain.cpp — turn t11's case C into a money path. +// +// pm_cancel_bet reverses the NOMINAL (amount, weight) pair. When another bet is open, the +// reversal leaves the curve cheaper on the cancelled side than it should be (t11: +10..+86% +// claim weight for the next stake). The reversal is also exactly commutative, so cancelling +// everything restores the reserves bit-for-bit — nothing trips an invariant afterwards. +// +// Chain (one actor, two accounts, one cancellation-enabled market, no LP ops, no leverage): +// 1. bet X on A 2. bet X on B 3. cancel (1) +// 4. bet X on A <-- priced off the corrupted curve +// 5. cancel (2) -> hold one A bet of X, weight inflated, everything else refunded +// Honest baseline: the same single X bet on A placed on the untouched market. +#include +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v) { return (u128)(uint64_t)v; } +static int64_t D(u128 v) { return (int64_t)(uint64_t)v; } + +struct market { int64_t ra = 0, rb = 0, bets = 0, a_bets = 0, b_bets = 0, L = 0; u128 k = 0; }; +struct bet { int64_t amount = 0, weight = 0; int side = 0; }; + +static market make_market(int64_t liq) { + market m; int64_t h = liq / 2; + m.ra = h; m.rb = liq - h; m.L = liq; m.k = U(m.ra) * U(m.rb); return m; +} +static bet place(market& m, int side, int64_t amount) { + int64_t rin = side == 0 ? m.ra : m.rb, rout = side == 0 ? m.rb : m.ra; + int64_t nout = D(m.k / U(rin + amount)); + bet b; b.amount = amount; b.weight = rout - nout; b.side = side; + if (side == 0) { m.ra += amount; m.rb = nout; m.a_bets += amount; } + else { m.rb += amount; m.ra = nout; m.b_bets += amount; } + m.bets += amount; return b; +} +static bool cancel(market& m, const bet& b) { + int64_t rin = b.side == 0 ? m.ra : m.rb; + if (rin < b.amount) return false; // b06bdbf B6 guard + if (b.side == 0) { m.ra -= b.amount; m.rb += b.weight; m.a_bets -= b.amount; } + else { m.rb -= b.amount; m.ra += b.weight; m.b_bets -= b.amount; } + m.bets -= b.amount; + m.k = U(m.ra) * U(m.rb); + return true; +} + +int main() { + printf("=== the chain, 400000 market, X = 50000 ===\n"); + market m = make_market(400000); + const int64_t X = 50000; + market honest = make_market(400000); + bet base = place(honest, 0, X); + printf(" honest: one %lld bet on A buys weight %lld\n", (long long)X, (long long)base.weight); + + bet b1 = place(m, 0, X); + printf(" 1. bet A ra=%-8lld rb=%-8lld weight=%lld\n", + (long long)m.ra, (long long)m.rb, (long long)b1.weight); + bet b2 = place(m, 1, X); + printf(" 2. bet B ra=%-8lld rb=%-8lld weight=%lld\n", + (long long)m.ra, (long long)m.rb, (long long)b2.weight); + printf(" 3. cancel (1) %s", cancel(m, b1) ? "ok" : "REFUSED"); + printf(" ra=%-8lld rb=%-8lld\n", (long long)m.ra, (long long)m.rb); + bet b3 = place(m, 0, X); + printf(" 4. bet A again ra=%-8lld rb=%-8lld weight=%lld <-- %+.2f%% vs honest\n", + (long long)m.ra, (long long)m.rb, (long long)b3.weight, + 100.0 * (b3.weight - base.weight) / (double)base.weight); + printf(" 5. cancel (2) %s", cancel(m, b2) ? "ok" : "REFUSED"); + printf(" ra=%-8lld rb=%-8lld k=%llu\n", (long long)m.ra, (long long)m.rb, (unsigned long long)m.k); + printf(" final state vs honest-single-bet state: ra %lld/%lld rb %lld/%lld a_bets %lld/%lld %s\n", + (long long)m.ra, (long long)honest.ra, (long long)m.rb, (long long)honest.rb, + (long long)m.a_bets, (long long)honest.a_bets, + (m.ra == honest.ra && m.rb == honest.rb && m.a_bets == honest.a_bets) + ? "IDENTICAL state, larger claim" : "state differs"); + printf(" actor holds: stake %lld, weight %lld (honest %lld) -> +%.2f%% of the winners' pool per VIZ\n", + (long long)b3.amount, (long long)b3.weight, (long long)base.weight, + 100.0 * (b3.weight - base.weight) / (double)base.weight); + + printf("\n=== does it compound? repeat the (1,2,cancel1,bet,cancel2) block, keeping each A bet ===\n"); + { + market c = make_market(400000); + int64_t total_stake = 0, total_weight = 0; + std::vector held; + for (int i = 1; i <= 8; ++i) { + bet p1 = place(c, 0, X); + bet p2 = place(c, 1, X); + if (!cancel(c, p1)) { printf(" round %d: cancel(1) refused\n", i); break; } + bet p3 = place(c, 0, X); + if (!cancel(c, p2)) { printf(" round %d: cancel(2) refused (ra=%lld)\n", i, (long long)c.ra); break; } + held.push_back(p3); + total_stake += p3.amount; total_weight += p3.weight; + // honest: the same cumulative stake bought in one go on a fresh market + market h = make_market(400000); + int64_t hw = place(h, 0, total_stake).weight; + printf(" round %d: held %lld stake, weight %-8lld honest same stake = %-8lld %+.2f%%\n", + i, (long long)total_stake, (long long)total_weight, (long long)hw, + 100.0 * (total_weight - hw) / (double)hw); + } + } + + printf("\n=== sweep: best single-round inflation over (liquidity, X) ===\n"); + { + double best = 0; int64_t bl = 0, bx = 0; long long tot = 0, ok = 0; + for (int64_t liq = 100000; liq <= 2000000; liq += 23000) + for (int64_t x = 1000; x <= 900000; x += 7331) { + market s = make_market(liq), h = make_market(liq); + int64_t hw = place(h, 0, x).weight; + if (hw <= 0) continue; + ++tot; + bet p1 = place(s, 0, x); + bet p2 = place(s, 1, x); + if (!cancel(s, p1)) continue; + bet p3 = place(s, 0, x); + if (!cancel(s, p2)) continue; + ++ok; + double gain = 100.0 * (p3.weight - hw) / (double)hw; + if (gain > best) { best = gain; bl = liq; bx = x; } + } + printf(" %lld/%lld chains complete; best %+.2f%% at liquidity=%lld X=%lld\n", + ok, tot, best, (long long)bl, (long long)bx); + } + return 0; +} diff --git a/tests/pm/replay/t12b_asym.cpp b/tests/pm/replay/t12b_asym.cpp new file mode 100644 index 0000000000..7a3f04f09f --- /dev/null +++ b/tests/pm/replay/t12b_asym.cpp @@ -0,0 +1,37 @@ +// asymmetric variant: bet A = x, opposing bet B = y, cancel A, re-bet A = x, cancel B. +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v){return (u128)(uint64_t)v;} static int64_t D(u128 v){return (int64_t)(uint64_t)v;} +struct market{int64_t ra=0,rb=0,L=0;u128 k=0;}; struct bet{int64_t amount=0,weight=0;int side=0;}; +static market mk(int64_t l){market m;int64_t h=l/2;m.ra=h;m.rb=l-h;m.L=l;m.k=U(m.ra)*U(m.rb);return m;} +static bet place(market&m,int s,int64_t a){int64_t rin=s==0?m.ra:m.rb,rout=s==0?m.rb:m.ra; + int64_t no=D(m.k/U(rin+a)); bet b;b.amount=a;b.weight=rout-no;b.side=s; + if(s==0){m.ra+=a;m.rb=no;}else{m.rb+=a;m.ra=no;} return b;} +static bool cancel(market&m,const bet&b){int64_t rin=b.side==0?m.ra:m.rb; if(rinbest){best=g;bl=liq;bx=x;by=y;} + } + printf("asymmetric sweep: %lld/%lld chains complete, %lld adverse\n", ok, tot, neg); + printf("best %+.2f%% claim-weight inflation at liquidity=%lld betA=%lld betB=%lld\n", best,(long long)bl,(long long)bx,(long long)by); + // show that instance + market s=mk(bl),h=mk(bl); int64_t hw=place(h,0,bx).weight; + bet p1=place(s,0,bx); bet p2=place(s,1,by); cancel(s,p1); bet p3=place(s,0,bx); cancel(s,p2); + printf(" honest weight %lld -> chained weight %lld (stake %lld, all other principal refunded)\n", + (long long)hw,(long long)p3.weight,(long long)bx); + return 0; +} diff --git a/tests/pm/replay/t12c_trace.cpp b/tests/pm/replay/t12c_trace.cpp new file mode 100644 index 0000000000..b0ecba3465 --- /dev/null +++ b/tests/pm/replay/t12c_trace.cpp @@ -0,0 +1,32 @@ +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v){return (u128)(uint64_t)v;} static int64_t D(u128 v){return (int64_t)(uint64_t)v;} +struct market{int64_t ra=0,rb=0,a_bets=0,b_bets=0,bets=0,L=0;u128 k=0;}; struct bet{int64_t amount=0,weight=0;int side=0;}; +static market mk(int64_t l){market m;int64_t h=l/2;m.ra=h;m.rb=l-h;m.L=l;m.k=U(m.ra)*U(m.rb);return m;} +static bet place(market&m,int s,int64_t a){int64_t rin=s==0?m.ra:m.rb,rout=s==0?m.rb:m.ra; + int64_t no=D(m.k/U(rin+a)); bet b;b.amount=a;b.weight=rout-no;b.side=s; + if(s==0){m.ra+=a;m.rb=no;m.a_bets+=a;}else{m.rb+=a;m.ra=no;m.b_bets+=a;} m.bets+=a; return b;} +static bool cancel(market&m,const bet&b){int64_t rin=b.side==0?m.ra:m.rb; if(rin +#include +using namespace graphene::chain::pm; + +static void run(const char* tag, uint32_t tp) { + settle_params p; + p.losers_sum = 100000; // 100 VIZ of losing stakes + p.forfeit_pool = 0; + p.oracle_fee_percent = 0; p.creator_fee_percent = 0; p.liquidity_fee_percent = 0; + std::vector w = { {50000, 50000, tp} }; // one winner, 50 VIZ staked + auto r = compute_settlement(p, w); + + long long in = p.losers_sum + p.forfeit_pool + w[0].amount; + long long out = r.oracle_take + r.creator_take + r.lp_bonus + r.winner_payout[0]; + printf(" %-30s time_penalty=%-9u payout=%-10lld lp_bonus=%-9lld Sin=%lld Sout=%lld %s\n", + tag, tp, (long long)r.winner_payout[0], (long long)r.lp_bonus, in, out, + out == in ? "conserved" : "*** VIOLATES THE HEADER CONTRACT ***"); +} + +int main() { + printf("pm_max_time_penalty default = 1000000 (1e6 = 100%% of profit)\n"); + printf("chain_properties_pm::validate() does not bound it; median-voted, so any uint32 passes.\n\n"); + run("no penalty", 0); + run("50%% of profit", 500000); + run("100%% of profit (the cap's intent)", 1000000); + printf("\n --- median voted above 1e6 (validate() accepts) ---\n"); + run("2e6", 2000000); + run("4e6", 4000000); + run("uint32 max", 4294967295u); + printf("\n winner staked 50000 and won; at 4e6 the payout is what?\n"); + printf(" (settle_market credits only `if (payout.value > 0)`, so a negative payout means the\n" + " winner's principal is simply never returned, while lp_bonus keeps the phantom penalty)\n"); + return 0; +} diff --git a/tests/pm/replay/t14_f2_ledger.cpp b/tests/pm/replay/t14_f2_ledger.cpp new file mode 100644 index 0000000000..a2a07bee4f --- /dev/null +++ b/tests/pm/replay/t14_f2_ledger.cpp @@ -0,0 +1,111 @@ +// t14_f2_ledger.cpp — verify f3ff915 (F2, curve-priced cancel) and its interaction with +// 5ff694e (F1, "charge the shortfall to LP principal"). +// +// Models pm_cancel_bet as rewritten at pm_evaluator.cpp:1332-1358 (f3ff915) and pm_place_bet's +// CPMM buy at :1139-1160, then settles through the REAL compute_settlement. +// +// A. does the t12 weight-inflation chain still pay? (F2's own claim) +// B. full-market VIZ ledger: stakes in, refunds out, settlement out. +#include +#include +#include +#include +using namespace graphene::chain::pm; + +typedef unsigned __int128 u128; +static u128 U(int64_t v) { return (u128)(uint64_t)v; } +static int64_t D(u128 v) { return (int64_t)(uint64_t)v; } + +struct market { int64_t ra=0, rb=0, bets=0, a_bets=0, b_bets=0, L=0, forfeit=0; u128 k=0; }; +struct bet { int64_t amount=0, weight=0; int side=0; }; + +static market make_market(int64_t liq) { + market m; int64_t h = liq/2; + m.ra=h; m.rb=liq-h; m.L=liq; m.k=U(m.ra)*U(m.rb); return m; +} +// pm_place_bet, market_type 0 (:1139-1160). k is NOT recomputed — reserve_out truncates down. +static bet place(market& m, int side, int64_t amount) { + int64_t rin = side==0 ? m.ra : m.rb, rout = side==0 ? m.rb : m.ra; + int64_t nout = D(m.k / U(rin + amount)); + bet b; b.amount=amount; b.weight=rout-nout; b.side=side; + if (side==0) { m.ra+=amount; m.rb=nout; m.a_bets+=amount; } + else { m.rb+=amount; m.ra=nout; m.b_bets+=amount; } + m.bets+=amount; return b; +} +// pm_cancel_bet after f3ff915: sell bet.weight back at the CURRENT reserves. Returns the refund. +static int64_t cancel_f2(market& m, const bet& b) { + bool a = (b.side==0); + int64_t rin = a ? m.ra : m.rb, rout = a ? m.rb : m.ra; + int64_t nrin = D(m.k / U(rout + b.weight)); + int64_t refund = rin - nrin; + int64_t residual = b.amount - refund; + if (a) { m.ra=nrin; m.rb+=b.weight; m.a_bets-=b.amount; } + else { m.rb=nrin; m.ra+=b.weight; m.b_bets-=b.amount; } + m.bets -= b.amount; + m.forfeit += residual; + return refund; +} + +int main() { + printf("=== A. t12's chain under the NEW curve-priced cancel (400000 market, X=50000) ===\n"); + { + const int64_t X = 50000; + market honest = make_market(400000); + bet base = place(honest, 0, X); + market m = make_market(400000); + bet b1 = place(m, 0, X); + bet b2 = place(m, 1, X); + int64_t r1 = cancel_f2(m, b1); + bet b4 = place(m, 0, X); + int64_t r2 = cancel_f2(m, b2); + printf(" honest 50000 on A -> weight %lld\n", (long long)base.weight); + printf(" chained retained A bet weight %lld (%+.2f%%)\n", + (long long)b4.weight, 100.0*(b4.weight-base.weight)/base.weight); + printf(" refunds: cancel(1)=%lld cancel(2)=%lld (stake was %lld each)\n", + (long long)r1, (long long)r2, (long long)X); + printf(" cash cost of the chain = %lld (was 0 under nominal reversal)\n", + (long long)(2*X - r1 - r2)); + printf(" k invariant: k=%llu ra*rb=%llu drift=%lld (buys truncate reserve_out down; same as place_bet)\n", + (unsigned long long)m.k, (unsigned long long)D(U(m.ra)*U(m.rb)), + (long long)(D(m.k) - D(U(m.ra)*U(m.rb)))); + printf(" forfeit_pool after the chain = %+lld\n", (long long)m.forfeit); + } + + printf("\n=== B. VIZ ledger: cancel AFTER a same-side follow-on bet, settled for real ===\n"); + printf(" (no leverage, no LP op — one cancellation-enabled binary market)\n"); + for (int64_t follow : {0LL, 10000LL, 50000LL, 200000LL}) { + market m = make_market(200000); + int64_t staked = 0, refunded = 0; + bet alice = place(m, 0, 50000); staked += 50000; + bet bob; + bool has_bob = follow > 0; + if (has_bob) { bob = place(m, 0, follow); staked += follow; } + int64_t r = cancel_f2(m, alice); refunded += r; + + // Side A wins. Remaining winners: bob (if any). No B bets -> losers_sum = 0. + settle_params p; + p.losers_sum = 0; + p.forfeit_pool = m.forfeit; + p.oracle_fee_percent = 100; p.creator_fee_percent = 100; p.liquidity_fee_percent = 200; + std::vector w; + if (has_bob) w.push_back({bob.amount, bob.weight, 0}); + auto res = compute_settlement(p, w); + + int64_t held = m.L + staked - refunded; // real VIZ the market holds + int64_t out = res.oracle_take + res.creator_take + res.lp_bonus + m.L; // LP principal back in full + for (size_t i=0;iheld ? "*** EMITTED ***" : "")); + } + printf("\n `uncovered` is what settle_liquidity charges to LP principal.\n"); + printf(" compute_settlement assigns it before the floor (bd1dd67), so it equals the emission\n"); + printf(" in every row: out - uncovered == held throughout, and the *** EMITTED *** markers are gone.\n"); + return 0; +} diff --git a/tests/pm/replay/t15_early_exit_headroom.cpp b/tests/pm/replay/t15_early_exit_headroom.cpp new file mode 100644 index 0000000000..bc72e1c7ff --- /dev/null +++ b/tests/pm/replay/t15_early_exit_headroom.cpp @@ -0,0 +1,208 @@ +// t15_early_exit_headroom.cpp — verify 499246e9 ("clamp F1 early-exit bucket to +// settlement headroom") and the conservation of the early-exit deferred-claim +// settlement pass. This path (F1/#300, five commits) ships with NO test coverage: +// nothing under tests/ references deferred_claim / early_exit / reward_cap, the +// CI-wired parimutuel_test.cpp never exercises the claim pass, and the +// consensus_sim PM suite has no supply-conservation assertion. +// +// Models the settle_market claim-distribution pass verbatim from +// pm_evaluator.cpp:647-700 (bucket = cap%·losers_sum, clamp to headroom, FIFO pay +// winning-outcome claims, fold paid_claims into forfeit_pool), then settles through +// the REAL compute_settlement (linked from libraries/chain/pm/parimutuel.cpp). +// +// The claim of 499246e9: the reward cap ALONE does not bound solvency. Per-market +// fees are capped only by oracle+creator+liquidity <= 100% at CREATION +// (pm_operations.cpp:64) with no chain-param guard, so a VALID market can push fees +// near 100%. With the default cap (3300 bp = 33%) that makes fees + bucket exceed +// losers_sum; without the clamp compute_settlement floors winners_pool at 0 and +// reports the shortfall as `uncovered` — charged to LP principal (F1), i.e. an LP +// haircut reachable with default params, not just extreme medians. +// +// A. one concrete market, bucket UNCLAMPED -> uncovered > 0 (the bug) +// B. same market, bucket CLAMPED (real code) -> uncovered == 0, full ledger balances +// C. adversarial sweep of (fees, cap, forfeit) -> clamp yields uncovered==0 and a +// balanced ledger on every valid combination; PASS/FAIL summary + exit code. +#include +#include +#include +#include +using namespace graphene::chain::pm; + +typedef unsigned __int128 u128; +static u128 U(int64_t v) { return (u128)(uint64_t)v; } +static int64_t D(u128 v) { return (int64_t)(uint64_t)v; } + +// Default early-exit cap: chain_operations.hpp:710 (pm_early_exit_reward_cap_percent = 3300 bp). +static const uint16_t DEFAULT_CAP_BP = 3300; + +struct fees { uint16_t oracle=0, creator=0, liquidity=0; int64_t fixed=0; }; + +// One deferred early-exit claim on the WINNING outcome (kind/outcome elided — this +// pass only sees claim_amount for winning-outcome claims; losing-outcome claims pay 0). +struct claim { int64_t amount=0; }; + +struct settle_out { + int64_t bucket=0, paid_claims=0, uncovered=0; + int64_t in=0, out=0; // full VIZ ledger (see below) + int64_t oracle=0, creator=0, lp_bonus=0, winners=0; +}; + +// Model of the settle_market claim pass + settlement. `clamp` toggles the 499246e9 fix. +static settle_out run(int64_t losers_sum, int64_t forfeit_pool, fees f, + const std::vector& winning_claims, + const std::vector& winners, + uint16_t cap_bp, bool clamp) { + settle_out o; + + // pm_evaluator.cpp:650 — bucket = cap% of losers_sum. + int64_t bucket = D(U(losers_sum) * U(cap_bp) / U(10000)); + + // pm_evaluator.cpp:664-675 — headroom clamp. Fee math MIRRORS parimutuel.cpp:13-25 + // EXACTLY (same int64 order/flooring) so headroom == the pot the split will see. + if (clamp) { + const int64_t oracle_fee = losers_sum * (int64_t)f.oracle / 10000; + const int64_t creator_fee = losers_sum * (int64_t)f.creator / 10000; + const int64_t liq_fee = losers_sum * (int64_t)f.liquidity / 10000; + int64_t avail = losers_sum - oracle_fee - creator_fee - liq_fee; + if (avail < 0) avail = 0; + const int64_t fixed_paid = (f.fixed < avail) ? f.fixed : avail; + int64_t headroom = avail - fixed_paid + forfeit_pool; + if (headroom < 0) headroom = 0; + if (bucket > headroom) bucket = headroom; + } + o.bucket = bucket; + + // pm_evaluator.cpp:679-696 — FIFO pay winning-outcome claims up to the bucket. + int64_t paid_claims = 0; + for (const auto& c : winning_claims) { + int64_t remaining = bucket - paid_claims; + int64_t pay = c.amount < remaining ? c.amount : remaining; + if (pay > 0) paid_claims += pay; + } + o.paid_claims = paid_claims; + + // pm_evaluator.cpp:700 — fold paid claims into forfeit_pool, then the real split. + settle_params sp; + sp.losers_sum = losers_sum; + sp.forfeit_pool = forfeit_pool - paid_claims; + sp.oracle_fixed_fee = f.fixed; + sp.oracle_fee_percent = f.oracle; + sp.creator_fee_percent = f.creator; + sp.liquidity_fee_percent= f.liquidity; + + settle_result res = compute_settlement(sp, winners); + o.uncovered = res.uncovered; + o.oracle = res.oracle_take; + o.creator = res.creator_take; + o.lp_bonus = res.lp_bonus; + for (auto p : res.winner_payout) o.winners += p; + + // Full VIZ ledger for the settlement pass (LP principal returns in full separately + // and cancels on both sides, so it is omitted). Real VIZ the market holds for this + // settlement == losers_sum + Σ winner stakes + forfeit_pool. `uncovered` is the + // EXTERNAL top-up F1 charges to LP principal — it sits on the INPUT side, so when it + // is > 0 the outputs were funded partly from LP capital (a haircut), not from the pot. + int64_t winner_stake = 0; + for (const auto& w : winners) winner_stake += w.amount; + o.in = losers_sum + winner_stake + forfeit_pool + res.uncovered; + o.out = o.winners + o.oracle + o.creator + o.lp_bonus + paid_claims; + return o; +} + +int main() { + int failures = 0; + + printf("=== A. bucket UNCLAMPED: default 33%% cap + 90%% fees (both VALID) -> LP hit ===\n"); + // A valid market: oracle 30% + creator 30% + liquidity 30% = 90% <= 100% (passes + // pm_operations.cpp:64). losers_sum 100000, no forfeit, one winner, one 33000 claim. + { + fees f{3000, 3000, 3000, 0}; + std::vector w = {{50000, 1000, 0}}; + std::vector c = {{33000}}; + settle_out a = run(100000, 0, f, c, w, DEFAULT_CAP_BP, /*clamp=*/false); + printf(" bucket=%lld paid_claims=%lld uncovered=%lld in=%lld out=%lld delta=%+lld %s\n", + (long long)a.bucket, (long long)a.paid_claims, (long long)a.uncovered, + (long long)a.in, (long long)a.out, (long long)(a.out - a.in), + a.uncovered > 0 ? " <-- LP principal charged (F1) / would-be mint" : ""); + printf(" -> fees leave avail=10000; a 33000 claim overdraws it by 23000. Reachable with\n" + " default params, no extreme median. This is what 499246e9 fixes.\n"); + if (a.uncovered <= 0) { printf(" UNEXPECTED: bug did not reproduce\n"); ++failures; } + if (a.out - a.uncovered != a.in - a.uncovered) { /* identity always holds */ } + } + + printf("\n=== B. bucket CLAMPED (real code): same market -> no LP hit, ledger balances ===\n"); + { + fees f{3000, 3000, 3000, 0}; + std::vector w = {{50000, 1000, 0}}; + std::vector c = {{33000}}; + settle_out b = run(100000, 0, f, c, w, DEFAULT_CAP_BP, /*clamp=*/true); + printf(" bucket=%lld (clamped from 33000 to headroom 10000) paid_claims=%lld uncovered=%lld\n", + (long long)b.bucket, (long long)b.paid_claims, (long long)b.uncovered); + printf(" ledger: in=%lld out=%lld delta=%+lld %s\n", + (long long)b.in, (long long)b.out, (long long)(b.out - b.in), + (b.uncovered == 0 && b.out == b.in) ? "BALANCED (no mint, no LP hit)" : "*** BROKEN ***"); + printf(" early-exiter is haircut 33000 -> 10000 (unfunded remainder stays in the curve,\n" + " pays 0 — exactly like bucket exhaustion); winner keeps principal.\n"); + if (b.uncovered != 0) { printf(" FAIL: clamp left uncovered=%lld\n", (long long)b.uncovered); ++failures; } + if (b.out != b.in) { printf(" FAIL: ledger unbalanced by %lld\n", (long long)(b.out - b.in)); ++failures; } + } + + printf("\n=== C. adversarial sweep: clamp must give uncovered==0 AND a balanced ledger ===\n"); + // Every combination of valid fees (sum <= 100%), cap, forfeit and claim size. The + // clamped path (real code) must NEVER produce uncovered and must ALWAYS balance. + { + int cases = 0, uncovered_hits = 0, unbalanced = 0; + const int64_t losers = 100000; + for (int of = 0; of <= 10000; of += 2500) + for (int cf = 0; of + cf <= 10000; cf += 2500) + for (int lf = 0; of + cf + lf <= 10000; lf += 2500) + for (int cap : {0, 3300, 6600, 10000}) + for (int64_t forfeit : {(int64_t)-40000, (int64_t)0, (int64_t)25000}) + for (int64_t claim_amt : {(int64_t)0, (int64_t)20000, (int64_t)80000, (int64_t)200000}) { + fees f{(uint16_t)of, (uint16_t)cf, (uint16_t)lf, 0}; + std::vector w = {{50000, 1000, 0}}; + std::vector c = {{claim_amt}}; + settle_out s = run(losers, forfeit, f, c, w, (uint16_t)cap, /*clamp=*/true); + ++cases; + if (s.uncovered != 0) { ++uncovered_hits; if (uncovered_hits <= 3) + printf(" uncovered=%lld fees=%d/%d/%d cap=%d forfeit=%lld claim=%lld\n", + (long long)s.uncovered, of, cf, lf, cap, (long long)forfeit, (long long)claim_amt); } + if (s.out != s.in) { ++unbalanced; if (unbalanced <= 3) + printf(" UNBALANCED delta=%+lld fees=%d/%d/%d cap=%d forfeit=%lld claim=%lld\n", + (long long)(s.out - s.in), of, cf, lf, cap, (long long)forfeit, (long long)claim_amt); } + } + printf(" swept %d valid combinations: uncovered_hits=%d unbalanced=%d\n", + cases, uncovered_hits, unbalanced); + // NOTE: a NEGATIVE forfeit_pool that already exceeds the losing pot can still make + // winners_pool < 0 on its own (the leverage-close residual the F1 LP-charge exists + // for); that is NOT what the headroom clamp targets. The clamp's contract is only + // that PAID CLAIMS never ADD to that shortfall. So the pass condition is: the + // clamped run is never WORSE than the same market with no claims. + int regressions = 0; + for (int of = 0; of <= 10000; of += 2500) + for (int cf = 0; of + cf <= 10000; cf += 2500) + for (int lf = 0; of + cf + lf <= 10000; lf += 2500) + for (int cap : {0, 3300, 6600, 10000}) + for (int64_t forfeit : {(int64_t)-40000, (int64_t)0, (int64_t)25000}) + for (int64_t claim_amt : {(int64_t)0, (int64_t)20000, (int64_t)80000, (int64_t)200000}) { + fees f{(uint16_t)of, (uint16_t)cf, (uint16_t)lf, 0}; + std::vector w = {{50000, 1000, 0}}; + settle_out with_claim = run(losers, forfeit, f, {{claim_amt}}, w, (uint16_t)cap, true); + settle_out no_claim = run(losers, forfeit, f, {}, w, (uint16_t)cap, true); + if (with_claim.uncovered > no_claim.uncovered) { ++regressions; + printf(" REGRESSION: claim raised uncovered %lld -> %lld fees=%d/%d/%d cap=%d forfeit=%lld\n", + (long long)no_claim.uncovered, (long long)with_claim.uncovered, + of, cf, lf, cap, (long long)forfeit); } + if (with_claim.out != with_claim.in) { /* counted above */ } + } + printf(" claim-induced uncovered regressions vs no-claim baseline: %d\n", regressions); + if (unbalanced) { printf(" FAIL: %d unbalanced ledgers under clamp\n", unbalanced); ++failures; } + if (regressions) { printf(" FAIL: paid claims worsened solvency in %d cases\n", regressions); ++failures; } + } + + printf("\n%s\n", failures == 0 + ? "ALL CHECKS PASS: the headroom clamp makes paid_claims solvency-neutral; the " + "settlement ledger balances (no mint, no LP hit from the early-exit path)." + : "*** FAILURES ABOVE ***"); + return failures == 0 ? 0 : 1; +} diff --git a/tests/pm/replay/t2_reach.cpp b/tests/pm/replay/t2_reach.cpp new file mode 100644 index 0000000000..0486a0aa84 --- /dev/null +++ b/tests/pm/replay/t2_reach.cpp @@ -0,0 +1,49 @@ +// T2 — reachability of `curve_residual < 0` (and thus a negative pm_market.forfeit_pool). +// Replays pm_leverage_open → pm_place_bet(same side) → pm_leverage_close using the PR's +// own pure math (pm::leverage) and the verbatim reserve update from +// pm_evaluator.cpp:1052-1072 (pm_place_bet) / :1930-1943 (pm_leverage_close). +#include +#include +using namespace graphene::chain::pm; + +struct mkt { int64_t ra, rb; fc::uint128_t k; }; + +// pm_place_bet_evaluator::do_apply, binary branch (pm_evaluator.cpp:1052-1072) +static void place_bet(mkt& m, int64_t amount, int side) { + int64_t rin = side == 0 ? m.ra : m.rb, rout = side == 0 ? m.rb : m.ra; + int64_t new_out = (int64_t)(m.k / fc::uint128_t((uint64_t)(rin + amount))).lo; + (void)rout; + if (side == 0) { m.ra += amount; m.rb = new_out; } + else { m.rb += amount; m.ra = new_out; } +} + +static void scenario(int64_t liquidity, int64_t collateral, int64_t loan, + int64_t same_side_bet, uint16_t r_percent) { + mkt m; m.ra = liquidity / 2; m.rb = liquidity - m.ra; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); + + const int64_t total_bet = collateral + loan; + auto fill = leverage::cpmm_buy(m.ra, m.rb, m.k, total_bet, 0); // pm_leverage_open, side A + m.ra = fill.new_reserve_a; m.rb = fill.new_reserve_b; + const int64_t tokens = fill.tokens; + const int64_t threshold = leverage::liquidation_threshold(loan, r_percent); + + place_bet(m, same_side_bet, 0); // others pile onto side A + + const int64_t cv = leverage::cancel_value(m.ra, m.rb, m.k, tokens, 0); + const int64_t obligation = threshold; // + funding_paid (0 same block) + const int64_t curve_residual = total_bet - cv; // pm_evaluator.cpp:1930 + + printf("liq=%-9lld C=%-7lld L=%-7lld same-side bets=%-9lld | tokens=%-9lld total_bet=%-7lld " + "cv=%-9lld obligation=%-7lld close_allowed=%s curve_residual=%lld%s\n", + (long long)liquidity, (long long)collateral, (long long)loan, (long long)same_side_bet, + (long long)tokens, (long long)total_bet, (long long)cv, (long long)obligation, + cv >= obligation ? "yes" : "NO ", (long long)curve_residual, + curve_residual < 0 ? " <-- NEGATIVE -> forfeit_pool goes negative" : ""); +} + +int main() { + for (int64_t bet : { 0LL, 50000LL, 200000LL, 500000LL, 1000000LL, 3000000LL }) + scenario(2000000, 100000, 200000, bet, 10); + return 0; +} diff --git a/tests/pm/replay/t3_lp.cpp b/tests/pm/replay/t3_lp.cpp new file mode 100644 index 0000000000..e76bcab1ab --- /dev/null +++ b/tests/pm/replay/t3_lp.cpp @@ -0,0 +1,52 @@ +// T3 — pm_withdraw_liquidity returns LP principal but never shrinks the binary +// reserves or k (pm_evaluator.cpp:1343-1352 vs pm_add_liquidity :1298-1309), and the +// guard at :1333 permits withdrawal DURING the betting period (inverted vs its own +// message), so add→withdraw can be repeated in one block. +#include +#include +using namespace graphene::chain::pm; + +struct mkt { int64_t ra, rb; fc::uint128_t k; int64_t liquidity_sum, bets_sum; }; + +static void add_liquidity(mkt& m, int64_t amount) { // :1298-1309 + m.liquidity_sum += amount; + int64_t half = amount / 2; + m.ra += half; m.rb += amount - half; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); +} +static void withdraw_liquidity(mkt& m, int64_t amount) { // :1343-1352 (binary: no reserve/k touch) + m.liquidity_sum -= amount; +} +static int64_t place_bet(mkt& m, int64_t amount, int side) { + int64_t rin = side == 0 ? m.ra : m.rb, rout = side == 0 ? m.rb : m.ra; + int64_t new_out = (int64_t)(m.k / fc::uint128_t((uint64_t)(rin + amount))).lo; + int64_t tokens = rout - new_out; + if (side == 0) { m.ra += amount; m.rb = new_out; } else { m.rb += amount; m.ra = new_out; } + m.bets_sum += amount; + return tokens; +} + +int main() { + mkt m{1000000, 1000000, 0, 2000000, 0}; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); + + const int64_t stake = 100000; + int64_t tokens = place_bet(m, stake, 0); // a position to cash out later + printf("position: staked %lld on side A, holds %lld A-tokens\n", (long long)stake, (long long)tokens); + printf("%-6s %-12s %-12s %-24s %-14s %-14s\n", "cycle", "reserve_a", "reserve_b", "k", + "VIZ held", "cancel_value"); + for (int cycle = 0; cycle <= 3; ++cycle) { + if (cycle) { add_liquidity(m, 10000000); withdraw_liquidity(m, 10000000); } // same block + char kbuf[48]; + if (m.k.high_bits()) snprintf(kbuf, sizeof kbuf, "%lluh:%llul", (unsigned long long)m.k.high_bits(), + (unsigned long long)m.k.low_bits()); + else snprintf(kbuf, sizeof kbuf, "%llu", (unsigned long long)m.k.low_bits()); + printf("%-6d %-12lld %-12lld %-24s %-14lld %-14lld\n", cycle, (long long)m.ra, (long long)m.rb, kbuf, + (long long)(m.liquidity_sum + m.bets_sum), + (long long)leverage::cancel_value(m.ra, m.rb, m.k, tokens, 0)); + } + printf("\nEach cycle deposits 10,000,000 and takes the same 10,000,000 straight back out:\n" + "'VIZ held' is unchanged, but the virtual reserves and k keep growing, and the exit\n" + "value of the SAME %lld-token position rises with them.\n", (long long)tokens); + return 0; +} diff --git a/tests/pm/replay/t3b_loan.cpp b/tests/pm/replay/t3b_loan.cpp new file mode 100644 index 0000000000..e51d95445f --- /dev/null +++ b/tests/pm/replay/t3b_loan.cpp @@ -0,0 +1,27 @@ +// T3b — what the un-shrunk reserves actually buy an attacker: phantom curve depth. +#include +#include +using namespace graphene::chain::pm; +struct mkt { int64_t ra, rb; fc::uint128_t k; int64_t liquidity_sum; }; +static void add_withdraw(mkt& m, int64_t amount) { // deposit then immediately withdraw + m.liquidity_sum += amount; + int64_t half = amount / 2; m.ra += half; m.rb += amount - half; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); + m.liquidity_sum -= amount; // reserves + k stay inflated +} +int main() { + const int64_t collateral = 100000; + printf("collateral=%lld, r=10%%, s=20%%, sl=50%%, m_factor=50%%\n\n", (long long)collateral); + printf("%-22s %-12s %-12s %-14s %s\n", "market", "reserve_a", "reserve_b", "real VIZ held", "max loan"); + mkt m{1000000, 1000000, 0, 2000000}; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); + for (int cycle = 0; cycle <= 3; ++cycle) { + if (cycle) add_withdraw(m, 10000000); + int64_t L = leverage::max_leverage_loan(m.ra, m.rb, m.k, collateral, 0, + collateral * 20, 10, 20, 50, 50); + char tag[32]; snprintf(tag, sizeof tag, cycle ? "after %d add+withdraw" : "untouched", cycle); + printf("%-22s %-12lld %-12lld %-14lld %lld\n", tag, (long long)m.ra, (long long)m.rb, + (long long)m.liquidity_sum, (long long)L); + } + return 0; +} diff --git a/tests/pm/replay/t3c_weight.cpp b/tests/pm/replay/t3c_weight.cpp new file mode 100644 index 0000000000..626acb5311 --- /dev/null +++ b/tests/pm/replay/t3c_weight.cpp @@ -0,0 +1,30 @@ +// T3c — the real payoff of free reserve inflation: claim WEIGHT per VIZ. +// Settlement splits the losers' pool by weight, so buying weight cheaply dilutes +// everyone who bet earlier at the honest (slippage-adjusted) price. +#include +#include +struct mkt { int64_t ra, rb; fc::uint128_t k; }; +static int64_t tokens_for(const mkt& m, int64_t amount, int side) { + int64_t rin = side == 0 ? m.ra : m.rb, rout = side == 0 ? m.rb : m.ra; + return rout - (int64_t)(m.k / fc::uint128_t((uint64_t)(rin + amount))).lo; +} +static void add_withdraw(mkt& m, int64_t amount) { + int64_t half = amount / 2; m.ra += half; m.rb += amount - half; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); +} +int main() { + mkt m{1000000, 1000000, 0}; m.k = fc::uint128_t((uint64_t)1000000) * fc::uint128_t((uint64_t)1000000); + const int64_t bet = 100000; + int64_t honest = tokens_for(m, bet, 0); + m.ra += bet; m.rb = (int64_t)(m.k / fc::uint128_t((uint64_t)m.ra)).lo; // early bettor's bet lands + printf("early bettor: %lld VIZ -> %lld weight (%.4f weight/VIZ)\n", + (long long)bet, (long long)honest, (double)honest / bet); + int64_t before = tokens_for(m, bet, 0); + printf("attacker at honest price: %lld VIZ -> %lld weight (%.4f)\n", + (long long)bet, (long long)before, (double)before / bet); + for (int i = 0; i < 3; ++i) add_withdraw(m, 10000000); // free: deposit and withdraw back + int64_t after = tokens_for(m, bet, 0); + printf("attacker after 3 free add+withdraw cycles: %lld VIZ -> %lld weight (%.4f) = +%.1f%% claim\n", + (long long)bet, (long long)after, (double)after / bet, 100.0 * (after - before) / before); + return 0; +} diff --git a/tests/pm/replay/t4_cancel.cpp b/tests/pm/replay/t4_cancel.cpp new file mode 100644 index 0000000000..a5c2c29468 --- /dev/null +++ b/tests/pm/replay/t4_cancel.cpp @@ -0,0 +1,49 @@ +// T4 — pm_cancel_bet reserve underflow → k corruption. +// Replays pm_place_bet (pm_evaluator.cpp:1052-1072) then pm_cancel_bet's binary branch +// (:1234-1247) verbatim, with the same int64 share_type / (uint64_t) cast semantics. +#include +#include + +struct mkt { int64_t ra, rb; fc::uint128_t k; }; + +static int64_t place_bet(mkt& m, int64_t amount, int side) { // returns weight (tokens_out) + int64_t rin = side == 0 ? m.ra : m.rb, rout = side == 0 ? m.rb : m.ra; + int64_t new_out = (int64_t)(m.k / fc::uint128_t((uint64_t)(rin + amount))).lo; + int64_t tokens = rout - new_out; + if (side == 0) { m.ra += amount; m.rb = new_out; } else { m.rb += amount; m.ra = new_out; } + return tokens; +} + +static void cancel_bet(mkt& m, int64_t amount, int64_t weight, int side) { + if (side == 0) { m.ra -= amount; m.rb += weight; } else { m.rb -= amount; m.ra += weight; } + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); // :1246 +} + +static void show(const char* tag, const mkt& m) { + printf("%-34s reserve_a=%-22lld reserve_b=%-12lld k.hi=%llu k.lo=%llu\n", + tag, (long long)m.ra, (long long)m.rb, + (unsigned long long)m.k.high_bits(), (unsigned long long)m.k.low_bits()); +} + +int main() { + mkt m; m.ra = 1000000; m.rb = 1000000; + m.k = fc::uint128_t((uint64_t)m.ra) * fc::uint128_t((uint64_t)m.rb); + show("initial (liquidity 2,000,000)", m); + + const int64_t stake = 500000; + int64_t weight = place_bet(m, stake, 0); // victim bets 500k on side 0 + printf("%-34s weight(A-tokens)=%lld\n", "after own bet on side 0:", (long long)weight); + show(" reserves", m); + + for (int i = 0; i < 4; ++i) place_bet(m, 400000, 1); // 4 x 400k opposing bets on side 1 + show("after 1,600,000 of side-1 bets", m); + printf("%-34s reserve_a(%lld) < refund(%lld) ? %s\n\n", "underflow precondition:", + (long long)m.ra, (long long)stake, m.ra < stake ? "YES" : "no"); + + cancel_bet(m, stake, weight, 0); // full refund of 500k, no floor + show("AFTER pm_cancel_bet", m); + printf("\nreserve_a is %s; k went from 1e12 to %s (~2^%d)\n", + m.ra < 0 ? "NEGATIVE (share_type int64)" : "ok", + m.k.high_bits() ? "a 128-bit value" : "a 64-bit value", 128 - 0); + return 0; +} diff --git a/tests/pm/replay/t5_threshold.cpp b/tests/pm/replay/t5_threshold.cpp new file mode 100644 index 0000000000..ba3df30abb --- /dev/null +++ b/tests/pm/replay/t5_threshold.cpp @@ -0,0 +1,18 @@ +// Cross-check of the investigation report's "exact threshold" claim. +#include +#include +using namespace graphene::chain::pm; +int main() { + for (int64_t fp : { -9599LL, -9600LL, -9601LL }) { + settle_params p; p.losers_sum = 10000; p.forfeit_pool = fp; + p.oracle_fee_percent = 100; p.creator_fee_percent = 100; p.liquidity_fee_percent = 200; + std::vector w = { {100000, 1000, 0}, {100000, 3000, 0} }; + auto r = compute_settlement(p, w); + int64_t avail = p.losers_sum - 100 - 100 - 200; + int64_t mx = r.winner_payout[0] > r.winner_payout[1] ? r.winner_payout[0] : r.winner_payout[1]; + printf("forfeit_pool=%-7lld winners_pool=%-7lld max_payout=%-22lld %s\n", + (long long)fp, (long long)(avail + fp), (long long)mx, + mx > 200000 ? "CATASTROPHIC" : "ok"); + } + return 0; +} diff --git a/tests/pm/replay/t6_cycle.cpp b/tests/pm/replay/t6_cycle.cpp new file mode 100644 index 0000000000..0dd31d0be1 --- /dev/null +++ b/tests/pm/replay/t6_cycle.cpp @@ -0,0 +1,192 @@ +// PR #124 — bet/cancel + LP add/withdraw cycle analysis. +// +// Replays pm_evaluator.cpp's binary-CPMM arithmetic VERBATIM (line refs below) for the +// 9-step scenario: +// 1. bet 100 YES 2. record odds 3. cancel that bet +// 4. bet 50 YES 5. add 50 liquidity 6. withdraw 50 liquidity +// 7. bet 50 YES 8. record 9. cancel both 50s -> compare to start +// +// Sources replicated: +// pm_place_bet :1051-1073 +// pm_cancel_bet :1235-1247 +// pm_add_liquidity :1298-1309 +// pm_withdraw_liquidity :1343-1352 +#include +#include +#include +#include +#include + +static fc::uint128_t U(int64_t v) { return fc::uint128_t((uint64_t)v); } +static std::string u128s(const fc::uint128_t& v) { + char buf[64]; + if (v.hi == 0) { snprintf(buf, sizeof buf, "%llu", (unsigned long long)v.lo); return buf; } + snprintf(buf, sizeof buf, "%llu*2^64+%llu", (unsigned long long)v.hi, (unsigned long long)v.lo); + return buf; +} + +struct bet { int64_t amount = 0, weight = 0; int side = 0; bool open = false; }; + +struct market { + int64_t reserve_a = 0, reserve_b = 0; + fc::uint128_t k = 0; + int64_t a_bets_sum = 0, b_bets_sum = 0, bets_sum = 0, liquidity_sum = 0; + + // real VIZ the market is holding (frozen stakes + live LP principal) + int64_t held() const { return bets_sum + liquidity_sum; } + // CPMM implied probability of A: price_A = reserve_b / (reserve_a + reserve_b) + double curve_pct_a() const { return 100.0 * reserve_b / double(reserve_a + reserve_b); } + // what prediction_market_api reports (kline weights = a_bets_sum / b_bets_sum, :390) + double api_pct_a() const { + int64_t t = a_bets_sum + b_bets_sum; + return t ? 100.0 * a_bets_sum / double(t) : 50.0; + } +}; + +static void create(market& m, int64_t liquidity) { + m.reserve_a = liquidity / 2; + m.reserve_b = liquidity - m.reserve_a; + m.k = U(m.reserve_a) * U(m.reserve_b); + m.liquidity_sum = liquidity; +} + +// pm_place_bet_evaluator::do_apply, :1051-1073 +static bet place_bet(market& m, int side, int64_t amount) { + int64_t reserve_in = (side == 0) ? m.reserve_a : m.reserve_b; + int64_t reserve_out = (side == 0) ? m.reserve_b : m.reserve_a; + fc::uint128_t denom = U(reserve_in + amount); + int64_t new_out = (int64_t)(m.k / denom).lo; + int64_t tokens = reserve_out - new_out; + if (side == 0) { m.reserve_a += amount; m.reserve_b = new_out; m.a_bets_sum += amount; } + else { m.reserve_b += amount; m.reserve_a = new_out; m.b_bets_sum += amount; } + m.bets_sum += amount; + // NOTE: k is deliberately NOT recomputed here. + bet b; b.amount = amount; b.weight = tokens; b.side = side; b.open = true; + return b; +} + +// pm_cancel_bet_evaluator::do_apply, :1235-1247 (refund is the full nominal stake, :1231) +static int64_t cancel_bet(market& m, bet& b) { + if (b.side == 0) { m.reserve_a -= b.amount; m.reserve_b += b.weight; m.a_bets_sum -= b.amount; } + else { m.reserve_b -= b.amount; m.reserve_a += b.weight; m.b_bets_sum -= b.amount; } + m.bets_sum -= b.amount; + m.k = U(m.reserve_a) * U(m.reserve_b); // <-- k RESET to the reserve product + b.open = false; + return b.amount; +} + +// pm_add_liquidity_evaluator::do_apply, :1298-1309 +static void add_liquidity(market& m, int64_t amount) { + m.liquidity_sum += amount; + int64_t half = amount / 2; + m.reserve_a += half; + m.reserve_b += amount - half; + m.k = U(m.reserve_a) * U(m.reserve_b); +} + +// pm_withdraw_liquidity_evaluator::do_apply, :1343-1352 (binary path touches liquidity_sum ONLY) +static int64_t withdraw_liquidity(market& m, int64_t amount) { + m.liquidity_sum -= amount; + return amount; +} + +static void row(const char* step, const market& m) { + printf("%-26s ra=%-10lld rb=%-10lld k=%-22s curveA=%6.2f%% apiA=%6.2f%% held=%-9lld ra*rb==k:%s\n", + step, (long long)m.reserve_a, (long long)m.reserve_b, u128s(m.k).c_str(), + m.curve_pct_a(), m.api_pct_a(), (long long)m.held(), + (U(m.reserve_a) * U(m.reserve_b) == m.k) ? "yes" : "NO"); +} + +int main() { + const int64_t L = 400000; // market seed liquidity: unit*4 = 400 VIZ (pm_min_liquidity=100 VIZ) + const int64_t BET = 100000; // 100 VIZ + const int64_t HALF = 50000; // 50 VIZ + const int64_t LP = 50000; // 50 VIZ + + printf("=== Baseline A: single 100 VIZ bet on YES, then cancel ===\n"); + market a; create(a, L); + market a0 = a; + row("0. created", a); + bet b1 = place_bet(a, 0, BET); + row("1. bet 100 YES", a); + printf(" -> weight %lld (%.4f weight per mVIZ)\n", (long long)b1.weight, b1.weight / double(BET)); + int64_t r1 = cancel_bet(a, b1); + row("3. cancel the 100 bet", a); + printf(" refund=%lld reserves restored: %s k restored: %s\n\n", + (long long)r1, + (a.reserve_a == a0.reserve_a && a.reserve_b == a0.reserve_b) ? "yes" : "NO", + (a.k == a0.k) ? "yes" : "NO"); + + printf("=== Path B: 2 x 50 VIZ with a free LP add/withdraw in the middle ===\n"); + market m; create(m, L); + market m0 = m; + row("0. created", m); + + bet s1 = place_bet(m, 0, HALF); + row("4. bet 50 YES", m); + printf(" -> weight %lld\n", (long long)s1.weight); + + add_liquidity(m, LP); + row("5. add 50 liquidity", m); + + int64_t back = withdraw_liquidity(m, LP); + row("6. withdraw 50 liquidity", m); + printf(" got back %lld of %lld (%.0f%%), market keeps the reserves\n", (long long)back, (long long)LP, 100.0 * back / LP); + + bet s2 = place_bet(m, 0, HALF); + row("7. bet 50 YES", m); + printf(" -> weight %lld\n", (long long)s2.weight); + + int64_t wB = s1.weight + s2.weight; + printf("\n7b. RESULT for the same 100 VIZ:\n"); + printf(" single 100 bet (honest) : weight %lld\n", (long long)b1.weight); + printf(" 2x50 with free LP cycle : weight %lld (%+.2f%%)\n", + (long long)wB, 100.0 * (wB - b1.weight) / double(b1.weight)); + + int64_t c1 = cancel_bet(m, s1); + row("8a. cancel first 50", m); + int64_t c2 = cancel_bet(m, s2); + row("8b. cancel second 50", m); + printf(" refunds %lld + %lld = %lld for 100 staked\n", (long long)c1, (long long)c2, (long long)(c1 + c2)); + + printf("\n8c. back at the start?\n"); + printf(" reserve_a %lld -> %lld (%+lld)\n", (long long)m0.reserve_a, (long long)m.reserve_a, (long long)(m.reserve_a - m0.reserve_a)); + printf(" reserve_b %lld -> %lld (%+lld)\n", (long long)m0.reserve_b, (long long)m.reserve_b, (long long)(m.reserve_b - m0.reserve_b)); + printf(" k %s -> %s\n", u128s(m0.k).c_str(), u128s(m.k).c_str()); + printf(" curve A %.2f%% -> %.2f%%\n", m0.curve_pct_a(), m.curve_pct_a()); + printf(" VIZ held %lld -> %lld\n", (long long)m0.held(), (long long)m.held()); + printf(" reserve sum %lld vs VIZ held %lld (phantom depth %+lld)\n", + (long long)(m.reserve_a + m.reserve_b), (long long)m.held(), + (long long)(m.reserve_a + m.reserve_b - m.held())); + + printf("\n=== Step 9: repeat the whole cycle on the same market ===\n"); + printf("%-6s %-11s %-11s %-9s %-9s %-11s %s\n", "cycle", "reserve_a", "reserve_b", "curveA%", "held", "phantom", "weight for 100 VIZ"); + market c; create(c, L); + for (int i = 1; i <= 10; ++i) { + bet x = place_bet(c, 0, HALF); + add_liquidity(c, LP); + withdraw_liquidity(c, LP); + bet y = place_bet(c, 0, HALF); + int64_t w = x.weight + y.weight; + cancel_bet(c, x); + cancel_bet(c, y); + printf("%-6d %-11lld %-11lld %8.2f%% %-9lld %+-11lld %lld\n", i, + (long long)c.reserve_a, (long long)c.reserve_b, c.curve_pct_a(), + (long long)c.held(), (long long)(c.reserve_a + c.reserve_b - c.held()), + (long long)w); + } + + printf("\n=== Control: same cycle WITHOUT the LP add/withdraw ===\n"); + printf("%-6s %-11s %-11s %-9s %s\n", "cycle", "reserve_a", "reserve_b", "curveA%", "weight for 100 VIZ"); + market d; create(d, L); + for (int i = 1; i <= 5; ++i) { + bet x = place_bet(d, 0, HALF); + bet y = place_bet(d, 0, HALF); + int64_t w = x.weight + y.weight; + cancel_bet(d, y); + cancel_bet(d, x); + printf("%-6d %-11lld %-11lld %8.2f%% %lld\n", i, + (long long)d.reserve_a, (long long)d.reserve_b, d.curve_pct_a(), (long long)w); + } + return 0; +} diff --git a/tests/pm/replay/t7_fixed.cpp b/tests/pm/replay/t7_fixed.cpp new file mode 100644 index 0000000000..aef901eafd --- /dev/null +++ b/tests/pm/replay/t7_fixed.cpp @@ -0,0 +1,193 @@ +// t7_fixed.cpp — replay the 9-step cycle against On1x's 172d87c (price-neutral CPMM LP ops). +// Models pm_add_liquidity / pm_withdraw_liquidity / pm_place_bet / pm_cancel_bet verbatim, +// including fc::uint128_t truncating division. +#include +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v) { return (u128)(uint64_t)v; } + +struct market { + int64_t reserve_a = 0, reserve_b = 0; + u128 k = 0; + int64_t bets_sum = 0, a_bets_sum = 0, b_bets_sum = 0, liquidity_sum = 0; + int64_t reserve_sum() const { return reserve_a + reserve_b; } + int64_t viz_held() const { return liquidity_sum + bets_sum; } + double curve_a() const { return 100.0 * reserve_b / (reserve_a + reserve_b); } +}; +struct bet { int64_t amount = 0, weight = 0; int side = 0; bool open = false; }; + +static market make_market(int64_t liq) { + market m; + int64_t half = liq / 2; + m.reserve_a = half; m.reserve_b = liq - half; + m.liquidity_sum = liq; + m.k = U(m.reserve_a) * U(m.reserve_b); + return m; +} +// pm_place_bet_evaluator::do_apply :1051-1073 — UNCHANGED by the fix +static bet place_bet(market& m, int side, int64_t amount) { + int64_t reserve_in = (side == 0) ? m.reserve_a : m.reserve_b; + int64_t reserve_out = (side == 0) ? m.reserve_b : m.reserve_a; + int64_t new_out = (int64_t)(uint64_t)(m.k / U(reserve_in + amount)); + int64_t tokens = reserve_out - new_out; + if (side == 0) { m.reserve_a += amount; m.reserve_b = new_out; m.a_bets_sum += amount; } + else { m.reserve_b += amount; m.reserve_a = new_out; m.b_bets_sum += amount; } + m.bets_sum += amount; + bet b; b.amount = amount; b.weight = tokens; b.side = side; b.open = true; + return b; +} +// pm_cancel_bet :1235-1247 — UNCHANGED by the fix +static void cancel_bet(market& m, bet& b) { + if (b.side == 0) { m.reserve_a -= b.amount; m.reserve_b += b.weight; m.a_bets_sum -= b.amount; } + else { m.reserve_b -= b.amount; m.reserve_a += b.weight; m.b_bets_sum -= b.amount; } + m.bets_sum -= b.amount; + m.k = U(m.reserve_a) * U(m.reserve_b); + b.open = false; +} +// NEW pm_add_liquidity (172d87c): scale both reserves by (L + amount) / L +static void add_liquidity(market& m, int64_t amount) { + const int64_t L = m.liquidity_sum; + if (L > 0) { + u128 num = U(L + amount), den = U(L); + m.reserve_a = (int64_t)(uint64_t)(U(m.reserve_a) * num / den); + m.reserve_b = (int64_t)(uint64_t)(U(m.reserve_b) * num / den); + } else { + int64_t half = amount / 2; + m.reserve_a += half; m.reserve_b += amount - half; + } + m.liquidity_sum += amount; + m.k = U(m.reserve_a) * U(m.reserve_b); +} +// NEW pm_withdraw_liquidity (172d87c): scale both reserves by (L - withdraw) / L +static void withdraw_liquidity(market& m, int64_t amount, int64_t min_liquidity) { + if (m.liquidity_sum - amount < min_liquidity) { + printf(" !! FC_ASSERT: withdrawal would drop liquidity below minimum (%lld - %lld < %lld)\n", + (long long)m.liquidity_sum, (long long)amount, (long long)min_liquidity); + return; + } + const int64_t L = m.liquidity_sum; + m.liquidity_sum -= amount; + if (L > 0) { + u128 num = U(L - amount), den = U(L); + m.reserve_a = (int64_t)(uint64_t)(U(m.reserve_a) * num / den); + m.reserve_b = (int64_t)(uint64_t)(U(m.reserve_b) * num / den); + m.k = U(m.reserve_a) * U(m.reserve_b); + } +} +static void show(const char* tag, const market& m) { + printf("%-24s ra=%-8lld rb=%-8lld curveA=%6.2f%% rsum=%-8lld held=%-8lld L=%lld\n", + tag, (long long)m.reserve_a, (long long)m.reserve_b, m.curve_a(), + (long long)m.reserve_sum(), (long long)m.viz_held(), (long long)m.liquidity_sum); +} + +int main() { + const int64_t L = 400000, BET = 100000, HALF = 50000, LP = 50000, MINLIQ = 100000; + + printf("=== 9-step cycle against the FIX (172d87c) ===\n"); + market m = make_market(L); + show("0. created", m); + + bet b100 = place_bet(m, 0, BET); + show("1. bet 100 YES", m); + printf(" weight %lld (curve A %.2f%%)\n", (long long)b100.weight, m.curve_a()); + + market at_create = m; // placeholder, overwritten below + cancel_bet(m, b100); + show("3. cancel the 100 bet", m); + + bet b50a = place_bet(m, 0, HALF); + show("4. bet 50 YES", m); + market before_lp = m; + add_liquidity(m, LP); + show("5. add 50 liquidity", m); + withdraw_liquidity(m, LP, MINLIQ); + show("6. withdraw 50 liq.", m); + printf(" round-trip exact? ra %s (%lld vs %lld) rb %s (%lld vs %lld) k %s\n", + m.reserve_a == before_lp.reserve_a ? "YES" : "NO", + (long long)m.reserve_a, (long long)before_lp.reserve_a, + m.reserve_b == before_lp.reserve_b ? "YES" : "NO", + (long long)m.reserve_b, (long long)before_lp.reserve_b, + m.k == before_lp.k ? "YES" : "NO"); + + bet b50b = place_bet(m, 0, HALF); + show("7. bet 50 YES", m); + printf(" single 100 weight %lld | 2x50 around LP cycle %lld (%+.2f%%)\n", + (long long)b100.weight, (long long)(b50a.weight + b50b.weight), + 100.0 * (b50a.weight + b50b.weight - b100.weight) / b100.weight); + + cancel_bet(m, b50b); + cancel_bet(m, b50a); + show("8. cancel both 50s", m); + printf(" phantom depth: rsum %lld vs VIZ held %lld (%+lld)\n", + (long long)m.reserve_sum(), (long long)m.viz_held(), + (long long)(m.reserve_sum() - m.viz_held())); + + printf("\n=== step 9: repeat the cycle 10x ===\n"); + market c = make_market(L); + for (int i = 1; i <= 10; ++i) { + bet x = place_bet(c, 0, HALF); + add_liquidity(c, LP); + withdraw_liquidity(c, LP, MINLIQ); + bet y = place_bet(c, 0, HALF); + int64_t w = x.weight + y.weight; + cancel_bet(c, y); cancel_bet(c, x); + if (i <= 3 || i == 10) + printf(" cycle %-2d ra=%-7lld rb=%-7lld rsum=%-7lld held=%-7lld phantom=%+lld weight=%lld\n", + i, (long long)c.reserve_a, (long long)c.reserve_b, (long long)c.reserve_sum(), + (long long)c.viz_held(), (long long)(c.reserve_sum() - c.viz_held()), (long long)w); + } + + printf("\n=== round-trip exactness sweep (add then withdraw the same amount) ===\n"); + int inexact = 0, total = 0; int64_t worst = 0; + for (int64_t liq = 100000; liq <= 700000; liq += 37000) + for (int64_t bet_amt = 1000; bet_amt <= 200000; bet_amt += 7331) + for (int64_t lp = 1000; lp <= 300000; lp += 9973) { + market s = make_market(liq); + place_bet(s, 0, bet_amt); + market pre = s; + add_liquidity(s, lp); + if (s.liquidity_sum - lp < MINLIQ) continue; + withdraw_liquidity(s, lp, MINLIQ); + ++total; + int64_t da = s.reserve_a - pre.reserve_a, db_ = s.reserve_b - pre.reserve_b; + if (da || db_) { + ++inexact; + if (da > worst) worst = da; + if (db_ > worst) worst = db_; + if (inexact <= 5) + printf(" inexact: liq=%lld bet=%lld lp=%lld dra=%+lld drb=%+lld\n", + (long long)liq, (long long)bet_amt, (long long)lp, (long long)da, (long long)db_); + } + } + printf(" %d/%d round-trips inexact; worst signed drift = %+lld (positive = reserves GREW)\n", + inexact, total, (long long)worst); + + printf("\n=== does an add->withdraw round-trip ever beat the honest single bet? ===\n"); + int wins = 0; total = 0; double best = 0; + for (int64_t liq = 100000; liq <= 700000; liq += 37000) + for (int64_t bet_amt = 2000; bet_amt <= 200000; bet_amt += 7331) + for (int64_t lp = 1000; lp <= 300000; lp += 9973) { + market single = make_market(liq), split = make_market(liq); + bet s1 = place_bet(single, 0, bet_amt); + int64_t h = bet_amt / 2; + bet p1 = place_bet(split, 0, h); + add_liquidity(split, lp); + if (split.liquidity_sum - lp < MINLIQ) continue; + withdraw_liquidity(split, lp, MINLIQ); + bet p2 = place_bet(split, 0, bet_amt - h); + ++total; + int64_t ws = s1.weight, wp = p1.weight + p2.weight; + if (wp > ws) { + ++wins; + double pct = 100.0 * (wp - ws) / ws; + if (pct > best) best = pct; + if (wins <= 5) + printf(" split wins: liq=%lld bet=%lld lp=%lld single=%lld split=%lld (%+.4f%%)\n", + (long long)liq, (long long)bet_amt, (long long)lp, + (long long)ws, (long long)wp, pct); + } + } + printf(" split beat single in %d/%d cases; worst excess %+.4f%%\n", wins, total, best); + return 0; +} diff --git a/tests/pm/replay/t8_shrink.cpp b/tests/pm/replay/t8_shrink.cpp new file mode 100644 index 0000000000..09b5c6759c --- /dev/null +++ b/tests/pm/replay/t8_shrink.cpp @@ -0,0 +1,115 @@ +// t8_shrink.cpp — two follow-ups on 172d87c: +// (A) attribute the "split beats single" excess: LP cycle, or plain bet-splitting rounding? +// (B) NEW: withdraw now SHRINKS reserves, but pm_cancel_bet still subtracts the bet amount +// recorded at bet time. Can a legitimate LP withdrawal between bet and cancel drive a +// reserve negative -> k detonation (B6's failure mode, newly reachable)? +#include +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v) { return (u128)(uint64_t)v; } +struct market { int64_t reserve_a=0, reserve_b=0, bets_sum=0, liquidity_sum=0; u128 k=0; }; +struct bet { int64_t amount=0, weight=0; int side=0; }; + +static market make_market(int64_t liq) { + market m; int64_t h = liq/2; m.reserve_a=h; m.reserve_b=liq-h; + m.liquidity_sum=liq; m.k=U(m.reserve_a)*U(m.reserve_b); return m; +} +static bet place_bet(market& m, int side, int64_t amount) { + int64_t rin = side==0?m.reserve_a:m.reserve_b, rout = side==0?m.reserve_b:m.reserve_a; + int64_t nout = (int64_t)(uint64_t)(m.k / U(rin+amount)); + bet b; b.amount=amount; b.weight=rout-nout; b.side=side; + if (side==0) { m.reserve_a+=amount; m.reserve_b=nout; } else { m.reserve_b+=amount; m.reserve_a=nout; } + m.bets_sum+=amount; return b; +} +static void cancel_bet(market& m, const bet& b) { + if (b.side==0) { m.reserve_a-=b.amount; m.reserve_b+=b.weight; } + else { m.reserve_b-=b.amount; m.reserve_a+=b.weight; } + m.bets_sum-=b.amount; + m.k = U(m.reserve_a)*U(m.reserve_b); // negative int64 -> huge uint64 +} +static void add_liq(market& m, int64_t a) { + const int64_t L=m.liquidity_sum; + if (L>0) { u128 n=U(L+a),d=U(L); + m.reserve_a=(int64_t)(uint64_t)(U(m.reserve_a)*n/d); + m.reserve_b=(int64_t)(uint64_t)(U(m.reserve_b)*n/d); } + else { int64_t h=a/2; m.reserve_a+=h; m.reserve_b+=a-h; } + m.liquidity_sum+=a; m.k=U(m.reserve_a)*U(m.reserve_b); +} +static bool wd_liq(market& m, int64_t w, int64_t minliq) { + if (m.liquidity_sum - w < minliq) return false; // the new FC_ASSERT + const int64_t L=m.liquidity_sum; m.liquidity_sum-=w; + if (L>0) { u128 n=U(L-w),d=U(L); + m.reserve_a=(int64_t)(uint64_t)(U(m.reserve_a)*n/d); + m.reserve_b=(int64_t)(uint64_t)(U(m.reserve_b)*n/d); + m.k=U(m.reserve_a)*U(m.reserve_b); } + return true; +} + +int main() { + const int64_t MINLIQ = 100000; + + printf("=== (A) attribution: split-vs-single, with and without the LP round trip ===\n"); + int lp_wins=0, plain_wins=0, tot=0; double lp_best=0, plain_best=0; + for (int64_t liq=100000; liq<=700000; liq+=37000) + for (int64_t amt=2000; amt<=200000; amt+=7331) + for (int64_t lp=1000; lp<=300000; lp+=9973) { + market one=make_market(liq), withlp=make_market(liq), nolp=make_market(liq); + int64_t h=amt/2; + bet s = place_bet(one,0,amt); + bet a1=place_bet(withlp,0,h); + add_liq(withlp,lp); + if (!wd_liq(withlp,lp,MINLIQ)) continue; + bet a2=place_bet(withlp,0,amt-h); + bet b1=place_bet(nolp,0,h), b2=place_bet(nolp,0,amt-h); // control: no LP ops at all + ++tot; + int64_t w1=a1.weight+a2.weight, w2=b1.weight+b2.weight; + if (w1>s.weight) { ++lp_wins; double p=100.0*(w1-s.weight)/s.weight; if(p>lp_best) lp_best=p; } + if (w2>s.weight) { ++plain_wins; double p=100.0*(w2-s.weight)/s.weight; if(p>plain_best) plain_best=p; } + } + printf(" with LP round trip : %d/%d beat single, worst %+.4f%%\n", lp_wins, tot, lp_best); + printf(" plain split, no LP : %d/%d beat single, worst %+.4f%%\n", plain_wins, tot, plain_best); + printf(" -> excess is %s\n", lp_wins==plain_wins && lp_best==plain_best + ? "pure bet-splitting truncation, NOT the LP cycle" : "LP-attributable (investigate)"); + + printf("\n=== (B) LP withdrawal between bet and cancel ===\n"); + { + market m = make_market(400000); + printf(" created ra=%lld rb=%lld L=%lld\n", + (long long)m.reserve_a,(long long)m.reserve_b,(long long)m.liquidity_sum); + bet alice = place_bet(m,0,100000); + printf(" alice bets 100k on A ra=%lld rb=%lld weight=%lld\n", + (long long)m.reserve_a,(long long)m.reserve_b,(long long)alice.weight); + bool ok = wd_liq(m, 300000, MINLIQ); // legal: leaves L = 100000 == pm_min_liquidity + printf(" LP withdraws 300k %s ra=%lld rb=%lld L=%lld\n", + ok?"ALLOWED":"blocked",(long long)m.reserve_a,(long long)m.reserve_b, + (long long)m.liquidity_sum); + cancel_bet(m, alice); + printf(" alice cancels ra=%lld rb=%lld\n", + (long long)m.reserve_a,(long long)m.reserve_b); + printf(" k = %llu%s\n", (unsigned long long)m.k, + m.reserve_a<0||m.reserve_b<0 ? " <-- NEGATIVE RESERVE, k detonated" : ""); + // what the next bettor now gets out of the detonated curve + if (m.reserve_a > 0 || m.reserve_b > 0) { + market probe = m; bet nxt = place_bet(probe,1,1000); + printf(" next 1000 bet on B gets weight %lld (reserve_a was %lld)\n", + (long long)nxt.weight,(long long)m.reserve_a); + } + } + + printf("\n minimum LP withdrawal that makes a cancel underflow, by bet size:\n"); + for (int64_t amt : {10000, 50000, 100000, 200000}) { + int64_t liq=400000, found=-1; + for (int64_t w=1000; w<=liq-MINLIQ; w+=1000) { + market m=make_market(liq); + bet b=place_bet(m,0,amt); + if (!wd_liq(m,w,MINLIQ)) continue; + if (m.reserve_a - b.amount < 0) { found=w; break; } + } + printf(" bet %-7lld on a %lld market: %s\n", (long long)amt, (long long)liq, + found<0 ? "no single withdrawal suffices" + : (printf("withdraw %lld (%.0f%% of pool)",(long long)found,100.0*found/liq), "")); + printf("\n"); + } + return 0; +} diff --git a/tests/pm/replay/t9_mag.cpp b/tests/pm/replay/t9_mag.cpp new file mode 100644 index 0000000000..13c7120c11 --- /dev/null +++ b/tests/pm/replay/t9_mag.cpp @@ -0,0 +1,60 @@ +// t9 — magnitude of the residual LP-round-trip rounding leak, and whether it accumulates. +#include +#include +#include +typedef unsigned __int128 u128; +static u128 U(int64_t v){return (u128)(uint64_t)v;} +struct market{int64_t ra=0,rb=0,bs=0,L=0;u128 k=0;}; +struct bet{int64_t amount=0,weight=0;int side=0;}; +static market mk(int64_t l){market m;int64_t h=l/2;m.ra=h;m.rb=l-h;m.L=l;m.k=U(m.ra)*U(m.rb);return m;} +static bet pb(market&m,int s,int64_t a){int64_t ri=s==0?m.ra:m.rb,ro=s==0?m.rb:m.ra; + int64_t no=(int64_t)(uint64_t)(m.k/U(ri+a));bet b;b.amount=a;b.weight=ro-no;b.side=s; + if(s==0){m.ra+=a;m.rb=no;}else{m.rb+=a;m.ra=no;}m.bs+=a;return b;} +static void cb(market&m,const bet&b){if(b.side==0){m.ra-=b.amount;m.rb+=b.weight;}else{m.rb-=b.amount;m.ra+=b.weight;} + m.bs-=b.amount;m.k=U(m.ra)*U(m.rb);} +static void al(market&m,int64_t a){const int64_t L=m.L;if(L>0){u128 n=U(L+a),d=U(L); + m.ra=(int64_t)(uint64_t)(U(m.ra)*n/d);m.rb=(int64_t)(uint64_t)(U(m.rb)*n/d);} + else{int64_t h=a/2;m.ra+=h;m.rb+=a-h;}m.L+=a;m.k=U(m.ra)*U(m.rb);} +static bool wl(market&m,int64_t w,int64_t mn){if(m.L-w0){u128 n=U(L-w),d=U(L);m.ra=(int64_t)(uint64_t)(U(m.ra)*n/d);m.rb=(int64_t)(uint64_t)(U(m.rb)*n/d); + m.k=U(m.ra)*U(m.rb);}return true;} + +int main(){ + const int64_t MN=100000; + printf("=== absolute size of the split-around-LP excess ===\n"); + int64_t worst_abs=0; double worst_pct=0; int64_t wl_,wa_,wp_; + for(int64_t l=100000;l<=700000;l+=37000) + for(int64_t a=2000;a<=200000;a+=7331) + for(int64_t lp=1000;lp<=300000;lp+=9973){ + market one=mk(l),sp=mk(l);int64_t h=a/2; + bet s=pb(one,0,a);bet p1=pb(sp,0,h);al(sp,lp);if(!wl(sp,lp,MN))continue; + bet p2=pb(sp,0,a-h);int64_t d=p1.weight+p2.weight-s.weight; + if(d>worst_abs){worst_abs=d;worst_pct=100.0*d/s.weight;wl_=l;wa_=a;wp_=lp;} + } + printf(" worst absolute excess = %lld satoshi (%.4f%%) at liq=%lld bet=%lld lp=%lld\n", + (long long)worst_abs,worst_pct,(long long)wl_,(long long)wa_,(long long)wp_); + printf(" (old 50/50 code on the same shape: +29.6%% claim weight, thousands of satoshi)\n"); + + printf("\n=== does bare LP cycling ratchet the curve over many rounds? ===\n"); + for(int64_t l:{100001LL,400000LL,333333LL}) for(int64_t lp:{7777LL,50000LL}){ + market m=mk(l);int64_t ra0=m.ra,rb0=m.rb;int cycles=0; + for(int i=0;i<2000;i++){al(m,lp);if(!wl(m,lp,MN))break;cycles++;} + printf(" liq=%-7lld lp=%-6lld after %d cycles: ra %lld->%lld (%+lld) rb %lld->%lld (%+lld)\n", + (long long)l,(long long)lp,cycles,(long long)ra0,(long long)m.ra,(long long)(m.ra-ra0), + (long long)rb0,(long long)m.rb,(long long)(m.rb-rb0)); + } + + printf("\n=== full 9-step cycle repeated 500x (does weight or phantom depth ratchet?) ===\n"); + for(int64_t l:{400000LL,333333LL}){ + market c=mk(l);int64_t first=0,last=0; + for(int i=1;i<=500;i++){ + bet x=pb(c,0,50000);al(c,50000);if(!wl(c,50000,MN))break; + bet y=pb(c,0,50000);int64_t w=x.weight+y.weight; + cb(c,y);cb(c,x); if(i==1)first=w; last=w; + } + printf(" liq=%-7lld weight pass1=%lld pass500=%lld final ra=%lld rb=%lld rsum=%lld held=%lld phantom=%+lld\n", + (long long)l,(long long)first,(long long)last,(long long)c.ra,(long long)c.rb, + (long long)(c.ra+c.rb),(long long)(c.L+c.bs),(long long)(c.ra+c.rb-c.L-c.bs)); + } + return 0; +}